fastlane 2.232.1 → 2.233.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +94 -94
  3. data/credentials_manager/lib/credentials_manager/appfile_config.rb +4 -0
  4. data/deliver/lib/deliver/detect_values.rb +2 -0
  5. data/deliver/lib/deliver/options.rb +23 -0
  6. data/deliver/lib/deliver/runner.rb +17 -12
  7. data/deliver/lib/deliver/sync_app_previews.rb +204 -0
  8. data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +5 -1
  9. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +20 -4
  10. data/fastlane/lib/fastlane/actions/docs/upload_to_testflight.md +3 -0
  11. data/fastlane/lib/fastlane/actions/resign.rb +13 -2
  12. data/fastlane/lib/fastlane/actions/swiftlint.rb +8 -1
  13. data/fastlane/lib/fastlane/actions/upload_to_app_store.rb +1 -1
  14. data/fastlane/lib/fastlane/cli_tools_distributor.rb +10 -1
  15. data/fastlane/lib/fastlane/console.rb +2 -2
  16. data/fastlane/lib/fastlane/helper/s3_client_helper.rb +5 -2
  17. data/fastlane/lib/fastlane/version.rb +1 -1
  18. data/fastlane/swift/Deliverfile.swift +1 -1
  19. data/fastlane/swift/DeliverfileProtocol.swift +29 -1
  20. data/fastlane/swift/Fastlane.swift +105 -9
  21. data/fastlane/swift/Gymfile.swift +1 -1
  22. data/fastlane/swift/GymfileProtocol.swift +8 -1
  23. data/fastlane/swift/LaneFileProtocol.swift +3 -3
  24. data/fastlane/swift/Matchfile.swift +1 -1
  25. data/fastlane/swift/MatchfileProtocol.swift +8 -1
  26. data/fastlane/swift/Precheckfile.swift +1 -1
  27. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  28. data/fastlane/swift/Scanfile.swift +1 -1
  29. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  30. data/fastlane/swift/Screengrabfile.swift +1 -1
  31. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  32. data/fastlane/swift/Snapshotfile.swift +1 -1
  33. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  34. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +48 -17
  35. data/fastlane_core/lib/fastlane_core/video_utils.rb +202 -0
  36. data/frameit/lib/frameit/device_types.rb +2 -2
  37. data/gym/lib/gym/generators/build_command_generator.rb +2 -1
  38. data/gym/lib/gym/options.rb +5 -0
  39. data/match/lib/match/generator.rb +3 -1
  40. data/match/lib/match/options.rb +5 -0
  41. data/match/lib/match/runner.rb +12 -7
  42. data/match/lib/match/storage/s3_storage.rb +4 -1
  43. data/match/lib/match/storage.rb +1 -0
  44. data/pilot/lib/pilot/build_manager.rb +4 -12
  45. data/pilot/lib/pilot/options.rb +4 -0
  46. data/precheck/lib/precheck/rules/rules_data/curse_word_hashes/README.md +54 -0
  47. data/precheck/lib/precheck/rules/rules_data/curse_word_hashes/en_us.txt +2 -1
  48. data/scan/lib/scan/detect_values.rb +11 -3
  49. data/sigh/lib/assets/resign.sh +18 -5
  50. data/sigh/lib/sigh/commands_generator.rb +1 -0
  51. data/sigh/lib/sigh/manager.rb +6 -6
  52. data/sigh/lib/sigh/resign.rb +9 -6
  53. data/spaceship/lib/spaceship/connect_api/models/app_preview_set.rb +54 -17
  54. data/spaceship/lib/spaceship/connect_api/models/app_store_version_localization.rb +1 -2
  55. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +2 -2
  56. data/trainer/lib/trainer/legacy_xcresult.rb +27 -20
  57. data/trainer/lib/trainer/xcresult/test_suite.rb +4 -1
  58. metadata +27 -25
@@ -46,6 +46,7 @@ module Match
46
46
  s3_region: params[:s3_region],
47
47
  s3_access_key: params[:s3_access_key],
48
48
  s3_secret_access_key: params[:s3_secret_access_key],
49
+ s3_session_token: params[:s3_session_token],
49
50
  s3_bucket: params[:s3_bucket],
50
51
  s3_object_prefix: params[:s3_object_prefix],
51
52
  readonly: params[:readonly],
@@ -398,7 +398,7 @@ module Pilot
398
398
 
399
399
  # If App Store Connect API token, use token.
