code/packages/native-ci/README.md
Native CI/CD helpers for React Native apps with Expo. Provides fingerprint-based build caching and Detox test runners for GitHub Actions.
@expo/fingerprint to detect when native rebuilds are needednpm install @tamagui/native-ci
# or
yarn add @tamagui/native-ci
# or
bun add @tamagui/native-ci
# Generate fingerprint for a platform
npx @tamagui/native-ci fingerprint ios
npx @tamagui/native-ci fingerprint android
# Generate pre-fingerprint hash (fast)
npx @tamagui/native-ci pre-hash yarn.lock app.json
# Generate cache key
npx @tamagui/native-ci cache-key ios <fingerprint>
# KV operations (requires env vars)
npx @tamagui/native-ci kv-get <key>
npx @tamagui/native-ci kv-set <key> <value>
--project-root <path> - Project root directory (default: cwd)--prefix <prefix> - Cache key prefix (default: native-build)--github-output - Output results for GitHub Actions--json - Output as JSONKV_STORE_REDIS_REST_URL - Redis REST API URL for fingerprint cachingKV_STORE_REDIS_REST_TOKEN - Redis REST API tokenRun Detox tests with Metro bundler and proper cleanup:
# iOS
bun run node_modules/@tamagui/native-ci/src/run-detox-ios.ts \
--project-root ./my-app \
--config ios.sim.debug
# Android
bun run node_modules/@tamagui/native-ci/src/run-detox-android.ts \
--project-root ./my-app \
--config android.emu.ci.debug \
--headless
| Option | Description | Default |
|---|---|---|
--config | Detox configuration name | ios.sim.debug / android.emu.ci.debug |
--project-root | Project root directory | Current directory |
--record-logs | Log recording: none, failing, all | all |
--retries | Number of test retries | 0 |
--headless | Run in headless mode (Android only) | false |
- name: Generate Fingerprint
uses: tamagui/tamagui/code/packages/native-ci/actions/fingerprint@main
id: fingerprint
with:
platform: ios # or android
project-root: ./my-app
kv-url: ${{ secrets.KV_STORE_REDIS_REST_URL }}
kv-token: ${{ secrets.KV_STORE_REDIS_REST_TOKEN }}
- name: Use fingerprint
run: echo "Fingerprint: ${{ steps.fingerprint.outputs.fingerprint }}"
| Input | Description | Default |
|---|---|---|
platform | Platform (ios or android) | Required |
project-root | Path to Expo project | . |
cache-prefix | Prefix for cache keys | native-build |
kv-url | Redis KV REST URL (optional) | - |
kv-token | Redis KV REST token (optional) | - |
pre-hash-files | Files for pre-fingerprint hash | yarn.lock,package-lock.json,app.json |
| Output | Description |
|---|---|
fingerprint | Generated fingerprint hash |
cache-key | Cache key for this build |
pre-fingerprint-hash | Quick pre-fingerprint hash |
cache-hit | Whether fingerprint was cached |
- name: Run iOS Detox Tests
uses: tamagui/tamagui/code/packages/native-ci/actions/test-detox-ios@main
with:
working-directory: ./my-app
config: ios.sim.debug
app-path: ${{ env.IOS_APP_PATH }}
| Input | Description | Default |
|---|---|---|
project-root | Path to project root | . |
working-directory | Working directory for tests | . |
config | Detox configuration name | ios.sim.debug |
record-logs | Log recording: none, failing, all | all |
retries | Number of test retries | 0 |
simulator | iOS simulator device type | iPhone 15 |
app-path | Path to built app (optional) | - |
- name: Run Android Detox Tests
uses: tamagui/tamagui/code/packages/native-ci/actions/test-detox-android@main
with:
working-directory: ./my-app
config: android.emu.ci.debug
| Input | Description | Default |
|---|---|---|
project-root | Path to project root | . |
working-directory | Working directory for tests | . |
config | Detox configuration name | android.emu.ci.debug |
record-logs | Log recording: none, failing, all | all |
retries | Number of test retries | 0 |
api-level | Android API level | 30 |
emulator-options | Emulator options | See defaults |
import {
// Fingerprinting
generateFingerprint,
generatePreFingerprintHash,
// Caching
createCacheKey,
saveFingerprintToKV,
getFingerprintFromKV,
// Build runner
runWithCache,
// Metro utilities
withMetro,
waitForMetro,
// Detox utilities
runDetoxTests,
parseDetoxArgs,
// Android utilities
setupAndroidDevice,
// Constants
METRO_PORT,
DETOX_SERVER_PORT,
} from '@tamagui/native-ci'
// Generate fingerprint
const { hash } = await generateFingerprint({
platform: 'ios',
projectRoot: './my-app',
})
// Run build with caching
const result = await runWithCache({
platform: 'ios',
buildCommand: 'xcodebuild ...',
outputPaths: ['./ios/build'],
})
// Run tests with Metro
const exitCode = await withMetro('ios', async () => {
return runDetoxTests({
config: 'ios.sim.debug',
projectRoot: './my-app',
recordLogs: 'failing',
retries: 0,
})
})
name: Native Tests
on:
push:
branches: [main]
pull_request:
jobs:
build-ios:
runs-on: macos-14
outputs:
cache-key: ${{ steps.fingerprint.outputs.cache-key }}
steps:
- uses: actions/checkout@v4
- name: Generate Fingerprint
uses: tamagui/tamagui/code/packages/native-ci/actions/fingerprint@main
id: fingerprint
with:
platform: ios
project-root: ./my-app
- name: Check Build Cache
uses: actions/cache/restore@v4
id: cache
with:
path: ./my-app/ios/build
key: ${{ steps.fingerprint.outputs.cache-key }}
lookup-only: true
- name: Build iOS App
if: steps.cache.outputs.cache-hit != 'true'
run: |
cd my-app
npx expo prebuild --platform ios
xcodebuild -workspace ios/*.xcworkspace ...
- name: Save Build Cache
if: steps.cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ./my-app/ios/build
key: ${{ steps.fingerprint.outputs.cache-key }}
test-ios:
needs: build-ios
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Restore Build
uses: actions/cache/restore@v4
with:
path: ./my-app/ios/build
key: ${{ needs.build-ios.outputs.cache-key }}
- name: Run Tests
uses: tamagui/tamagui/code/packages/native-ci/actions/test-detox-ios@main
with:
working-directory: ./my-app
yarn.lock, app.json, etc.@expo/fingerprint for accurate native dependency detectionThis 2-level approach means:
src/
├── constants.ts # Shared constants and types
├── fingerprint.ts # Fingerprint generation
├── cache.ts # KV store and local cache
├── runner.ts # Build runner with caching
├── metro.ts # Metro bundler utilities
├── detox.ts # Detox test utilities
├── android.ts # Android-specific utilities
├── cli.ts # CLI entry point
├── run-detox-ios.ts # iOS test runner script
└── run-detox-android.ts # Android test runner script
MIT