Files
orca/mobile/fastlane/ios_release_version.rb
Neil 3830851a83 fix(mobile): unblock iOS releases and prepare 0.0.36 (#10888)
* fix(mobile): block iOS uploads below the last shipped App Store version

The closed-train guard looked up each candidate version's own App Store
record, but a version only gets one once it is submitted for review.
0.0.34 reached TestFlight and was never submitted, so it had no record,
nothing looked closed, and the patch-bump walk stopped there — while
0.0.35 had already shipped. Apple rejected the upload after a 24-minute
build (90186 closed train, 90062 needs a higher CFBundleShortVersionString).

Fetch the highest closed version once and treat everything at or below it
as closed, comparing semver numerically so 0.0.10 outranks 0.0.9.

Also read appVersionState alongside appStoreState: the latter is
deprecated in App Store Connect API 3.3 and renames the shipped state to
READY_FOR_DISTRIBUTION, so reading only the old field would silently find
zero closed versions once Apple stops populating it.

* chore(mobile): prepare 0.0.36

app.json sat at 0.0.32 while 0.0.35 shipped on the App Store, because
release versions are resolved on the runner and never committed back.
Close the four-version drift so the checked-in version matches reality
and the iOS release no longer depends on the closed-train walk to find
an open version.

Bump Android versionCode 8 -> 9 in the same commit: the version is shared
between platforms, and shipping 0.0.36 with the code that already shipped
for 0.0.32 produces an APK that cannot install over the released build.
2026-07-27 01:53:11 -07:00

57 lines
1.8 KiB
Ruby

module IosReleaseVersion
module_function
SEMVER_PATTERN = /\A(\d+)\.(\d+)\.(\d+)\z/.freeze
def truthy?(value)
%w[1 true yes on].include?(value.to_s.strip.downcase)
end
# [major, minor, patch] for semver comparison, or nil for anything else.
def parse(version)
match = version.to_s.strip.match(SEMVER_PATTERN)
return nil unless match
[match[1].to_i, match[2].to_i, match[3].to_i]
end
def bump_patch(version)
parts = parse(version)
raise ArgumentError, "Cannot bump non-semver mobile version '#{version}'" unless parts
"#{parts[0]}.#{parts[1]}.#{parts[2] + 1}"
end
# Highest semver in `versions`, skipping entries App Store Connect reports in
# a non-semver shape. Compares numerically: "0.0.10" beats "0.0.9", which a
# string sort gets backwards.
def max_version(versions)
Array(versions)
.map { |version| version.to_s.strip }
.select { |version| parse(version) }
.max_by { |version| parse(version) }
end
# Apple rejects an upload whose CFBundleShortVersionString is not higher than
# the last approved version (90062) and reports that version's train as closed
# (90186). So every version at or below `highest_closed` is unusable, not just
# the ones whose own App Store record sits in a closed state.
def closed_train?(version, highest_closed)
candidate = parse(version)
ceiling = parse(highest_closed)
return false unless candidate && ceiling
(candidate <=> ceiling) <= 0
end
def resolve(requested:, bump_patch:, current_version:, train_closed:)
exact_version = requested.to_s.strip
return exact_version unless exact_version.empty?
return current_version unless truthy?(bump_patch)
candidate = bump_patch(current_version)
candidate = bump_patch(candidate) while train_closed.call(candidate)
candidate
end
end