mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
* ci(release): explain App Store 'previous version in review' failures
When a release is tagged before the previous version clears Apple review,
App Store Connect refuses to create the version or attach the build, and
the lane died with a raw spaceship stacktrace ('cannot create a new
version...' / 'pre-release build could not be added'). Both hit v18.13.1
(iOS + macOS) and v18.12.1 (macOS).
Match those two messages and replace the stacktrace with a one-line,
actionable annotation (let the previous version finish review, or pull it
from review, then re-run) -- still a hard failure, since the version did
not go out and a green run would falsely read as 'released'. The existing
'review submission already in progress' race stays a soft success.
Static annotation text only (no e/options interpolation; key-material).
* fix(sync): name discarded title in LWW conflict banner; fix fr dismiss label (#8694)
* fix(sync): surface the last discarded title in LWW conflict banner
Multi-review follow-up to the #8694 banner:
- Show the LAST non-empty discarded title (the user's final offline rename),
not the first — a stale intermediate rename is misleading. Also simpler
(drops the first-wins guard).
- Document why the kept==discarded equality guard is correct: when a title
edit loses to a concurrent other-field remote win, the current state still
shows the rejected local title, so an annotation would only repeat it —
silencing it is right, not a hidden divergence.
fr.json G.DISMISS "Rejeter"->"Ignorer" (prior commit) is an intentional,
owner-approved deviation from the en-only rule: it is a mistranslation fix
for the shared banner dismiss label, not a new string. Other locales may
carry the same "Reject"-flavored label — left as a follow-up.
174 lines
8.4 KiB
Ruby
174 lines
8.4 KiB
Ruby
# iOS and macOS App Store submission lanes.
|
|
#
|
|
# The signed release binaries are produced in CI:
|
|
# - iOS: .github/workflows/build-ios.yml -> .ipa
|
|
# - macOS: .github/workflows/build-publish-to-mac-store-on-release.yml -> MAS .pkg
|
|
#
|
|
# These lanes take the finished artifact, upload it to App Store Connect, set
|
|
# the "What's New" text, submit the version for review and flag it to be
|
|
# released automatically once Apple approves it. The only step that cannot be
|
|
# automated is Apple's human review itself.
|
|
#
|
|
# Authentication uses an App Store Connect API key (not an Apple ID +
|
|
# app-specific password), which is far more robust in CI.
|
|
#
|
|
# IMPORTANT: the API key must belong to a user with the "App Manager" role (or
|
|
# higher). A key with only the "Developer" role can upload/notarize but cannot
|
|
# create a version or submit it for review.
|
|
#
|
|
# SECURITY: never enable verbose mode (--verbose / FASTLANE_VERBOSE / verbose:
|
|
# true) in these lanes. Verbose output can dump the deliver options hash, which
|
|
# contains the App Store Connect API key material.
|
|
#
|
|
# Listing metadata (description, keywords, screenshots, ...) is managed manually
|
|
# in App Store Connect and is intentionally NOT overwritten here. METADATA_PATH
|
|
# is a dedicated dir that contains ONLY <locale>/release_notes.txt, so deliver's
|
|
# load_from_filesystem reads just the release notes (it skips fields with no
|
|
# local file: `next unless File.exist?`, and upload skips nil fields). There is
|
|
# no remote read-back, so other listing fields are left untouched. Do NOT set
|
|
# `skip_metadata` here: it makes deliver return early and upload no release notes
|
|
# at all. `skip_screenshots` is safe — screenshots live elsewhere.
|
|
#
|
|
# Required env vars (wired from GitHub secrets in the workflows):
|
|
# ASC_KEY_ID App Store Connect API key id (secrets.mac_api_key_id)
|
|
# ASC_ISSUER_ID App Store Connect API issuer id (secrets.mac_api_key_issuer_id)
|
|
# ASC_KEY_CONTENT Contents of the .p8 key file (secrets.mac_api_key)
|
|
# IPA_PATH Path to the built .ipa (ios lane)
|
|
# PKG_PATH Path to the built MAS .pkg (mac lane)
|
|
# Optional:
|
|
# METADATA_PATH release-notes dir (default: fastlane/appstore_metadata)
|
|
# SUBMIT_FOR_REVIEW "false" to only upload the build without submitting
|
|
|
|
APP_IDENTIFIER = 'com.super-productivity.app'
|
|
|
|
def asc_api_key
|
|
app_store_connect_api_key(
|
|
key_id: ENV.fetch('ASC_KEY_ID'),
|
|
issuer_id: ENV.fetch('ASC_ISSUER_ID'),
|
|
key_content: ENV.fetch('ASC_KEY_CONTENT'),
|
|
is_key_content_base64: false,
|
|
)
|
|
end
|
|
|
|
def submit_for_review?
|
|
ENV.fetch('SUBMIT_FOR_REVIEW', 'true') != 'false'
|
|
end
|
|
|
|
# Shared deliver options. `extra` carries the platform-specific binary path.
|
|
def deliver_options(extra)
|
|
{
|
|
api_key: asc_api_key,
|
|
app_identifier: APP_IDENTIFIER,
|
|
# Dir holding only <locale>/release_notes.txt -> deliver uploads just the
|
|
# "What's New" text and leaves every other listing field as-is. (Do not add
|
|
# skip_metadata; see the header comment.)
|
|
metadata_path: ENV.fetch('METADATA_PATH', 'fastlane/appstore_metadata'),
|
|
skip_screenshots: true,
|
|
submit_for_review: submit_for_review?,
|
|
automatic_release: true,
|
|
# Precheck inspects listing metadata we don't manage from here.
|
|
run_precheck_before_submit: false,
|
|
submission_information: {
|
|
add_id_info_uses_idfa: false,
|
|
# The app only relies on exempt encryption (HTTPS/standard OS crypto), so
|
|
# it does not use non-exempt encryption. Without this, App Store Connect
|
|
# rejects the submission with "Export compliance is required to submit".
|
|
# Mirrors ITSAppUsesNonExemptEncryption=false in ios/App/App/Info.plist.
|
|
export_compliance_uses_encryption: false,
|
|
},
|
|
# Non-interactive: skip the HTML report confirmation prompt.
|
|
force: true,
|
|
}.merge(extra)
|
|
end
|
|
|
|
# Both release lanes run on the same `v*` tag push (see the two release
|
|
# workflows) and can race on App Store Connect's per-app review-submission
|
|
# state. When they collide, the lane that submits second gets "Cannot submit
|
|
# for review - A review submission is already in progress" -- but only AFTER its
|
|
# binary has already uploaded and processed successfully, so the build is safe
|
|
# on App Store Connect. Treat that one collision as a soft success and emit a CI
|
|
# warning: the build just needs to be added to the already-open submission for
|
|
# THIS version in the App Store Connect UI (otherwise this platform skips this
|
|
# version and users get it at the next release). Every other failure -- and any
|
|
# failure before the binary uploads, e.g. the version-creation race -- still
|
|
# aborts the lane loudly.
|
|
#
|
|
# No stable error code is surfaced through deliver, so this is message-matched
|
|
# (verified against fastlane deliver 2.225.0). If Apple/fastlane rephrase it,
|
|
# the match stops firing and the lane falls back to hard-failing -- the safe
|
|
# direction.
|
|
REVIEW_SUBMISSION_RACE = 'review submission is already in progress'
|
|
|
|
# App Store Connect also serializes review state per app across releases: while
|
|
# one version is still in review, the next version can neither be created nor
|
|
# have a build attached. So a release tagged before the previous one clears
|
|
# review fails either at version creation ("You cannot create a new version of
|
|
# the App in the current state") or at build-attach ("The specified pre-release
|
|
# build could not be added"). Unlike the race above this is NOT softened -- the
|
|
# version genuinely did not go out, and a green run would falsely read as
|
|
# "released". We only trade the raw spaceship stacktrace for a one-line,
|
|
# actionable explanation; the lane still fails. Message-matched against fastlane
|
|
# deliver 2.225.0 -- if the phrasing drifts it simply falls back to the raw
|
|
# error, which is fine (the job still fails).
|
|
VERSION_IN_REVIEW_BLOCKS = [
|
|
'cannot create a new version',
|
|
'pre-release build could not be added',
|
|
].freeze
|
|
|
|
def submit_to_app_store(extra)
|
|
upload_to_app_store(deliver_options(extra))
|
|
rescue => e
|
|
msg = e.message.to_s.downcase
|
|
|
|
# Soft success: the other platform's release lane already opened the shared
|
|
# per-app review submission. The binary is uploaded and safe; it just needs to
|
|
# be added to the open submission by hand. Surface as a GitHub Actions run
|
|
# annotation, not just a buried log line, so a green job still flags the
|
|
# required manual step. Static text only -- never interpolate e/options here
|
|
# (they can carry API key material; see header).
|
|
if submit_for_review? && msg.include?(REVIEW_SUBMISSION_RACE)
|
|
if ENV['GITHUB_ACTIONS']
|
|
puts '::warning title=App Store build uploaded but NOT submitted::A review ' \
|
|
'submission is already open for this app. Add this build to it in App ' \
|
|
'Store Connect, or this platform skips this version.'
|
|
end
|
|
UI.important('Build uploaded, but a review submission is already open for this app')
|
|
UI.important("(the other platform's release lane created it first). Add this build to")
|
|
UI.important('the open submission in App Store Connect for this version, or this platform')
|
|
UI.important('will skip this version. Not failing the lane: the binary is uploaded.')
|
|
return
|
|
end
|
|
|
|
# Known "a previous version is still in review" block. Still a hard failure --
|
|
# we only replace the raw stacktrace with a clear, actionable one-liner (the
|
|
# raw error still prints after the re-raise). Static text only, same
|
|
# key-material reason as above.
|
|
if submit_for_review? && VERSION_IN_REVIEW_BLOCKS.any? { |m| msg.include?(m) }
|
|
if ENV['GITHUB_ACTIONS']
|
|
puts '::error title=App Store submit blocked: previous version still in review::' \
|
|
'App Store Connect will not create or submit this version while the ' \
|
|
'previous version is still in review. Let it finish review (or remove ' \
|
|
'it from review in App Store Connect), then re-run this release.'
|
|
end
|
|
UI.error('App Store submit blocked: a previous version is still in review.')
|
|
UI.error('App Store Connect will not create or submit this version until the')
|
|
UI.error('previous one clears review. Let it finish (or pull it from review in')
|
|
UI.error('App Store Connect), then re-run this release.')
|
|
end
|
|
|
|
raise
|
|
end
|
|
|
|
platform :ios do
|
|
desc 'Upload the prebuilt iOS .ipa to App Store Connect and submit for review'
|
|
lane :release do
|
|
submit_to_app_store(platform: 'ios', ipa: ENV.fetch('IPA_PATH'))
|
|
end
|
|
end
|
|
|
|
platform :mac do
|
|
desc 'Upload the prebuilt macOS .pkg to App Store Connect and submit for review'
|
|
lane :release do
|
|
submit_to_app_store(platform: 'osx', pkg: ENV.fetch('PKG_PATH'))
|
|
end
|
|
end
|