400
400
  # If api_key is specified and it is an Individual API Key, don't use token but use username.
401
- # If itc_provider was explicitly specified, use it.
401
+ # If itc_provider or provider_public_id was explicitly specified, use it.
402
402
  # If there are multiple teams, infer the provider from the selected team name.
403
403
  # If there are fewer than two teams, don't infer the provider.
404
404
  def transporter_for_selected_team(options)
@@ -414,24 +414,16 @@ module Pilot
414
414
  api_key
415
415
  end
416
416
 
417
- # Currently no kind of transporters accept an Individual API Key. Use username and app-specific password instead.
418
- # See https://github.com/fastlane/fastlane/issues/22115
419
- is_individual_key = !api_key.nil? && api_key[:issuer_id].nil?
420
- if is_individual_key
421
- api_key = nil
422
- api_token = nil
423
- end
424
-
425
417
  unless api_token.nil?
426
418
  api_token.refresh! if api_token.expired?
427
- return FastlaneCore::ItunesTransporter.new(nil, nil, false, nil, api_token.text, altool_compatible_command: true, api_key: api_key)
419
+ return FastlaneCore::ItunesTransporter.new(nil, nil, false, nil, api_token.text, altool_compatible_command: true, api_key: api_key, provider_public_id: nil)
428
420
  end
429
421
 
430
422
  # Otherwise use username and password
431
423
  tunes_client = Spaceship::ConnectAPI.client ? Spaceship::ConnectAPI.client.tunes_client : nil
432
424
 
433
- generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider], altool_compatible_command: true, api_key: api_key)
434
- return generic_transporter if options[:itc_provider] || tunes_client.nil?
425
+ generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider], altool_compatible_command: true, api_key: api_key, provider_public_id: options[:provider_public_id])
426
+ return generic_transporter if options[:itc_provider] || options[:provider_public_id] || tunes_client.nil?
435
427
  return generic_transporter unless tunes_client.teams.count > 1
436
428
 
437
429
  begin
@@ -293,6 +293,10 @@ module Pilot
293
293
  env_name: "PILOT_ITC_PROVIDER",
294
294
  description: "The provider short name to be used with the iTMSTransporter to identify your team. This value will override the automatically detected provider short name. To get provider short name run `pathToXcode.app/Contents/Applications/Application\\ Loader.app/Contents/itms/bin/iTMSTransporter -m provider -u 'USERNAME' -p 'PASSWORD' -account_type itunes_connect -v off`. The short names of providers should be listed in the second column",
295
295
  optional: true),
296
+ FastlaneCore::ConfigItem.new(key: :provider_public_id,
297
+ env_name: "PILOT_PROVIDER_PUBLIC_ID",
298
+ description: "The provider public ID to be used with altool (--provider-public-id). This value will override the automatically detected provider value for altool uploads. Required after Xcode 26 when your account is associated with multiple providers and using username/app-password authentication",
299
+ optional: true),
296
300
  # rubocop:enable Layout/LineLength
297
301
 
298
302
  # waiting and uploaded build
