diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 78f28da..44bbc4f 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -39,6 +39,12 @@ "description": "Incremental migration strategy for adopting React Native or Expo in native iOS/Android apps using @callstack/react-native-brownfield, including initial setup, packaging, and phased host integration.", "source": "./", "skills": ["./skills/react-native-brownfield-migration"] + }, + { + "name": "deployment", + "description": "React Native deployment and release workflows for App Store and Play Store, including publish checklists, Fastlane automation, CI/CD patterns, and E2E pre-publish quality gates.", + "source": "./", + "skills": ["./skills/deployment"] } ] } diff --git a/README.md b/README.md index 2e36bbd..80ffd5e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A collection of agent-optimized skills for AI coding assistants. The repo ships | [github-actions](./skills/github-actions/) | GitHub Actions workflow patterns for React Native simulator/emulator build artifacts | | [upgrading-react-native](./skills/upgrading-react-native/) | React Native upgrade workflow: templates, dependencies, and common pitfalls | | [react-native-brownfield-migration](./skills/react-native-brownfield-migration/) | Incremental migration strategy to adopt React Native or Expo in native apps using @callstack/react-native-brownfield, with setup, packaging, and phased integration steps | +| [deployment](./skills/deployment/) | React Native release engineering workflows: publish checklists, Fastlane automation, and E2E pre-publish quality gates | ## React Native Best Practices @@ -47,6 +48,7 @@ Other available installs: /plugin install github-actions@callstack-agent-skills /plugin install upgrading-react-native@callstack-agent-skills /plugin install react-native-brownfield-migration@callstack-agent-skills +/plugin install deployment@callstack-agent-skills ``` Or use the interactive menu: @@ -238,10 +240,15 @@ agent-skills/ │ ├── SKILL.md # Main skill file with RN upgrade workflow routing │ └── references/ # Detailed upgrade flow files │ - └── react-native-brownfield-migration/ - ├── SKILL.md # Main skill file for Expo/bare path routing + ├── react-native-brownfield-migration/ + │ ├── SKILL.md # Main skill file for Expo/bare path routing + │ ├── agents/openai.yaml # Codex Skills UI metadata + │ └── references/ # Brownfield packaging and integration flow files + │ + └── deployment/ + ├── SKILL.md # Main skill file for RN deployment and release workflows ├── agents/openai.yaml # Codex Skills UI metadata - └── references/ # Brownfield packaging and integration flow files + └── references/ # Publish checklist, Fastlane automation, E2E release gate flows ``` Use `.claude-plugin/marketplace.json` for Claude Code plugin installs and `.agents/plugins/marketplace.json` for Codex plugin installs. diff --git a/skills/deployment/SKILL.md b/skills/deployment/SKILL.md new file mode 100644 index 0000000..745c730 --- /dev/null +++ b/skills/deployment/SKILL.md @@ -0,0 +1,51 @@ +--- +name: deployment +description: React Native mobile release and publishing workflows using Fastlane, GitHub Actions, and pre-publish quality gates. Use when preparing App Store or Play Store releases, automating mobile deployment pipelines, or validating pre-release readiness. +license: MIT +metadata: + author: Callstack + tags: react-native, deployment, fastlane, app-store, play-store, ci-cd, github-actions, release, e2e +--- + +# React Native Deployment + +## Overview + +Covers release-readiness checks, Fastlane automation for Android/iOS, and E2E quality gates before App Store and Play Store submission. + +## When to Apply + +Use this skill when: +- Preparing a production React Native release. +- Setting up Fastlane for App Store Connect and Google Play deployment. +- Designing GitHub Actions release pipelines triggered by tags or release branches. +- Enforcing E2E pre-publish checks that block failing releases. + +## Typical Deployment Sequence + +1. (Android prerequisite) Verify Android 16KB page-size alignment locally for the release build. If the local zipalign verification fails, stop before any Android deployment. +2. Configure GitHub Actions repository secrets for store uploads from [fastlane-deployment.md][fastlane-deployment] (do not commit raw keys to the repo). +3. Run release readiness checks from [publish-checklist.md][publish-checklist]. +4. Configure lanes and CI automation from [fastlane-deployment.md][fastlane-deployment]. +5. Enforce release gates with [e2e-prepublish-tests.md][e2e-prepublish-tests]. + +## Quick Reference + +| File | Description | +|------|-------------| +| [publish-checklist.md][publish-checklist] | Android/iOS/shared release readiness and store submission checks | +| [fastlane-deployment.md][fastlane-deployment] | Fastlane + GitHub Actions deployment automation for both stores | +| [e2e-prepublish-tests.md][e2e-prepublish-tests] | E2E gate design, flaky handling, and pre-publish smoke checks | + +## Problem -> Skill Mapping + +| Problem | Start With | +|---------|------------| +| Need a go/no-go checklist before submission | [publish-checklist.md][publish-checklist] | +| Need Fastlane lanes for TestFlight and Play tracks | [fastlane-deployment.md][fastlane-deployment] | +| Need release to fail when E2E tests fail | [e2e-prepublish-tests.md][e2e-prepublish-tests] | +| Need secret naming and CI handling conventions | [fastlane-deployment.md][fastlane-deployment] | + +[publish-checklist]: references/publish-checklist.md +[fastlane-deployment]: references/fastlane-deployment.md +[e2e-prepublish-tests]: references/e2e-prepublish-tests.md diff --git a/skills/deployment/agents/openai.yaml b/skills/deployment/agents/openai.yaml new file mode 100644 index 0000000..c32f3d1 --- /dev/null +++ b/skills/deployment/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "React Native Deployment" + short_description: "Fastlane, store publish checklists, and release gating for RN" + default_prompt: "Use $deployment to set up React Native Fastlane release automation, pre-publish validation, and App Store or Play Store delivery workflows." diff --git a/skills/deployment/references/e2e-prepublish-tests.md b/skills/deployment/references/e2e-prepublish-tests.md new file mode 100644 index 0000000..5ec1bd4 --- /dev/null +++ b/skills/deployment/references/e2e-prepublish-tests.md @@ -0,0 +1,113 @@ +--- +title: E2E Pre-Publish Release Gates +impact: HIGH +tags: react-native, e2e, detox, maestro, playwright, release-gates, ci +--- + +# Skill: E2E Pre-Publish Release Gates + +Use this workflow to enforce automated quality gates before store submission. + +## Quick Pattern + +- Run E2E against release-like builds, not only debug builds. +- Fail deployment jobs when required E2E suite fails. +- Keep a minimal smoke suite for every release candidate. +- Quarantine flaky cases with owner and expiry date. + +## When to Use + +- Before invoking production Fastlane lanes. +- When stabilizing CI-based release quality gates. +- When selecting tooling for cross-platform pre-publish checks. + +## Tool Selection Guide + +| Tool | Best For | Notes | +|---|---|---| +| Detox | React Native app-level E2E on simulators/emulators | Strong JS integration, good for CI gating | +| Maestro | Fast authoring of mobile UI flows | Good for smoke and regression flows | +| Playwright | Web or hybrid webview-heavy flows | Not a primary native mobile E2E tool | + +## CI Execution Targets + +- Android emulator (API level aligned to supported production baseline). +- iOS simulator (latest stable plus one fallback runtime if needed). +- Release-equivalent build config to catch signing/permission/runtime differences. + +## Required Release Gates + +Define mandatory checks: +1. Lint and unit tests pass. +2. E2E smoke suite passes on Android and iOS. +3. Crash-free launch and critical-path user flow pass. +4. Fastlane release lanes run only if all gates succeed. + +## GitHub Actions Gate Example + +```yaml +name: Prepublish Gates + +on: + pull_request: + branches: [main] + push: + tags: + - "v*.*.*" + +jobs: + quality-gates: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - name: Install dependencies + run: npm ci + - name: Lint + run: npm run lint + - name: Unit tests + run: npm test -- --ci + - name: Detox smoke + run: npm run e2e:detox:smoke + - name: Maestro smoke + run: npm run e2e:maestro:smoke + + release: + needs: quality-gates + if: startsWith(github.ref, 'refs/tags/v') + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Deploy after gates + run: bundle exec fastlane ios ios_beta && bundle exec fastlane android android_internal +``` + +## Smoke Checklist (Minimum) + +- First launch and authentication flow. +- Main navigation tabs/routes open successfully. +- One critical transaction path succeeds. +- Foreground/background transition is stable. +- Logout/session renewal works. + +## Flaky Test Handling Policy + +- Allow at most one automatic rerun for known flaky group. +- Track flaky tests in a quarantine list with ticket owner. +- Set expiry date for quarantine; expired flaky tests become blockers. +- Never bypass smoke-suite failures for production submission. + +## Common Pitfalls + +- Running E2E only on debug builds. +- Treating quarantined tests as permanently ignored. +- Releasing from branches that skip required checks. +- Missing parity between local and CI simulator/emulator configs. + +## Related Skills + +- [publish-checklist.md](publish-checklist.md) +- [fastlane-deployment.md](fastlane-deployment.md) diff --git a/skills/deployment/references/fastlane-deployment.md b/skills/deployment/references/fastlane-deployment.md new file mode 100644 index 0000000..11cfb1c --- /dev/null +++ b/skills/deployment/references/fastlane-deployment.md @@ -0,0 +1,404 @@ +--- +title: Fastlane Deployment for React Native +impact: CRITICAL +tags: react-native, fastlane, github-actions, app-store, play-store, ci-cd, release-automation +--- + +# Skill: Fastlane Deployment for React Native + +Use this workflow to set up repeatable mobile releases with Fastlane and GitHub Actions. + +## Quick Config + +1. Initialize Fastlane in `android/` and `ios/`. +2. Add shared lane logic in `fastlane/Fastfile`. +3. Configure `fastlane/Appfile` with placeholder values. +4. Store credentials in CI secrets only. +5. Trigger release lanes from semantic version tags. + +## When to Use + +- Creating first automated deployment pipeline. +- Replacing manual Play/App Store uploads with lane-driven release. +- Standardizing version increments and track promotion rules. + +## Secret and Config Placeholders + +Use placeholders only. Never commit real keys. + +This repo’s deployment workflow expects credentials to be provided as **GitHub Actions repository secrets**. +Fastlane should read only the decoded files created during the workflow (for example `fastlane/play-store-account.json` and `fastlane/AuthKey.p8`). + +| GitHub Actions secret variable | Purpose | +|---|---| +| `GOOGLE_PLAY_JSON_KEY_BASE64` | Google Play service account JSON (base64). Decoded to `fastlane/play-store-account.json`. | +| `ANDROID_KEYSTORE_BASE64` | Android release keystore (base64). Optional if your signing uses on-disk files or Gradle signing already. | +| `ANDROID_KEY_ALIAS` | Keystore alias (optional; used only if you wire keystore decoding/signing in CI). | +| `ANDROID_KEYSTORE_PASSWORD` | Keystore password (optional; used only if you wire keystore decoding/signing in CI). | +| `ANDROID_KEY_PASSWORD` | Key password (optional; used only if you wire keystore decoding/signing in CI). | +| `APP_STORE_CONNECT_KEY_ID` | App Store Connect API key id. Used for API-key authentication. | +| `APP_STORE_CONNECT_ISSUER_ID` | App Store Connect issuer id. Used for API-key authentication. | +| `APP_STORE_CONNECT_API_KEY_BASE64` | App Store Connect private key `.p8` (base64). Decoded to `fastlane/AuthKey.p8`. | +| `MATCH_PASSWORD` | Fastlane match encryption password (only if you use `match`). | + +## One-time Setup: Add secrets in GitHub Actions + +After setting up your Fastlane project, add the following secrets in: +`GitHub Repo -> Settings -> Secrets and variables -> Actions -> New repository secret`. + +Use base64-encoded values: +- For service account JSON: base64 encode the contents of your `google-play-service-account.json`. +- For App Store Connect `.p8`: base64 encode the contents of your downloaded API key `.p8`. + +Then re-run your workflow. The workflow will decode: +- `GOOGLE_PLAY_JSON_KEY_BASE64` -> `fastlane/play-store-account.json` +- `APP_STORE_CONNECT_API_KEY_BASE64` -> `fastlane/AuthKey.p8` + +> Important: do not add raw keystore files or raw `.p8` files to the repo. Keep them only in GitHub Actions secrets. + +## Fastlane Setup Commands + +```bash +# From app root +bundle exec fastlane init + +# Optional explicit platform setup +cd android && bundle exec fastlane init +cd ../ios && bundle exec fastlane init +``` + +## `fastlane/Appfile` Template + +```ruby +app_identifier(ENV["IOS_BUNDLE_ID"]) +apple_id(ENV["APPLE_ID"]) # Optional when using API-key auth; keep if your existing Fastlane setup relies on it. +itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"]) +team_id(ENV["IOS_DEV_PORTAL_TEAM_ID"]) + +package_name(ENV["ANDROID_PACKAGE_NAME"]) +json_key_file("./fastlane/play-store-account.json") + +app_store_connect_api_key( + key_id: ENV["APP_STORE_CONNECT_KEY_ID"], + issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"], + key_filepath: "./fastlane/AuthKey.p8", +) +``` + +## `fastlane/Fastfile` Template + +```ruby +default_platform(:ios) + +platform :android do + desc "Upload Android build to internal track" + lane :android_internal do + gradle(task: "clean bundleRelease") + upload_to_play_store( + track: "internal", + aab: "../android/app/build/outputs/bundle/release/app-release.aab", + json_key: "./fastlane/play-store-account.json" + ) + end + + desc "Promote to production" + lane :android_production do + upload_to_play_store(track: "production", track_promote_to: "production") + end +end + +platform :ios do + desc "Build and upload to TestFlight" + lane :ios_beta do + increment_build_number(xcodeproj: "../ios/App.xcodeproj") + build_app(scheme: "App", export_method: "app-store") + upload_to_testflight(skip_waiting_for_build_processing: true) + end + + desc "Submit to App Store" + lane :ios_release do + submit_for_review( + automatic_release: false, + submit_review: true + ) + end +end +``` + +## Recommended Android Lane (Production-Grade) + +Use this lane when you need dynamic track targeting and automatic version code management from Play Console state. + +Prerequisite (run locally): Android 16KB page-size alignment for the release build must pass (local `zipalign -c -P 16 -v 4` verification) before running this lane. See [native-android-16kb-alignment.md](../../react-native-best-practices/references/native-android-16kb-alignment.md). + +```ruby +platform :android do + desc "Build AAB and upload to Play Store with dynamic track + versioning" + lane :deploy do |options| + track = options[:track] || "internal" # internal, alpha, beta, production + UI.message("Deploying to Play Store track: #{track}") + + version_name = ENV["VERSION_NAME"] + version_name = nil if version_name.to_s.strip.empty? + UI.message("Version Name: #{version_name || 'not provided, Gradle default will be used'}") + + tracks = ["internal", "alpha", "beta", "production"] + all_codes = [] + + tracks.each do |t| + begin + codes = google_play_track_version_codes(track: t) + all_codes += codes if codes && !codes.empty? + rescue + UI.message("No builds found in #{t}") + end + end + + latest_version_code = all_codes.max || 0 + new_version_code = latest_version_code + 1 + + UI.message("Latest version found: #{latest_version_code}") + UI.message("Next version to use: #{new_version_code}") + + gradle_properties = { + "android.injected.version.code" => new_version_code + } + gradle_properties["android.injected.version.name"] = version_name if version_name + + gradle( + task: "bundle", + build_type: "Release", + properties: gradle_properties + ) + + upload_to_play_store( + track: track, + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + + UI.message("Deployment completed successfully") + end +end +``` + +Use `android_internal`/`android_production` from the baseline template as a simpler fallback when dynamic versioning is not required. + +## Recommended iOS Lane (Production-Grade) + +Use this lane when you need one entrypoint for TestFlight internal, TestFlight external, and App Store upload. + +```ruby +platform :ios do + desc "Build and upload iOS app to TestFlight or App Store" + lane :release do |options| + release_target = options[:release_target] || "testflight_internal" + UI.message("Release Target: #{release_target}") + + scheme = "GTTIANS" + workspace = "GTTIANS.xcworkspace" + + begin + latest_build = latest_testflight_build_number( + app_identifier: ENV["APP_IDENTIFIER"] + ) + build_number = latest_build + 1 + rescue => e + UI.important("Could not fetch latest TestFlight build number: #{e.message}") + build_number = 1 + end + + UI.message("Build Number: #{build_number}") + increment_build_number(build_number: build_number) + + match( + type: "appstore", + readonly: true + ) + + build_app( + workspace: workspace, + scheme: scheme, + export_method: "app-store", + output_directory: "build", + output_name: "GTTIANS.ipa", + clean: true + ) + + case release_target + when "testflight_internal" + UI.message("Uploading to TestFlight Internal") + upload_to_testflight( + distribute_external: false, + skip_waiting_for_build_processing: true + ) + when "testflight_external" + UI.message("Uploading to TestFlight External") + upload_to_testflight( + distribute_external: true, + groups: ["Beta Testers"], + skip_waiting_for_build_processing: true + ) + when "appstore" + UI.message("Uploading to App Store") + upload_to_app_store( + skip_metadata: true, + skip_screenshots: true, + submit_for_review: false, + automatic_release: false + ) + else + UI.user_error!("Invalid release target: #{release_target}") + end + + UI.success("iOS release complete: #{release_target}") + end +end +``` + +Expected invocation examples: + +```bash +bundle exec fastlane ios release release_target:testflight_internal +bundle exec fastlane ios release release_target:testflight_external +bundle exec fastlane ios release release_target:appstore +``` + +## GitHub Actions Release Workflow Template + +```yaml +name: Mobile Release + +on: + workflow_dispatch: + inputs: + android_track: + description: "Android track (internal | alpha | beta | production)" + required: true + default: "internal" + type: choice + options: + - internal + - alpha + - beta + - production + ios_release_target: + description: "iOS release target (testflight_internal | testflight_external | appstore)" + required: true + default: "testflight_internal" + type: choice + options: + - testflight_internal + - testflight_external + - appstore + push: + tags: + - "v*.*.*" + +concurrency: + group: mobile-release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Verify required secrets exist + run: | + test -n "${GOOGLE_PLAY_JSON_KEY_BASE64:-}" || (echo "Missing GOOGLE_PLAY_JSON_KEY_BASE64 secret" && exit 1) + test -n "${APP_STORE_CONNECT_API_KEY_BASE64:-}" || (echo "Missing APP_STORE_CONNECT_API_KEY_BASE64 secret" && exit 1) + env: + GOOGLE_PLAY_JSON_KEY_BASE64: ${{ secrets.GOOGLE_PLAY_JSON_KEY_BASE64 }} + APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }} + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - name: Install dependencies + run: | + npm ci + bundle install + - name: Decode Play key + run: echo "$GOOGLE_PLAY_JSON_KEY_BASE64" | base64 --decode > fastlane/play-store-account.json + env: + GOOGLE_PLAY_JSON_KEY_BASE64: ${{ secrets.GOOGLE_PLAY_JSON_KEY_BASE64 }} + - name: Decode App Store key + run: echo "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode > fastlane/AuthKey.p8 + env: + APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }} + - name: Run tests + run: npm test -- --ci + - name: Resolve release targets + id: release-targets + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "android_track=${{ github.event.inputs.android_track }}" >> "$GITHUB_OUTPUT" + echo "ios_release_target=${{ github.event.inputs.ios_release_target }}" >> "$GITHUB_OUTPUT" + else + echo "android_track=internal" >> "$GITHUB_OUTPUT" + echo "ios_release_target=testflight_internal" >> "$GITHUB_OUTPUT" + fi + - name: Android release (dynamic lane) + run: bundle exec fastlane android deploy track:${{ steps.release-targets.outputs.android_track }} + env: + VERSION_NAME: ${{ github.ref_name }} + - name: iOS release (dynamic lane) + run: bundle exec fastlane ios release release_target:${{ steps.release-targets.outputs.ios_release_target }} +``` + +## GitHub Actions Concurrency Guard + +Use a single release concurrency group per branch or tag to prevent duplicate Android `versionCode` allocation when two workflows run at the same time. + +```yaml +concurrency: + group: mobile-release-${{ github.ref }} + cancel-in-progress: false +``` + +For manual release workflows, keep `cancel-in-progress: false` so an active deployment is not interrupted mid-publish. + +## Versioning Strategy + +- Parse semantic version from Git tag (`vX.Y.Z`) and write to app version fields. +- Auto-increment Android `versionCode` on each CI release. +- Auto-increment iOS `CFBundleVersion` per build. +- Promote from internal/beta to production only after verification gates pass. + +## Release Channel Strategy + +- Android: `internal` -> `beta` -> `production`. +- iOS: TestFlight external testers -> App Store submission. +- Require explicit approval for production lane invocation. + +## Monorepo Targeting Notes + +- Resolve target app directory before lane execution. +- Execute Fastlane from app directory containing `ios/` and `android/`. +- Use path-aware env variables when the repository contains multiple apps. + +## Rollback / Hotfix Playbook + +- Create `hotfix/` branch from latest production tag. +- Ship patched build to internal/TestFlight first. +- Promote only after smoke/E2E pass. +- Communicate rollback reason, impact window, and next stable target. + +## Common Pitfalls + +- Committing real credential files. +- Reusing same build number for multiple iOS uploads. +- Running parallel Android release jobs that compute the same next version code. +- Missing Play Developer API permissions for track version lookups. +- Promoting to production without staged validation. +- Running lanes from wrong directory in monorepos. + +## Related Skills + +- [publish-checklist.md](publish-checklist.md) +- [e2e-prepublish-tests.md](e2e-prepublish-tests.md) diff --git a/skills/deployment/references/publish-checklist.md b/skills/deployment/references/publish-checklist.md new file mode 100644 index 0000000..8d59c36 --- /dev/null +++ b/skills/deployment/references/publish-checklist.md @@ -0,0 +1,83 @@ +--- +title: React Native Publish Checklist +impact: CRITICAL +tags: react-native, release, play-store, app-store, checklist, signing, metadata +--- + +# Skill: React Native Publish Checklist + +Use this checklist to decide go/no-go for App Store and Play Store submission. + +## Quick Reference + +- Treat missing signing or provisioning as blockers. +- Treat missing production analytics and crash reporting as blockers. +- Treat incomplete store metadata as blockers for production rollout. +- Do not publish if E2E smoke fails on release builds. + +## When to Use + +- Releasing to Play internal/closed/open/production tracks. +- Releasing to TestFlight or App Store production. +- Auditing release readiness before invoking Fastlane lanes. + +## Blockers vs Warnings + +| Type | Rule | Action | +|------|------|--------| +| Blocker | Missing keystore or invalid Play service account auth | Stop release | +| Blocker | Missing iOS provisioning/profile or signing mismatch | Stop release | +| Blocker | Production API endpoint or env is not validated | Stop release | +| Blocker | Crash reporter or analytics disabled in release build | Stop release | +| Blocker | Android 16KB alignment verification fails for release build | Stop release | +| Blocker | Version/build values unchanged from last release | Stop release | +| Warning | Optional marketing metadata refinement | Continue with owner approval | + +## Android Readiness + +- Confirm `applicationId` matches Play Console package. +- Verify Android 16KB page-size alignment for the release build locally before Play submission (zipalign check). See [native-android-16kb-alignment.md](../../react-native-best-practices/references/native-android-16kb-alignment.md). +- Confirm signing config resolves to valid upload key. +- Confirm `versionCode` increments and `versionName` is semver-aligned. +- Confirm R8/ProGuard rules preserve required SDK classes. +- Confirm release permission set is minimal and justified. +- Confirm Play listing assets and privacy policy URL are complete. + +## iOS Readiness + +- Confirm bundle identifier matches App Store Connect app. +- Confirm certificate and provisioning profile map to release target. +- Confirm marketing version and build number increment. +- Confirm entitlements align with enabled capabilities. +- Confirm privacy manifest/privacy labels are complete. +- Confirm App Store metadata, screenshots, and review info are complete. + +## Shared Readiness + +- Confirm `.env.production` values resolve in release builds. +- Confirm API base URL points to production backend only. +- Confirm feature flags and remote config defaults are production-safe. +- Confirm Sentry/Crashlytics release upload is enabled. +- Confirm release notes/changelog are prepared. +- Confirm rollback owner and communication channel are assigned. +- Confirm store upload credentials are available via GitHub Actions Secrets (and never committed to the repo). See [fastlane-deployment.md](fastlane-deployment.md) for expected secret names and decoding steps. + +## Pre-Submit Smoke Checklist + +Run release smoke on real device or store-like build: +- App launch and login. +- Primary navigation flow. +- Purchase/subscription flow (if applicable). +- Push notification registration and open handling. +- Crash-free startup and no redboxes. + +## Rollback Readiness + +- Keep previous production binary and metadata references. +- Keep hotfix branch naming convention (`hotfix/x.y.z+1`). +- Define rollback lane owner and response SLA. + +## Related Skills + +- [fastlane-deployment.md](fastlane-deployment.md) +- [e2e-prepublish-tests.md](e2e-prepublish-tests.md)