mobile/ with the EAS CLI, signed in to the Expo account that owns the project.
eas.json requires EAS CLI version 20 or newer.
App identity
Frommobile/app.json:
The iOS app includes a widget extension target,
ExpoWidgetsTarget, declared under extra.eas.build.experimental.ios.appExtensions with an app group entitlement. The expo-widgets plugin config declares three home screen widgets: WorkoutToday, NutritionToday and Streak.
Build profiles
eas.json defines three profiles.
The
preview profile points at the production API. A preview build reads and writes real data. There is no separate staging API in the configuration.cli.appVersionSource is remote. EAS stores the build number (iOS buildNumber, Android versionCode) on its servers and increments it for each production build. The user-facing version comes from expo.version in app.json. Keep version in package.json equal to it.
Commands
All scripts are inmobile/package.json.
Build
The local iOS scripts put the system directories first on
PATH so the build uses the system toolchain and not a version manager’s shims.
Build and submit
The
submit.production profile sends iOS builds to App Store Connect and Android builds to the production track with release status completed.
Signing credentials and store API keys are managed by EAS and are not in the repository. Ask the project owner which submission key to use.
Run locally
Native configuration
ios/ and android/ are generated by prebuild. Change native settings in these places instead of editing the generated folders:
app.json, including the plugin list:expo-build-properties,expo-router,expo-splash-screen,expo-secure-store,expo-image,expo-image-picker,expo-notifications,expo-local-authentication,expo-localization,expo-sharing,expo-camera,expo-media-library,expo-widgets,expo-video,@kingstinct/react-native-healthkit,react-native-health-connect,expo-task-manager,expo-background-task,expo-navigation-bar.- The two local config plugins in
mobile/plugins, which add the Health Connect permission delegate and the permissions rationale activity on Android. - The local native module in
mobile/modules/cardio-notification, an Android ongoing notification with a native chronometer for cardio sessions. - The patches in
mobile/patches, applied by pnpm at install time.
app.json. Android requests exact alarms, biometrics, camera, audio recording, and Health Connect step reading including background reads. It explicitly blocks the broad media read permissions. iOS declares HealthKit with background delivery and time-sensitive notifications.
When a new store build is required
An OTA update can only change JavaScript and assets. Cut a new store build for both platforms when you:- add, remove or upgrade a dependency that contains native code,
- add or change a config plugin or its options,
- change permissions, entitlements,
infoPlistvalues, icons or the splash screen, - change the local native module or a patch that touches native code,
- upgrade Expo or React Native,
- bump
expo.version.
Bumping the version
- Change
versioninapp.jsonand inpackage.jsonto the same value. - Build and submit both platforms.
- After the builds are live in the stores, set
TRAINEE_APP_IOS_VERSIONandTRAINEE_APP_ANDROID_VERSIONon the backend to the new version. The app compares its own version with these and prompts the trainee to update when it is older. Leave them unchanged until the store listing actually serves the new build, or trainees are sent to a store page that has nothing newer. - Add the previous version to the list of runtimes that still need OTA updates. See OTA updates.
appVersion, a new version is also a new OTA runtime. Updates published for the old version do not reach the new binary and the other way round.
Environment variables in builds
EXPO_PUBLIC_* values are compiled into the JavaScript bundle. For EAS builds they come from the env block of the build profile in eas.json. For a local production build they come from mobile/.env.production. Keep the two in sync. The .env.example file says the same.
These values are public. Anyone can read them from the app bundle.
The trainee preview export
Coaches see a live preview of the trainee app inside the web app. It is the mobile app exported for the web.scripts/export-preview.sh runs expo export -p web into a temporary folder with PREVIEW_BASE_PATH (default /trainee-preview) and an optional PREVIEW_API_URL, checks that index.html was produced, then replaces ../frontend/apps/saas/public/trainee-preview with the result. app.config.js reads the two variables and sets experiments.baseUrl and extra.apiUrl for that export only.
The web app serves the bundle through a rewrite in apps/saas/next.config.ts. The backend issues short-lived preview tokens for it (PREVIEW_TTL_SECONDS is 15 minutes in trainee.token.ts), and the trainee lane answers FORBIDDEN with reason PREVIEW_READ_ONLY to any non-GET request made with a preview token.
Native-only modules do not exist on the web. metro.config.js maps them to the replacements in src/lib/web-shims for the web export.