@@ -0,0 +1,54 @@
1
+ # Updating Curse Word Hashes for `fastlane precheck`
2
+
3
+ ## What does `precheck/rules/curse_words_rule` do?
4
+
5
+ It uses the metadata fetched from the App Store by _precheck_ and reviews it word by word, failing if it matches one of the committed terms.
6
+
7
+ ## What Terms to Hash
8
+
9
+ According to "Want to improve precheck's rules?" in [precheck's docs](https://docs.fastlane.tools/actions/precheck/), phrases flagged by a reviewer in your recent App Store Rejection.
10
+
11
+ ## Format
12
+
13
+ These phrases are committed in repo under https://github.com/fastlane/fastlane/tree/master/precheck/lib/precheck/rules/rules_data/curse_word_hashes as sha 256 digest (one way hash).
14
+
15
+ ### Before Generating
16
+
17
+ Make sure the term you are adding is:
18
+ 1. A single word phrase
19
+ 2. In all lowercase letters
20
+ 3. Void of punctuation
21
+
22
+ This criteria mimics the transformations performed on the input data that is getting checked in https://github.com/fastlane/fastlane/tree/master/precheck/lib/precheck/rules/curse_words_rule.rb
23
+
24
+ ### Generating a New Hash
25
+
26
+ ```rb
27
+ irb(main):001:0> require 'digest'
28
+ => true
29
+ irb(main):002:0> new_term_to_add = "oneword"
30
+ => "oneword"
31
+ irb(main):003:0> Digest::SHA256.hexdigest(new_term_to_add)
32
+ => "31be3624bc03aa68bc050cce316dc80cfe1ace3d0f58fa5f5b20c9e781c44a07"
33
+ irb(main):004:0>
34
+ ```
35
+
36
+ Append this to the end of the file (with a newline afterward).
37
+
38
+ ## How to Test Your Newly Added Term
39
+
40
+ ### Hack at the unit tests to include your phrase
41
+
42
+ Update the tests in https://github.com/fastlane/fastlane/blob/master/precheck/spec/rules/curse_words_rule_spec.rb to include your new phrase.
43
+
44
+ ```diff
45
+ let(:happy_item) { TextItemToCheck.new("tacos are really delicious, seriously, I can't even", :description, "description") }
46
+ - let(:curse_item) { TextItemToCheck.new("please excuse the use of 'shit' in this description", :description, "description") }
47
+ + let(:curse_item) { TextItemToCheck.new("please excuse the use of 'oneword' in this description", :description, "description") }
48
+
49
+ it "passes for non-curse item" do
50
+ ```
51
+
52
+ ### Update your App's listing and run pre-check
53
+
54
+ Add your term to your App's description or keywords and run `bundle exec fastlane precheck` to ensure that it fails the check.
@@ -346,4 +346,5 @@ bc8059fe9118fcf7b2c20a0bf9fdd8111942f2cb4754b45fc0b16916ab679ddb
346
346
  9f02f229004cc97cbe3e41ad839a73dab5f68d4c296dd64055317cf90dd11a1a
347
347
  dab70d211b90bf5e931b5626bc558850752e107daf995bf9e8cb1a08ca7309bf
348
348
  4efb200187aa257858d45bc72b8e590bac33db176aba6576cdd6623655a5a74f
349
- 0be9b885f4a35a18f6af6b1ac0acd7fa4b19993999c3fbf66dd1e3e0c4c753c8
349
+ 0be9b885f4a35a18f6af6b1ac0acd7fa4b19993999c3fbf66dd1e3e0c4c753c8
350
+ c6603565c5159fbe846a53e991829d452a1546d41150c0d3c73ddbd7f476ee0d
@@ -111,7 +111,9 @@ module Scan
111
111
 
112
112
  def self.default_os_version(os_type)
113
113
  @os_versions ||= {}
114
- @os_versions[os_type] ||= begin
114
+ return @os_versions[os_type] if @os_versions.key?(os_type)
115
+
116
+ @os_versions[os_type] = begin
115
117
  UI.crash!("Unknown platform: #{os_type}") unless PLATFORMS.key?(os_type)
116
118
  platform = PLATFORMS[os_type]
117
119
 
@@ -128,7 +130,8 @@ module Scan
128
130
  sdks_output, status = Open3.capture2('xcodebuild -showsdks -json')
129
131
  sdk_version = begin
130
132
  raise status unless status.success?
131
- JSON.parse(sdks_output).find { |e| e['platform'] == platform[:simulator] }['sdkVersion']
133
+ entry = JSON.parse(sdks_output).find { |e| e['platform'] == platform[:simulator] }
134
+ entry['productVersion'] || entry['sdkVersion']
132
135
  rescue StandardError => e
133
136
  UI.error(e)
134
137
  UI.error("xcodebuild CLI broken, please run `xcodebuild` and make sure it works")
@@ -147,7 +150,12 @@ module Scan
147
150
  end
148
151
 
149
152
  # Get OS version corresponding to build
150
- Gem::Version.new(FastlaneCore::DeviceManager.runtime_build_os_versions[runtime_build])
153
+ os_version = FastlaneCore::DeviceManager.runtime_build_os_versions[runtime_build]
154
+ unless os_version
155
+ UI.important("Runtime build '#{runtime_build}' not found in installed runtimes, falling back to SDK version '#{sdk_version}'")
156
+ os_version = sdk_version
157
+ end
158
+ Gem::Version.new(os_version)
151
159
  end
152
160
  end
153
161
  end
@@ -152,6 +152,7 @@ usage() {
152
152
  echo -e "\t --use-app-entitlements\t\tExtract app bundle codesigning entitlements and combine with entitlements from new provisioning profile." >&2
153
153
  echo -e "\t\t\t\t\t\t\tCan't use together with '-e, --entitlements' option." >&2
154
154
  echo -e "\t--keychain-path path\t\t\tSpecify the path to a keychain that /usr/bin/codesign should use." >&2
155
+ echo -e "\t--pagesize size\t\t\t\tSpecify the page size (in bytes) for codesign. Must be a power of two." >&2
155
156
  echo -e "\t-v, --verbose\t\t\t\tVerbose output." >&2
156
157
  echo -e "\t-h, --help\t\t\t\tDisplay help message." >&2
157
158
  exit 2
@@ -171,11 +172,13 @@ VERSION_NUMBER=""
171
172
  SHORT_VERSION=
172
173
  BUNDLE_VERSION=
173
174
  KEYCHAIN_PATH=
175
+ PAGESIZE=
174
176
  RAW_PROVISIONS=()
175
177
  PROVISIONS_BY_ID=()
176
178
  DEFAULT_PROVISION=""
177
179
  TEMP_DIR="_floatsignTemp"
178
180
  USE_APP_ENTITLEMENTS=""
181
+ VERBOSE=""
179
182
  XCODE_VERSION="$(xcodebuild -version | grep "Xcode" | /usr/bin/cut -f 2 -d ' ')"
180
183
 
181
184
  # List of plist keys used for reference to and from nested apps and extensions
@@ -226,6 +229,10 @@ while [ "$1" != "" ]; do
226
229
  shift
227
230
  KEYCHAIN_PATH="$1"
228
231
  ;;
232
+ --pagesize )
233
+ shift
234
+ PAGESIZE="$1"
235
+ ;;
229
236
  -v | --verbose )
