expo/expo

40 workflows · maturity 67% · 9 patterns · GitHub ↗

Security 0.62/100

Practices

✓ Matrix✓ Permissions○ Security scan○ AI review✓ Cache✓ Concurrency○ Reusable workflows

Detected patterns

Security dimensions

permissions
0.6
security scan
0
supply chain
0
secret handling
0
harden runner
0

Workflows (40)

android-instrumentation-tests matrix .github/workflows/android-instrumentation-tests.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04
Jobs
test
Matrix
api-level→ 36
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
View raw YAML
name: Android Instrumentation Tests

on:
  workflow_dispatch: {}
  push:
    branches: [main]
    paths:
      - .github/workflows/android-instrumentation-tests.yml
      - apps/expo-go/android/**
      - fastlane/**
      # - packages/**/android/**
      - packages/expo-eas-client/android/**
      - packages/expo-file-system/android/**
      - packages/expo-json-utils/android/**
      - packages/expo-manifests/android/**
      - packages/expo-notifications/android/**
      - packages/expo-updates/android/**
      - packages/expo-modules-core/**
      - tools/src/commands/AndroidNativeUnitTests.ts
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'
      - '!**.md'
      - '!**/__tests__/**'
      - '!**/__mocks__/**'
  pull_request:
    paths:
      - .github/workflows/android-instrumentation-tests.yml
      - apps/expo-go/android/**
      - fastlane/**
      # - packages/**/android/**
      - packages/expo-eas-client/android/**
      - packages/expo-file-system/android/**
      - packages/expo-json-utils/android/**
      - packages/expo-manifests/android/**
      - packages/expo-notifications/android/**
      - packages/expo-updates/android/**
      - packages/expo-modules-core/**
      - tools/src/commands/AndroidNativeUnitTests.ts
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'
      - '!**.md'
      - '!**/__tests__/**'
      - '!**/__mocks__/**'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-24.04
    timeout-minutes: 150
    env:
      ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64
      GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=2048m -Dorg.gradle.daemon=false
    strategy:
      matrix:
        api-level: [36]
    steps:
      - name: 👀 Check out repository
        uses: actions/checkout@v4
        with:
          submodules: true
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🔨 Use JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          gradle: 'true'
          react-native-gradle-downloads: 'true'
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 📱 Run instrumented unit tests
        timeout-minutes: 80
        uses: ./.github/actions/use-android-emulator
        with:
          avd-api: ${{ matrix.api-level }}
          avd-name: avd-${{ matrix.api-level }}
          script: pnpm expotools android-native-unit-tests --type instrumented
      - name: 💾 Save test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: packages/**/build/outputs/androidTest-results/**/*
android-unit-tests .github/workflows/android-unit-tests.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04
Jobs
test
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
  • ./gradlew spotlessCheck || { echo '::error Spotless lint failed. Run `./gradlew spotlessApply` from `apps/expo-go/android` to automatically fix formatting.' && exit 1; }
  • pnpm expotools native-unit-tests --platform android
View raw YAML
name: Android Unit Tests

on:
  workflow_dispatch: {}
  push:
    branches: [main]
    paths:
      - .github/workflows/android-unit-tests.yml
      - apps/expo-go/android/**
      - fastlane/**
      - packages/**/android/**
      - tools/**
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'
      - '!tools/src/commands/GenerateDocsAPIData.ts'
  pull_request:
    paths:
      - .github/workflows/android-unit-tests.yml
      - apps/expo-go/android/**
      - fastlane/**
      - packages/**/android/**
      - tools/**
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'
      - '!tools/src/commands/GenerateDocsAPIData.ts'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-24.04
    timeout-minutes: 100
    env:
      ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64
      GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx3072m -XX:MaxMetaspaceSize=1024m
    steps:
      - name: 👀 Check out repository
        uses: actions/checkout@v4
        with:
          submodules: true
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🔨 Use JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          react-native-gradle-downloads: 'true'
          gradle: 'true'
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 💅 Run Spotless lint check
        working-directory: apps/expo-go/android
        run: ./gradlew spotlessCheck || { echo '::error Spotless lint failed. Run `./gradlew spotlessApply` from `apps/expo-go/android` to automatically fix formatting.' && exit 1; }
      - name: 🎸 Run native Android unit tests
        if: always()
        timeout-minutes: 30
        run: pnpm expotools native-unit-tests --platform android
      - name: 💾 Save test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: packages/**/build/test-results/**/*xml
bare-diffs .github/workflows/bare-diffs.yml
Triggers
workflow_dispatch
Runs on
ubuntu-24.04
Jobs
bare-diffs
Actions
pnpm/action-setup
Commands
  • pnpm install --ignore-scripts --frozen-lockfile
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • echo "Checking that expo-template-bare-minimum diffs are updated" pnpm expotools generate-bare-diffs --check
View raw YAML
name: Check Template Bare Minimum Diffs

on:
  workflow_dispatch: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  bare-diffs:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          yarn-docs: 'true'
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🧐 Check diffs
        run: |
          echo "Checking that expo-template-bare-minimum diffs are updated"
          pnpm expotools generate-bare-diffs --check
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && github.event.ref == 'refs/heads/main'
        with:
          webhook: ${{ secrets.slack_webhook_docs }}
          author_name: Check for Changes in Bare Diffs
      - name: 💾 Store artifacts of diff failures
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: template-bare-minimum-bare-diff-failure
          path: docs/public/static/diffs/template-bare-minimum/raw
check-issues-nightly .github/workflows/check-issues-nightly.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-24.04
Jobs
validate-pending-issues
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools validate-issue --issue "*"
View raw YAML
name: Check issues with pending validation labels

on:
  workflow_dispatch: {}
  schedule:
    - cron: '0 8 * * *' # 8:00 AM UTC time every day

jobs:
  validate-pending-issues:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 🔎 Validate issues with pending labels
        run: pnpm expotools validate-issue --issue "*"
        env:
          GITHUB_TOKEN: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
cli matrix .github/workflows/cli.yml
Triggers
push, pull_request, schedule
Runs on
ubuntu-24.04, windows-2022, ubuntu-24.04, windows-2022
Jobs
jest-ubuntu, jest-windows, playwright-ubuntu, playwright-windows
Matrix
shard→ 1, 2, 3, 4
Actions
pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun
Commands
  • git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
  • pnpm install --frozen-lockfile
  • pnpm typecheck
  • pnpm test:e2e --max-workers 1 --shard ${{ matrix.shard }}/${{ strategy.job-total }}
  • git config --global core.autocrlf false git config --global core.eol lf
  • git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
  • pnpm install --frozen-lockfile
  • pnpm tsc --noEmit
View raw YAML
name: CLI

