| name | mobile-ci-cd |
|---|---|
| description | Set up CI/CD pipelines for React Native/Expo or Flutter apps. Covers GitHub Actions workflows, EAS Build integration, build caching, code signing in CI, secrets management, PR preview builds, and conditional platform builds. Use when the user wants automated builds, tests, or deployments on push or pull request. |
| standards-version | 1.7.0 |
Use this skill when the user:
- Wants automated builds or tests on push or PR
- Asks about GitHub Actions, EAS Build, or CI/CD for mobile
- Needs code signing (certificates, keystores) in CI
- Mentions "continuous integration", "CI", "CD", "pipeline", "automated build", "github actions", or "deploy on merge"
- Wants PR preview builds or staged deployment
- Framework: Expo (React Native) or Flutter
- CI provider: GitHub Actions (default and recommended)
- Build targets: iOS, Android, or both
- Deployment target: TestFlight, Play Console internal track, EAS Update, or none (CI only)
-
Generate a basic CI workflow. Use the MCP tool for a starter config:
The
mobile_setupCItool creates.github/workflows/ci.ymlwith lint, type check, and test steps. Customize it after generation. -
Expo CI workflow structure. A production-grade pipeline:
name: CI on: push: branches: [main] pull_request: branches: [main] jobs: lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - name: Type check run: npx tsc --noEmit - name: Lint run: npx expo lint - name: Test run: npx jest --ci --coverage eas-build: needs: lint-and-test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - uses: expo/expo-github-action@v8 with: eas-version: latest token: ${{ secrets.EXPO_TOKEN }} - name: Build run: eas build --platform all --profile production --non-interactive
-
Flutter CI workflow structure.
name: CI on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable cache: true - run: flutter pub get - run: flutter analyze - run: flutter test --coverage build-android: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable cache: true - run: flutter pub get - run: flutter build apk --release build-ios: needs: test if: github.ref == 'refs/heads/main' runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable cache: true - run: flutter pub get - run: flutter build ios --release --no-codesign
-
Manage secrets for code signing. Store credentials as GitHub encrypted secrets:
Secret Purpose How to get it EXPO_TOKENEAS CLI auth eas credentialsor expo.dev dashboardKEYSTORE_BASE64Android signing keystore base64 -i release.keystoreKEYSTORE_PASSWORDKeystore password From your key generation step KEY_ALIASKey alias From your key generation step KEY_PASSWORDKey password From your key generation step For Expo/EAS projects, code signing is managed by EAS. You only need
EXPO_TOKEN.For bare React Native or Flutter, decode the keystore in CI:
- name: Decode keystore run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/release.keystore
-
Cache dependencies for faster builds. Node modules and Flutter SDK caching:
# Node (already handled by setup-node cache: npm) - uses: actions/setup-node@v4 with: node-version: 20 cache: npm # Flutter (already handled by flutter-action cache: true) - uses: subosito/flutter-action@v2 with: channel: stable cache: true # Gradle cache for Android builds - uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} # CocoaPods cache for iOS builds - uses: actions/cache@v4 with: path: ios/Pods key: pods-${{ hashFiles('ios/Podfile.lock') }}
-
Add PR preview builds with EAS. Build a preview version on every PR:
Add a
previewprofile ineas.json:{ "build": { "preview": { "distribution": "internal", "channel": "preview", "ios": { "simulator": true } } } }Add a workflow job:
preview: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - uses: expo/expo-github-action@v8 with: eas-version: latest token: ${{ secrets.EXPO_TOKEN }} - run: eas build --platform all --profile preview --non-interactive
-
Conditional platform builds. Only build the platform that changed:
changes: runs-on: ubuntu-latest outputs: ios: ${{ steps.filter.outputs.ios }} android: ${{ steps.filter.outputs.android }} steps: - uses: dorny/paths-filter@v3 id: filter with: filters: | ios: - 'ios/**' - 'Podfile.lock' android: - 'android/**' - 'build.gradle'
-
Deploy on merge. Submit to stores automatically when merging to main:
deploy: needs: eas-build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - uses: expo/expo-github-action@v8 with: eas-version: latest token: ${{ secrets.EXPO_TOKEN }} - run: eas submit --platform all --profile production --non-interactive
- Expo: GitHub Actions
- expo-github-action
- EAS Build profiles
- Flutter: CI/CD
- GitHub Actions: Encrypted secrets
User: "Set up CI for my Expo app. I want tests on every PR and a production build when I merge to main."
Agent:
- Runs
mobile_setupCIwith framework=expo, platforms=both, include_tests=true, include_eas_build=true - Reviews the generated
.github/workflows/ci.yml - Adds a preview build job that triggers on pull_request
- Adds Gradle and CocoaPods cache steps for faster builds
- Instructs user to add
EXPO_TOKENsecret in GitHub repo settings - Suggests adding branch protection rules requiring the
lint-and-testjob to pass - Commits the workflow file
| Step | MCP Tool | Description |
|---|---|---|
| Generate workflow | mobile_setupCI |
Create GitHub Actions CI/CD workflow file |
| Run tests locally first | mobile_runTests |
Verify tests pass before pushing to CI |
| Validate build config | mobile_checkBuildHealth |
Check that app.json and dependencies are valid |
| Build for store | mobile_buildForStore |
Trigger a production EAS Build locally or in CI |
| Submit to stores | mobile_submitToAppStore, mobile_submitToPlayStore |
Automate store submission from CI |
- Committing secrets to the repo - Never hardcode
EXPO_TOKEN, keystores, or API keys in workflow files. Use GitHub encrypted secrets. - Using ubuntu for iOS builds - iOS builds require macOS runners. Use
runs-on: macos-latestfor Xcode and simulator steps. EAS Build handles this for you. - No dependency caching - Without caching,
npm ciandflutter pub getdownload everything on each run. Use thecacheoption in setup actions. - Building on every push to every branch - Limit triggers to
mainand pull requests. Building on feature branch pushes wastes CI minutes. - Not pinning action versions - Use
@v4not@latestfor third-party actions. Unpinned versions can break without warning. - Skipping type checks in CI -
npx tsc --noEmitcatches type errors that developers may ignore locally. Always include it. - EAS Build timeout - EAS Build can take 10-30 minutes. Set appropriate job timeouts and do not block PR merges on build completion.
- Mobile Testing - unit and integration test setup
- Mobile E2E Testing - end-to-end tests to run in CI
- Mobile iOS Submission - EAS Submit for App Store
- Mobile Android Submission - EAS Submit for Play Console