230
237
  VERBOSE="--verbose"
231
238
  ;;
@@ -247,6 +254,11 @@ if [ -n "$KEYCHAIN_PATH" ]; then
247
254
  KEYCHAIN_FLAG="--keychain $KEYCHAIN_PATH"
248
255
  fi
249
256
 
257
+ PAGESIZE_ARGS=()
258
+ if [ -n "$PAGESIZE" ]; then
259
+ PAGESIZE_ARGS=(--pagesize "$PAGESIZE")
260
+ fi
261
+
250
262
  # Log the options
251
263
  for provision in "${RAW_PROVISIONS[@]}"; do
252
264
  if [[ "$provision" =~ .+=.+ ]]; then
@@ -266,6 +278,7 @@ log "Certificate: '$CERTIFICATE'"
266
278
  [[ -n "${SHORT_VERSION}" ]] && log "Specified short version to use: '$SHORT_VERSION'"
267
279
  [[ -n "${BUNDLE_VERSION}" ]] && log "Specified bundle version to use: '$BUNDLE_VERSION'"
268
280
  [[ -n "${KEYCHAIN_FLAG}" ]] && log "Specified keychain to use: '$KEYCHAIN_PATH'"
281
+ [[ -n "${PAGESIZE}" ]] && log "Specified page size: '$PAGESIZE'"
269
282
  [[ -n "${NEW_FILE}" ]] && log "Output file name: '$NEW_FILE'"
270
283
  [[ -n "${USE_APP_ENTITLEMENTS}" ]] && log "Extract app entitlements: YES"
271
284
 
