Files
edgeever/docs/store-delivery.md
T
tianma-if fabfede7e0 fix(ios): stop xcodegen from archiving the previous App Store version
Cloud #50 exported 1.74.0 because project.yml hardcoded MARKETING_VERSION
and ci_post_clone regenerated the Xcode project from that YAML. Keep the
version in Version.xcconfig and stamp it into the pbxproj before archive.
2026-09-20 23:57:33 +08:00

6.5 KiB

Mobile Store Delivery

GitHub Releases and mobile store delivery are separate operations:

  • bun run release creates and audits the GitHub Release. Android assets cannot be published until they pass the Play app-signing gate. When the audited range includes iOS runtime changes, the same command also starts Xcode Cloud and submits the resulting build to App Review. An iOS failure leaves the GitHub Release published; retry with bun run publish:stores.
  • bun run publish:stores dispatches a store-delivery workflow for a matching Draft or an existing formal Release tag. When Android is rebuilt, run it against the Draft so the Play-signed APK replaces the temporary locally signed APK before publication. Omit --ios-build-number to start Xcode Cloud; pass it only to submit an already uploaded App Store Connect build.
  • Store delivery is the authorization to submit. By default, Google Play uses the Production track, while iOS continues from App Store Connect upload into App Review. Approved builds are released automatically.

Safety Model

The workflow checks out the immutable Release tag rather than main. Before any store build starts, it verifies that:

  • the tag belongs to a matching Draft or formal, non-prerelease GitHub Release;
  • the Release target and Git tag resolve to the same commit;
  • the audited range since the previous formal Release contains mobile runtime changes;
  • root and mobile app versions both match the Release tag;
  • Android versionCode increased.

A Release that reused the previous store binary for the selected platform is intentionally rejected. It does not represent a new store binary and should not be uploaded again. Android delivery requires apps/mobile runtime changes and an increased versionCode. iOS delivery requires apps/ios or shared editor runtime changes and MARKETING_VERSION equal to the Release tag.

The publication gate accepts only ANDROID_PLAY_APP_SIGNER_SHA256. An APK signed by the local upload certificate may temporarily exist in a Draft for store processing, but cannot become the final Android asset of a formal Release. A failed gate leaves the Release as a Draft. If someone bypasses the release command and publishes manually, the published audit rejects that APK and restores Draft state.

Prerequisites

Configure these GitHub repository secrets:

  • EXPO_TOKEN
  • ANDROID_KEYSTORE_BASE64
  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD
  • ANDROID_PLAY_APP_SIGNER_SHA256
  • GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64
  • APP_STORE_CONNECT_API_KEY_ID
  • APP_STORE_CONNECT_API_ISSUER_ID
  • APP_STORE_CONNECT_API_KEY_P8_BASE64

Upload the Google Play service-account key to the Android application's EAS Submit credentials, and store the same service-account JSON as base64 in the repository secret listed above. Copy the SHA-256 fingerprint of the app signing certificate (not the upload certificate) from Play Console into ANDROID_PLAY_APP_SIGNER_SHA256. Configure the iOS distribution credentials and App Store Connect API key in EAS. Credentials and private keys must remain outside the repository.

Create these GitHub Environments:

  • store-delivery for Android testing tracks and Apple App Review delivery.
  • store-production for Google Play production delivery.

EAS Submit requires the application to exist in each store and Google Play API submission requires the service account to have access to the application. Follow the official EAS Android submission guide and EAS Submit configuration reference.

Commands

Submit both platforms to Google Play Production and Apple App Review when both runtimes changed:

bun run publish:stores -- --release v1.7.0

Deliver only iOS, starting the Manual Xcode Cloud Archive workflow:

bun run publish:stores -- --release v1.7.0 --platform ios

Prepare the Android Play-signed asset for a Draft before formal publication:

bun run publish:stores -- \
  --release v1.7.0 \
  --platform android \
  --android-track production

Deliver only Android to a closed testing track:

bun run publish:stores -- \
  --release v1.7.0 \
  --platform android \
  --android-track beta

Use --dry-run to print the GitHub workflow dispatch without starting it.

Platform Behavior

Google Play

The self-hosted release runner builds an arm64-v8a signed AAB from the selected tag, verifies its signature and R8 mapping, preserves both as GitHub Actions artifacts, and uploads the AAB through EAS Submit.

After Google Play finishes processing the bundle, the workflow downloads the Play-signed universal APK, verifies the pinned app-signing certificate, and replaces the Draft or formal GitHub Release Android asset. A Draft replacement must also pass the independent pre-publication signature gate. This makes installations from Google Play and GitHub mutually updateable. The uploaded AAB is intentionally limited to arm64-v8a, so the Play-generated universal APK does not bundle unused 32-bit ARM or x86 native libraries. Automatic Protection must be disabled for the release. The downloader fails closed when Play reports installer-locked artifacts, preventing a protected APK from being published for GitHub sideloading.

Internal, Alpha, Beta, and Production profiles use a completed release on the selected track. The default command targets Production; use --android-track internal, alpha, or beta only when a testing delivery is explicitly requested.

App Store Connect

Native iOS store binaries come from apps/ios (SwiftUI), not Expo EAS. On macOS beta hosts, archives must go through Xcode Cloud so BuildMachineOSBuild is a release OS image — see iOS Xcode Cloud. The store-delivery workflow starts that Manual Archive workflow on its configured default branch, requires the Cloud source's MARKETING_VERSION to match the Release tag, waits until App Store Connect marks the build Valid, then Fastlane (apps/ios submit_review) submits App Review and configures automatic release after approval. Pass --ios-build-number only to reuse an already uploaded build. Missing metadata, agreements, review information, or credentials cause the workflow to fail without submitting a different build.

Store listing localizations (including Japanese) live in apps/mobile/store-assets/ and are pasted in App Store Connect and Play Console. Do not add a new locale to Fastlane submit_review until that locale is complete in the console. An incomplete localization can fail App Review without changing the binary.