on:
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/cli.yml
      - packages/@expo/cli/**
      - packages/@expo/metro-runtime/**
      - packages/@expo/metro-config/src/**
      - packages/@expo/config/src/**
      - packages/@expo/config-plugins/src/**
      - packages/@expo/env/src/**
      - packages/@expo/json-file/src/**
      - packages/@expo/package-manager/src/**
      - packages/@expo/prebuild-config/src/**
      - packages/@expo/router-server/**
      - packages/@expo/log-box/**
      - packages/create-expo/**
      - packages/eslint-config-expo/**
      - packages/eslint-plugin-expo/**
      - packages/expo-router/**
      - packages/expo-server/**
      - pnpm-lock.yaml
      - '!**.md'
  pull_request:
    paths:
      - .github/workflows/cli.yml
      - packages/@expo/cli/**
      - packages/@expo/metro-runtime/**
      - packages/@expo/metro-config/src/**
      - packages/@expo/config/src/**
      - packages/@expo/config-plugins/src/**
      - packages/@expo/env/src/**
      - packages/@expo/json-file/src/**
      - packages/@expo/package-manager/src/**
      - packages/@expo/prebuild-config/src/**
      - packages/@expo/router-server/**
      - packages/@expo/log-box/**
      - packages/create-expo/**
      - packages/eslint-config-expo/**
      - packages/eslint-plugin-expo/**
      - packages/expo-router/**
      - packages/expo-server/**
      - pnpm-lock.yaml
      - '!**.md'
  schedule:
    - cron: 0 14 * * *

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  jest-ubuntu:
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
        if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'

      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: 🏗️ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest

      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile


      - name: 🔎 Type Check CLI
        working-directory: packages/@expo/cli
        run: pnpm typecheck

      - name: 🧪 E2E Test CLI
        working-directory: packages/@expo/cli
        run: pnpm test:e2e --max-workers 1 --shard ${{ matrix.shard }}/${{ strategy.job-total }}

  jest-windows:
    runs-on: windows-2022
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    env:
      # Change bun, npm, and pnpm caches to the much faster D:\ drive
      # See: https://github.com/twpayne/chezmoi/pull/4064
      BUN_INSTALL_CACHE_DIR: D:\_temp\bun
      NPM_CACHE_LOCATION: D:\_temp\npm
      # Change the Expo E2E project directory to the same drive as the repository,
      # allowing to run Expo from source (avoiding Metro's multi-drive limitation)
      EXPO_E2E_TEMP_DIR: D:\_temp\expo-e2e
    steps:
      - name: 🪟 Setup Windows
        shell: bash
        # Configure Git to use LF line endlings
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
        if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'

      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: 🏗️ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest

      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile


      - name: 🔎 Type Check CLI
        working-directory: packages/@expo/cli
        # TODO(cedric): expo module scripts is incompatible with Windows, forcing us to manually set up these steps instead of `pnpm typecheck`
        run: pnpm tsc --noEmit

      - name: 🧪 E2E Test CLI
        working-directory: packages/@expo/cli
        run: pnpm test:e2e --max-workers 1 --shard ${{ matrix.shard }}/${{ strategy.job-total }}

  playwright-ubuntu:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
        if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'

      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: 🏗️ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest

      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile

      - name: 🎭 Get Playwright information
        id: playwright-info
        shell: bash
        working-directory: packages/@expo/cli
        run: |
          echo version="$(pnpm exec playwright --version)" >> $GITHUB_OUTPUT
      - name: 🎭 Cache Playwright browser binaries
        uses: actions/cache@v4
        id: playwright-browser-cache
        with:
          path: ~/.cache/ms-playwright
          key: ${{ runner.os }}-playwright-${{ steps.playwright-info.outputs.version }}
      - name: 🎭 Install Playwright Browsers
        run: pnpm exec playwright install --with-deps chromium
        working-directory: packages/@expo/cli
        if: steps.playwright-browser-cache.outputs.cache-hit != 'true'

      - name: 🧪 E2E (playwright) Test CLI
        working-directory: packages/@expo/cli
        run: pnpm test:playwright

      - name: 🗄️ Upload playwright report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: packages/@expo/cli/playwright-report/
          retention-days: 30

  playwright-windows:
    runs-on: windows-2022
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2]
    env:
      # Change bun, npm, and pnpm caches to the much faster D:\ drive
      # See: https://github.com/twpayne/chezmoi/pull/4064
      BUN_INSTALL_CACHE_DIR: D:\_temp\bun
      NPM_CACHE_LOCATION: D:\_temp\npm
      # Change playwright browser cache to the much faster D:\ drive
      PLAYWRIGHT_BROWSERS_PATH: D:\_temp\playwright-browsers
      # Change the Expo E2E project directory to the same drive as the repository,
      # allowing to run Expo from source (avoiding Metro's multi-drive limitation)
      EXPO_E2E_TEMP_DIR: D:\_temp\expo-e2e
    steps:
      - name: 🪟 Setup Windows
        shell: bash
        # Configure Git to use LF line endlings
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
        if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'

      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: 🏗️ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest

      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile


      - name: 🎭 Get Playwright information
        id: playwright-info
        shell: bash
        working-directory: packages/@expo/cli
        run: |
          echo version="$(pnpm exec playwright --version)" >> $GITHUB_OUTPUT
          echo dir="$(echo $PLAYWRIGHT_BROWSERS_PATH)" >> $GITHUB_OUTPUT
      - name: 🎭 Cache Playwright browser binaries
        uses: actions/cache@v4
        id: playwright-browser-cache
        with:
          path: ${{ steps.playwright-info.outputs.dir }}
          key: ${{ runner.os }}-playwright-${{ steps.playwright-info.outputs.version }}
      - name: 🎭 Install Playwright Browsers
        run: pnpm exec playwright install --with-deps chromium
        working-directory: packages/@expo/cli
        if: steps.playwright-browser-cache.outputs.cache-hit != 'true'

      - name: 🧪 E2E (playwright) Test CLI
        working-directory: packages/@expo/cli
        run: pnpm test:playwright --shard ${{ matrix.shard }}/${{ strategy.job-total }}

      - name: 🗄️ Upload playwright report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: packages/@expo/cli/playwright-report/
          retention-days: 30
code-review .github/workflows/code-review.yml
Triggers
workflow_dispatch, pull_request_target
Runs on
ubuntu-24.04
Jobs
code_review
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools code-review --pr ${{ github.event.inputs.pullNumber || github.event.number }}
View raw YAML
name: Code Review

on:
  workflow_dispatch:
    inputs:
      pullNumber:
        description: 'Number of the pull request to review'
        required: true
  pull_request_target:

concurrency:
  group: ${{ github.workflow }}-${{ github.event.inputs.pullNumber || github.event.number }}
  cancel-in-progress: true

jobs:
  code_review:
    runs-on: ubuntu-24.04
    timeout-minutes: 30
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches

      ### This job is run on Ubuntu which doesn't have SwiftLint installed, so we need to build it from sources.
      ### Building it on each run may take too much time, so we bundle the binary in `@expo/swiftlint` package.
      ### To update SwiftLint, uncomment steps below, download the artifact and update that package.
      # - name: 👷 Build and install SwiftLint
      #   run: |
      #     git clone https://github.com/realm/SwiftLint.git --branch 0.57.0 --depth 1
      #     cd SwiftLint
      #     swift build -c release
      #     cp .build/release/swiftlint /usr/local/bin/
      # - name: 📦 Make an artifact
      #   uses: actions/upload-artifact@v4
      #   with:
      #     name: swiftlint
      #     path: SwiftLint/.build
      #     include-hidden-files: true

      - name: 🔬 Reviewing a pull request
        run: pnpm expotools code-review --pr ${{ github.event.inputs.pullNumber || github.event.number }}
        env:
          GITHUB_TOKEN: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
codemention .github/workflows/codemention.yaml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
codemention
Actions
tobyhs/codemention
View raw YAML
name: codemention
on:
  pull_request_target:
    types: [opened, synchronize, ready_for_review]
jobs:
  codemention:
    if: github.repository == 'expo/expo'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: tobyhs/codemention@v1.4.0
        with:
          githubToken: ${{ secrets.GITHUB_TOKEN }}
commentator .github/workflows/commentator.yml
Triggers
workflow_dispatch
Runs on
ubuntu-24.04
Jobs
comment
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools commentator --payload "${{ github.event.inputs.payload }}"
View raw YAML
name: Comments on GitHub issues

on:
  workflow_dispatch:
    inputs:
      payload:
        description: 'Serialized and escaped JSON describing what and where to comment.'
        required: true

jobs:
  comment:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 💬 Comment on GitHub issues as github-actions bot
        run: pnpm expotools commentator --payload "${{ github.event.inputs.payload }}"
        env:
          GITHUB_TOKEN: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
create-expo-app matrix .github/workflows/create-expo-app.yml
Triggers
push, pull_request, schedule
Runs on
ubuntu-24.04
Jobs
test
Matrix
node→ 20, 22
Actions
pnpm/action-setup, oven-sh/setup-bun
Commands
  • git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
  • pnpm install --frozen-lockfile
  • pnpm run prepublishOnly
  • pnpm test:e2e
View raw YAML
name: Create Expo App

on:
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/create-expo-app.yml
      - packages/create-expo/**
      - pnpm-lock.yaml
  pull_request:
    paths:
      - .github/workflows/create-expo-app.yml
      - packages/create-expo/**
      - pnpm-lock.yaml
  schedule:
    - cron: 0 14 * * *

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        node: [20, 22]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
        if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v1
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'pnpm'
      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile
      - name: 🛠 Build create-expo
        run: pnpm run prepublishOnly
        working-directory: packages/create-expo
      - name: E2E Test create-expo
        run: pnpm test:e2e
        working-directory: packages/create-expo
create-expo-module matrix .github/workflows/create-expo-module.yml
Triggers
push, pull_request, schedule
Runs on
ubuntu-24.04
Jobs
test
Matrix
node→ 20, 22
Actions
pnpm/action-setup
Commands
  • git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
  • pnpm install --frozen-lockfile
  • pnpm run prepublishOnly
  • pnpm test:e2e
View raw YAML
name: Create Expo Module

on:
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/create-expo-module.yml
      - packages/create-expo-module/**
      - pnpm-lock.yaml
  pull_request:
    paths:
      - .github/workflows/create-expo-module.yml
      - packages/create-expo-module/**
      - pnpm-lock.yaml
  schedule:
    - cron: 0 14 * * *

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        node: [20, 22]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
        if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'pnpm'
      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile
      - name: 🛠 Build create-expo-module
        run: pnpm run prepublishOnly
        working-directory: packages/create-expo-module
      - name: 🧪 E2E Test create-expo-module
        run: pnpm test:e2e
        working-directory: packages/create-expo-module
development-client matrix .github/workflows/development-client.yml
Triggers
workflow_dispatch
Runs on
ubuntu-24.04, macos-15
Jobs
android, ios
Matrix
ndk-version→ 21.4.7075529
Actions
pnpm/action-setup, pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
  • pnpm create expo-app ./development-client-android-test --yes
  • node -e " \ const pkg = require('./package.json'); \ pkg.resolutions = { \ ...pkg.resolutions, \ 'expo-dev-menu': 'file:../expo/packages/expo-dev-menu', \ 'expo-dev-menu-interface': 'file:../expo/packages/expo-dev-menu-interface', \ 'expo-dev-launcher': 'file:../expo/packages/expo-dev-launcher', \ 'expo-updates-interface': 'file:../expo/packages/expo-updates-interface', \ 'expo-updates': 'file:../expo/packages/expo-updates', \ 'expo-manifests': 'file:../expo/packages/expo-manifests', \ 'expo-json-utils': 'file:../expo/packages/expo-json-utils', \ }; \ fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');"
  • pnpm add file:../expo/packages/expo-dev-client
  • echo "{\"name\":\"development-client-android-test\",\"plugins\":[\"expo-dev-client\"],\"android\":{\"package\":\"com.devclient.test\"},\"ios\":{\"bundleIdentifier\":\"com.devclient.test\"}}" > app.config.json
  • pnpm expo prebuild --platform android
  • sed -i -e 's/buildToolsVersion\ =\ \"29\..\..\"/buildToolsVersion\ = \"30\.0\.3\"/' ./android/build.gradle
View raw YAML
name: Development Client

on:
  workflow_dispatch: {}
  # This task will fail due to migration to `expo-modules-core`.
  # We temporary disable it.
  # pull_request:
  #   paths:
  #     - .github/workflows/development-client.yml
  #     - packages/expo-dev-*/**
  # push:
  #   branches: [main]
  #   paths:
  #     - .github/workflows/development-client.yml
  #     - packages/expo-dev-*/**

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  android:
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        ndk-version: [21.4.7075529]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          ndk: 'true'
          ndk-version: ${{ matrix.ndk-version }}
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: Init new expo app
        working-directory: ../
        run: pnpm create expo-app ./development-client-android-test --yes
      - name: Make Yarn resolve expo-dev-client dependencies locally
        working-directory: ../development-client-android-test
        run: |
          node -e " \
            const pkg = require('./package.json'); \
            pkg.resolutions = { \
              ...pkg.resolutions, \
              'expo-dev-menu': 'file:../expo/packages/expo-dev-menu', \
              'expo-dev-menu-interface': 'file:../expo/packages/expo-dev-menu-interface', \
              'expo-dev-launcher': 'file:../expo/packages/expo-dev-launcher', \
              'expo-updates-interface': 'file:../expo/packages/expo-updates-interface', \
              'expo-updates': 'file:../expo/packages/expo-updates', \
              'expo-manifests': 'file:../expo/packages/expo-manifests', \
              'expo-json-utils': 'file:../expo/packages/expo-json-utils', \
            }; \
            fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');"
      - name: Add dependencies
        working-directory: ../development-client-android-test
        run: pnpm add file:../expo/packages/expo-dev-client
      - name: Setup app.config.json
        working-directory: ../development-client-android-test
        run: echo "{\"name\":\"development-client-android-test\",\"plugins\":[\"expo-dev-client\"],\"android\":{\"package\":\"com.devclient.test\"},\"ios\":{\"bundleIdentifier\":\"com.devclient.test\"}}" > app.config.json
      - name: Prebuild Android
        working-directory: ../development-client-android-test
        run: pnpm expo prebuild --platform android
      - name: Bump `build tools`
        working-directory: ../development-client-android-test
        run: sed -i -e 's/buildToolsVersion\ =\ \"29\..\..\"/buildToolsVersion\ = \"30\.0\.3\"/' ./android/build.gradle
      - name: Bump `android build tools`
        working-directory: ../development-client-android-test
        run: sed -i -e 's/com\.android\.tools\.build:gradle:3\..\../com\.android\.tools\.build:gradle:3\.5\.4/' ./android/build.gradle
      - name: 🏗️ Build debug version
        env:
          ANDROID_NDK_HOME: /usr/local/lib/android/sdk/ndk/${{ matrix.ndk-version }}/
        working-directory: ../development-client-android-test/android
        run: ./gradlew assembleDebug

  ios:
    runs-on: macos-15
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: Init new expo app
        working-directory: ../
        run: pnpm create expo-app ./development-client-ios-test --yes
      - name: Make Yarn resolve expo-dev-client dependencies locally
        working-directory: ../development-client-ios-test
        run: |
          node -e " \
            const pkg = require('./package.json'); \
            pkg.resolutions = { \
              ...pkg.resolutions, \
              'expo-dev-menu': 'file:../expo/packages/expo-dev-menu', \
              'expo-dev-menu-interface': 'file:../expo/packages/expo-dev-menu-interface', \
              'expo-dev-launcher': 'file:../expo/packages/expo-dev-launcher', \
              'expo-updates-interface': 'file:../expo/packages/expo-updates-interface', \
              'expo-updates': 'file:../expo/packages/expo-updates', \
              'expo-manifests': 'file:../expo/packages/expo-manifests', \
              'expo-json-utils': 'file:../expo/packages/expo-json-utils', \
            }; \
            fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');"
      - name: Add dependencies
        working-directory: ../development-client-ios-test
        run: pnpm add file:../expo/packages/expo-dev-client
      - name: Setup app.config.json
        working-directory: ../development-client-ios-test
        run: echo "{\"name\":\"development-client-ios-test\",\"plugins\":[\"expo-dev-client\"],\"android\":{\"package\":\"com.devclient.test\"},\"ios\":{\"bundleIdentifier\":\"com.devclient.test\"}}" > app.config.json
      - name: Prebuild iOS
        working-directory: ../development-client-ios-test
        run: pnpm expo prebuild --platform ios
      - name: 🏗️ Build debug version
        working-directory: ../development-client-ios-test
        run: xcodebuild -workspace ios/developmentclientiostest.xcworkspace -scheme developmentclientiostest -configuration debug -sdk iphonesimulator -arch x86_64
development-client-e2e matrix .github/workflows/development-client-e2e.yml
Triggers
workflow_dispatch, pull_request, push
Runs on
ubuntu-24.04
Jobs
detox_e2e
Matrix
api-level→ 36
Actions
ruby/setup-ruby, pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • brew tap wix/brew brew install applesimutils brew install watchman
  • sudo gem install cocoapods
  • pnpm install --frozen-lockfile
  • pnpm add -g expo-test-runner@$(cat package.json | grep '"expo-test-runner": "[0-9]*\.[0-9]*\.[0-9]*' | head -n 1 | awk '{print $2}' | sed 's/"//g; s/,//g')
  • pnpm e2e
View raw YAML
name: Development Client e2e

on:
  workflow_dispatch: {}
  pull_request:
    paths:
      - .github/workflows/development-client-e2e.yml
      - packages/expo-dev-*/**
  push:
    branches: [main]
    paths:
      - .github/workflows/development-client-e2e.yml
      - packages/expo-dev-*/**

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  detox_e2e:
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        api-level: [36]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🍺 Install required tools
        run: |
          brew tap wix/brew
          brew install applesimutils
          brew install watchman
      - name: 💎 Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.2.2
      - name: 💎 Install cocoapods
        run: sudo gem install cocoapods
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          gradle: 'true'
      - name: 🤖 Set up android emulator
        uses: ./.github/actions/use-android-emulator
        with:
          avd-api: ${{ matrix.api-level }}
          avd-name: avd-${{ matrix.api-level }}
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🧶 Install `expo-test-runner`
        run: |
          pnpm add -g expo-test-runner@$(cat package.json | grep '"expo-test-runner": "[0-9]*\.[0-9]*\.[0-9]*' | head -n 1 | awk '{print $2}' | sed 's/"//g; s/,//g')
        working-directory: packages/expo-dev-client
      - name: 🧪 Run tests
        run: |
          pnpm e2e
        working-directory: packages/expo-dev-client
      - name: 💾 Store artifacts of build failures
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: expo-dev-client-e2e-artifacts
          path: packages/expo-dev-client/artifacts
development-client-latest-e2e matrix .github/workflows/development-client-latest-e2e.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-24.04
Jobs
detox_latest_e2e
Matrix
api-level→ 36
Actions
ruby/setup-ruby, pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • brew tap wix/brew brew install applesimutils brew install watchman
  • sudo gem install cocoapods
  • pnpm install --frozen-lockfile
  • pnpm add -g expo-test-runner@$(cat package.json | grep '"expo-test-runner": "[0-9]*\.[0-9]*\.[0-9]*' | head -n 1 | awk '{print $2}' | sed 's/"//g; s/,//g')
  • pnpm latest-e2e
View raw YAML
name: Development Client latest e2e

on:
  workflow_dispatch: {}
  schedule:
    - cron: '0 0 * * SUN' # 0:00 AM UTC time every Sunday

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  detox_latest_e2e:
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        api-level: [36]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🍺 Install required tools
        run: |
          brew tap wix/brew
          brew install applesimutils
          brew install watchman
      - name: 💎 Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.2.2
      - name: 💎 Install cocoapods
        run: sudo gem install cocoapods
      - name: 🔨 Use JDK 11
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '11'
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          gradle: 'true'
      - name: 🤖 Set up android emulator
        uses: ./.github/actions/use-android-emulator
        with:
          avd-api: ${{ matrix.api-level }}
          avd-name: avd-${{ matrix.api-level }}
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🧶 Install `expo-test-runner`
        run: |
          pnpm add -g expo-test-runner@$(cat package.json | grep '"expo-test-runner": "[0-9]*\.[0-9]*\.[0-9]*' | head -n 1 | awk '{print $2}' | sed 's/"//g; s/,//g')
        working-directory: packages/expo-dev-client
      - name: 🧪 Run tests
        run: |
          pnpm latest-e2e
        working-directory: packages/expo-dev-client
      - name: 💾 Store artifacts of build failures
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: expo-dev-client-latest-e2e-artifacts
          path: packages/expo-dev-client/artifacts
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure()
        with:
          webhook: ${{ secrets.slack_webhook_dev_client }}
          channel: '#dev-clients'
          author_name: Dev Client e2e
docs .github/workflows/docs.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04
Jobs
docs
Actions
pnpm/action-setup, errata-ai/vale-action, cloudflare/wrangler-action
Commands
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm install --ignore-scripts --frozen-lockfile
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • expotools generate-docs-api-data
  • yarn install --immutable
  • yarn test
  • yarn test:worker
  • yarn lint --max-warnings 0
View raw YAML
name: Docs Website

defaults:
  run:
    shell: bash

