Files
paseo/packages/app/fastlane/Fastfile
Mohamed Boudra f4a4e0c25c fix(ios-release): submit each iOS build to its own App Store version
The submit_review fastlane lane identified the just-uploaded build by
CFBundleVersion alone. In this project the production iOS profile resets
the build number per marketing version, so every release sits at build 2
and Spaceship::ConnectAPI::Build.all returns the whole TestFlight history.
The .find call picked an arbitrary match, which is how 0.1.76's binary got
attached to the 0.1.75 App Store version slot and submitted for review
under the wrong label.

The release workflow now forwards app_version and app_build_version from
the build_ios outputs into the fastlane step as env vars, and the Fastfile
filters builds by marketing version plus build number to pin the exact
binary. The standalone resubmit workflow takes the same identifiers as
optional workflow_dispatch inputs; with both unset it falls back to "most
recently uploaded iOS build" so manual reruns still work.

Pinning fastlane in the Gemfile to lock the Spaceship behavior this lane
depends on.
2026-05-16 16:46:10 +07:00

113 lines
4.4 KiB
Ruby

default_platform(:ios)
APP_IDENTIFIER = "sh.paseo"
platform :ios do
desc "Submit the latest TestFlight build to App Store Review"
lane :submit_review do
api_key = app_store_connect_api_key(
key_id: ENV.fetch("ASC_KEY_ID"),
issuer_id: ENV.fetch("ASC_ISSUER_ID"),
key_filepath: ENV.fetch("ASC_KEY_P8"),
)
require "spaceship"
Spaceship::ConnectAPI.token = Spaceship::ConnectAPI::Token.create(
key_id: ENV.fetch("ASC_KEY_ID"),
issuer_id: ENV.fetch("ASC_ISSUER_ID"),
filepath: ENV.fetch("ASC_KEY_P8"),
)
app = Spaceship::ConnectAPI::App.find(APP_IDENTIFIER)
UI.user_error!("Could not find app #{APP_IDENTIFIER} on App Store Connect") if app.nil?
# The release workflow forwards the marketing version and CFBundleVersion of
# the binary it just built. Use them to identify the exact build, since
# CFBundleVersion is not unique across marketing versions in this project
# (the production profile resets it per app_version). The resubmit workflow
# leaves these unset and falls back to "most recently uploaded iOS build".
target_version = ENV["APP_VERSION"].to_s.strip
target_build = ENV["APP_BUILD_VERSION"].to_s.strip
target_version = nil if target_version.empty?
target_build = nil if target_build.empty?
if target_version
UI.message("Targeting App Store version #{target_version}#{target_build ? " build #{target_build}" : ""} from workflow input")
else
UI.message("No APP_VERSION provided; selecting the most recently uploaded iOS build")
end
valid_build = nil
deadline = Time.now + (30 * 60)
loop do
builds = Spaceship::ConnectAPI::Build.all(
app_id: app.id,
sort: "-uploadedDate",
limit: 100,
).select { |b| b.pre_release_version&.platform == "IOS" }
builds = builds.select { |b| b.pre_release_version&.version == target_version } if target_version
builds = builds.select { |b| b.version == target_build } if target_build
build = builds.first
if build.nil?
UI.user_error!("Timed out waiting for build to appear on App Store Connect.") if Time.now > deadline
UI.message("No matching iOS build visible on App Store Connect yet; waiting...")
sleep(30)
next
end
state = build.processing_state
UI.message("Build #{build.pre_release_version.version} (#{build.version}) processing state: #{state || 'unknown'}")
if state == "VALID"
valid_build = build
break
end
if state == "INVALID" || state == "FAILED"
UI.user_error!("Build #{build.version} failed App Store processing (state: #{state}).")
end
if Time.now > deadline
UI.user_error!("Timed out waiting for build #{build.version} to finish processing on App Store Connect.")
end
sleep(30)
end
app_version = valid_build.pre_release_version.version
build_number = valid_build.version
UI.message("Submitting build #{build_number} as App Store version #{app_version} for review")
# Ensure an editable App Store version exists for this marketing version.
# deliver requires a "Prepare for Submission" version on App Store Connect
# and won't auto-create one when skip_metadata + skip_screenshots are set.
app.ensure_version!(app_version, platform: Spaceship::ConnectAPI::Platform::IOS)
# Apple requires a non-empty "What's New" on every new App Store version
# before it can be submitted for review. With skip_metadata: true, deliver
# never sets this, so we populate it directly per-locale.
edit_version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::IOS)
release_notes = "Bug fixes and improvements."
edit_version.get_app_store_version_localizations.each do |localization|
if localization.whats_new.to_s.strip.empty?
UI.message("Setting whatsNew on #{localization.locale}")
localization.update(attributes: { whatsNew: release_notes })
end
end
deliver(
api_key: api_key,
app_identifier: APP_IDENTIFIER,
app_version: app_version,
build_number: build_number.to_s,
submit_for_review: true,
automatic_release: true,
force: true,
skip_binary_upload: true,
skip_screenshots: true,
skip_metadata: true,
precheck_include_in_app_purchases: false,
submission_information: {
add_id_info_uses_idfa: false,
export_compliance_uses_encryption: false,
},
)
end
end