@@ -549,7 +562,7 @@ function resign {
549
562
  do
550
563
  if [[ "$assetpack" == *.assetpack ]]; then
551
564
  rm -rf "$assetpack"/_CodeSignature
552
- /usr/bin/codesign "${VERBOSE}" --generate-entitlement-der "${KEYCHAIN_FLAG}" -f -s "$CERTIFICATE" "$assetpack"
565
+ /usr/bin/codesign ${VERBOSE} "${PAGESIZE_ARGS[@]}" --generate-entitlement-der "${KEYCHAIN_FLAG}" -f -s "$CERTIFICATE" "$assetpack"
553
566
  checkStatus
554
567
  else
555
568
  log "Ignoring non-assetpack: $assetpack"
@@ -571,7 +584,7 @@ function resign {
571
584
  log "Resigning '$framework'"
572
585
  # Must not quote KEYCHAIN_FLAG because it needs to be unwrapped and passed to codesign with spaces
573
586
  # shellcheck disable=SC2086
574
- /usr/bin/codesign ${VERBOSE} --generate-entitlement-der ${KEYCHAIN_FLAG} -f -s "$CERTIFICATE" "$framework"
587
+ /usr/bin/codesign ${VERBOSE} "${PAGESIZE_ARGS[@]}" --generate-entitlement-der ${KEYCHAIN_FLAG} -f -s "$CERTIFICATE" "$framework"
575
588
  checkStatus
576
589
  else
577
590
  log "Ignoring non-framework: $framework"
@@ -625,7 +638,7 @@ function resign {
625
638
  log "Creating an archived-expanded-entitlements.xcent file for Xcode 9 builds or earlier"
626
639
  cp -f "$ENTITLEMENTS" "$APP_PATH/archived-expanded-entitlements.xcent"
627
640
  fi
628
- /usr/bin/codesign "${VERBOSE}" --generate-entitlement-der -f -s "$CERTIFICATE" --entitlements "$ENTITLEMENTS" "$APP_PATH"
641
+ /usr/bin/codesign ${VERBOSE} "${PAGESIZE_ARGS[@]}" --generate-entitlement-der -f -s "$CERTIFICATE" --entitlements "$ENTITLEMENTS" "$APP_PATH"
629
642
  checkStatus
630
643
  elif [[ -n "${USE_APP_ENTITLEMENTS}" ]]; then
631
644
  # Extract entitlements from provisioning profile and from the app binary
@@ -870,7 +883,7 @@ function resign {
870
883
  log "Creating an archived-expanded-entitlements.xcent file for Xcode 9 builds or earlier"
871
884
  cp -f "$PATCHED_ENTITLEMENTS" "$APP_PATH/archived-expanded-entitlements.xcent"
872
885
  fi
873
- /usr/bin/codesign "${VERBOSE}" --generate-entitlement-der -f -s "$CERTIFICATE" --entitlements "$PATCHED_ENTITLEMENTS" "$APP_PATH"
886
+ /usr/bin/codesign ${VERBOSE} "${PAGESIZE_ARGS[@]}" --generate-entitlement-der -f -s "$CERTIFICATE" --entitlements "$PATCHED_ENTITLEMENTS" "$APP_PATH"
874
887
  checkStatus
875
888
  else
876
889
  log "Extracting entitlements from provisioning profile"
@@ -884,7 +897,7 @@ function resign {
884
897
  fi
885
898
  # Must not quote KEYCHAIN_FLAG because it needs to be unwrapped and passed to codesign with spaces
886
899
  # shellcheck disable=SC2086
887
- /usr/bin/codesign ${VERBOSE} --generate-entitlement-der ${KEYCHAIN_FLAG} -f -s "$CERTIFICATE" --entitlements "$TEMP_DIR/newEntitlements" "$APP_PATH"
900
+ /usr/bin/codesign ${VERBOSE} "${PAGESIZE_ARGS[@]}" --generate-entitlement-der ${KEYCHAIN_FLAG} -f -s "$CERTIFICATE" --entitlements "$TEMP_DIR/newEntitlements" "$APP_PATH"
888
901
  checkStatus
889
902
  fi
890
903
 
@@ -114,6 +114,7 @@ module Sigh
114
114
  c.option('--use_app_entitlements', 'Extract app bundle codesigning entitlements and combine with entitlements from new provisioning profile.')
115
115
  c.option('-g', '--new_bundle_id STRING', String, 'New application bundle ID (CFBundleIdentifier)')
116
116
  c.option('--keychain_path STRING', String, 'Path to the keychain that /usr/bin/codesign should use')
117
+ c.option('--page_size STRING', String, 'Page size in bytes for codesign --pagesize (power of two)')
117
118
 
118
119
  c.action do |args, options|
119
120
  Sigh::Resign.new.run(options, args)
@@ -4,7 +4,7 @@ require_relative 'runner'
4
4
 
5
5
  module Sigh
6
6
  class Manager
7
- def self.start
7
+ def self.start(keychain_path: nil)
8
8
  path = Sigh::Runner.new.run
9
9
 
10
10
  return nil unless path
@@ -23,7 +23,7 @@ module Sigh
23
23
  # in case it already exists
24
24
  end
25
25
 
26
- install_profile(output) unless Sigh.config[:skip_install]
26
+ install_profile(output, keychain_path) unless Sigh.config[:skip_install]
27
27
 
28
28
  puts(output.green)
29
29
 
@@ -35,13 +35,13 @@ module Sigh
35
35
  DownloadAll.new.download_all(download_xcode_profiles: download_xcode_profiles)
36
36
  end
37
37
 
38
- def self.install_profile(profile)
39
- uuid = FastlaneCore::ProvisioningProfile.uuid(profile)
40
- name = FastlaneCore::ProvisioningProfile.name(profile)
38
+ def self.install_profile(profile, keychain_path = nil)
39
+ uuid = FastlaneCore::ProvisioningProfile.uuid(profile, keychain_path)
40
+ name = FastlaneCore::ProvisioningProfile.name(profile, keychain_path)
41
41
  ENV["SIGH_UDID"] = ENV["SIGH_UUID"] = uuid if uuid
42
42
  ENV["SIGH_NAME"] = name if name
43
43
 
44
- FastlaneCore::ProvisioningProfile.install(profile)
44
+ FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
45
45
  end
46
46
  end
47
47
  end
@@ -8,18 +8,18 @@ module Sigh
8
8
  class Resign
9
9
  def run(options, args)
10
10
  # get the command line inputs and parse those into the vars we need...
11
- ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path = get_inputs(options, args)
11
+ ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path, page_size = get_inputs(options, args)
12
12
  # ... then invoke our programmatic interface with these vars
13
- unless resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path)
13
+ unless resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path, page_size)
14
14
  UI.user_error!("Failed to re-sign .ipa")
15
15
  end
16
16
  end
17
17
 
18
- def self.resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path)
19
- self.new.resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path)
18
+ def self.resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path, pagesize = nil)
19
+ self.new.resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path, pagesize)
20
20
  end
