Running a release
This is the whole train, in order, as it actually runs. Each step is labelled with who acts:
| Label | Who acts |
|---|---|
| Lane | A lane or skill does it. |
| You | You do it by hand. |
| Gate | The train stops until you say so. |
Two things to know before the first command:
- Placeholders come from your app’s
reflutter.json:<prefix>is thelanePrefix,<stage>a stage name. - Every lane defaults to a dry run;
live:trueexecutes. Dry-run first, always. Read the summary and confirm the version,rc_tag, andbuild_numberbefore going live, because live burns them permanently.
Before you start
-
Lane · Readiness. Start clean, on
main— the prepare lane refuses anything else:git checkout main && git pull git status --short # must be empty
Staging
-
Gate, then lane · Prepare (staging). Dry run, read the plan, then your burn approval starts it live: analysis and tests, env-sync proof, bump, RC cut, clean build of both artifacts, freshness verified.
bundle exec fastlane <prefix>_prepare_<stage> bundle exec fastlane <prefix>_prepare_<stage> live:trueThe live run prints two note-file paths plus the exact
rc_tagandbuild_number— keep them; every later step uses them. -
Gate · Store copy. The upload lane refuses to run without the two note files — that refusal is the gate working. The notes are drafted for you from the changelog, you edit and approve, and only then are they written at the exact paths the lane printed. Approved once, they flow verbatim through everything downstream.
-
Lane · Upload (staging), behind your per-upload approval. Uploads the artifacts already built — never rebuilds in between — and waits until the store reports the build processed:
bundle exec fastlane <prefix>_upload_internal_<stage> rc_tag:<RC_TAG> build_number:<N> bundle exec fastlane <prefix>_upload_internal_<stage> rc_tag:<RC_TAG> build_number:<N> live:true -
You · Verify on device. Install from the internal track / TestFlight and use it. Your “verified, run the promote” is both the verification signal and the go-ahead — nothing infers it for you.
Production
-
Lane · Promote (production). Not a re-prepare: the same RC tag is checked out and the production flavor is rebuilt with the same version and build number. Production note files reuse the already-approved copy — no new copy gate. Then upload, again behind your approval:
bundle exec fastlane <prefix>_promote_<from>_to_<stage> rc_tag:<RC_TAG> build_number:<N> bundle exec fastlane <prefix>_promote_<from>_to_<stage> rc_tag:<RC_TAG> build_number:<N> live:true -
You · Begin public promotion. A manual console act (Play Console → Production; App Store Connect → add the build to a version). This happens before finalize.
Closing the train
-
Lane · Integrity gate. Before the destructive step, prove the candidate is safe to finalize: nothing stranded on
rc/X.Y.Z,mainstill at the bump commit, every promoted stage done. Any FAIL is a hard stop. -
Gate, then lane · Finalize. Once per release, at the very end — it deletes the RC scaffolding, tags
X.Y.Zon the bump commit, and pushes:git checkout main bundle exec fastlane <prefix>_finalize_release bundle exec fastlane <prefix>_finalize_release live:true -
You · The record. The GitHub release (drafted, published on your word), the public site changelog, and — the last act of the whole train — pasting the App Store Connect fields and submitting both stores for review.
When something goes wrong
Don’t relay a red lane, diagnose it. Two skills load at any point without touching the pipeline:
diagnosing-lane-failuresmaps the failure to its real cause and one next action, and never retries a step that burned a build number.checking-train-statusreconstructs the station from evidence and names the next step, for when you have lost track mid-train.
Single-stage apps skip steps 6–7: prepare, upload, verify, finalize, done.