Skip to content
Merged
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Tags follow plain SemVer: `vMAJOR.MINOR.PATCH`. There is no pre-release suffix

A single release workflow (`release.yaml`) listens on the `v*` tag pattern and uses a guard job to route based on the PATCH component: patch tags go through the internal lane (`prerelease: true` on GitHub), MAJOR/MINOR tags through the production-candidate lane (`prerelease: false`). Either way the build lands in the Test tracks first — the App Store / Play Store production track is never updated by a tag push.

The `beta` lanes push the **store listing** (Fastlane metadata + screenshots) to App Store Connect / Play Console alongside every binary, so a tag-driven release keeps the listing in sync with the build (production promotion / final submit stay manual — see `store-metadata.yaml`). Because of that, `release.yaml` runs the same `scripts/check-store-metadata.sh` preflight (FIXME placeholders + character limits) as `store-metadata.yaml` in a gating `store-metadata-preflight` job before either deploy lane runs — a tag can never ship a `FIXME-` placeholder or an oversize field to the live consoles.
Both `beta` lanes push the store **listing** (Fastlane metadata + screenshots) alongside the binary on every tag-driven release, so the listings stay in sync with the build. Android `supply` writes the (global) Play listing with the AAB. iOS `deliver` runs with `skip_app_version_update: false`, so it creates or selects the editable App Store version itself (a TestFlight-only app has none yet) and stages the listing there — the Deliverfile's `submit_for_review false` + `automatic_release false` keep it staged for a human to submit; deliver never auto-submits or releases. The same surface can also be synced without a binary via the `store_metadata` lane / `store-metadata.yaml`. Either way, `release.yaml` runs the same `scripts/check-store-metadata.sh` preflight (FIXME placeholders + character/URL limits) as `store-metadata.yaml` in a gating `store-metadata-preflight` job before either deploy lane runs — a tag can never ship a `FIXME-` placeholder or an oversize field to the live consoles. Production promotion / final submit stay manual.

The build number is derived deterministically from the tag by `tool/generate_release_info.dart` using `MAJOR * 10_000_000 + MINOR * 100_000 + PATCH * 1_000 + 999`. The fixed `+999` suffix keeps every new build strictly above the legacy beta build codes; the first new build `v1.0.15` lands at `10_015_999`, comfortably above the highest published legacy beta `v1.0.0-beta.14` at `10_000_014`.

Expand Down
27 changes: 19 additions & 8 deletions android/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,28 @@ platform :android do
)
end

# Upload the binary + changelog only. The changelog is tied to this
# build's version code (taken from the AAB); the listing metadata and
# images are pushed by the dedicated call below, keeping the two
# concerns separate and avoiding a duplicate metadata upload.
upload_to_play_store(
track: release_track,
aab: aab_path
aab: aab_path,
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)

# Push the Play Store listing (metadata + screenshots) alongside the AAB.
# Defaults come from Appfile (package_name + json_key_file).
# No changelog here: supply can only attach a changelog to a version
# code, which only a binary upload provides — uploading a changelog
# without a binary aborts ("no version code given"). The binary call
# above owns the changelog. Defaults come from Appfile.
upload_to_play_store(
metadata_path: "./fastlane/metadata/android",
skip_upload_apk: true,
skip_upload_aab: true,
skip_upload_changelogs: false,
skip_upload_changelogs: true,
skip_upload_metadata: false,
skip_upload_images: false,
skip_upload_screenshots: false,
Expand All @@ -104,15 +114,16 @@ platform :android do

desc "Upload Play Store listing (metadata + screenshots) without a binary"
lane :store_metadata do
# Hard-pin the metadata-only push to the internal track. The main store
# listing is global, but the changelog is per-track — pinning "internal"
# (not release_track) guarantees a metadata sync can never write to the
# production track even if release_track is ever changed.
# Metadata-only listing sync (no binary): skip the changelog because
# supply needs a version code to attach it, which only a binary upload
# provides. Hard-pin the internal track — the listing is global but the
# changelog is per-track, so this can never touch the production track
# even if release_track is ever changed.
upload_to_play_store(
metadata_path: "./fastlane/metadata/android",
skip_upload_apk: true,
skip_upload_aab: true,
skip_upload_changelogs: false,
skip_upload_changelogs: true,
skip_upload_metadata: false,
skip_upload_images: false,
skip_upload_screenshots: false,
Expand Down
19 changes: 16 additions & 3 deletions ios/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,20 @@ platform :ios do
)

# Push the App Store listing (metadata + screenshots) alongside the binary.
# Defaults come from Deliverfile + Appfile.
# `skip_app_version_update: false` lets `deliver` create or select the
# editable App Store version (named after marketing_version) itself — a
# TestFlight-only app has no editable version yet, so without this deliver
# aborts with "could not find an editable version for 'IOS'". The
# Deliverfile keeps this safe: `force` (no prompts), `submit_for_review false`
# and `automatic_release false`, so deliver only *stages* the listing for a
# human to submit — it never auto-submits or releases to the public.
deliver(
api_key: get_api_key,
app_version: marketing_version,
metadata_path: "./fastlane/metadata",
screenshots_path: "./fastlane/screenshots",
skip_binary_upload: true, # binary already uploaded by upload_to_testflight
skip_app_version_update: true,
skip_app_version_update: false, # create/select the editable App Store version
skip_metadata: false,
skip_screenshots: false
)
Expand All @@ -111,12 +118,18 @@ platform :ios do
desc "Upload App Store listing (metadata + screenshots) without a binary"
lane :store_metadata do
setup_ci
# Metadata-only sync (no binary). skip_app_version_update: false so deliver
# targets — and, if none exists yet, creates — the editable App Store
# version to attach the listing to. Safe via the Deliverfile
# (submit_for_review false + automatic_release false): staged, never
# submitted. app_version is read from the project when not given; in
# practice the editable version is already present from a prior release.
deliver(
api_key: get_api_key,
metadata_path: "./fastlane/metadata",
screenshots_path: "./fastlane/screenshots",
skip_binary_upload: true,
skip_app_version_update: true,
skip_app_version_update: false,
skip_metadata: false,
skip_screenshots: false
)
Expand Down
Loading