Mobile Release Integration
This guide covers how to integrate mobile app releases with your CI/CD pipeline, including automated version management, release coordination, and deployment strategies.
Release Integration Overview
Section titled “Release Integration Overview”The PersiaNation mobile app uses an integrated release process that coordinates:
- Version Management: Automated version bumping and tagging
- Build Automation: Triggered builds on release events
- Store Submission: Automated app store deployments
- OTA Coordination: Managing over-the-air updates alongside releases
- Release Notes: Automated changelog generation
Release Workflow Integration
Section titled “Release Workflow Integration”1. Automated Version Management
Section titled “1. Automated Version Management”Version Bump Workflow
Section titled “Version Bump Workflow”Create .github/workflows/version-bump.yml:
name: Version Bumpon: workflow_dispatch: inputs: version_type: description: "Version bump type" required: true default: "patch" type: choice options: - major - minor - patch - prerelease
jobs: version-bump: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0
- uses: actions/setup-node@v4 with: node-version: 18.x
- uses: pnpm/action-setup@v2 with: version: latest
- name: Install dependencies run: pnpm install
- name: Configure git run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Bump version run: | # Update package.json version npm version ${{ github.event.inputs.version_type }} --no-git-tag-version
# Get new version NEW_VERSION=$(node -p "require('./package.json').version") echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
# Update app.config.ts version sed -i "s/VERSION: '.*'/VERSION: '$NEW_VERSION'/" env.js
- name: Update build number run: | # Increment build number CURRENT_BUILD=$(grep -o "BUILD_NUMBER: '[^']*'" env.js | sed "s/BUILD_NUMBER: '//;s/'//") NEW_BUILD=$((CURRENT_BUILD + 1)) sed -i "s/BUILD_NUMBER: '.*'/BUILD_NUMBER: '$NEW_BUILD'/" env.js echo "NEW_BUILD=$NEW_BUILD" >> $GITHUB_ENV
- name: Commit changes run: | git add package.json env.js git commit -m "chore: bump version to $NEW_VERSION (build $NEW_BUILD)" git push origin main
- name: Create release tag run: | git tag "v$NEW_VERSION" git push origin "v$NEW_VERSION"
- name: Create release PR run: | gh pr create \ --title "Release v$NEW_VERSION" \ --body "Automated release preparation for version $NEW_VERSION" \ --base main \ --head main env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Automatic Version Detection
Section titled “Automatic Version Detection”import { execSync } from "child_process";import { readFileSync } from "fs";
export function getCurrentVersion(): string { const packageJson = JSON.parse(readFileSync("package.json", "utf8")); return packageJson.version;}
export function getNextVersion(type: "major" | "minor" | "patch"): string { const current = getCurrentVersion(); const [major, minor, patch] = current.split(".").map(Number);
switch (type) { case "major": return `${major + 1}.0.0`; case "minor": return `${major}.${minor + 1}.0`; case "patch": return `${major}.${minor}.${patch + 1}`; default: throw new Error(`Invalid version type: ${type}`); }}
export function getBuildNumber(): number { try { const gitCount = execSync("git rev-list --count HEAD", { encoding: "utf8", }); return parseInt(gitCount.trim()); } catch { return Date.now(); }}2. Release-Triggered Builds
Section titled “2. Release-Triggered Builds”Production Build on Release
Section titled “Production Build on Release”Create .github/workflows/release-build.yml:
name: Release Buildon: release: types: [published]
jobs: build-and-submit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: actions/setup-node@v4 with: node-version: 18.x
- uses: pnpm/action-setup@v2 with: version: latest
- uses: expo/expo-github-action@v8 with: eas-version: latest token: ${{ secrets.EXPO_TOKEN }}
- name: Install dependencies run: pnpm install
- name: Build for production run: | eas build \ --platform all \ --profile production \ --non-interactive \ --wait
- name: Submit to stores if: github.event.release.prerelease == false run: | eas submit \ --platform all \ --profile production \ --latest \ --non-interactive
- name: Deploy OTA update run: | eas update \ --branch production-branch \ --message "Release ${{ github.event.release.tag_name }}" \ --auto
- name: Update release with build info run: | # Get build URLs BUILD_INFO=$(eas build:list --limit=1 --json)
# Update GitHub release with build information gh release edit ${{ github.event.release.tag_name }} \ --notes "${{ github.event.release.body }}
## Build Information - iOS Build: [View in EAS](https://expo.dev/builds/...) - Android Build: [View in EAS](https://expo.dev/builds/...) - OTA Update: Deployed to production channel" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}3. Coordinated Release Strategy
Section titled “3. Coordinated Release Strategy”Multi-Environment Release Pipeline
Section titled “Multi-Environment Release Pipeline”name: Release Pipelineon: push: tags: - "v*"
jobs: validate: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} is-prerelease: ${{ steps.version.outputs.prerelease }} steps: - uses: actions/checkout@v4
- name: Extract version info id: version run: | VERSION=${GITHUB_REF#refs/tags/v} echo "version=$VERSION" >> $GITHUB_OUTPUT
if [[ $VERSION == *"-"* ]]; then echo "prerelease=true" >> $GITHUB_OUTPUT else echo "prerelease=false" >> $GITHUB_OUTPUT fi
staging-build: needs: validate if: needs.validate.outputs.is-prerelease == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: expo/expo-github-action@v8 with: token: ${{ secrets.EXPO_TOKEN }}
- name: Build staging run: | eas build \ --platform all \ --profile staging \ --non-interactive
production-build: needs: validate if: needs.validate.outputs.is-prerelease == 'false' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: expo/expo-github-action@v8 with: token: ${{ secrets.EXPO_TOKEN }}
- name: Build production run: | eas build \ --platform all \ --profile production \ --non-interactive \ --wait
- name: Submit to stores run: | eas submit \ --platform all \ --profile production \ --latest \ --non-interactive4. Release Notes Automation
Section titled “4. Release Notes Automation”Automated Changelog Generation
Section titled “Automated Changelog Generation”name: Generate Release Noteson: release: types: [created]
jobs: generate-notes: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0
- name: Generate changelog id: changelog run: | # Get commits since last release LAST_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then COMMITS=$(git log $LAST_TAG..HEAD --pretty=format:"- %s (%h)" --grep="feat:" --grep="fix:" --grep="BREAKING CHANGE:") else COMMITS=$(git log --pretty=format:"- %s (%h)" --grep="feat:" --grep="fix:" --grep="BREAKING CHANGE:") fi
# Categorize commits FEATURES=$(echo "$COMMITS" | grep "feat:" || echo "") FIXES=$(echo "$COMMITS" | grep "fix:" || echo "") BREAKING=$(echo "$COMMITS" | grep "BREAKING CHANGE:" || echo "")
# Generate release notes NOTES="## What's New
$(if [ -n "$FEATURES" ]; then echo "### ✨ New Features"; echo "$FEATURES"; echo ""; fi) $(if [ -n "$FIXES" ]; then echo "### 🐛 Bug Fixes"; echo "$FIXES"; echo ""; fi) $(if [ -n "$BREAKING" ]; then echo "### ⚠️ Breaking Changes"; echo "$BREAKING"; echo ""; fi)
## Installation
### iOS - Download from the App Store: [PersiaNation](https://apps.apple.com/app/...) - TestFlight (Beta): [Join Beta](https://testflight.apple.com/join/...)
### Android - Download from Google Play: [PersiaNation](https://play.google.com/store/apps/details?id=...) - Internal Testing: Contact team for access
## Technical Details
- **Version**: ${{ github.event.release.tag_name }} - **Build Date**: $(date -u +"%Y-%m-%d %H:%M:%S UTC") - **Commit**: ${{ github.sha }} - **EAS Build**: [View builds](https://expo.dev/builds) "
# Save to file for multiline output echo "$NOTES" > release_notes.md
- name: Update release run: | gh release edit ${{ github.event.release.tag_name }} \ --notes-file release_notes.md env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}5. OTA Update Coordination
Section titled “5. OTA Update Coordination”Smart OTA Deployment
Section titled “Smart OTA Deployment”import { execSync } from "child_process";
interface DeploymentConfig { branch: string; channel: string; message: string; rollout?: number;}
export async function deployOTA(config: DeploymentConfig) { const { branch, channel, message, rollout = 100 } = config;
console.log(`Deploying OTA update to ${channel} channel...`);
try { // Deploy update const command = `eas update --branch ${branch} --message "${message}" --auto`; execSync(command, { stdio: "inherit" });
// If gradual rollout is specified if (rollout < 100) { console.log(`Setting gradual rollout to ${rollout}%`); execSync(`eas update:configure --branch ${branch} --rollout ${rollout}`, { stdio: "inherit", }); }
console.log("OTA update deployed successfully!");
// Get update info const updateInfo = execSync( `eas update:list --branch ${branch} --limit 1 --json`, { encoding: "utf8", } );
return JSON.parse(updateInfo)[0]; } catch (error) { console.error("OTA deployment failed:", error); throw error; }}
// Usage in release workflowexport async function coordinateRelease(version: string) { // Deploy to staging first await deployOTA({ branch: "staging-branch", channel: "staging", message: `Release ${version} - Staging deployment`, });
// Wait for QA approval (this would be manual step) console.log("Waiting for QA approval...");
// Deploy to production with gradual rollout await deployOTA({ branch: "production-branch", channel: "production", message: `Release ${version}`, rollout: 25, // Start with 25% rollout });
// Schedule full rollout after monitoring period setTimeout(async () => { await deployOTA({ branch: "production-branch", channel: "production", message: `Release ${version} - Full rollout`, rollout: 100, }); }, 24 * 60 * 60 * 1000); // 24 hours}6. Release Monitoring & Rollback
Section titled “6. Release Monitoring & Rollback”Automated Health Checks
Section titled “Automated Health Checks”name: Release Health Checkon: release: types: [published] schedule: - cron: "*/15 * * * *" # Every 15 minutes
jobs: health-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Check app health metrics run: | # Check crash rates from app store APIs # Check user feedback and ratings # Monitor error reporting services
CRASH_RATE=$(curl -s "https://api.appstoreconnect.apple.com/v1/apps/.../crashRates" \ -H "Authorization: Bearer ${{ secrets.APP_STORE_CONNECT_TOKEN }}" \ | jq '.data[0].attributes.crashRate')
if (( $(echo "$CRASH_RATE > 0.05" | bc -l) )); then echo "High crash rate detected: $CRASH_RATE" exit 1 fi
- name: Rollback if unhealthy if: failure() run: | # Rollback OTA update eas update:rollback --branch production-branch --group-id previous
# Notify team curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \ -H 'Content-type: application/json' \ --data '{"text":"🚨 Release rollback triggered due to health check failure"}'7. Release Dashboard Integration
Section titled “7. Release Dashboard Integration”Custom Release Dashboard
Section titled “Custom Release Dashboard”interface ReleaseMetrics { version: string; buildStatus: "pending" | "building" | "completed" | "failed"; otaStatus: "pending" | "deployed" | "rolled-back"; storeStatus: "pending" | "review" | "approved" | "rejected"; crashRate: number; adoptionRate: number;}
export class ReleaseDashboard { async getMetrics(version: string): Promise<ReleaseMetrics> { const [buildInfo, otaInfo, storeInfo, crashInfo] = await Promise.all([ this.getBuildStatus(version), this.getOTAStatus(version), this.getStoreStatus(version), this.getCrashMetrics(version), ]);
return { version, buildStatus: buildInfo.status, otaStatus: otaInfo.status, storeStatus: storeInfo.status, crashRate: crashInfo.rate, adoptionRate: otaInfo.adoptionRate, }; }
async generateReport(version: string): Promise<string> { const metrics = await this.getMetrics(version);
return `# Release Report: ${version}
## Build Status: ${metrics.buildStatus}- iOS: ${metrics.buildStatus === "completed" ? "✅" : "⏳"}- Android: ${metrics.buildStatus === "completed" ? "✅" : "⏳"}
## OTA Deployment: ${metrics.otaStatus}- Adoption Rate: ${(metrics.adoptionRate * 100).toFixed(1)}%- Status: ${metrics.otaStatus === "deployed" ? "✅" : "⏳"}
## Store Status: ${metrics.storeStatus}- iOS App Store: ${metrics.storeStatus}- Google Play: ${metrics.storeStatus}
## Health Metrics- Crash Rate: ${(metrics.crashRate * 100).toFixed(2)}%- Status: ${metrics.crashRate < 0.05 ? "✅ Healthy" : "⚠️ Monitoring"}
---*Generated at ${new Date().toISOString()}* `.trim(); }}Integration Best Practices
Section titled “Integration Best Practices”1. Release Planning
Section titled “1. Release Planning”- Schedule regular releases: Weekly or bi-weekly cadence
- Feature freeze periods: Stop new features before releases
- QA cycles: Dedicated testing time for each release
- Rollback procedures: Always have a rollback plan ready
2. Automation Guidelines
Section titled “2. Automation Guidelines”- Fail fast: Stop the pipeline on critical failures
- Gradual rollouts: Start with small percentages
- Monitoring: Watch metrics closely after releases
- Communication: Keep stakeholders informed
3. Version Strategy
Section titled “3. Version Strategy”- Semantic versioning: MAJOR.MINOR.PATCH format
- Build numbers: Increment for each build
- Branch strategy: Use release branches for stability
- Tag consistency: Always tag releases in git
4. Quality Gates
Section titled “4. Quality Gates”- All tests pass: No releases with failing tests
- Code review: Require reviews for release branches
- Performance benchmarks: Meet performance criteria
- Security scans: Pass security vulnerability checks
Troubleshooting Release Integration
Section titled “Troubleshooting Release Integration”Common Issues
Section titled “Common Issues”-
Build failures during release
- Check EAS build logs
- Verify environment variables
- Ensure dependencies are locked
-
OTA update conflicts
- Check channel configurations
- Verify branch linkage
- Review update compatibility
-
Store submission rejections
- Review app store guidelines
- Check metadata and screenshots
- Verify app signing certificates
Debug Commands
Section titled “Debug Commands”# Check release statusgh release listgh release view v1.2.3
# Monitor EAS buildseas build:list --limit 10eas build:view <build-id>
# Check OTA updateseas update:list --branch production-brancheas channel:list
# Verify store submissionseas submit:listRelated Documentation
Section titled “Related Documentation”- Mobile App Release & CI/CD: Main release guide
- Mobile CI/CD Workflows: Detailed workflow documentation
- EAS Documentation: Official Expo documentation