on:
  workflow_dispatch: {}
  push:
    branches: [main]
    paths:
      - 'docs/**'
      - '.github/workflows/docs.yml'
  pull_request:
    paths:
      - '.github/workflows/docs.yml'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  docs:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 30000
      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          yarn-docs: 'true'
          docs-api-data: 'true'
      - name: 📦 Install workspace deps
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: 📦 Install tools deps
        working-directory: tools
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 📝 Regenerate `unversioned` data for Docs
        if: steps.expo-caches.outputs.docs-api-data-hit != 'true'
        run: expotools generate-docs-api-data
      - name: 🧶 Yarn install
        working-directory: docs
        if: steps.expo-caches.outputs.yarn-docs-hit != 'true'
        run: yarn install --immutable
      - name: 🧪 Run Docs tests
        working-directory: docs
        run: yarn test
      - name: 🧪 Test Cloudflare worker and routes
        working-directory: docs
        run: yarn test:worker
      - name: 🚨 Lint Docs website code
        working-directory: docs
        env:
          NODE_ENV: production
        run: yarn lint --max-warnings 0
      - name: 💬 Lint Docs website content
        uses: errata-ai/vale-action@v2.1.1
        with:
          version: 3.12.0
          reporter: github-pr-check
          files: 'docs/pages'
          vale_flags: '--config=./docs/.vale.ini'
          fail_on_error: true
          # Override bundled reviewdog (0.17.0) to avoid 300-file diff limit
          reviewdog_url: https://github.com/reviewdog/reviewdog/releases/download/v0.21.0/reviewdog_0.21.0_Linux_x86_64.tar.gz
      - name: 🏗️ Build Docs website for deploy
        working-directory: docs
        run: yarn export
        timeout-minutes: 20
        env:
          # NOTE(@krystofwoldrich): Missing token won't fail the build
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
      - name: 🔗 Lint pages links
        working-directory: docs
        run: yarn lint-links --quiet
      - name: 🚀 Deploy Docs website
        if: github.ref == 'refs/heads/main'
        id: deploy
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_PAGES_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy --branch=main
          workingDirectory: docs
      - name: 🧹 Prune old production deployments
        if: github.ref == 'refs/heads/main'
        working-directory: docs
        env:
          CLOUDFLARE_PAGES_API_TOKEN: ${{ secrets.CLOUDFLARE_PAGES_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
        run: node scripts/prune-cloudflare-deployments.mjs --project=expo-docs --branch=main --keep=2
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && github.event.ref == 'refs/heads/main'
        with:
          webhook: ${{ secrets.slack_webhook_docs }}
          channel: '#docs'
          author_name: Docs
docs-pr .github/workflows/docs-pr.yml
Triggers
workflow_dispatch, pull_request
Runs on
ubuntu-24.04
Jobs
docs-pr
Actions
errata-ai/vale-action, cloudflare/wrangler-action
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • yarn install --immutable
  • yarn test
  • yarn test:worker
  • yarn lint --max-warnings 0
  • yarn lint-links --quiet
  • yarn export
View raw YAML
name: Docs Website PR

defaults:
  run:
    shell: bash
    working-directory: docs

on:
  workflow_dispatch: {}
  pull_request:
    paths:
      - 'docs/**'
      - '.github/workflows/docs-pr.yml'
    types:
      - opened
      - labeled
      - synchronize

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  docs-pr:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v6
      - name: ⬢ Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 22
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          yarn-docs: 'true'
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🧶 Yarn install
        if: steps.expo-caches.outputs.yarn-docs-hit != 'true'
        run: yarn install --immutable
      - name: 🧪 Run Docs tests
        run: yarn test
      - name: 🧪 Test Cloudflare worker and routes
        run: yarn test:worker
      - name: 🚨 Lint Docs website code
        env:
          NODE_ENV: production
        run: yarn lint --max-warnings 0
      - name: 💬 Lint Docs website content
        uses: errata-ai/vale-action@v2.1.1
        with:
          version: 3.12.0
          reporter: github-pr-check
          files: 'docs/pages'
          vale_flags: '--config=./docs/.vale.ini'
          fail_on_error: true
          # Override bundled reviewdog (0.17.0) to avoid 300-file diff limit
          reviewdog_url: https://github.com/reviewdog/reviewdog/releases/download/v0.21.0/reviewdog_0.21.0_Linux_x86_64.tar.gz
      - name: 🔗 Lint pages links
        run: yarn lint-links --quiet
      - name: 🏗️ Build Docs website
        if: contains(github.event.pull_request.labels.*.name, 'preview')
        run: yarn export
        timeout-minutes: 20
        env:
          # NOTE(@krystofwoldrich): Missing token won't fail the build
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
      - name: 🚀 Deploy Docs website
        if: contains(github.event.pull_request.labels.*.name, 'preview')
        id: deploy
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_PAGES_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy --branch=pr-${{ github.event.pull_request.number }}
          workingDirectory: docs
      - name: 💬 Comment with preview URL
        if: contains(github.event.pull_request.labels.*.name, 'preview')
        uses: actions/github-script@v8
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            const body = '📘 Your docs [preview website](https://pr-${{ github.event.pull_request.number }}.expo-docs.pages.dev/) is ready!';
            const comments = await github.paginate(github.rest.issues.listComments, {
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
            });
            const existing = comments.find(c => c.user.login.startsWith('expo-bot') && c.body.includes('📘 Your docs'));
            if (existing) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: existing.id,
                body,
              });
            } else {
              await github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body,
              });
            }
docs-pr-destroy .github/workflows/docs-pr-destroy.yml
Triggers
pull_request
Runs on
ubuntu-24.04
Jobs
docs-pr-destroy
Commands
  • node scripts/prune-cloudflare-deployments.mjs --project=expo-docs --branch=pr-${{ github.event.pull_request.number }} --keep=0
View raw YAML
name: Docs Website PR Destroy Preview

defaults:
  run:
    shell: bash

