From harness-claude
Configures EAS Build profiles, environment-specific app configs, OTA updates, and automated submissions for deploying React Native apps to App Store and Google Play.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:mobile-deployment-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Deploy React Native apps with EAS Build, EAS Submit, OTA updates, and automated CI/CD pipelines
Deploy React Native apps with EAS Build, EAS Submit, OTA updates, and automated CI/CD pipelines
// eas.json
{
"cli": { "version": ">= 8.0.0" },
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": { "APP_ENV": "development" },
"ios": { "simulator": true }
},
"preview": {
"distribution": "internal",
"env": { "APP_ENV": "staging" },
"channel": "preview"
},
"production": {
"env": { "APP_ENV": "production" },
"channel": "production",
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"appleId": "[email protected]",
"ascAppId": "1234567890",
"appleTeamId": "ABCDE12345"
},
"android": {
"serviceAccountKeyPath": "./play-store-key.json",
"track": "internal"
}
}
}
}
app.config.ts.const IS_PROD = process.env.APP_ENV === 'production';
const IS_STAGING = process.env.APP_ENV === 'staging';
export default {
name: IS_PROD ? 'MyApp' : IS_STAGING ? 'MyApp (Staging)' : 'MyApp (Dev)',
slug: 'my-app',
ios: {
bundleIdentifier: IS_PROD ? 'com.company.myapp' : 'com.company.myapp.dev',
},
android: {
package: IS_PROD ? 'com.company.myapp' : 'com.company.myapp.dev',
},
extra: {
apiUrl: IS_PROD
? 'https://api.company.com'
: IS_STAGING
? 'https://api.staging.company.com'
: 'https://api.dev.company.com',
},
};
# Build for production
eas build --platform all --profile production
# Submit to stores after build completes
eas submit --platform ios --profile production
eas submit --platform android --profile production
# Build and auto-submit in one command
eas build --platform all --profile production --auto-submit
npx expo install expo-updates
// app.config.ts
export default {
updates: {
url: 'https://u.expo.dev/your-project-id',
},
runtimeVersion: {
policy: 'appVersion', // or 'sdkVersion', 'fingerprint'
},
};
# Publish an OTA update to the production channel
eas update --branch production --message "Fix checkout button alignment"
# Publish to preview channel
eas update --branch preview --message "Test new onboarding flow"
import * as Updates from 'expo-updates';
async function checkForUpdates() {
if (__DEV__) return; // Updates do not work in development
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
// Restart to apply the update
await Updates.reloadAsync();
}
} catch (error) {
console.error('Update check failed:', error);
}
}
// Check on app foreground
useEffect(() => {
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') checkForUpdates();
});
return () => subscription.remove();
}, []);
# .github/workflows/build.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm test
build:
needs: 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 }
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: npm ci
- run: eas build --platform all --profile production --non-interactive --auto-submit
update:
needs: test
if: github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[native]')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: npm ci
- run: eas update --branch production --message "${{ github.event.head_commit.message }}"
autoIncrement in EAS Build for automatic build number bumps. Semantic version the version field in app.config.ts.# Bump version
npm version patch # 1.0.0 -> 1.0.1
npm version minor # 1.0.0 -> 1.1.0
npm version major # 1.0.0 -> 2.0.0
eas build --platform ios --profile preview
# Install via QR code or direct link from EAS dashboard
OTA update limitations: OTA updates can only change JavaScript and assets. Changes to native code (new native modules, permission changes, SDK upgrades) require a new native build through the stores. Use runtimeVersion with fingerprint policy to automatically detect when a native build is needed.
App Store review tips:
Google Play review: Initial review takes hours to days. Use internal testing tracks for team testing, closed testing for beta users, and production for release.
Release strategy:
Common mistakes:
runtimeVersion when native dependencies changehttps://docs.expo.dev/deploy/build-project/
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeDeploys Expo apps to production via EAS Build, app store submission, and OTA updates. Use when publishing to iOS/Android or managing release channels.
Deploys Expo apps to iOS App Store, Android Play Store, TestFlight, and web hosting using EAS CLI. Guides builds, submissions, eas.json configs, and CI/CD workflows.
Deploys Expo apps to production via app stores (iOS App Store, Google Play) and OTA updates. Guides builds, submissions, release channels, and optimization.