Skip to main content

Mobile deployment

Status: Proposed 2026-09-24

vegan-grove-mobile ships through EAS Build. No ios/ or android/ directory exists in git: the native projects are generated on the build machine from app.config.ts and the config plugins (continuous native generation). A committed native directory is a review failure; it freezes plugin output and is where keys end up.

Profiles​

eas.json defines five profiles. All pin Node 24 and use the remote app version source.

ProfileDistributionPurpose
developmentinternal, dev clientday-to-day work; required because MapLibre is native and Expo Go cannot load it
previewinternala release-mode build for testers, installed by link, not through a store
productionstorethe base the two store profiles extend
testflightstore, iOSextends production, submits to TestFlight
playstorestore, Android app bundleextends production, submits to the Play internal track

appVersionSource: "remote" and autoIncrement: true on the store profiles mean EAS holds the build number and version code and bumps them per build. Nothing about versions is committed per release except the marketing version in app.config.ts, which is bumped by hand when the store listing changes.

The three-layer release guard​

The scaffold fails a store build if a development-only edit is still in the tree.

  1. scripts/check-prod-ready.sh greps app/, src/, app.config.ts, and eas.json for localhost, sk_live, AKIA, AIza, and private IPv4 ranges, and exits non-zero on a hit.
  2. scripts/eas-build-pre-install.sh runs it on the EAS build machine when EAS_BUILD_PROFILE is production, testflight, or playstore. A hit fails the build before npm install.
  3. npm run validate runs the same script on every PR as check:prod.

The layers are redundant by design. One catches the edit at the PR, one at the build, and the script itself is the single definition of what a leak looks like. Build and release lists the patterns and how to extend them.

iOS: TestFlight​

eas build --profile testflight --platform ios
eas submit --profile testflight --platform ios --latest

The build lands in TestFlight for internal testers immediately and for external testers after Apple's TestFlight review. App Store release is a manual promotion in App Store Connect after the build has been tested.

Android: Play internal track​

eas build --profile playstore --platform android
eas submit --profile playstore --platform android --latest

The bundle goes to the internal testing track. Promotion to closed, open, or production tracks is done in the Play Console. The first bundle for a new app cannot be submitted by EAS; it is uploaded by hand to create the app record.

One-time setup​

These need the maintainer's accounts and are done once:

  • EAS project: eas init links the repo to a project and writes extra.eas.projectId into the config.
  • Apple app record: bundle id org.vegangrove.app registered, app created in App Store Connect, and ascAppId placed in the submit.testflight profile. Signing certificates and the push key are managed by EAS, never downloaded into the repo.
  • Play listing: package org.vegangrove.app, the listing created, the first bundle uploaded manually, and a service-account key stored as an EAS secret for later submissions.
  • Push: the APNs key through EAS credentials, the FCM configuration file as an EAS file secret referenced from app.config.ts.

Free tier: 30 EAS builds a month on the lower-priority queue. Store profiles are used only after a preview build has been tested, so the quota goes a long way.