on:
  pull_request:
    types: [closed, unlabeled]

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  docs-pr-destroy:
    if: (github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'preview')) ||
        (github.event.action == 'unlabeled' && github.event.label.name == 'preview')
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v6
      - name: ⬢ Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24
      - name: 🧹 Delete preview deployments
        working-directory: docs
        env:
          CLOUDFLARE_PAGES_API_TOKEN: ${{ secrets.CLOUDFLARE_PAGES_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
        run: node scripts/prune-cloudflare-deployments.mjs --project=expo-docs --branch=pr-${{ github.event.pull_request.number }} --keep=0
expotools .github/workflows/expotools.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04
Jobs
build
Actions
pnpm/action-setup
Commands
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm build
  • pnpm tsc --noEmit
  • pnpm lint --max-warnings 0
View raw YAML
name: Expotools

on:
  workflow_dispatch: {}
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/expotools.yml
      - tools/**
  pull_request:
    paths:
      - .github/workflows/expotools.yml
      - tools/**

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 🛠 Compile TypeScript sources
        run: pnpm build
        working-directory: tools
      - name: 🛠 Typecheck sources
        run: pnpm tsc --noEmit
        working-directory: tools
      - name: 🚨 Lint TypeScript sources
        run: pnpm lint --max-warnings 0
        working-directory: tools
fingerprint matrix .github/workflows/fingerprint.yml
Triggers
workflow_dispatch, push, pull_request, schedule
Runs on
${{ matrix.os }}, ubuntu-latest
Jobs
test, cross-platform-checks
Matrix
os→ macos-latest, ubuntu-latest, windows-latest
Actions
pnpm/action-setup, oven-sh/setup-bun
Commands
  • git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm tsc pnpm jest --config e2e/jest.config.js --ci --runInBand
  • cd .. bunx create-expo-app -t blank@sdk-52 my-app
  • cd ../my-app # Add dependencies with native binaries bun add -D sharp cat > fingerprint.config.js <<EOF /** @type {import('expo/fingerprint').Config} */ const config = { extraSources: [ { type: 'dir', filePath: 'node_modules/@img', }, { type: 'dir', filePath: 'node_modules/sharp', }, ], }; module.exports = config; EOF if [[ "${{ env.ACTIONS_STEP_DEBUG }}" == "true" ]]; then ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate --debug > ../expo/fingerprint-${{ matrix.os }}.json else ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate > ../expo/fingerprint-${{ matrix.os }}.json fi
  • cd ../my-app # Add dependencies with native binaries bun add -D sharp @" /** @type {import('expo/fingerprint').Config} */ const config = { extraSources: [ { type: 'dir', filePath: 'node_modules/@img', }, { type: 'dir', filePath: 'node_modules/sharp', }, ], }; module.exports = config; "@ | Set-Content fingerprint.config.js if ($env:ACTIONS_STEP_DEBUG -eq "true") { node ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate --debug > ../expo/fingerprint-${{ matrix.os }}.json } else { node ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate > ../expo/fingerprint-${{ matrix.os }}.json }
  • hash_macos=$(jq -r '.hash' fingerprint-macos-latest.json) hash_ubuntu=$(jq -r '.hash' fingerprint-ubuntu-latest.json) hash_windows=$(jq -r '.hash' fingerprint-windows-latest.json) echo "Hash from fingerprint-macos-latest.json: $hash_macos" echo "Hash from fingerprint-ubuntu-latest.json: $hash_ubuntu" echo "Hash from fingerprint-windows-latest.json: $hash_windows" if [[ "$hash_macos" != "$hash_ubuntu" || "$hash_ubuntu" != "$hash_windows" ]]; then echo "Hashes do not match!" exit 1 else echo "All hashes match!" fi
View raw YAML
name: Fingerprint

on:
  workflow_dispatch: {}
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/fingerprint.yml
      - packages/@expo/fingerprint/**
      - packages/create-expo/**
      - pnpm-lock.yaml
  pull_request:
    paths:
      - .github/workflows/fingerprint.yml
      - packages/@expo/fingerprint/**
      - packages/create-expo/**
      - pnpm-lock.yaml
  schedule:
    - cron: 0 14 * * *

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 🚀 Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.event.before || github.base_ref || 'main' }}:${{ github.event.before || github.base_ref || 'main' }} --depth 100
        if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: E2E Test @expo/fingerprint
        run: |
          pnpm tsc
          pnpm jest --config e2e/jest.config.js --ci --runInBand
        working-directory: packages/@expo/fingerprint
      - name: ⚙️  Create temp project
        run: |
          cd ..
          bunx create-expo-app -t blank@sdk-52 my-app
      - name: 💾 Create fingerprint from the temp project
        if: ${{ startsWith(matrix.os, 'windows') == false }}
        env:
          ACTIONS_STEP_DEBUG: ${{ secrets.ACTIONS_STEP_DEBUG }}
        run: |
          cd ../my-app

          # Add dependencies with native binaries
          bun add -D sharp
          cat > fingerprint.config.js <<EOF
          /** @type {import('expo/fingerprint').Config} */
          const config = {
            extraSources: [
              {
                type: 'dir',
                filePath: 'node_modules/@img',
              },
              {
                type: 'dir',
                filePath: 'node_modules/sharp',
              },
            ],
          };
          module.exports = config;
          EOF

          if [[ "${{ env.ACTIONS_STEP_DEBUG }}" == "true" ]]; then
            ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate --debug > ../expo/fingerprint-${{ matrix.os }}.json
          else
            ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate > ../expo/fingerprint-${{ matrix.os }}.json
          fi
      - name: 💾 Create fingerprint from the temp project (Windows)
        if: ${{ startsWith(matrix.os, 'windows') }}
        env:
          ACTIONS_STEP_DEBUG: ${{ secrets.ACTIONS_STEP_DEBUG }}
        shell: pwsh
        run: |
          cd ../my-app

          # Add dependencies with native binaries
          bun add -D sharp
          @"
          /** @type {import('expo/fingerprint').Config} */
          const config = {
            extraSources: [
              {
                type: 'dir',
                filePath: 'node_modules/@img',
              },
              {
                type: 'dir',
                filePath: 'node_modules/sharp',
              },
            ],
          };
          module.exports = config;
          "@ | Set-Content fingerprint.config.js

          if ($env:ACTIONS_STEP_DEBUG -eq "true") {
            node ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate --debug > ../expo/fingerprint-${{ matrix.os }}.json
          } else {
            node ../expo/packages/@expo/fingerprint/bin/cli.js fingerprint:generate > ../expo/fingerprint-${{ matrix.os }}.json
          }
      - name: 📤 Upload fingerprint
        uses: actions/upload-artifact@v4
        with:
          name: fingerprint-${{ matrix.os }}
          path: fingerprint-${{ matrix.os }}.json

  cross-platform-checks:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: 📥 Download fingerprint (macos)
        uses: actions/download-artifact@v4
        with:
          name: fingerprint-macos-latest

      - name: 📥 Download fingerprint (ubuntu)
        uses: actions/download-artifact@v4
        with:
          name: fingerprint-ubuntu-latest

      - name: 📥 Download fingerprint (windows)
        uses: actions/download-artifact@v4
        with:
          name: fingerprint-windows-latest

      - name: 👀 Check fingerprint consistency
        shell: bash
        run: |
          hash_macos=$(jq -r '.hash' fingerprint-macos-latest.json)
          hash_ubuntu=$(jq -r '.hash' fingerprint-ubuntu-latest.json)
          hash_windows=$(jq -r '.hash' fingerprint-windows-latest.json)

          echo "Hash from fingerprint-macos-latest.json: $hash_macos"
          echo "Hash from fingerprint-ubuntu-latest.json: $hash_ubuntu"
          echo "Hash from fingerprint-windows-latest.json: $hash_windows"

          if [[ "$hash_macos" != "$hash_ubuntu" || "$hash_ubuntu" != "$hash_windows" ]]; then
            echo "Hashes do not match!"
            exit 1
          else
            echo "All hashes match!"
          fi
ios-prebuild-xcframeworks matrix .github/workflows/ios-prebuild-xcframeworks.yml
Triggers
workflow_dispatch
Runs on
macos-15
Jobs
prebuild
Matrix
flavor→ Debug, Release
Actions
pnpm/action-setup
Commands
  • sudo xcode-select --switch /Applications/Xcode_26.2.app
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
  • ARGS="-f ${{ matrix.flavor }} -v" if [ -n "$INPUT_PACKAGES" ]; then ARGS="$ARGS $INPUT_PACKAGES" fi if [ "$INPUT_INCLUDE_EXTERNAL" = "true" ]; then ARGS="$ARGS --include-external" fi if [ -n "$INPUT_CONCURRENCY" ]; then ARGS="$ARGS -j $INPUT_CONCURRENCY" fi echo "Running: et prebuild $ARGS" et prebuild $ARGS
View raw YAML
name: Prebuild iOS XCFrameworks

on:
  workflow_dispatch:
    inputs:
      packages:
        description: 'Space-separated package names (leave empty for all)'
        required: false
        default: ''
      include-external:
        description: 'Include external (third-party) packages'
        required: false
        type: boolean
        default: false
      concurrency:
        description: 'Max parallel packages (default: auto)'
        required: false
        default: ''

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  prebuild:
    runs-on: macos-15
    timeout-minutes: 120
    strategy:
      matrix:
        flavor: [Debug, Release]
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: true

      - name: Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app

      - name: Add bin to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'

      - name: Install node modules
        run: pnpm install --frozen-lockfile

      - name: Prebuild XCFrameworks (${{ matrix.flavor }})
        env:
          INPUT_PACKAGES: ${{ github.event.inputs.packages }}
          INPUT_INCLUDE_EXTERNAL: ${{ github.event.inputs.include-external }}
          INPUT_CONCURRENCY: ${{ github.event.inputs.concurrency }}
        run: |
          ARGS="-f ${{ matrix.flavor }} -v"

          if [ -n "$INPUT_PACKAGES" ]; then
            ARGS="$ARGS $INPUT_PACKAGES"
          fi

          if [ "$INPUT_INCLUDE_EXTERNAL" = "true" ]; then
            ARGS="$ARGS --include-external"
          fi

          if [ -n "$INPUT_CONCURRENCY" ]; then
            ARGS="$ARGS -j $INPUT_CONCURRENCY"
          fi

          echo "Running: et prebuild $ARGS"
          et prebuild $ARGS

      - name: Upload XCFrameworks
        uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: xcframeworks-${{ matrix.flavor }}
          path: packages/precompile/.build/*/output/${{ matrix.flavor == 'Debug' && 'debug' || 'release' }}/xcframeworks/*.tar.gz
          if-no-files-found: error
          retention-days: 14
          include-hidden-files: true
ios-static-frameworks .github/workflows/ios-static-frameworks.yml
Triggers
workflow_dispatch, schedule, pull_request
Runs on
macos-15
Jobs
build
Actions
ruby/setup-ruby, pnpm/action-setup
Commands
  • sudo xcode-select --switch /Applications/Xcode_26.2.app
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • gem install cocoapods xcpretty
  • pnpm install --frozen-lockfile
  • set -o pipefail jq '.["ios.useFrameworks"] = "static" | .["ios.buildReactNativeFromSource"] = "true"' ios/Podfile.properties.json > temp.json && mv temp.json ios/Podfile.properties.json pod install --project-directory=ios xcodebuild -workspace ios/BareExpo.xcworkspace -scheme BareExpo -configuration Release -sdk iphonesimulator -derivedDataPath "ios/build" | xcpretty
View raw YAML
name: Test iOS Static Frameworks

on:
  workflow_dispatch: {}
  schedule:
    - cron: '0 0 * * SUN' # 0:00 AM UTC time every Sunday
  pull_request:
    paths:
      - .github/workflows/ios-static-frameworks.yml

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: macos-15
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 💎 Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.2.2
      - name: 💎 Install Ruby gems
        run: gem install cocoapods xcpretty
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🍏 Build iOS Project
        working-directory: ./apps/bare-expo
        run: |
          set -o pipefail
          jq '.["ios.useFrameworks"] = "static" | .["ios.buildReactNativeFromSource"] = "true"' ios/Podfile.properties.json > temp.json && mv temp.json ios/Podfile.properties.json
          pod install --project-directory=ios
          xcodebuild -workspace ios/BareExpo.xcworkspace -scheme BareExpo -configuration Release -sdk iphonesimulator -derivedDataPath "ios/build" | xcpretty
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: Static Frameworks iOS Test (minimal-tester)
ios-unit-tests .github/workflows/ios-unit-tests.yml
Triggers
workflow_dispatch, pull_request, push
Runs on
macos-15
Jobs
build
Actions
ruby/setup-ruby, pnpm/action-setup
Commands
  • sudo xcode-select --switch /Applications/Xcode_26.2.app
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • xcrun simctl list devices -j | jq -r '.devices | to_entries[] | select(.key | contains("iOS") | not) | .value[].udid' | xargs -r -n1 xcrun simctl delete xcrun simctl list devices --json xcrun simctl erase all xcrun simctl shutdown all sudo xcodebuild -runFirstLaunch
  • pnpm install --frozen-lockfile
  • pod install
  • pnpm expotools native-unit-tests --platform ios
View raw YAML
name: iOS Unit Tests

on:
  workflow_dispatch: {}
  pull_request:
    paths:
      - .github/workflows/ios-unit-tests.yml
      - apps/expo-go/ios/**
      - packages/**/ios/**
      - apps/native-tests/ios/**
      - tools/src/dynamic-macros/**
      - tools/src/commands/IosGenerateDynamicMacros.ts
      - tools/src/commands/IosNativeUnitTests.ts
      - tools/src/commands/NativeUnitTests.ts
      - secrets/**
      - fastlane/**
      - Gemfile.lock
      - '!packages/@expo/cli/**'
  push:
    branches: [main]
    paths:
      - .github/workflows/ios-unit-tests.yml
      - apps/expo-go/ios/**
      - packages/**/ios/**
      - tools/src/dynamic-macros/**
      - tools/src/commands/IosGenerateDynamicMacros.ts
      - tools/src/commands/IosNativeUnitTests.ts
      - tools/src/commands/NativeUnitTests.ts
      - secrets/**
      - fastlane/**
      - Gemfile.lock
      - '!packages/@expo/cli/**'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: macos-15
    timeout-minutes: 75
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 💎 Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.2.2
      - name: 🧹 Cleanup unused simulator runtimes
        run: |
          xcrun simctl list devices -j | jq -r '.devices | to_entries[] | select(.key | contains("iOS") | not) | .value[].udid' | xargs -r -n1 xcrun simctl delete
          xcrun simctl list devices --json
          xcrun simctl erase all
          xcrun simctl shutdown all
          sudo xcodebuild -runFirstLaunch
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          native-tests-pods: 'true'
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🥥 Install CocoaPods in `apps/native-tests/ios`
        if: steps.expo-caches.outputs.bare-expo-pods-hit != 'true'
        run: pod install
        env:
          RCT_USE_PREBUILT_RNCORE: 1
          RCT_USE_RN_DEP: 1
        working-directory: apps/native-tests/ios
      - name: 🧪 Run native iOS unit tests
        timeout-minutes: 30
        env:
          FASTLANE_SKIP_UPDATE_CHECK: '1'
          FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 30
          FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 8
        run: pnpm expotools native-unit-tests --platform ios
issue-closed .github/workflows/issue-closed.yml
Triggers
issues
Runs on
ubuntu-24.04
Jobs
run-on-issue-accepted
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools close-linear-issue-from-github --issue "${{ github.event.issue.number }}"
View raw YAML
name: Process when issue is closed

on:
  issues:
    types: [closed]

jobs:
  run-on-issue-accepted:
    runs-on: ubuntu-24.04
    if: contains(github.event.issue.labels.*.name, 'Issue accepted')
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 🔎 Close Linear issue
        # accepted issues should have a corresponding Linear issue (from issue-triage.yml)
        # if not, we don't want to fail the workflow
        continue-on-error: true
        run: pnpm expotools close-linear-issue-from-github --issue "${{ github.event.issue.number }}"
        env:
          GITHUB_TOKEN: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
issue-opened .github/workflows/issue-opened.yml
Triggers
issues
Runs on
ubuntu-24.04, ubuntu-24.04
Jobs
check-if-trusted, validate
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools validate-issue --issue "${{ github.event.issue.number }}"
View raw YAML
name: Process a newly opened issue

env:
  TRUSTED_USERS: 'RodolfoGS awinograd hesyifei LinusU SimenB actuallymentor derekstavis wscotten wcandillon tevonsb MaRaSu devashishsethia bndkt'

on:
  issues:
    types: [opened]

jobs:
  check-if-trusted:
    runs-on: ubuntu-24.04
    steps:
      - uses: ./.github/actions/slack-notify
        if: ${{ contains( env.TRUSTED_USERS, github.event.issue.user.login ) }}
        with:
          webhook: ${{ secrets.slack_webhook_expo_support }}
          channel: '#support'
          text: 'This issue should be triaged ASAP: ${{ github.event.issue.html_url }}'
          author_name: ${{ github.event.issue.user.login }}
          fields: repo
      - uses: actions/github-script@v7
        if: ${{ contains( env.TRUSTED_USERS, github.event.issue.user.login ) }}
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.addLabels({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ['!']
            })

  validate:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 🔎 Validate issue complies with requirements
        run: pnpm expotools validate-issue --issue "${{ github.event.issue.number }}"
        env:
          GITHUB_TOKEN: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
issue-stale .github/workflows/issue-stale.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-24.04
Jobs
close-issues
Actions
actions/stale
View raw YAML
name: Close inactive issues
on:
  workflow_dispatch:
  schedule:
    - cron: '0 * * * *'

jobs:
  close-issues:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/stale@v4
        with:
          ascending: false
          operations-per-run: 300
          days-before-issue-stale: 90
          days-before-issue-close: 7
          stale-issue-label: 'stale'
          exempt-issue-labels: 'Issue accepted,✋ on hold'
          stale-issue-message: 'This issue is stale because it has been open for 90 days with no activity. If there is no activity in the next 7 days, the issue will be closed.'
          close-issue-message: 'This issue was closed because it has been inactive for 7 days since being marked as stale. Please open a new issue if you believe you are encountering a related problem.'
          days-before-pr-stale: -1
          days-before-pr-close: -1
          enable-statistics: true
issue-triage .github/workflows/issue-triage.yml
Triggers
issues, pull_request_target
Runs on
ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, ubuntu-24.04
Jobs
needs-repro, needs-info, issue-accepted, question, feature-request, third-party, react-native-core, comments-on-closed, eas-build-troubleshooting, version-bump
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools import-github-issue-to-linear --issue "${{ github.event.issue.number }}" --importer "${{ github.triggering_actor }}"
View raw YAML
name: Issue Triage
on:
  issues:
    types: [labeled]
  pull_request_target:
    types: [labeled]

jobs:
  needs-repro:
    runs-on: ubuntu-24.04
    if: "${{ contains(github.event.label.name, 'incomplete issue: missing or invalid repro') }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hi there! It looks like your issue requires a minimal reproducible example, but it is invalid or absent. Please prepare such an example and share it in a new issue.

              **The best way to get attention to your issue is to provide a clean and easy way for a developer to reproduce the issue on their own machine.** Please do not provide your entire project, or a project with more code than is necessary to reproduce the issue.

              A side benefit of going through the process of narrowing down the minimal amount of code needed to reproduce the issue is that you may get lucky and discover that the bug is due to a mistake in your application code that you can quickly fix on your own.

              ### Resources

              - ["How to create a Minimal, Reproducible Example"](https://stackoverflow.com/help/minimal-reproducible-example)
              - ["How to narrow down the source of an error"](https://expo.fyi/manual-debugging)

              ### Common concerns

              #### "I've only been able to reproduce it in private, proprietary code"

              You may not have spent enough time narrowing down the root cause of the issue. Try out the techniques discussed in this [manual debugging guide](https://expo.fyi/manual-debugging) to learn how to isolate the problem from the rest of your codebase.

              #### "I didn't have time to create one"

              That's understandable, it can take some time to prepare. We ask that you hold off on filing an issue until you are able to fully complete the required fields in the issue template.

              #### "You can reproduce it by yourself by creating a project and following these steps"

              This is useful knowledge, but it's still valuable to have the resulting project that is produced from running the steps, where you have verified you can reproduce the issue.
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })

  needs-info:
    runs-on: ubuntu-24.04
    if: "${{ contains(github.event.label.name, 'incomplete issue: missing info') }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hello! There isn't enough information provided in your issue description for us to be able to help you. The best way to get help is to provide information to enable other developers to understand the issue and reproduce it on their own machines.

              Please create a new issue and fill out the entire issue template to the best of your ability. Refer to the following resources for more information on how to create a good issue report.

              ### Resources

              - ["How do I ask a good question?"](https://stackoverflow.com/help/how-to-ask)
              - ["How to create a Minimal, Reproducible Example"](https://stackoverflow.com/help/minimal-reproducible-example)
              - ["How to narrow down the source of an error"](https://expo.fyi/manual-debugging)
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })

  issue-accepted:
    runs-on: ubuntu-24.04
    permissions:
      issues: write
    if: github.event.label.name == 'issue accepted'
    steps:
      - name: Comment on issue
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Thank you for filing this issue!
              This comment acknowledges we believe this may be a bug and there’s enough information to investigate it.
              However, we can’t promise any sort of timeline for resolution. We prioritize issues based on severity, breadth of impact, and alignment with our roadmap. If you’d like to help move it more quickly, you can continue to investigate it more deeply and/or you can open a pull request that fixes the cause.
            `})
      - name: Remove "Needs Review" label
        uses: actions/github-script@v7
        with:
          script: |
            try {
              await github.rest.issues.removeLabel({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                name: ['needs review']
              })
            } catch (e) {
              if (e.status != 404) {
                throw e;
              }
            }
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 🔎 Import issue to Linear
        run: pnpm expotools import-github-issue-to-linear --issue "${{ github.event.issue.number }}" --importer "${{ github.triggering_actor }}"
        env:
          GITHUB_TOKEN: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

  question:
    runs-on: ubuntu-24.04
    if: "${{ contains(github.event.label.name, 'invalid issue: question') }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hello! Our GitHub issues are reserved for bug reports.

              If you have a question about Expo or related tools, please join our Discord at https://chat.expo.dev/

              ### Resources

              - ["How do I ask a good question?"](https://stackoverflow.com/help/how-to-ask)
              - ["Join the community"](https://docs.expo.dev/next-steps/community/)
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })

  feature-request:
    runs-on: ubuntu-24.04
    if: "${{ contains(github.event.label.name, 'invalid issue: feature request') }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hello! Our GitHub issues are reserved for bug reports.

              If you would like to request a feature, please post to https://expo.canny.io/feature-requests.
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })

  third-party:
    runs-on: ubuntu-24.04
    if: "${{ contains(github.event.label.name, 'invalid issue: third-party library') }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hi there! It looks like your issue is more closely related to a third-party tool.

              If you are able to narrow down the root cause to a bug relevant to this repository, please create an issue here with a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). Otherwise, we recommend posting this issue to the repository for the tool that you are using.

              ### Resources

              - ["How to create a Minimal, Reproducible Example"](https://stackoverflow.com/help/minimal-reproducible-example)
              - ["How to narrow down the source of an error"](https://expo.fyi/manual-debugging)
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })
  react-native-core:
    runs-on: ubuntu-24.04
    if: "${{ contains(github.event.label.name, 'invalid issue: react-native-core') }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hi there! It looks like your issue concerns code in react-native core, rather than Expo tools.

              We recommend posting this issue directly to the [react-native repository](https://github.com/facebook/react-native/issues/new/choose).
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })
  comments-on-closed:
    runs-on: ubuntu-24.04
    if: "${{ github.event.label.name == 'Comments on closed issue' }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hi there! It looks you are commenting on an issue that has been marked as resolved.

              If you are still experiencing this problem or a related one, please [open a new issue](https://github.com/expo/expo/issues/new/choose) and fill out the issue template.
            `})
            github.rest.issues.lock({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              lock_reason: 'resolved'
            })
  eas-build-troubleshooting:
    runs-on: ubuntu-24.04
    if: "${{ github.event.label.name == 'invalid issue: EAS Build troubleshooting' }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hi there! The issues in this repository are reserved for demonstrable bug reports with Expo tools.

              For assistance with your problem, refer to the [Troubleshoot build errors and crashes](https://docs.expo.dev/build-reference/troubleshooting/). If you follow that guide and you're still stuck, follow [the guide for asking a good question](https://docs.expo.dev/build-reference/troubleshooting/#how-to-ask-a-good-question) at the bottom of the page.
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })
  version-bump:
    runs-on: ubuntu-24.04
    if: "${{ github.event.label.name == 'Version bump PR' }}"
    steps:
      - uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Hi there! We appreciate your desire to contribute to the project!

              We generally do not accept PRs that bump versions of native dependencies. The Expo team handles bumping these dependencies as part of our release process for each Expo SDK. Additionally, the process for pulling in a new version and adequately requires a fair amount of context on how Expo Go works.

              We understand you may be eager to bump this version in your project, and you can do this today without any changes to this repository! You can ignore any warnings related to dependency validation with the [`expo.install.exclude`](https://docs.expo.dev/more/expo-cli/#configuring-dependency-validation) configuration in your **package.json**. You may use any version of a package that you like with [development builds](https://docs.expo.dev/develop/development-builds/create-a-build/), only the Expo Go sandbox environment is limited to pre-packaged native modules.

              If you're interested in contributing to Expo, a good way to find something to help on is the ["Issue accepted" label](https://github.com/expo/expo/issues?q=is%3Aissue+is%3Aopen+label%3A%22Issue+accepted%22). Learn more about contributing in [CONTRIBUTING.md](https://github.com/expo/expo/blob/main/CONTRIBUTING.md). Thank you!
            `})
            github.rest.issues.update({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'closed'
            })
lock perms .github/workflows/lock.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-24.04
Jobs
action
Actions
dessant/lock-threads
View raw YAML
# Configuration for Lock Threads - https://github.com/dessant/lock-threads

name: 'Lock Threads'

on:
  schedule:
    - cron: '0 0 * * MON' # 0:00 AM UTC time every Monday
  workflow_dispatch: {}

permissions:
  issues: write

concurrency:
  group: lock

jobs:
  action:
    runs-on: ubuntu-24.04
    steps:
      - uses: dessant/lock-threads@v5
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          issue-inactive-days: '180'
          add-issue-labels: 'outdated'
          exclude-any-issue-labels: 'help wanted'
          process-only: 'issues'
native-component-list .github/workflows/native-component-list.yml
Triggers
workflow_dispatch, pull_request, push
Runs on
ubuntu-24.04
Jobs
build
Actions
pnpm/action-setup
Commands
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm tsc
  • pnpm lint --max-warnings 0
View raw YAML
name: Native Component List app

on:
  workflow_dispatch: {}
  pull_request:
    paths:
      - .github/workflows/native-component-list.yml
      - apps/native-component-list/**
      - packages/**
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'
  push:
    branches: [main]
    paths:
      - .github/workflows/native-component-list.yml
      - apps/native-component-list/**
      - packages/**
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: 🛠 Compile NCL sources
        run: pnpm tsc
        working-directory: apps/native-component-list
      - name: 🚨 Lint NCL app
        run: pnpm lint --max-warnings 0
        working-directory: apps/native-component-list
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main')
        with:
          webhook: ${{ secrets.slack_webhook_api }}
          author_name: Build Native Component List
pr-labeler .github/workflows/pr-labeler.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04
Jobs
test-suite-fingerprint
Actions
pnpm/action-setup, expo/actions/fingerprint, peter-evans/find-comment
Commands
  • git fetch origin main:main --depth 100
  • pnpm install --ignore-scripts --frozen-lockfile
  • # Since we limit this pr-labeler workflow only triggered from limited paths, we should use custom base commit echo base-commit=$(git log -n 1 main --pretty=format:'%H' -- .github/workflows/pr-labeler.yml apps/bare-expo apps/test-suite packages pnpm-lock.yaml) >> "$GITHUB_OUTPUT"
  • echo "previousGitCommit=${{ steps.fingerprint.outputs.previous-git-commit }} currentGitCommit=${{ steps.fingerprint.outputs.current-git-commit }}" echo "isPreviousFingerprintEmpty=${{ steps.fingerprint.outputs.previous-fingerprint == '' }}"
View raw YAML
name: PR labeler

on:
  workflow_dispatch: {}
  push:
    branches: [main]
    paths:
      - .github/workflows/pr-labeler.yml
      - apps/bare-expo/**
      - apps/test-suite/**
      - packages/**
      - pnpm-lock.yaml
      # When adding new paths, also update the paths of the "Get the base commit" step below
  pull_request:
    types: [opened, synchronize]
    paths:
      - .github/workflows/pr-labeler.yml
      - apps/bare-expo/**
      - apps/test-suite/**
      - packages/**
      - pnpm-lock.yaml

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test-suite-fingerprint:
    runs-on: ubuntu-24.04
    if: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' }}
    # REQUIRED: limit concurrency when pushing main(default) branch to prevent conflict for this action to update its fingerprint database
    concurrency: fingerprint-${{ github.event_name != 'pull_request' && 'main' || github.run_id }}
    permissions:
      # REQUIRED: Allow comments of PRs
      pull-requests: write
      # REQUIRED: Allow updating fingerprint in action caches
      actions: write
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin main:main --depth 100
        if: github.event_name == 'pull_request'
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: Get the base commit
        id: base-commit
        run: |
          # Since we limit this pr-labeler workflow only triggered from limited paths, we should use custom base commit
          echo base-commit=$(git log -n 1 main --pretty=format:'%H' -- .github/workflows/pr-labeler.yml apps/bare-expo apps/test-suite packages pnpm-lock.yaml) >> "$GITHUB_OUTPUT"
      - name: 📷 Check fingerprint
        id: fingerprint
        uses: expo/actions/fingerprint@main
        with:
          working-directory: apps/bare-expo
          previous-git-commit: ${{ steps.base-commit.outputs.base-commit }}
          fingerprint-db-cache-path: .fingerprint.db
          fingerprint-state-output-file: ${{ runner.temp }}/fingerprint-state.json

      - name: 👀 Debug fingerprint
        run: |
          echo "previousGitCommit=${{ steps.fingerprint.outputs.previous-git-commit }} currentGitCommit=${{ steps.fingerprint.outputs.current-git-commit }}"
          echo "isPreviousFingerprintEmpty=${{ steps.fingerprint.outputs.previous-fingerprint == '' }}"

      - name: 🏷️ Labeling PR
        uses: actions/github-script@v7
        if: ${{ github.event_name == 'pull_request' && steps.fingerprint.outputs.previous-fingerprint != '' && steps.fingerprint.outputs.fingerprint-diff == '[]' }}
        with:
          script: |
            try {
              await github.rest.issues.removeLabel({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                name: ['bot: fingerprint changed']
              })
            } catch (e) {
              if (e.status != 404) {
                throw e;
              }
            }
            github.rest.issues.addLabels({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ['bot: fingerprint compatible']
            })
      - name: 🏷️ Labeling PR
        uses: actions/github-script@v7
        if: ${{ github.event_name == 'pull_request' && steps.fingerprint.outputs.previous-fingerprint != '' && steps.fingerprint.outputs.fingerprint-diff != '[]' }}
        with:
          script: |
            try {
              await github.rest.issues.removeLabel({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                name: ['bot: fingerprint compatible']
              })
            } catch (e) {
              if (e.status != 404) {
                throw e;
              }
            }
            github.rest.issues.addLabels({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ['bot: fingerprint changed']
            })

      - name: 🔍 Find old comment if it exists
        uses: peter-evans/find-comment@v3
        if: ${{ github.event_name == 'pull_request' }}
        id: old_comment
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "expo-bot"
          body-includes: <!-- pr-labeler comment -->
      - name: 💬 Add comment with fingerprint
        if: ${{ github.event_name == 'pull_request' && steps.fingerprint.outputs.previous-fingerprint != '' && steps.fingerprint.outputs.fingerprint-diff != '[]' && steps.old_comment.outputs.comment-id == '' }}
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            const state = JSON.parse(fs.readFileSync(process.env.RUNNER_TEMP + '/fingerprint-state.json', 'utf8'));
            const diff = JSON.stringify(state.diff, null, 2);
            const body = `<!-- pr-labeler comment -->
            The Pull Request introduced fingerprint changes against the base commit: ${state.previousGitCommitHash}
            <details><summary>Fingerprint diff</summary>

            \`\`\`json
            ${diff}
            \`\`\`

            </details>

            ---
            *Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
            `;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body,
            });
      - name: 💬 Update comment with fingerprint
        if: ${{ github.event_name == 'pull_request' && steps.fingerprint.outputs.previous-fingerprint != '' && steps.fingerprint.outputs.fingerprint-diff != '[]' && steps.old_comment.outputs.comment-id != '' }}
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            const state = JSON.parse(fs.readFileSync(process.env.RUNNER_TEMP + '/fingerprint-state.json', 'utf8'));
            const diff = JSON.stringify(state.diff, null, 2);
            const body = `<!-- pr-labeler comment -->
            The Pull Request introduced fingerprint changes against the base commit: ${state.previousGitCommitHash}
            <details><summary>Fingerprint diff</summary>

            \`\`\`json
            ${diff}
            \`\`\`

            </details>

            ---
            *Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
            `;

            github.rest.issues.updateComment({
              issue_number: context.issue.number,
              comment_id: '${{ steps.old_comment.outputs.comment-id }}',
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body,
            });
      - name: 💬 Delete comment with fingerprint
        if: ${{ github.event_name == 'pull_request' && steps.fingerprint.outputs.previous-fingerprint != '' && steps.fingerprint.outputs.fingerprint-diff == '[]' && steps.old_comment.outputs.comment-id != '' }}
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
          script: |
            github.rest.issues.deleteComment({
              issue_number: context.issue.number,
              comment_id: '${{ steps.old_comment.outputs.comment-id }}',
              owner: context.repo.owner,
              repo: context.repo.repo,
            });
publish-canaries .github/workflows/publish-canaries.yml
Triggers
workflow_dispatch, pull_request, schedule
Runs on
ubuntu-24.04
Jobs
publish-canaries
Actions
pnpm/action-setup
Commands
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools publish-packages --canary ${{ github.event_name != 'workflow_dispatch' && '--dry' || '' }}
View raw YAML
name: Publish Canaries

on:
  workflow_dispatch: {}
  pull_request:
    paths:
      - .github/workflows/publish-canaries.yml
  schedule:
    - cron: 0 1 * * * # Nightly at 1:00 AM UTC

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  publish-canaries:
    runs-on: ubuntu-24.04
    env:
      NODE_AUTH_TOKEN: ${{ secrets.EXPO_BOT_NPM_TOKEN }}
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event_name == 'schedule' && 'sdk-56' || github.ref }}
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
          registry-url: https://registry.npmjs.org/
      - name: 🔨 Use JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: 📦 Publish canaries
        run: pnpm expotools publish-packages --canary ${{ github.event_name != 'workflow_dispatch' && '--dry' || '' }}
        env:
          FORCE_COLOR: '2'
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && github.event_name == 'schedule'
        with:
          webhook: ${{ secrets.slack_webhook_api }}
          author_name: Publish Canaries
          fields: job,message,author,took
router .github/workflows/router.yml
Triggers
push, pull_request
Runs on
ubuntu-24.04
Jobs
router-test-types
Actions
pnpm/action-setup, oven-sh/setup-bun
Commands
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm test:types
View raw YAML
name: Router

on:
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/router.yml
      - packages/expo-router/**
      - pnpm-lock.yaml
      - '!**.md'
  pull_request:
    paths:
      - .github/workflows/router.yml
      - packages/expo-router/**
      - pnpm-lock.yaml
      - '!**.md'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  router-test-types:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: 🧪 Run types tests
        working-directory: packages/expo-router
        run: pnpm test:types
sdk .github/workflows/sdk.yml
Triggers
workflow_dispatch, push, pull_request, schedule
Runs on
ubuntu-24.04, windows-latest
Jobs
check-packages, check-packages-windows
Actions
dorny/paths-filter, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun
Commands
  • git fetch origin ${{ github.base_ref || github.event.before || 'main' }}:${{ github.base_ref || github.event.before || 'main' }} --depth 100
  • pnpm install --frozen-lockfile
  • pnpm expotools check-packages --all
  • pnpm expotools check-packages --since $SINCE --core
  • pnpm install --frozen-lockfile
  • pnpm expotools check-packages --all
View raw YAML
name: SDK

on:
  workflow_dispatch:
    inputs:
      checkAll:
        description: 'type "check-all" to force checking all packages'
        required: false
        default: ''
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/sdk.yml
      - tools/**
      - packages/**
      - pnpm-lock.yaml
      - pnpm-workspace.yaml
  pull_request:
    paths:
      - .github/workflows/sdk.yml
      - tools/**
      - packages/**
      - pnpm-lock.yaml
      - pnpm-workspace.yaml
  schedule:
    - cron: 0 14 * * *

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  check-packages:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            module-scripts:
              - 'packages/expo-module-scripts/**'
              - 'packages/jest-expo/**'
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin ${{ github.base_ref || github.event.before || 'main' }}:${{ github.base_ref || github.event.before || 'main' }} --depth 100
        if: github.event_name == 'pull_request'
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - name: 📦 Install dependencies
        run: pnpm install --frozen-lockfile
      - name: 🧐 Check all packages
        if: github.event_name == 'schedule' ||
          github.event.inputs.checkAll == 'check-all' ||
          steps.filter.outputs.module-scripts == 'true'
        run: pnpm expotools check-packages --all
      - name: 🧐 Check changed packages
        if: github.event_name != 'schedule' &&
          github.event.inputs.checkAll != 'check-all' &&
          steps.filter.outputs.module-scripts != 'true'
        env:
          SINCE: ${{ github.base_ref || github.event.before || 'main' }}
        run: pnpm expotools check-packages --since $SINCE --core
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_api }}
          author_name: Check packages

  check-packages-windows:
    runs-on: windows-latest
    if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 100
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - name: 📦 Install dependencies
        run: pnpm install --frozen-lockfile
      - name: 🧐 Check all packages
        run: pnpm expotools check-packages --all
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_api }}
          author_name: Check packages
sdk-cutoff .github/workflows/sdk-cutoff.yml
Triggers
workflow_dispatch
Runs on
ubuntu-24.04
Jobs
cut-release
Commands
  • git checkout -b sdk-${{ github.event.inputs.sdkVersion }} git push origin sdk-${{ github.event.inputs.sdkVersion }} echo "Created new SDK branch: https://github.com/${{ github.repository }}/tree/sdk-${{ github.event.inputs.sdkVersion }}"
  • echo "Creating labels for SDK ${{ github.event.inputs.sdkVersion }}" gh label create "SDK ${{ github.event.inputs.sdkVersion }}" --color "f39c12" --description "Pull request needs cherry-pick to sdk-${{ github.event.inputs.sdkVersion }} branch" gh label create "SDK ${{ github.event.inputs.sdkVersion }} cherry-picked" --color "2ecc71" --description "Pull request was cherry-picked to sdk-${{ github.event.inputs.sdkVersion }} branch"
View raw YAML
name: Cut off release branch

on:
  workflow_dispatch:
    inputs:
      sdkVersion:
        description: 'Enter the SDK version'
        required: true

jobs:
  cut-release:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: Cut release branch
        run: |
          git checkout -b sdk-${{ github.event.inputs.sdkVersion }}
          git push origin sdk-${{ github.event.inputs.sdkVersion }}
          echo "Created new SDK branch: https://github.com/${{ github.repository }}/tree/sdk-${{ github.event.inputs.sdkVersion }}"
      - name: Create SDK cherry-pick labels
        run: |
          echo "Creating labels for SDK ${{ github.event.inputs.sdkVersion }}"
          gh label create "SDK ${{ github.event.inputs.sdkVersion }}" --color "f39c12" --description "Pull request needs cherry-pick to sdk-${{ github.event.inputs.sdkVersion }} branch"
          gh label create "SDK ${{ github.event.inputs.sdkVersion }} cherry-picked" --color "2ecc71" --description "Pull request was cherry-picked to sdk-${{ github.event.inputs.sdkVersion }} branch"
        env:
          GH_TOKEN: ${{ github.token }}
sync-template .github/workflows/sync-template.yml
Triggers
workflow_dispatch, push
Runs on
ubuntu-24.04
Jobs
build
Actions
cpina/github-action-push-to-another-repository
Commands
  • mv ./templates/expo-template-default/gitignore ./templates/expo-template-default/.gitignore
View raw YAML
name: Sync App Template Repository

on:
  workflow_dispatch: {}
  push:
    branches: [sdk-54]
    paths:
      - .github/workflows/sync-template.yml
      - templates/expo-template-default/**

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: 🚚 Rename .gitignore
        run: mv ./templates/expo-template-default/gitignore ./templates/expo-template-default/.gitignore
      - name: 🚀 Push changes to the template repository
        uses: cpina/github-action-push-to-another-repository@main
        env:
          API_TOKEN_GITHUB: ${{ secrets.EXPO_BOT_GITHUB_TOKEN }}
        with:
          source-directory: 'templates/expo-template-default'
          destination-github-username: 'expo'
          destination-repository-name: 'expo-template-default'
          user-name: Expo Bot
          user-email: expo-bot@users.noreply.github.com
          target-branch: main
          commit-message: Sync the default project template from ORIGIN_COMMIT
test-react-native-nightly matrix .github/workflows/test-react-native-nightly.yml
Triggers
workflow_dispatch, schedule, push, pull_request
Runs on
macos-15, ubuntu-24.04
Jobs
ios-build, android-build
Matrix
build-type→ debug, release
Actions
pnpm/action-setup, oven-sh/setup-bun, ruby/setup-ruby, pnpm/action-setup, oven-sh/setup-bun
Commands
  • sudo xcode-select --switch /Applications/Xcode_26.2.app
  • brew install watchman || true
  • gem install cocoapods xcpretty
  • pnpm install --frozen-lockfile
  • bun src/index.ts --expo-repo ${{ github.workspace }} --no-install ${{ runner.temp }}
  • echo "Available simulators" xcrun simctl list devices available -j | jq -r '.devices | to_entries[] | select(.value | length > 0)' DEVICE_ID=$(defaults read com.apple.iphonesimulator CurrentDeviceUDID || xcrun simctl list devices available -j | jq -r '.devices | to_entries[] | select(.value | length > 0) | select(.key | startswith("com.apple.CoreSimulator.SimRuntime.iOS")) | .value[] | select(.isAvailable == true) | .udid' | head -n 1) echo "DEVICE_ID=$DEVICE_ID" >> $GITHUB_ENV
  • pod install --project-directory=ios xcodebuild -workspace ios/testnightlies.xcworkspace -scheme testnightlies -configuration $CONFIGURATION -sdk iphonesimulator -destination "id=$DEVICE_ID" -derivedDataPath "ios/build" | xcpretty
  • pnpm install --frozen-lockfile
View raw YAML
name: Test React Native nightly build for Expo Modules

on:
  workflow_dispatch: {}
  schedule:
    - cron: '0 3 * * *' # 03:00 AM UTC everyday
  push:
    branches: [main]
    paths:
      - .github/workflows/test-react-native-nightly.yml
      - packages/create-expo-nightly/**
  pull_request:
    paths:
      - .github/workflows/test-react-native-nightly.yml
      - packages/create-expo-nightly/**

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ios-build:
    strategy:
      fail-fast: true
      matrix:
        build-type: [debug, release]
    runs-on: macos-15
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: 🍺 Install required tools
        run: |
          brew install watchman || true
      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🚀 Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - name: 💎 Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.2.2
      - name: 💎 Install Ruby gems
        run: gem install cocoapods xcpretty
      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile
      - name: ⭐️ Create test-nightlies Project
        run: bun src/index.ts --expo-repo ${{ github.workspace }} --no-install ${{ runner.temp }}
        working-directory: packages/create-expo-nightly
      - name: 🍏 Query available simulator device ID
        run: |
          echo "Available simulators"
          xcrun simctl list devices available -j | jq -r '.devices | to_entries[] | select(.value | length > 0)'
          DEVICE_ID=$(defaults read com.apple.iphonesimulator CurrentDeviceUDID || xcrun simctl list devices available -j | jq -r '.devices | to_entries[] | select(.value | length > 0) | select(.key | startswith("com.apple.CoreSimulator.SimRuntime.iOS")) | .value[] | select(.isAvailable == true) | .udid' | head -n 1)
          echo "DEVICE_ID=$DEVICE_ID" >> $GITHUB_ENV
      - name: 🍏 Build iOS Project
        run: |
          pod install --project-directory=ios
          xcodebuild -workspace ios/testnightlies.xcworkspace -scheme testnightlies -configuration $CONFIGURATION -sdk iphonesimulator -destination "id=$DEVICE_ID" -derivedDataPath "ios/build" | xcpretty
        shell: bash
        working-directory: ${{ runner.temp }}/test-nightlies
        env:
          NODE_ENV: production
          CONFIGURATION: ${{ matrix.build-type == 'release' && 'Release' || 'Debug' }}
      - name: 📸 Upload builds
        uses: actions/upload-artifact@v4
        if: ${{ github.event_name == 'workflow_dispatch' && matrix.build-type == 'release' }} # Only archive release builds
        with:
          name: ios-builds-arch-${{ matrix.build-type }}
          path: ${{ runner.temp }}/test-nightlies/ios/build/**/testnightlies.app/
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main')
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: React Native Nightly (iOS)

  android-build:
    strategy:
      fail-fast: true
      matrix:
        build-type: [debug, release]
    runs-on: ubuntu-24.04
    env:
      ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64
      GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:MaxMetaspaceSize=4096m"
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🏗️ Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🚀 Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - name: 🔨 Use JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: 📦 Install node modules in root dir
        run: pnpm install --frozen-lockfile
      - name: ⭐️ Create test-nightlies Project
        run: bun src/index.ts --expo-repo ${{ github.workspace }} --no-install ${{ runner.temp }}
        working-directory: packages/create-expo-nightly
      - name: 🤖 Build Android project
        run: |
          cd android && ./gradlew ":app:assemble$VARIANT"
        shell: bash
        working-directory: ${{ runner.temp }}/test-nightlies
        env:
          NODE_ENV: production
          VARIANT: ${{ matrix.build-type == 'release' && 'Release' || 'Debug' }}
      - name: 📸 Upload builds
        uses: actions/upload-artifact@v4
        if: ${{ github.event_name == 'workflow_dispatch' && matrix.build-type == 'release' }} # Only archive release builds
        with:
          name: android-builds-${{ matrix.build-type }}
          path: ${{ runner.temp }}/test-nightlies/android/app/build/outputs/apk/
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main')
        with:
          webhook: ${{ secrets.slack_webhook_android }}
          channel: '#expo-android'
          author_name: React Native Nightly (Android)
test-suite matrix .github/workflows/test-suite.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04, macos-15, macos-15, ubuntu-24.04, ubuntu-24.04
Jobs
detect-platform-changes, ios-build, ios-test-e2e, android-build, android-test-e2e
Matrix
api-level→ 36
Actions
oven-sh/setup-bun, ruby/setup-ruby, pnpm/action-setup, expo/actions/fingerprint, expo/actions/repack-app-artifact, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, expo/actions/fingerprint, expo/actions/repack-app-artifact, oven-sh/setup-bun, pnpm/action-setup
Commands
  • sudo xcode-select --switch /Applications/Xcode_26.2.app
  • brew install watchman || true
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • ccache -z
  • pnpm install --frozen-lockfile
  • git diff Podfile.lock Pods/Manifest.lock
  • pnpm expo-modules-autolinking react-native-config --platform ios
  • git fetch origin main:main --depth 100 --no-recurse-submodules
View raw YAML
name: Test Suite e2e

on:
  workflow_dispatch: {}
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/actions/detect-platform-change/action.yml
      - .github/workflows/test-suite.yml
      - .github/actions/use-android-emulator/action.yml
      - apps/bare-expo/**
      - apps/test-suite/**
      - packages/**
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'
      - '!packages/create-expo/**'
      - '!packages/create-expo-nightly/**'
      - '!packages/create-expo-module/**'
      - '!**.md'
      - '!**/__tests__/**'
      - '!**/__mocks__/**'
  pull_request:
    paths:
      - .github/actions/detect-platform-change/action.yml
      - .github/workflows/test-suite.yml
      - .github/actions/use-android-emulator/action.yml
      - apps/bare-expo/**
      - apps/test-suite/**
      - packages/**
      - pnpm-lock.yaml
      - '!packages/@expo/cli/**'
      - '!packages/create-expo/**'
      - '!packages/create-expo-nightly/**'
      - '!packages/create-expo-module/**'
      - '!**.md'
      - '!**/__tests__/**'
      - '!**/__mocks__/**'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  detect-platform-changes:
    runs-on: ubuntu-24.04
    outputs:
      should_build_ios: ${{ steps.build.outputs.should_run_ios }}
      should_build_android: ${{ steps.build.outputs.should_run_android }}
      should_test_ios: ${{ steps.e2e.outputs.should_run_ios }}
      should_test_android: ${{ steps.e2e.outputs.should_run_android }}
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🧐 Detect build-relevant platform changes
        id: build
        uses: ./.github/actions/detect-platform-change
        with:
          android-paths: >-
            "packages/**/android/**",
            "apps/bare-expo/**/android/**"

          ios-paths: >-
            "packages/**/ios/**",
            "apps/bare-expo/**/ios/**"

          common-paths: >-
            .github/workflows/test-suite.yml,
            .github/actions/use-android-emulator/action.yml,
            "apps/bare-expo/**",
            "apps/test-suite/**",
            "packages/**",
            "!packages/@expo/cli/**",
            "!packages/{create-expo,create-expo-nightly,create-expo-module}/**",
            pnpm-lock.yaml,
            "!packages/**/{ios,android}/**",
            "!apps/bare-expo/**/{ios,android}/**"

      - name: 🧐 Detect e2e-test relevant platform changes
        id: e2e
        uses: ./.github/actions/detect-platform-change
        with:
          android-paths: >-
            "packages/expo-modules-core/**/android/**",
            "packages/expo-modules-autolinking/**/android/**",
            "packages/expo/**/android/**",
            "packages/expo-constants/**/android/**",
            "packages/expo-crypto/**/android/**",
            "packages/expo-file-system/**/android/**",
            "packages/expo-haptics/**/android/**",
            "packages/expo-image/**/android/**",
            "packages/expo-keep-awake/**/android/**",
            "packages/expo-linear-gradient/**/android/**",
            "packages/expo-localization/**/android/**",
            "packages/expo-sqlite/**/android/**",
            "packages/expo-video/**/android/**",
            "apps/bare-expo/**/android/**"

          ios-paths: >-
            "packages/expo-modules-core/**/ios/**",
            "packages/expo-modules-autolinking/**/ios/**",
            "packages/expo/**/ios/**",
            "packages/expo-constants/**/ios/**",
            "packages/expo-crypto/**/ios/**",
            "packages/expo-file-system/**/ios/**",
            "packages/expo-haptics/**/ios/**",
            "packages/expo-image/**/ios/**",
            "packages/expo-keep-awake/**/ios/**",
            "packages/expo-linear-gradient/**/ios/**",
            "packages/expo-localization/**/ios/**",
            "packages/expo-sqlite/**/ios/**",
            "packages/expo-video/**/ios/**",
            "apps/bare-expo/**/ios/**"

          common-paths: >-
            .github/workflows/test-suite.yml,
            .github/actions/use-android-emulator/action.yml,
            "apps/bare-expo/**",
            "apps/test-suite/**",
            "packages/expo-modules-core/**",
            "packages/expo-modules-autolinking/**",
            "packages/expo/**",
            "packages/expo-constants/**",
            "packages/expo-crypto/**",
            "packages/expo-file-system/**",
            "packages/expo-haptics/**",
            "packages/expo-image/**",
            "packages/expo-keep-awake/**",
            "packages/expo-linear-gradient/**",
            "packages/expo-localization/**",
            "packages/expo-sqlite/**",
            "packages/expo-video/**",
            pnpm-lock.yaml,
            "!packages/**/{ios,android}/**",
            "!apps/bare-expo/**/{ios,android}/**"
  ios-build:
    runs-on: macos-15
    needs: detect-platform-changes
    if: needs.detect-platform-changes.outputs.should_build_ios == 'true'
    permissions:
      # REQUIRED: Allow updating fingerprint in action caches
      actions: write
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: 🍺 Install required tools
        run: |
          brew install watchman || true
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 💎 Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.2.2
      - name: Setup ccache
        uses: ./.github/actions/setup-ccache
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          ccache: 'true'
          bare-expo-pods: 'true'
      - name: Reset ccache stats
        run: ccache -z
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🕵️ Debug CocoaPods lockfiles
        run: git diff Podfile.lock Pods/Manifest.lock
        working-directory: apps/bare-expo/ios
        continue-on-error: true
      - name: ⚛️ Display React Native config
        run: pnpm expo-modules-autolinking react-native-config --platform ios
        working-directory: apps/bare-expo
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin main:main --depth 100 --no-recurse-submodules
        if: github.event_name == 'pull_request'
      - name: 🔎 Get the base commit
        id: base-commit
        run: |
          # Since we limit this workflow only triggered from limited paths, we should use custom base commit
          echo base-commit=$(git log -n 1 main --pretty=format:'%H' -- .github/workflows/test-suite.yml apps/bare-expo apps/test-suite packages pnpm-lock.yaml) >> "$GITHUB_OUTPUT"
      - name: 📹 Check fingerprint
        id: fingerprint
        uses: expo/actions/fingerprint@main
        with:
          working-directory: apps/bare-expo
          previous-git-commit: ${{ steps.base-commit.outputs.base-commit }}
          fingerprint-state-output-file: ${{ runner.temp }}/fingerprint-state.json
          fingerprint-db-cache-path: .fingerprint.db
      - name: 🏗️ Build/Repack iOS project (bare-expo)
        uses: expo/actions/repack-app-artifact@main
        timeout-minutes: 55
        env:
          EXPO_DEBUG: 1
          NODE_ENV: production
          RCT_USE_PREBUILT_RNCORE: 1
          RCT_USE_RN_DEP: 1
        with:
          platform: ios
          fingerprint-state-file: ${{ runner.temp }}/fingerprint-state.json
          fingerprint-db-cache-path: .fingerprint.db
          working-directory: apps/bare-expo
          build-command: |
            pushd ios
            pod env
            pod install
            popd
            ./scripts/start-ios-e2e-test.ts --build
          build-output: apps/bare-expo/ios/build/BareExpo.app
          artifact-name: bare-expo-ios-builds
      - name: Show ccache stats
        run: ccache -s -v
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: Test Suite e2e (iOS)

  ios-test-e2e:
    needs: [ios-build, detect-platform-changes]
    if: needs.detect-platform-changes.outputs.should_test_ios == 'true'
    runs-on: macos-15
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
        with:
          submodules: true
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🌠 Download builds
        uses: actions/download-artifact@v4
        with:
          name: bare-expo-ios-builds
          path: apps/bare-expo/ios/build/BareExpo.app
      - name: 🧹 Cleanup unused simulator runtimes
        run: |
          xcrun simctl list devices -j | jq -r '.devices | to_entries[] | select(.key | contains("iOS") | not) | .value[].udid' | xargs -r -n1 xcrun simctl delete
          xcrun simctl list devices --json
          xcrun simctl erase all
          xcrun simctl shutdown all
          sudo xcodebuild -runFirstLaunch
      - name: 🍺 Install Maestro
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: Build Screen Inspector
        run: ./scripts/build.sh
        working-directory: apps/bare-expo/e2e/image-comparison/inspector
      - name: 🖼️ Start image comparison server
        run: |
          mkdir -p ~/.maestro/tests
          ./image-comparison/src/server.ts > ~/.maestro/tests/screenshot-server-logs 2>&1 &
          echo $! > /tmp/image-server.pid
          sleep 2
          if ! kill -0 $(cat /tmp/image-server.pid) 2>/dev/null; then
            echo "Image comparison server failed to start. Logs:"
            cat ~/.maestro/tests/screenshot-server-logs
            exit 1
          fi
        working-directory: apps/bare-expo/e2e
      - name: 🧪 Run e2e tests (bare-expo)
        run: ./scripts/start-ios-e2e-test.ts --test
        working-directory: apps/bare-expo
        env:
          MAESTRO_CLI_NO_ANALYTICS: 1
        timeout-minutes: 40
      - name: 📦 Prepare testing artifacts
        if: always()
        run: |
          # Rename .maestro to maestroArtifacts so it's not a hidden folder when downloaded locally
          mv ~/.maestro ~/maestroArtifacts
      - name: 📸 Store testing artifacts
        if: always()
        uses: actions/upload-artifact@v4
        id: upload-artifacts
        with:
          name: bare-expo-artifacts-ios
          path: |
            ~/maestroArtifacts/tests/**/*
            ~/Library/Logs/maestro/**/*
          overwrite: true
      - name: 🔗 Artifacts download URL
        if: always()
        run: |
          echo "Artifacts URL: ${{ steps.upload-artifacts.outputs.artifact-url }}"
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: Test Suite e2e (iOS)
          artifact_url: ${{ steps.upload-artifacts.outputs.artifact-url }}
      - name: 🛑 Stop image comparison server
        if: always()
        run: |
          if [ -f /tmp/image-server.pid ]; then
            kill $(cat /tmp/image-server.pid) 2>/dev/null || true
            rm -f /tmp/image-server.pid
          fi

  android-build:
    runs-on: ubuntu-24.04
    needs: detect-platform-changes
    if: needs.detect-platform-changes.outputs.should_build_android == 'true'
    permissions:
      # REQUIRED: Allow updating fingerprint in action caches
      actions: write
    env:
      ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64
      GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:MaxMetaspaceSize=4096m"
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Use JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: Setup ccache
        uses: ./.github/actions/setup-ccache
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          ccache: 'true'
          gradle: 'true'
          react-native-gradle-downloads: 'true'
      - name: Reset ccache stats
        run: ccache -z
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🧪 Test image comparison utilities
        run: bun test
        working-directory: apps/bare-expo/e2e/image-comparison
      - name: ⚛️ Display React Native config
        run: pnpm expo-modules-autolinking react-native-config --platform android
        working-directory: apps/bare-expo
      - name: ⬇️ Fetch commits from base branch
        run: git fetch origin main:main --depth 100 --no-recurse-submodules
        if: github.event_name == 'pull_request'
      - name: 🔎 Get the base commit
        id: base-commit
        run: |
          # Since we limit this workflow only triggered from limited paths, we should use custom base commit
          echo base-commit=$(git log -n 1 main --pretty=format:'%H' -- .github/workflows/test-suite.yml apps/bare-expo apps/test-suite packages pnpm-lock.yaml) >> "$GITHUB_OUTPUT"
      - name: 📹 Check fingerprint
        id: fingerprint
        uses: expo/actions/fingerprint@main
        with:
          working-directory: apps/bare-expo
          previous-git-commit: ${{ steps.base-commit.outputs.base-commit }}
          fingerprint-state-output-file: ${{ runner.temp }}/fingerprint-state.json
          fingerprint-db-cache-path: .fingerprint.db
      - name: 🏗️ Build/Repack Android project (bare-expo)
        uses: expo/actions/repack-app-artifact@main
        timeout-minutes: 35
        env:
          EXPO_DEBUG: 1
          NODE_ENV: production
        with:
          platform: android
          fingerprint-state-file: ${{ runner.temp }}/fingerprint-state.json
          fingerprint-db-cache-path: .fingerprint.db
          working-directory: apps/bare-expo
          build-command: |
            ./scripts/start-android-e2e-test.ts --build
          build-output: apps/bare-expo/android/app/build/outputs/apk/release
          artifact-name: bare-expo-android-builds
      - name: Show ccache stats
        run: ccache -s -v
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_android }}
          channel: '#expo-android'
          author_name: Test Suite e2e (Android)

  android-test-e2e:
    needs: [android-build, detect-platform-changes]
    if: needs.detect-platform-changes.outputs.should_test_android == 'true'
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        api-level: [36]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🌠 Download builds
        uses: actions/download-artifact@v4
        with:
          name: bare-expo-android-builds
          path: apps/bare-expo/android/app/build/outputs/apk/release
      - name: 🍺 Install Maestro
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🧪 Run e2e tests (bare-expo)
        uses: ./.github/actions/use-android-emulator
        with:
          avd-api: ${{ matrix.api-level }}
          avd-name: avd-${{ matrix.api-level }}
          script: |
            # Start image comparison server (needs adb from use-android-emulator)
            # Kill any leftover server from a previous retry
            fuser -k 7123/tcp 2>/dev/null || true
            mkdir -p ~/.maestro/tests
            LOG=~/.maestro/tests/screenshot-server-logs-$(date +%s); ./e2e/image-comparison/src/server.ts > "$LOG" 2>&1 & echo $! > /tmp/image-server.pid; sleep 2; kill -0 $(cat /tmp/image-server.pid) 2>/dev/null || { echo "Image comparison server failed to start. Logs:"; cat "$LOG"; exit 1; }
            # Run the actual tests
            ./scripts/start-android-e2e-test.ts --test
          working-directory: ./apps/bare-expo
      - name: 📦 Prepare testing artifacts
        if: always()
        run: |
          # Rename .maestro to maestroArtifacts so it's not a hidden folder when downloaded locally
          mv ~/.maestro ~/maestroArtifacts
      - name: 📸 Store testing artifacts
        if: always()
        id: upload-artifacts
        uses: actions/upload-artifact@v4
        with:
          name: bare-expo-artifacts-android
          path: |
            ~/maestroArtifacts/tests/**/*
          overwrite: true
      - name: 🔗 Artifacts download URL
        if: always()
        run: |
          echo "Artifacts URL: ${{ steps.upload-artifacts.outputs.artifact-url }}"
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_android }}
          channel: '#expo-android'
          author_name: Test Suite e2e (Android)
          artifact_url: ${{ steps.upload-artifacts.outputs.artifact-url }}
      - name: 🛑 Stop image comparison server
        if: always()
        run: |
          if [ -f /tmp/image-server.pid ]; then
            kill $(cat /tmp/image-server.pid) 2>/dev/null || true
            rm -f /tmp/image-server.pid
          fi
