Skip to content

Native Release

The Tomoda native iOS and Android apps are built locally from a developer's machine and submitted to the App Store / Google Play by hand. The build process pulls Sentry secrets from GCP Secret Manager at build time via the wrapper script at frontend/scripts/build-with-secrets.sh — see Local build (current default) below.

This page covers the iOS + Android flow at the conceptual level. For portal-side management of each store, see Play Store and App Store. For local dev environment setup, see Local development.

EAS is documented as the alternative path

The sections below cover EAS (Expo Application Services) — Expo's hosted-build platform — as an alternative we may adopt later. Tomoda doesn't have an EAS account today; you can ignore the EAS-specific instructions until that decision is revisited.

Local build (current default)

cd frontend

# iOS release build
./scripts/build-with-secrets.sh ios

# Android release build
./scripts/build-with-secrets.sh android

# Regenerate native projects before a release (if app.config.js changed)
./scripts/build-with-secrets.sh prebuild

The script pulls two values from GCP Secret Manager — tomoda-sentry-dsn and tomoda-sentry-auth-token — into environment variables for the build process and execs the corresponding expo command. No secret values are written to disk. See scripts.md for the full reference.

After the build completes, upload the resulting .ipa to TestFlight via Transporter (or xcrun altool) and the .aab / .apk to Google Play Console manually. The store-side workflow is covered in App Store and Play Store.


EAS-based build (alternative, not adopted)

Build profiles

The build matrix is defined in frontend/eas.json:

Profile Use for Distribution Notes
development-simulator Local iOS simulator dev internal developmentClient: true, ios.simulator: true
development EAS dev client on a real device internal developmentClient: true
preview Internal QA / TestFlight beta / Play internal track internal Stand-alone build, debug-stripped
production App Store + Google Play release store autoIncrement: true on version code; production env vars baked in

Production env vars

The production profile pins two EXPO_PUBLIC_* values directly in eas.json:

"production": {
  "autoIncrement": true,
  "env": {
    "EXPO_PUBLIC_API_URL": "https://api.tomoda.life/api/v1",
    "EXPO_PUBLIC_WS_URL": "wss://api.tomoda.life/ws"
  }
}

Other EXPO_PUBLIC_* values (the Google OAuth client IDs in particular) come from the EAS Secret store or the developer's environment at build time. As with the web frontend, anything EXPO_PUBLIC_* is shipped to clients — never put a real secret behind one. See Secrets.

App version source

cli.appVersionSource: "remote" means EAS — not app.json — is the source of truth for the version code / build number. autoIncrement: true on the production profile means EAS bumps the build number on each production build. Marketing version (1.2.3) still comes from app.json.

Building

cd frontend

# Local sim build, instant
eas build --profile development-simulator --platform ios

# Real-device dev client (iOS + Android in parallel)
eas build --profile development --platform all

# Internal preview / TestFlight / Play internal
eas build --profile preview --platform all

# Production
eas build --profile production --platform all

Builds run on EAS infrastructure; you get a link to monitor progress and download the artifact. For production builds you'll also want to make sure you've run the test suite locally first (task test — see Testing).

Submitting

Once a production build is done, push it to the stores:

# iOS — App Store Connect
eas submit --profile production --platform ios

# Android — Google Play
eas submit --profile production --platform android

The submit configuration is at the bottom of eas.json:

"submit": {
  "production": {
    "ios": { "ascAppId": "6765908734" }
  }
}

This is the App Store Connect numeric app ID for tomoda — eas submit --platform ios uploads the latest production build to that record.

iOS — TestFlight workflow

  1. eas build --profile production --platform ios finishes and uploads to App Store Connect.
  2. Apple processes the build (~5–30 min).
  3. In App Store Connect → TestFlight, the new build appears under your app. Add it to an internal or external test group.
  4. External groups require beta review (typically a day on first submission, faster after).
  5. Once tested, promote to the App Store: create a new version (or attach to the in-progress one), select the build, fill in metadata, submit for review.

Android — Google Play workflow

  1. eas submit --profile production --platform android uploads the AAB to Play Console.
  2. The release goes to the internal testing track by default. Pass --track production or use Play Console to promote.
  3. Set the rollout percentage in Play Console for a staged rollout. Halt the rollout if early metrics look bad.

OTA updates (EAS Update)

For JS-only changes (no native code / dependency changes), you can ship an OTA update without going through the stores:

# Publish to the "production" channel
eas update --branch production --message "Fix calendar timezone bug"

Clients on a build subscribed to that channel pick up the update on their next launch. To revert:

eas update:rollback --branch production

OTA updates are bounded by Apple/Google rules — anything that materially changes app behavior or adds features outside the reviewed scope should go through a full native release.

Beta channels

The preview build profile (internal distribution) is the standard channel for beta testing — pre-release builds you want to put in front of a small group before pushing to TestFlight / Play. Distribute via the EAS internal-distribution page; testers install the build with a one-click link on iOS (UDID-provisioned) or APK on Android.

See also