GenerateSaaS

Release

Shipping the mobile app - build profiles, versioning, when an update goes over the air, and what the release workflow needs.

Releases run through EAS Build and the Mobile release workflow, which is dispatch-only in a generated project: nothing ships because a commit landed.

Build profiles

apps/mobile/eas.json ships four:

ProfileDistributionChannelFor
developmentinternal, dev clientdevelopmentDay-to-day work. Its iOS artifact sets ios.simulator, so it runs on a simulator
development-deviceinternal, dev clientdevelopmentThe same dev client on a real phone - no ios.simulator, so the artifact installs
previewinternalpreviewSharing a build with testers
productionstoreproductionThe build you submit

Both development profiles share the development channel, so one published update reaches a simulator and a phone alike. A development build is development-signed: iOS 16 and later asks for Developer Mode on the phone (Settings -> Privacy & Security), which an ad-hoc preview build does not.

A profile's FIRST build has to run interactively. eas build --non-interactive cannot create a signing certificate or a provisioning profile, so run the profile once without that flag (or eas credentials) and let EAS make them. If credentials:configure-build then fails while syncing Apple capabilities - a known eas-cli 22.6 defect, reported by a project generated from this repo - set EXPO_NO_CAPABILITY_SYNC=1 and enable the capabilities in App Store Connect instead.

Versioning

The version is computed the way every other release here is: a feat since the last tag bumps the minor, otherwise the patch, and a major needs a deliberate -f bump=major dispatch.

FactBehaviour
Tag linemobile-v*, its own; it never moves with the web release
Where the number goesapps/mobile/package.json in the runner's tree, never committed back
What it gates onA diff over the mobile app, the shared styles, and the @repo/* packages the app depends on
Build numbersEAS owns them: appVersionSource is remote and production auto-increments

Over the air, or a new build

CaseWhat ships
The fingerprint matches that platform's newest finished production buildAn OTA update - no store review, no new binary
It differs, or there is no prior buildA full EAS build for that platform
apps/mobile/fingerprint.config.js is goneA full build every time

That file's sourceSkips keeps the version fields the workflow rewrites out of the hash, so a version-only release still matches. It REPLACES the defaults, so keep both skip names.

What the workflow needs

ValueKindEffect when missing
EXPO_TOKENRepository secretThe workflow skips green, naming it; a repo without EAS credentials is never a red release
EAS_PROJECT_IDRepository variableThe same green skip; app.config.ts reads it
MOBILE_ANDROID_SUBMIT_READYRepository variableAndroid builds but is not auto-submitted

EXPO_TOKEN is the EAS robot token, not EXPO_ACCESS_TOKEN, which is the backend's push bearer. Upload the APNs key and FCM V1 credentials once with pnpm --filter mobile exec eas credentials.

Submitting

The first submission on each store is manual: the listing must exist first, and no automated path creates it - App Store Connect's API offers no create for apps, so that record is made once in the web UI. Everything after it can run unattended. From the second release a configured submit profile adds --auto-submit, and Android's release status is draft, so a submitted build waits for you to roll it out.

Set MOBILE_ANDROID_SUBMIT_READY to true once eas credentials shows a Google Service Account key on the production profile. That key lives in EAS, not in eas.json, so nothing in the repo can detect it.

An UNATTENDED iOS submission needs four fields on the submit profile, not one: ascAppId plus ascApiKeyPath, ascApiKeyId and ascApiKeyIssuerId. Without ascAppId, eas submit --non-interactive cannot run at all; the three key fields are what let it authenticate from a local App Store Connect API key instead of prompting. This repo ships none of them - the first submission is manual by design, and an empty string in any of them fails eas build too. Apple's own xcrun altool --upload-package <ipa> --api-key <key-id> --api-issuer <issuer-id> is the alternative when you would rather not put a key path in the repo.

Once a store approves, paste its listing URL into packages/config/src/mobile.mjs. The "get the app" section stays hidden until one is set, and a store without a URL drops only its own badge.

FieldPaste
config.mobile.stores.iosApp Store listing URL
config.mobile.stores.androidGoogle Play listing URL

On this page