test-suite-brownfield-isolated matrix .github/workflows/test-suite-brownfield-isolated.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04, ubuntu-24.04, ubuntu-24.04, macos-15
Jobs
detect-platform-for-e2e, android-build, android-e2e, ios-build-and-e2e
Matrix
api-level→ 36
Actions
oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun, ruby/setup-ruby, pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
  • npx expo prebuild --clean -p android npx expo-brownfield build:android --repo MavenLocal --verbose
  • ./gradlew clean assembleDebug --refresh-dependencies
  • ./gradlew clean ./gradlew assembleRelease --refresh-dependencies
  • curl -Ls "https://get.maestro.mobile.dev" | bash echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
  • pnpm start --clear & npx wait-on http://localhost:8081 --timeout 60000
View raw YAML
name: E2E Test Suite for Expo Brownfield

on:
  workflow_dispatch: {}
  push:
    branches: [main, 'sdk-*']
    paths:
      - .github/workflows/test-suite-brownfield-isolated.yml
      - apps/brownfield-tester/expo-app/**
      - packages/expo-brownfield/**
      - '!**.md'
      - '!**/__tests__/**'
      - '!**/__mocks__/**'
  pull_request:
    paths:
      - .github/workflows/test-suite-brownfield-isolated.yml
      - apps/brownfield-tester/expo-app/**
      - packages/expo-brownfield/**
      - '!**.md'
      - '!**/__tests__/**'
      - '!**/__mocks__/**'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  detect-platform-for-e2e:
    runs-on: ubuntu-24.04
    outputs:
      should_run_ios: ${{ steps.changes.outputs.should_run_ios }}
      should_run_android: ${{ steps.changes.outputs.should_run_android }}
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🧐 Detect platform change
        id: changes
        uses: ./.github/actions/detect-platform-change
        with:
          android-paths: >-
            "packages/expo-brownfield/android/**",
            "packages/expo-brownfield/gradle-plugins/**"

          ios-paths: >-
            "packages/expo-brownfield/ios/**"

          common-paths: >-
            .github/workflows/test-suite-brownfield-isolated.yml,
            "apps/brownfield-tester/expo-app/**",
            "packages/expo-brownfield/cli/**",
            "packages/expo-brownfield/plugin/**",
            "packages/expo-brownfield/src/**",
            "packages/expo-brownfield/e2e/maestro/**"

  android-build:
    runs-on: ubuntu-24.04
    needs: detect-platform-for-e2e
    if: needs.detect-platform-for-e2e.outputs.should_run_android == 'true'
    env:
      ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64
      GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:MaxMetaspaceSize=4096m"
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Use JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          gradle: 'true'
          react-native-gradle-downloads: 'true'
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🤖 Build and publish Android artifacts (apps/brownfield-tester/expo-app)
        run: |
          npx expo prebuild --clean -p android
          npx expo-brownfield build:android --repo MavenLocal --verbose
        working-directory: apps/brownfield-tester/expo-app
      - name: 🏗️ Build Debug APK
        run: |
          ./gradlew clean assembleDebug --refresh-dependencies
        working-directory: apps/brownfield-tester/android-integrated
      - name: 💾 Store Debug APK artifact
        uses: actions/upload-artifact@v4
        with:
          name: expo-brownfield-android-debug
          path: apps/brownfield-tester/android-integrated/app/build/outputs/apk/debug/app-debug.apk
      - name: 🏗️ Build Release APK
        run: |
          ./gradlew clean
          ./gradlew assembleRelease --refresh-dependencies
        working-directory: apps/brownfield-tester/android-integrated
      - name: 💾 Store Release APK artifact
        uses: actions/upload-artifact@v4
        with:
          name: expo-brownfield-android-release
          path: apps/brownfield-tester/android-integrated/app/build/outputs/apk/release/app-release.apk

  android-e2e:
    runs-on: ubuntu-24.04
    needs: android-build
    strategy:
      matrix:
        api-level: [36]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🌠 Download build (Release)
        uses: actions/download-artifact@v4
        with:
          name: expo-brownfield-android-release
          path: apps/brownfield-tester/android-integrated/apk
      - name: 🌠 Download build (Debug)
        uses: actions/download-artifact@v4
        with:
          name: expo-brownfield-android-debug
          path: apps/brownfield-tester/android-integrated/apk
      - name: 🍺 Install Maestro
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
      - name: 🧪 Run e2e tests (Release)
        uses: ./.github/actions/use-android-emulator
        with:
          avd-api: ${{ matrix.api-level }}
          avd-name: avd-${{ matrix.api-level }}
          script: 
            ./packages/expo-brownfield/e2e/scripts/run-e2e-android.sh
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🚇 Start Metro server
        run: |
          pnpm start --clear &
          npx wait-on http://localhost:8081 --timeout 60000
        working-directory: apps/brownfield-tester/expo-app
      - name: 🧪 Run e2e tests (Debug)
        uses: ./.github/actions/use-android-emulator
        with:
          avd-api: ${{ matrix.api-level }}
          avd-name: avd-${{ matrix.api-level }}
          script: |
            CONFIGURATION=Debug ./packages/expo-brownfield/e2e/scripts/run-e2e-android.sh
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_android }}
          channel: '#expo-android'
          author_name: E2E Test Suite for Expo Brownfield

  ios-build-and-e2e:
    runs-on: macos-15
    needs: detect-platform-for-e2e
    if: needs.detect-platform-for-e2e.outputs.should_run_ios == 'true'
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: 🍺 Install required tools
        run: |
          brew install watchman || true
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 💎 Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.2.2
      - name: 🔄 Setup ccache
        uses: ./.github/actions/setup-ccache
      - name: ♻️ Restore ccache
        id: ccache-restore
        uses: actions/cache/restore@v4
        with:
          path: ${{ runner.temp }}/.ccache
          key: ccache-ios-brownfield-${{ hashFiles('pnpm-lock.yaml', '**/Podfile.lock') }}-${{ github.sha }}
          restore-keys: |
            ccache-ios-brownfield-${{ hashFiles('pnpm-lock.yaml', '**/Podfile.lock') }}-
            ccache-ios-brownfield-
      - name: 🔄 Reset ccache statistics
        run: ccache -z
      - name: 💎 Install xcodeproj gem
        run: gem install xcodeproj
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🍏 Build iOS artifacts (Release)
        run: |
          npx expo prebuild --clean -p ios
          npx expo-brownfield build:ios -r --verbose -a ../../../artifacts -p BrownfieldPackage
        working-directory: apps/brownfield-tester/expo-app
      - name: 🔨 Add XCFrameworks to SwiftUI project
        run: ruby packages/expo-brownfield/e2e/scripts/add_xcframeworks.rb
      - name: 🍺 Install Maestro
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
      - name: 🧪 Run e2e tests (Release)
        run: ./packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh
      # Tests are unstable on GitHub macOS runner - might be
      # the best to migrate them to EAS
      # - name: 🍏 Build iOS artifacts (Debug)
      #   run: |
      #     npx expo-brownfield build:ios -d --verbose -a ../../../artifacts -p BrownfieldPackage
      #   working-directory: apps/brownfield-tester/expo-app
      # - name: 🔨 Add XCFrameworks to SwiftUI project
      #   run: ruby packages/expo-brownfield/e2e/scripts/add_xcframeworks.rb
      # - name: 🚇 Start Metro server
      #   run: |
      #     yarn start --clear &
      #     npx wait-on http://localhost:8081 --timeout 60000
      #   working-directory: apps/brownfield-tester/expo-app
      # - name: 🧪 Run e2e tests (Debug)
      #   run: CONFIGURATION=Debug ./packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh
      - name: 💾 Save ccache
        if: always()
        uses: actions/cache/save@v4
        with:
          path: ${{ runner.temp }}/.ccache
          key: ${{ steps.ccache-restore.outputs.cache-primary-key }}
      - name: Show ccache stats
        run: ccache -s -v
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: E2E Test Suite for Expo Brownfield
test-suite-lint .github/workflows/test-suite-lint.yml
Triggers
workflow_dispatch, push, pull_request
Runs on
ubuntu-24.04
Jobs
lint
Actions
pnpm/action-setup
Commands
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm lint --max-warnings 0
  • pnpm test
