expo/expo
40 workflows · maturity 67% · 9 patterns · GitHub ↗
Practices
✓ Matrix✓ Permissions○ Security scan○ AI review✓ Cache✓ Concurrency○ Reusable workflows
Detected patterns
Security dimensions
Workflows (40)
android-instrumentation-tests matrix .github/workflows/android-instrumentation-tests.yml
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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