require "fileutils" require "tmpdir" default_platform(:ios) APP_IDENTIFIER = "sh.paseo" # Store screenshots are rendered into assets/store-screenshots/output; see the # README there. Google Play and F-Droid both read fastlane/metadata/android at # the repo root, whose screenshot files link to those renders. REPO_ROOT = File.expand_path("../../..", __dir__) STORE_SCREENSHOTS = File.join(REPO_ROOT, "assets/store-screenshots/output") ANDROID_METADATA = File.join(REPO_ROOT, "fastlane/metadata/android") # deliver reads screenshots from one folder per language and picks the device # slot from each image's size, so both Apple sets go into en-US together. def stage_app_store_screenshots root = Dir.mktmpdir("app-store-screenshots") locale = File.join(root, "en-US") FileUtils.mkdir_p(locale) { "apple" => "iphone", "apple-ipad" => "ipad" }.each do |set, device| Dir.glob(File.join(STORE_SCREENSHOTS, set, "*.png")).sort.each do |file| FileUtils.cp(file, File.join(locale, "#{device}-#{File.basename(file)}")) end end root end 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") 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, reject_if_possible: true, release_notes: { "en-US" => "Bug fixes and improvements.", }, force: true, skip_binary_upload: true, screenshots_path: stage_app_store_screenshots, overwrite_screenshots: true, precheck_include_in_app_purchases: false, submission_information: { add_id_info_uses_idfa: false, export_compliance_uses_encryption: false, }, ) end end platform :android do desc "Replace the Google Play store screenshots with the ones in the repo" lane :upload_screenshots do # supply attaches listing changes to a release on the production track. # The release workflow passes the version code it just submitted; without # it, supply uses the track's only release. version_code = ENV["ANDROID_VERSION_CODE"].to_s.strip supply( package_name: APP_IDENTIFIER, json_key: ENV.fetch("GOOGLE_PLAY_JSON_KEY"), metadata_path: ANDROID_METADATA, version_code: version_code.empty? ? nil : version_code.to_i, skip_upload_apk: true, skip_upload_aab: true, skip_upload_metadata: true, skip_upload_changelogs: true, skip_upload_images: true, sync_image_upload: true, ) end end