View raw YAML
name: Test Suite Lint & Test

on:
  workflow_dispatch: {}
  push:
    branches: [main]
    paths:
      - apps/test-suite/**
  pull_request:
    paths:
      - apps/test-suite/**

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: ⬢ Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: 🚨 Lint test-suite files
        run: pnpm lint --max-warnings 0
        working-directory: apps/test-suite
      - name: 🧪 Run unit tests
        run: pnpm test
        working-directory: apps/test-suite
test-suite-macos .github/workflows/test-suite-macos.yml
Triggers
workflow_dispatch, pull_request
Runs on
macos-15
Jobs
macos-build
Actions
ruby/setup-ruby, pnpm/action-setup
Commands
  • sudo xcode-select --switch /Applications/Xcode_26.2.app
  • brew install watchman || true
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
  • git diff Podfile.lock Pods/Manifest.lock
  • pod env
  • ./scripts/setup-macos-project.sh
  • pod install
View raw YAML
name: Test Suite macOS

on:
  workflow_dispatch: {}
  pull_request:
    paths:
      - .github/workflows/test-suite-macos.yml
      - apps/bare-expo/macos/**
      - packages/expo/**
      - packages/expo-asset/**
      - packages/expo-constants/**
      - packages/expo-crypto/**
      - packages/expo-dev-client/**
      - packages/expo-dev-launcher/**
      - packages/expo-dev-menu/**
      - packages/expo-eas-client/**
      - packages/expo-file-system/**
      - packages/expo-font/**
      - packages/expo-keep-awake/**
      - packages/expo-linking/**
      - packages/expo-local-authentication/**
      - packages/expo-manifests/**
      - packages/expo-mesh-gradient/**
      - packages/expo-modules-autolinking/**
      - packages/expo-modules-core/**
      - packages/expo-sqlite/**
      - packages/expo-structured-headers/**
      - packages/expo-updates/**
      - packages/expo-updates-interface/**
      - packages/expo-web-browser/**
      - '!packages/**/android/**'
      # Common ignores from detect-platform-change action
      - '!**/*.md'
      - '!**/LICENSE*'
      - '!**/.gitignore'
      - '!**/CHANGELOG*'
      - '!**/.npmignore'
      - '!**/.eslintrc*'
      - '!**/.prettierrc*'
      - '!**/.gitattributes'
      - '!**/.watchmanconfig'
      - '!**/.fingerprintignore'
      - '!**/__tests__/**'
      - '!**/__mocks__/**'

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  macos-build:
    runs-on: macos-15
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: 🍺 Install required tools
        run: |
          brew install watchman || true
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 💎 Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.2.2
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          bare-expo-macos-pods: 'true'
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🕵️ Debug CocoaPods lockfiles
        run: git diff Podfile.lock Pods/Manifest.lock
        working-directory: apps/bare-expo/macos
        continue-on-error: true
      - name: 🌳 Display pod environment
        run: pod env
        working-directory: apps/bare-expo/macos
      - name: 🖥️ Run bare-expo macos setup script
        run: ./scripts/setup-macos-project.sh
        working-directory: apps/bare-expo/
      - name: 🥥 Install pods in apps/bare-expo/macos
        if: steps.expo-caches.outputs.bare-expo-pods-hit != 'true'
        run: pod install
        working-directory: apps/bare-expo/macos
      - name: 🏗️ Build macOS project
        run: |
          set -o pipefail
          xcodebuild -workspace macos/BareExpo-macOS.xcworkspace -scheme ExpoMacOS-macOS -sdk macosx -derivedDataPath "macos/build" | xcpretty
        working-directory: apps/bare-expo
        timeout-minutes: 55
        env:
          EXPO_DEBUG: 1
          NODE_ENV: production
      - name: 📸 Upload builds
        uses: actions/upload-artifact@v4
        with:
          name: bare-expo-macos-builds
          path: apps/bare-expo/macos/build/BareExpo.app
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-'))
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: Test Suite (macOS)
test-suite-nightly matrix .github/workflows/test-suite-nightly.yml
Triggers
workflow_dispatch, schedule, push, pull_request
Runs on
macos-15, macos-15, ubuntu-24.04, ubuntu-24.04
Jobs
ios-build, ios-test, android-build, android-test
Matrix
api-level→ 36
Actions
oven-sh/setup-bun, ruby/setup-ruby, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup, oven-sh/setup-bun, pnpm/action-setup
Commands
  • sudo xcode-select --switch /Applications/Xcode_26.2.app
  • brew install watchman || true
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --frozen-lockfile
  • et setup-react-native-nightly
  • pnpm expo-modules-autolinking react-native-config --platform ios
  • pod env
  • pod update RCT-Folly SocketRocket --no-repo-update
