b0VIM 8.0y\7joshJoshs-MacBook-Pro.local~josh/Projects/fastlane/fastlane/pilot/lib/pilot/build_manager.rbutf-8 3210#"! UtpXVXbK\[adXn; q& w v E D Q C B  a W V   ,    mC`@-{#SFU1 latest_build = FastlaneCore::BuildWatcher.wait_for_build_processing_to_be_complete(app_id: app.id, platform: platform, train_version: app_version, build_version: app_build, poll_interval: config[:wait_processing_interval], return_spaceship_testflight_build: false) app_build = FastlaneCore::IpaFileAnalyser.fetch_app_build(config[:ipa]) app_version = FastlaneCore::IpaFileAnalyser.fetch_app_version(config[:ipa]) platform = fetch_app_platform def wait_for_build_processing_to_be_complete end end end UI.user_error!("No changelog provided for new build. Please either disable `distribute_external` or provide a changelog using the `changelog` option") else options[:changelog] = UI.input("No changelog provided for new build. You can provide a changelog using the `changelog` option. For now, please provide a changelog here:") if UI.interactive? if !has_changelog_or_whats_new?(options) && options[:distribute_external] == true def check_for_changelog_or_whats_new!(options) end return has_changelog end end has_changelog = v.key?(:whats_new) || v.key?('whats_new') v ||= {} next if has_changelog infos_by_lang.each do |k, v| infos_by_lang = options[:localized_build_info] || [] unless has_changelog # Look for :whats_new in :localized_build_info has_changelog = !options[:changelog].nil? # Look for legacy :changelog option def has_changelog_or_whats_new?(options) end distribute(options, build: latest_build) latest_build = wait_for_build_processing_to_be_complete UI.message("If you want to skip waiting for the processing to be finished, use the `skip_waiting_for_build_processing` option") login unless should_login_in_start # Calling login again here is needed if login was not called during 'start' end return UI.important("This means that no changelog will be set and no build will be distributed to testers") UI.important("Skip waiting for build processing") if config[:skip_waiting_for_build_processing] UI.success("Successfully uploaded the new binary to App Store Connect") end UI.user_error!("Error uploading ipa file, for more information see above") unless result result = transporter.upload(fetch_app_id, package_path) transporter = transporter_for_selected_team(options) platform: platform) package_path: dir, ipa_path: options[:ipa], package_path = FastlaneCore::IpaUploadPackageBuilder.new.generate(app_id: fetch_app_id, platform = fetch_app_platform dir = Dir.mktmpdir UI.success("Ready to upload new build to TestFlight (App: #{fetch_app_id})...") check_for_changelog_or_whats_new!(options) UI.user_error!("No ipa file given") unless config[:ipa] start(options, should_login: should_login_in_start) should_login_in_start = options[:apple_id].nil? # 'login' will be deferred until before waiting for build processing # Only need to login before upload if no apple_id was given def upload(options) class BuildManager < Manager # rubocop:disable Metrics/ClassLengthmodule Pilotrequire_relative 'manager'require 'fastlane_core/ipa_upload_package_builder'require 'fastlane_core/build_watcher'require 'fastlane_core/itunes_transporter'require 'emoji_regex'require 'terminal-table'require 'tmpdir'ad > ^z w o i B > eend # rubocop:enable Metrics/ClassLength end end client.patch_build_beta_details(build_beta_details_id: build_beta_detail.id, attributes: attributes) client = Spaceship::ConnectAPI::Base.client attributes[:autoNotifyEnabled] = info[:auto_notify_enabled] if info.key?(:auto_notify_enabled) attributes = {} build_beta_detail = build.build_beta_detail def update_build_beta_details(build, info) end end client.post_beta_build_localizations(build_id: build.id, attributes: attributes) attributes[:locale] = locale if locale else client.patch_beta_build_localizations(localization_id: localization.id, attributes: attributes)ad,V U   k j / H y G = <  a 6 5  w{0('e[Z<|rqT]SIHW title: "#{app.name} Builds".green, puts(Terminal::Table.new( end )) rows: FastlaneCore::PrintTable.transform_output(build_deliveries) headings: ["Version #", "Build #"], title: "#{app.name} Processing Builds".green, puts(Terminal::Table.new( unless build_deliveries.empty? # Only show table if there are any build deliveries end ] build.beta_build_metrics.map(&:install_count).reduce(:+) build.version, build.app_version, [ builds = app.get_builds(includes: "betaBuildMetrics,preReleaseVersion", sort: "-uploadedDate").map do |build| # Get processed builds end ] build_delivery.cf_build_version build_delivery.cf_build_short_version_string, [ build_deliveries = app.get_build_deliveries.map do |build_delivery| # Get processing builds end config[:app_identifier] = UI.input("App Identifier: ") if config[:apple_id].to_s.length == 0 && config[:app_identifier].to_s.length == 0 start(options) def list(options) end UI.success("Successfully distributed build to #{type} testers 🚀") type = options[:distribute_external] ? 'External' : 'Internal' distribute_build(build, options) end end UI.success("Deleted beta app review submission for previous build: #{waiting_for_review_build.app_version} - #{waiting_for_review_build.version}") waiting_for_review_build.beta_app_review_submission.delete! UI.important("Deleting beta app review submission for build: #{waiting_for_review_build.app_version} - #{waiting_for_review_build.version}") UI.important("Another build is already in review. Going to remove that build and submit the new one.") unless waiting_for_review_build.nil? waiting_for_review_build = build.app.get_builds(filter: { "betaAppReviewSubmission.betaReviewState" => "WAITING_FOR_REVIEW" }, includes: "betaAppReviewSubmission,preReleaseVersion").first if options[:reject_build_waiting_for_review] return if config[:skip_submission] update_beta_app_meta(options, build) # 5. Auto notify enabled with config[:notify_external_testers] # 4. Localized build info # 3. Localized app info # 2. App info # 1. Demo account required # Update beta app meta info end UI.user_error!("No build to distribute!") if build.nil? # Error out if no build end build = Spaceship::ConnectAPI::Build.get(build_id: build.id) UI.important("Fetching a new build with all the information needed") UI.important("Build did include information for app, build beta detail and pre release version") if build && (!build.app || !build.build_beta_detail || !build.pre_release_version) # and fetch a new build if not # Verify the build has all the includes that we need build ||= Spaceship::ConnectAPI::Build.all(app_id: app.id, sort: "-uploadedDate", limit: 1).first # Get latest uploaded build if no build specified end config[:app_identifier] = UI.input("App Identifier: ") if config[:apple_id].to_s.length == 0 && config[:app_identifier].to_s.length == 0 start(options) def distribute(options, build: nil) end return latest_build end UI.important("Uploaded app #{app_version} - #{app_build}, but received build #{latest_build.app_version} - #{latest_build.version}.") unless latest_build.app_version == app_version && latest_build.version == app_buildadUb~uml?q-#" e K v h  @ 4 * ) i [ e Y O N % IqB[7/."!N# e:21 # If itc_provider was explicitly specified, use it. end !options[:localized_build_info].nil? def should_update_localized_build_information?(options) end !options[:localized_app_info].nil? def should_update_localized_app_information?(options) end options[:beta_app_description].to_s.length > 0 || options[:beta_app_feedback_email].to_s.length > 0 def should_update_app_test_information?(options) end options[:changelog].to_s.length > 0 def should_update_build_information?(options) end !options[:beta_app_review_info].nil? def should_update_beta_app_review_info(options) end return row build.install_count] build.build_version, row = [build.train_version, def describe_build(build) private end truncate_changelog(changelog) changelog = strip_emoji(changelog) def self.sanitize_changelog(changelog) end changelog end UI.important("Emoji symbols have been removed from the changelog, since they're not allowed by Apple.") changelog.gsub!(EmojiRegex::Regex, "") if changelog && changelog =~ EmojiRegex::Regex def self.strip_emoji(changelog) end changelog end UI.important("Changelog has been truncated since it exceeds Apple's #{max_changelog_length} character limit. It currently contains #{original_length} characters.") changelog = "#{changelog[0...max_changelog_length - bottom_message.length]}#{bottom_message}" bottom_message = "..." original_length = changelog.length if changelog && changelog.length > max_changelog_length max_changelog_length = 4000 def self.truncate_changelog(changelog) end }) auto_notify_enabled: options[:notify_external_testers] update_build_beta_details(build, { end end UI.user_error!("Could not set changelog: #{ex}") rescue => ex UI.success("Successfully set the changelog for build") update_localized_build_review(build, {}, default_info: { whats_new: options[:changelog] }) begin elsif should_update_build_information?(options) update_localized_build_review(build, options[:localized_build_info]) if should_update_localized_build_information?(options) end end UI.user_error!("Could not set beta_app_feedback_email and/or beta_app_description: #{ex}") rescue => ex UI.success("Successfully set the beta_app_feedback_email and/or beta_app_description") update_localized_app_review(build, {}, default_info: default_info) begin default_info[:description] = options[:beta_app_description] if options[:beta_app_description] default_info[:feedback_email] = options[:beta_app_feedback_email] if options[:beta_app_feedback_email] default_info = {} elsif should_update_app_test_information?(options) update_localized_app_review(build, options[:localized_app_info]) if should_update_localized_app_information?(options) end update_review_detail(build, options[:beta_app_review_info]) if should_update_beta_app_review_info(options) update_review_detail(build, { demo_account_required: options[:demo_account_required] }) # Setting account required wth AppStore Connect API def update_beta_app_meta(options, build) end )) rows: FastlaneCore::PrintTable.transform_output(builds) headings: ["Version #", "Build #", "Installs"],adcKj;h  P  g T z r q ? l - , v  sC76zPFE:21n! XNFExcb attributes[:contactPhone] = info[:contact_phone] if info.key?(:contact_phone) attributes[:contactLastName] = info[:contact_last_name] if info.key?(:contact_last_name) attributes[:contactFirstName] = info[:contact_first_name] if info.key?(:contact_first_name) attributes[:contactEmail] = info[:contact_email] if info.key?(:contact_email) attributes = {} info = info.collect { |k, v| [k.to_sym, v] }.to_h def update_review_detail(build, info) end end wait_for_build_processing_to_be_complete UI.important("Set 'ITSAppUsesNonExemptEncryption' in the 'Info.plist' to skip this step and speed up the submission") UI.important("Export compliance has been set to '#{uses_non_exempt_encryption}'. Need to wait for build to finishing processing again...") client.patch_builds(build_id: uploaded_build.id, attributes: attributes) client = Spaceship::ConnectAPI::Base.client attributes = { usesNonExemptEncryption: uses_non_exempt_encryption } uses_non_exempt_encryption = options[:uses_non_exempt_encryption] if uploaded_build.uses_non_exempt_encryption.nil? def set_export_compliance_if_needed(uploaded_build, options) end true end UI.user_error!("You must specify at least one group using the `:groups` option to distribute externally") # get selected but this no longer exists with Spaceship::ConnectAPI # Legacy Spaceship::TestFlight API used to have a `default_external_group` that would automatically if options[:distribute_external] && options[:groups].nil? end end uploaded_build.add_beta_groups(beta_groups: beta_groups) unless beta_groups.empty? end options[:groups].include?(group.name) beta_groups = app.get_beta_groups.select do |group| app = uploaded_build.app if options[:groups] end end UI.message("Build #{uploaded_build.app_version} - #{uploaded_build.version} already submitted for review") else uploaded_build.post_beta_app_review_submission if uploaded_build.ready_for_beta_submission? if options[:groups] || options[:distribute_external] set_export_compliance_if_needed(uploaded_build, options) # This is where we could add a check to see if encryption is required and has been updated UI.message("Distributing new build to testers: #{uploaded_build.app_version} - #{uploaded_build.version}") def distribute_build(uploaded_build, options) end end return generic_transporter UI.verbose("Couldn't infer a provider short name for team with id #{Spaceship::Tunes.client.team_id} automatically: #{ex}. Proceeding without provider short name.") rescue => ex return FastlaneCore::ItunesTransporter.new(options[:username], nil, false, provider_id) UI.verbose("Inferred provider id #{provider_id} for team #{name}.") provider_id = generic_transporter.provider_ids[name] name = team['contentProvider']['name'] team = Spaceship::Tunes.client.teams.find { |t| t['contentProvider']['contentProviderId'].to_s == Spaceship::Tunes.client.team_id } begin return generic_transporter unless Spaceship::Tunes.client.teams.count > 1 return generic_transporter if options[:itc_provider] || Spaceship::Tunes.client.nil? generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider]) def transporter_for_selected_team(options) # If there are fewer than two teams, don't infer the provider. # If there are multiple teams, infer the provider from the selected team name.ad(\6[ f e O )  [ 2 ( '  _ U T ! : 0 ( ' l  GI>[N-|Qed<zd if localization client = Spaceship::ConnectAPI::Base.client attributes[:whatsNew] = self.class.sanitize_changelog(info[:whats_new]) if info.key?(:whats_new) attributes = {} def update_localized_build_review_for_lang(build, localization, locale, info) end end update_localized_build_review_for_lang(build, localization, lang_code, info) if info info = default_info unless info info = info_by_lang[lang_code] localizations_by_lang.each do |lang_code, localization| # Create or update localized app review info end localizations_by_lang[localization.locale.to_sym] = localization localizations.each do |localization| localizations = build.get_beta_build_localizations # Validate locales exist end localizations_by_lang[key] = nil info_by_lang.each_key do |key| localizations_by_lang = {} # Initialize hash of lang codes with info_by_lang keys end default_info = info_by_lang.delete(:default) else info_by_lang.delete(:default) if default_info info_by_lang = info_by_lang.collect { |k, v| [k.to_sym, v] }.to_h def update_localized_build_review(build, info_by_lang, default_info: nil) end end client.post_beta_app_localizations(app_id: app.id, attributes: attributes) attributes[:locale] = locale if locale else client.patch_beta_app_localizations(localization_id: localization.id, attributes: attributes) if localization client = Spaceship::ConnectAPI::Base.client attributes[:description] = info[:description] if info.key?(:description) attributes[:tvOsPrivacyPolicy] = info[:tv_os_privacy_policy_url] if info.key?(:tv_os_privacy_policy_url) attributes[:privacyPolicyUrl] = info[:privacy_policy_url] if info.key?(:privacy_policy_url) attributes[:marketingUrl] = info[:marketing_url] if info.key?(:marketing_url) attributes[:feedbackEmail] = info[:feedback_email] if info.key?(:feedback_email) attributes = {} def update_localized_app_review_for_lang(app, localization, locale, info) end end update_localized_app_review_for_lang(app, localization, lang_code, info) if info info = default_info unless info info = info_by_lang[lang_code] localizations_by_lang.each do |lang_code, localization| # Create or update localized app review info end localizations_by_lang[localization.locale.to_sym] = localization localizations.each do |localization| localizations = app.get_beta_app_localizations # Validate locales exist end localizations_by_lang[key] = nil info_by_lang.each_key do |key| localizations_by_lang = {} # Initialize hash of lang codes with info_by_lang keys end default_info = info_by_lang.delete(:default) else info_by_lang.delete(:default) if default_info info_by_lang = info_by_lang.collect { |k, v| [k.to_sym, v] }.to_h def update_localized_app_review(build, info_by_lang, default_info: nil) end client.patch_beta_app_review_detail(app_id: build.app.id, attributes: attributes) client = Spaceship::ConnectAPI::Base.client attributes[:notes] = info[:notes] if info.key?(:notes) attributes[:demoAccountRequired] = info[:demo_account_required] if info.key?(:demo_account_required) attributes[:demoAccountPassword] = info[:demo_account_password] if info.key?(:demo_account_password) attributes[:demoAccountName] = info[:demo_account_name] if info.key?(:demo_account_name)