21
21
 
22
- def resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path)
22
+ def resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path, pagesize = nil)
23
23
  resign_path = find_resign_path
24
24
 
25
25
  if keychain_path
@@ -53,6 +53,7 @@ module Sigh
53
53
  bundle_id = "-b '#{new_bundle_id}'" if new_bundle_id
54
54
  use_app_entitlements_flag = "--use-app-entitlements" if use_app_entitlements
55
55
  specific_keychain = "--keychain-path #{keychain_path.shellescape}" if keychain_path
56
+ pagesize_option = "--pagesize #{pagesize}" if pagesize
56
57
 
57
58
  command = [
58
59
  resign_path.shellescape,
@@ -68,6 +69,7 @@ module Sigh
68
69
  verbose,
69
70
  bundle_id,
70
71
  specific_keychain,
72
+ pagesize_option,
71
73
  ipa.shellescape # Output path must always be last argument
72
74
  ].join(' ')
73
75
 
@@ -97,12 +99,13 @@ module Sigh
97
99
  new_bundle_id = options.new_bundle_id || nil
98
100
  use_app_entitlements = options.use_app_entitlements || nil
99
101
  keychain_path = options.keychain_path || nil
102
+ page_size = options.page_size || nil
100
103
 
101
104
  if options.provisioning_name
102
105
  UI.important("The provisioning_name (-n) option is not applicable to resign. You should use provisioning_profile (-p) instead")
103
106
  end
104
107
 
105
- return ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path
108
+ return ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path, page_size
106
109
  end
107
110
 
108
111
  def find_resign_path
@@ -11,35 +11,37 @@ module Spaceship
11
11
  attr_accessor :app_previews
12
12
 
13
13
  module PreviewType
14
- IPHONE_35 = "IPHONE_35"
15
- IPHONE_40 = "IPHONE_40"
16
- IPHONE_47 = "IPHONE_47"
17
- IPHONE_55 = "IPHONE_55"
18
- IPHONE_58 = "IPHONE_58"
19
- IPHONE_65 = "IPHONE_65"
20
- IPHONE_67 = "IPHONE_67"
21
-
22
- IPAD_97 = "IPAD_97"
23
- IPAD_105 = "IPAD_105"
24
- IPAD_PRO_3GEN_11 = "IPAD_PRO_3GEN_11"
25
- IPAD_PRO_129 = "IPAD_PRO_129"
26
- IPAD_PRO_3GEN_129 = "IPAD_PRO_3GEN_129"
14
+ # https://developer.apple.com/documentation/appstoreconnectapi/previewtype
15
+ IPHONE_35 = "IPHONE_35" # not supported anymore
16
+ IPHONE_40 = "IPHONE_40" # 4"
17
+ IPHONE_47 = "IPHONE_47" # 4.7"
18
+ IPHONE_55 = "IPHONE_55" # 5.5"
19
+ IPHONE_58 = "IPHONE_58" # 6.1"
20
+ IPHONE_61 = "IPHONE_61" # 6.3"
21
+ IPHONE_65 = "IPHONE_65" # 6.5"
22
+ IPHONE_67 = "IPHONE_67" # 6.9"
23
+
24
+ IPAD_97 = "IPAD_97" # 9.7"
25
+ IPAD_105 = "IPAD_105" # 10.5"
26
+ IPAD_PRO_129 = "IPAD_PRO_129" # 12.9"
27
+ IPAD_PRO_3GEN_11 = "IPAD_PRO_3GEN_11" # 11"
28
+ IPAD_PRO_3GEN_129 = "IPAD_PRO_3GEN_129" # 13"
27
29
 