View raw YAML
name: Test Suite on React Native nightly build

on:
  workflow_dispatch: {}
  schedule:
    - cron: '0 10 * * SAT' # 10:00 AM UTC time every Saturday
  push:
    branches: [main]
    paths:
      - .github/workflows/test-suite-nightly.yml
      - tools/src/commands/SetupReactNativeNightly.ts
  pull_request:
    paths:
      - .github/workflows/test-suite-nightly.yml
      - tools/src/commands/SetupReactNativeNightly.ts

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ios-build:
    runs-on: macos-15
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
        with:
          submodules: true
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: 🍺 Install required tools
        run: |
          brew install watchman || true
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 💎 Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.2.2
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: ⚙️ Setup react-native nightly
        run: et setup-react-native-nightly
      - name: ⚛️ Display React Native config
        run: pnpm expo-modules-autolinking react-native-config --platform ios
        working-directory: apps/bare-expo
      - name: 🌳 Display pod environment
        run: pod env
        working-directory: apps/bare-expo/ios
      - name: 🥥 Update RCT-Folly and SocketRocket pods in apps/bare-expo/ios
        run: pod update RCT-Folly SocketRocket --no-repo-update
        working-directory: apps/bare-expo/ios
      - name: 🍏 Build iOS Project
        run: ./scripts/start-ios-e2e-test.ts --build
        working-directory: apps/bare-expo
        env:
          EXPO_DEBUG: 1
          NODE_ENV: production
      - name: 📸 Upload builds
        uses: actions/upload-artifact@v4
        with:
          name: bare-expo-ios-builds
          path: apps/bare-expo/ios/build/BareExpo.app
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main')
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: Test Suite Nightly (iOS)

  ios-test:
    needs: ios-build
    runs-on: macos-15
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
        with:
          submodules: true
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Switch to Xcode 26.2
        run: sudo xcode-select --switch /Applications/Xcode_26.2.app
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🌠 Download builds
        uses: actions/download-artifact@v4
        with:
          name: bare-expo-ios-builds
          path: apps/bare-expo/ios/build/BareExpo.app
      - name: 🧹 Cleanup unused simulator runtimes
        run: |
          xcrun simctl list devices -j | jq -r '.devices | to_entries[] | select(.key | contains("iOS") | not) | .value[].udid' | xargs -r -n1 xcrun simctl delete
          xcrun simctl list devices --json
          xcrun simctl erase all
          xcrun simctl shutdown all
          sudo xcodebuild -runFirstLaunch
      - name: 🍺 Install Maestro
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🍏 Run iOS tests
        run: ./scripts/start-ios-e2e-test.ts --test
        working-directory: apps/bare-expo
      - name: 📸 Store testing artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: bare-expo-artifacts-ios
          path: |
            ~/.maestro/tests/**/*
            ~/Library/Logs/maestro/**/*
          overwrite: true
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main')
        with:
          webhook: ${{ secrets.slack_webhook_ios }}
          channel: '#expo-ios'
          author_name: Test Suite Nightly (iOS)

  android-build:
    runs-on: ubuntu-24.04
    env:
      ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64
      GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:MaxMetaspaceSize=4096m"
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v5
        with:
          submodules: true
      - name: 🧹 Cleanup GitHub Linux runner disk space
        uses: ./.github/actions/cleanup-linux-disk-space
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🔨 Use JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
        with:
          gradle: 'true'
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: ⚙️ Setup react-native nightly
        run: et setup-react-native-nightly
      - name: ⚛️ Display React Native config
        run: pnpm expo-modules-autolinking react-native-config --platform android
        working-directory: apps/bare-expo
      - name: 🤖 Gradle prebuild for Android project (workaround to fix build error)
        run: ./gradlew preBuild
        working-directory: apps/bare-expo/android
      - name: 🤖 Build Android project
        run: ./scripts/start-android-e2e-test.ts --build
        working-directory: apps/bare-expo
      - name: 📸 Upload builds
        uses: actions/upload-artifact@v4
        with:
          name: bare-expo-android-builds
          path: apps/bare-expo/android/app/build/outputs/apk
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main')
        with:
          webhook: ${{ secrets.slack_webhook_android }}
          channel: '#expo-android'
          author_name: Test Suite Nightly (Android)

  android-test:
    needs: android-build
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        api-level: [36]
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: 🏗️ Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37
          # TODO(cedric): swap `latest` back once the issue is resolved
          bun-version: latest
      - name: 🌠 Download builds
        uses: actions/download-artifact@v4
        with:
          name: bare-expo-android-builds
          path: apps/bare-expo/android/app/build/outputs/apk
      - name: 🍺 Install Maestro
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 📦 Install node modules
        run: pnpm install --frozen-lockfile
      - name: 🤖 Run Android tests
        uses: ./.github/actions/use-android-emulator
        with:
          avd-api: ${{ matrix.api-level }}
          avd-name: avd-${{ matrix.api-level }}
          script: ./scripts/start-android-e2e-test.ts --test
          working-directory: ./apps/bare-expo
      - name: 📸 Store testing artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: bare-expo-artifacts-android
          path: |
            ~/.maestro/tests/**/*
            ~/Library/Logs/maestro/**/*
          overwrite: true
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure() && (github.event_name == 'schedule' || github.event.ref == 'refs/heads/main')
        with:
          webhook: ${{ secrets.slack_webhook_android }}
          channel: '#expo-android'
          author_name: Test Suite Nightly (Android)