28
30
  DESKTOP = "DESKTOP"
29
31
 
30
32
  ALL = [
31
- IPHONE_35,
32
33
  IPHONE_40,
33
34
  IPHONE_47,
34
35
  IPHONE_55,
35
36
  IPHONE_58,
37
+ IPHONE_61,
36
38
  IPHONE_65,
37
39
  IPHONE_67,
38
40
 
39
41
  IPAD_97,
40
42
  IPAD_105,
41
- IPAD_PRO_3GEN_11,
42
43
  IPAD_PRO_129,
44
+ IPAD_PRO_3GEN_11,
43
45
  IPAD_PRO_3GEN_129,
44
46
 
45
47
  DESKTOP
@@ -60,9 +62,9 @@ module Spaceship
60
62
  # API
61
63
  #
62
64
 
63
- def self.all(client: nil, filter: {}, includes: nil, limit: nil, sort: nil)
65
+ def self.all(client: nil, app_store_version_localization_id: nil, filter: {}, includes: nil, limit: nil, sort: nil)
64
66
  client ||= Spaceship::ConnectAPI
65
- resp = client.get_app_preview_sets(filter: filter, includes: includes, limit: limit, sort: sort)
67
+ resp = client.get_app_preview_sets(app_store_version_localization_id: app_store_version_localization_id, filter: filter, includes: includes, limit: limit, sort: sort)
66
68
  return resp.to_models
67
69
  end
68
70
 
@@ -106,6 +108,41 @@ module Spaceship
106
108
 
107
109
  return client.get_app_preview_set(app_preview_set_id: id, includes: "appPreviews").first
108
110
  end
111
+
112
+ # Validate video resolution (portrait canonical sizes) for provided preview_type.
113
+ # Returns true if the resolution matches any accepted pair.
114
+ def self.validate_video_resolution(width, height, preview_type)
115
+ return false unless width && height
116
+ if width > height
117
+ width, height = height, width
118
+ end
119
+ # for a list of valid resolutions, look for "Accepted resolutions" at https://developer.apple.com/help/app-store-connect/reference/app-information/app-preview-specifications
120
+ # resolutions below are sorted by display inch from biggest to smallest (see top of the module)
121
+ canonical = {
122
+ # iPhone
123
+ PreviewType::IPHONE_67 => [[886, 1920]],
124
+ PreviewType::IPHONE_65 => [[886, 1920]],
125
+ PreviewType::IPHONE_61 => [[886, 1920]],
126
+ PreviewType::IPHONE_58 => [[886, 1920]],
127
+ PreviewType::IPHONE_55 => [[1080, 1920]],
128
+ PreviewType::IPHONE_47 => [[750, 1334]],
129
+ PreviewType::IPHONE_40 => [[1080, 1920]],
130
+
131
+ # iPad
132
+ PreviewType::IPAD_PRO_3GEN_129 => [[1200, 1600]],
133
+ PreviewType::IPAD_PRO_3GEN_11 => [[1200, 1600]],
134
+ PreviewType::IPAD_PRO_129 => [[1200, 1600], [900, 1200]],
135
+ PreviewType::IPAD_105 => [[1200, 1600]],
136
+ PreviewType::IPAD_97 => [[900, 1200]],
137
+ }
138
+
139
+ pairs = canonical[preview_type] || []
140
+ pairs.any? { |(canon_width, canon_height)| width == canon_width && height == canon_height }
141
+ end
142
+
143
+ def self.preview_type_from_filename(name, preview_types = PreviewType::ALL)
144
+ preview_types.find { |type| name.to_s.upcase.include?(type) }
145
+ end
109
146
  end
110
147
  end
111
148
  end
@@ -78,8 +78,7 @@ module Spaceship
78
78
  def get_app_preview_sets(client: nil, filter: {}, includes: "appPreviews", limit: nil, sort: nil)
79
79
  client ||= Spaceship::ConnectAPI
80
80
  filter ||= {}
81
- filter["appStoreVersionLocalization"] = id
82
- return Spaceship::ConnectAPI::AppPreviewSet.all(client: client, filter: filter, includes: includes, limit: limit, sort: sort)
81
+ return Spaceship::ConnectAPI::AppPreviewSet.all(client: client, app_store_version_localization_id: id, filter: filter, includes: includes, limit: limit, sort: sort)
83
82
  rescue => error
84
83
  raise Spaceship::AppStoreAppPreviewError.new(@locale, error)
85
84
  end
@@ -367,9 +367,9 @@ module Spaceship
367
367
  # appPreviewSets
368
368
  #
369
369
 
370
- def get_app_preview_sets(filter: {}, includes: nil, limit: nil, sort: nil)
370
+ def get_app_preview_sets(app_store_version_localization_id: nil, filter: {}, includes: nil, limit: nil, sort: nil)
371
371
  params = tunes_request_client.build_params(filter: filter, includes: includes, limit: limit, sort: sort)
372
- tunes_request_client.get("#{Version::V1}/appPreviewSets", params)
372
+ tunes_request_client.get("#{Version::V1}/appStoreVersionLocalizations/#{app_store_version_localization_id}/appPreviewSets", params)
373
373
  end
374
374
 
375
375
  def get_app_preview_set(app_preview_set_id: nil, filter: {}, includes: nil, limit: nil, sort: nil)
@@ -502,26 +502,7 @@ module Trainer
502
502
 
503
503
  # Remove retry attempts from the count and test rows
504
504
  if output_remove_retry_attempts
505
- test_rows = test_rows.reject do |test_row|
506
- remove = false
507
-
508
- identifier = test_row[:identifier]
509
- info = tests_by_identifier[identifier]
510
-
511
- # Remove if this row is a retry and is a failure
512
- if info[:retry_count] > 0
513
- remove = !(test_row[:failures] || []).empty?
514
- end
515
-
516
- # Remove all failure and retry count if test did eventually pass
517
- if remove
518
- info[:failure_count] -= 1
519
- info[:retry_count] -= 1
520
- tests_by_identifier[identifier] = info
521
- end
522
-
523
- remove
524
- end
505
+ test_rows, tests_by_identifier = remove_retry_attempts(test_rows, tests_by_identifier)
525
506
  end
526
507
 
527
508
  row = {
@@ -549,6 +530,32 @@ module Trainer
549
530
  rows
550
531
  end
551
532
 
533
+ def remove_retry_attempts(test_rows, tests_by_identifier)
534
+ # Group test rows by identifier and keep only the last one for each identifier
535
+ test_rows_by_identifier = test_rows.group_by { |row| row[:identifier] }
536
+
537
+ # Update counts for removed retry attempts
538
+ test_rows_by_identifier.each do |identifier, rows|
539
+ rows_to_remove = rows[0...-1]
540
+
541
+ info = tests_by_identifier[identifier]
542
+ rows_to_remove.each do |row|
543
+ if !(row[:failures] || []).empty?
544
+ info[:failure_count] -= 1
545
+ elsif row[:skipped] == true
546
+ info[:skip_count] -= 1
547
+ end
548
+ info[:retry_count] -= 1
549
+ end
550
+ tests_by_identifier[identifier] = info
551
+ end
552
+
553
+ # Keep only the last row for each identifier
554
+ filtered_test_rows = test_rows_by_identifier.values.map(&:last)
555
+
556
+ [filtered_test_rows, tests_by_identifier]
557
+ end
558
+
552
559
  def test_summaries_to_configuration_names(test_summaries)
553
560
  summary_to_name = {}
554
561
  test_summaries.each do |summary|
@@ -106,7 +106,10 @@ module Trainer
106
106
  # Add test cases
107
107
  @test_cases.each do |test_case|
108
108
  runs = test_case.to_xml_nodes
109
- runs = runs.last(1) if output_remove_retry_attempts
109
+ # Remove retry attempts: keep only the final run (whether it passes, fails, or is skipped)
110
+ if output_remove_retry_attempts
111
+ runs = runs.last(1)
112
+ end
110
113
  runs.each { |node| testsuite.add_element(node) }
111
114
  end
112
115