validate-npm-owners .github/workflows/validate-npm-owners.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-24.04
Jobs
validate-npm-owners
Actions
pnpm/action-setup
Commands
  • echo "$(pwd)/bin" >> $GITHUB_PATH
  • pnpm install --ignore-scripts --frozen-lockfile
  • pnpm expotools validate-npm-owners
View raw YAML
name: Validate owners of packages on npm

on:
  workflow_dispatch: {}
  schedule:
    - cron: '0 8 * * *' # 8:00 AM UTC time every day

jobs:
  validate-npm-owners:
    runs-on: ubuntu-24.04
    steps:
      - name: 👀 Checkout
        uses: actions/checkout@v4
      - name: ➕ Add `bin` to GITHUB_PATH
        run: echo "$(pwd)/bin" >> $GITHUB_PATH
      - name: 🔨 Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
      - name: 🔨 Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: 📦 Install dependencies
        run: pnpm install --ignore-scripts --frozen-lockfile
      - name: ♻️ Restore caches
        uses: ./.github/actions/expo-caches
        id: expo-caches
      - name: 🔎 Validate
        run: pnpm expotools validate-npm-owners
        env:
          NPM_TOKEN_READ_ONLY: ${{ secrets.NPM_TOKEN_READ_ONLY }}
      - name: 🔔 Notify on Slack
        uses: ./.github/actions/slack-notify
        if: failure()
        with:
          webhook: ${{ secrets.slack_webhook_api }}
          author_name: Validate npm owners