fastlane 2.236.1 → 2.238.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +109 -102
  3. data/deliver/lib/deliver/options.rb +1 -1
  4. data/fastlane/lib/fastlane/actions/deploygate.rb +3 -2
  5. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +7 -3
  6. data/fastlane/lib/fastlane/actions/hockey.rb +3 -2
  7. data/fastlane/lib/fastlane/actions/installr.rb +3 -2
  8. data/fastlane/lib/fastlane/actions/mailgun.rb +1 -0
  9. data/fastlane/lib/fastlane/actions/notarize.rb +3 -18
  10. data/fastlane/lib/fastlane/actions/register_devices.rb +21 -5
  11. data/fastlane/lib/fastlane/actions/setup_ci.rb +7 -2
  12. data/fastlane/lib/fastlane/actions/spm.rb +6 -0
  13. data/fastlane/lib/fastlane/actions/swiftlint.rb +10 -1
  14. data/fastlane/lib/fastlane/actions/testfairy.rb +5 -3
  15. data/fastlane/lib/fastlane/actions/tryouts.rb +3 -2
  16. data/fastlane/lib/fastlane/actions/update_project_team.rb +17 -9
  17. data/fastlane/lib/fastlane/actions/upload_symbols_to_crashlytics.rb +33 -7
  18. data/fastlane/lib/fastlane/helper/sh_helper.rb +7 -3
  19. data/fastlane/lib/fastlane/lane_manager.rb +0 -1
  20. data/fastlane/lib/fastlane/plugins/template/.github/workflows/test.yml +0 -7
  21. data/fastlane/lib/fastlane/plugins/template/.gitignore +0 -1
  22. data/fastlane/lib/fastlane/plugins/template/.rspec +0 -2
  23. data/fastlane/lib/fastlane/plugins/template/Gemfile.erb +0 -2
  24. data/fastlane/lib/fastlane/version.rb +1 -1
  25. data/fastlane/swift/Deliverfile.swift +1 -1
  26. data/fastlane/swift/DeliverfileProtocol.swift +2 -2
  27. data/fastlane/swift/Fastlane.swift +50 -20
  28. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.pbxproj +1 -1
  29. data/fastlane/swift/Gymfile.swift +1 -1
  30. data/fastlane/swift/GymfileProtocol.swift +1 -1
  31. data/fastlane/swift/Matchfile.swift +1 -1
  32. data/fastlane/swift/MatchfileProtocol.swift +8 -1
  33. data/fastlane/swift/Precheckfile.swift +1 -1
  34. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  35. data/fastlane/swift/Scanfile.swift +1 -1
  36. data/fastlane/swift/ScanfileProtocol.swift +8 -1
  37. data/fastlane/swift/Screengrabfile.swift +1 -1
  38. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  39. data/fastlane/swift/Snapshotfile.swift +1 -1
  40. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  41. data/fastlane_core/lib/fastlane_core/analytics/analytics_event_builder.rb +31 -5
  42. data/fastlane_core/lib/fastlane_core/analytics/analytics_ingester_client.rb +24 -12
  43. data/fastlane_core/lib/fastlane_core/analytics/analytics_session.rb +22 -12
  44. data/fastlane_core/lib/fastlane_core/cert_checker.rb +22 -9
  45. data/fastlane_core/lib/fastlane_core/helper.rb +4 -3
  46. data/fastlane_core/lib/fastlane_core/ipa_upload_package_builder.rb +6 -2
  47. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +3 -1
  48. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +1 -1
  49. data/fastlane_core/lib/fastlane_core/pkg_upload_package_builder.rb +1 -1
  50. data/fastlane_core/lib/fastlane_core/print_table.rb +0 -16
  51. data/fastlane_core/lib/fastlane_core/project.rb +3 -1
  52. data/fastlane_core/lib/fastlane_core/queue_worker.rb +10 -1
  53. data/fastlane_core/lib/fastlane_core/ui/errors/fastlane_shell_error.rb +6 -0
  54. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +1 -1
  55. data/fastlane_core/lib/fastlane_core/video_utils.rb +4 -2
  56. data/gym/lib/gym/error_handler.rb +11 -1
  57. data/gym/lib/gym/generators/build_command_generator.rb +2 -0
  58. data/gym/lib/gym/module.rb +20 -2
  59. data/match/lib/match/options.rb +8 -0
  60. data/match/lib/match/runner.rb +4 -2
  61. data/match/lib/match/storage/git_storage.rb +18 -2
  62. data/pilot/lib/pilot/build_manager.rb +2 -2
  63. data/pilot/lib/pilot/options.rb +2 -2
  64. data/pilot/lib/pilot/tester_manager.rb +2 -2
  65. data/precheck/lib/precheck/rules/copyright_date_rule.rb +16 -10
  66. data/precheck/lib/precheck/rules/unreachable_urls_rule.rb +2 -2
  67. data/scan/lib/scan/options.rb +5 -0
  68. data/scan/lib/scan/runner.rb +1 -0
  69. data/scan/lib/scan/test_command_generator.rb +2 -0
  70. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +4 -1
  71. data/spaceship/lib/spaceship/client.rb +0 -1
  72. data/spaceship/lib/spaceship/connect_api/file_uploader.rb +1 -1
  73. data/spaceship/lib/spaceship/connect_api/models/age_rating_declaration.rb +6 -0
  74. data/spaceship/lib/spaceship/connect_api/models/beta_group.rb +5 -0
  75. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +7 -2
  76. data/spaceship/lib/spaceship/helper/plist_middleware.rb +37 -8
  77. data/supply/lib/supply/uploader.rb +7 -1
  78. data/trainer/lib/trainer/xcresult/test_case.rb +21 -2
  79. data/trainer/lib/trainer/xcresult/test_plan.rb +3 -3
  80. data/trainer/lib/trainer/xcresult/test_suite.rb +28 -19
  81. metadata +74 -32
@@ -34,21 +34,33 @@ module FastlaneCore
34
34
 
35
35
  def post_request(event)
36
36
  connection = Faraday.new(GA_URL) do |conn|
37
- conn.request(:url_encoded)
37
+ # This request runs while fastlane is shutting down,
38
+ # so keep the timeouts short to not delay the user
39
+ conn.options.open_timeout = 5
40
+ conn.options.timeout = 5
38
41
  conn.adapter(Faraday.default_adapter)
39
42
  end
40
43
  connection.headers[:user_agent] = 'fastlane/' + Fastlane::VERSION
41
- connection.post("/collect", {
42
- v: "1", # API Version
43
- tid: @ga_tracking, # Tracking ID / Property ID
44
- cid: event[:client_id], # Client ID
45
- t: "event", # Event hit type
46
- ec: event[:category], # Event category
47
- ea: event[:action], # Event action
48
- el: event[:label] || "na", # Event label
49
- ev: event[:value] || "0", # Event value
50
- aip: "1" # IP anonymization
51
- })
44
+
45
+ # GA4 protocol, event parameters are sent as `ep.<name>` query parameters
46
+ params = {
47
+ v: "2", # Protocol version (GA4)
48
+ tid: @ga_tracking, # GA4 measurement ID
49
+ cid: event[:client_id], # Client ID
50
+ sid: event[:session_id], # Session ID
51
+ _ss: "1", # Session start
52
+ seg: "1", # Session engaged
53
+ # Engagement time in ms, required for events to count towards active users
54
+ _et: (event[:engagement_time_msec] || 100).to_s,
55
+ en: event[:name].to_s # Event name
56
+ }
57
+ (event[:params] || {}).each do |key, value|
58
+ params["ep.#{key}"] = value.to_s
59
+ end
60
+
61
+ connection.post("/g/collect") do |request|
62
+ request.params = params
63
+ end
52
64
  end
53
65
  end
54
66
  end
@@ -4,18 +4,20 @@ require_relative 'analytics_event_builder'
4
4
 
5
5
  module FastlaneCore
6
6
  class AnalyticsSession
7
- GA_TRACKING = "UA-121171860-1"
7
+ GA_TRACKING = "G-94HQ3VVP0X"
8
8
 
9
9
  private_constant :GA_TRACKING
10
10
  attr_accessor :session_id
11
11
  attr_accessor :client
12
12
 
13
13
  def initialize(analytics_ingester_client: AnalyticsIngesterClient.new(GA_TRACKING))
14
- require 'securerandom'
15
- @session_id = SecureRandom.uuid
14
+ # GA4 requires a numeric session ID for events to be attributed to a session
15
+ @session_id = Time.now.to_i.to_s
16
+ @session_start_time = Time.now
16
17
  @client = analytics_ingester_client
17
18
  @threads = []
18
- @launch_event_sent = false
19
+ @launch_event = nil
20
+ @launch_event_captured = false
19
21
  end
20
22
 
21
23
  def action_launched(launch_context: nil)
@@ -23,23 +25,22 @@ module FastlaneCore
23
25
  show_message
24
26
  end
25
27
 
26
- if @launch_event_sent || launch_context.p_hash.nil?
28
+ if @launch_event_captured || launch_context.p_hash.nil?
27
29
  return
28
30
  end
29
31
 
30
- @launch_event_sent = true
32
+ @launch_event_captured = true
31
33
  builder = AnalyticsEventBuilder.new(
32
34
  p_hash: launch_context.p_hash,
33
35
  session_id: session_id,
34
36
  action_name: nil,
35
- fastlane_client_language: launch_context.fastlane_client_language
37
+ fastlane_client_language: launch_context.fastlane_client_language,
38
+ build_tool_version: launch_context.build_tool_version
36
39
  )
37
40
 
38
- launch_event = builder.new_event(:launch)
39
- post_thread = client.post_event(launch_event)
40
- unless post_thread.nil?
41
- @threads << post_thread
42
- end
41
+ # The event is not posted until finalize_session, so that the
42
+ # run duration can be reported as GA4 engagement time
43
+ @launch_event = builder.new_event(:launch)
43
44
  end
44
45
 
45
46
  def action_completed(completion_context: nil)
@@ -64,6 +65,15 @@ module FastlaneCore
64
65
  end
65
66
 
66
67
  def finalize_session
68
+ unless @launch_event.nil?
69
+ @launch_event[:engagement_time_msec] = ((Time.now - @session_start_time) * 1000).round
70
+ post_thread = client.post_event(@launch_event)
71
+ unless post_thread.nil?
72
+ @threads << post_thread
73
+ end
74
+ @launch_event = nil
75
+ end
76
+
67
77
  @threads.map(&:join)
68
78
  end
69
79
  end
@@ -43,6 +43,15 @@ WWDRCA_CERTIFICATES = [
43
43
  }
44
44
  ]
45
45
 
46
+ # Common names of the certificates in `WWDRCA_CERTIFICATES`. The WWDR
47
+ # certificates (G2-G6) and the Developer ID certificates (DEV-ID-G1,
48
+ # DEV-ID-G2) use different common names, so each of them has to be queried
49
+ # separately when looking up the installed certificates
50
+ WWDRCA_CERTIFICATE_NAMES = [
51
+ 'Apple Worldwide Developer Relations',
52
+ 'Developer ID Certification Authority'
53
+ ]
54
+
46
55
  module FastlaneCore
47
56
  # This class checks if a specific certificate is installed on the current mac
48
57
  class CertChecker
@@ -123,18 +132,21 @@ module FastlaneCore
123
132
  end
124
133
 
125
134
  def self.installed_wwdr_certificates(keychain: nil)
126
- certificate_name = "Apple Worldwide Developer Relations"
127
135
  keychain ||= wwdr_keychain # backwards compatibility
128
136
 
129
- # Find all installed WWDRCA certificates
137
+ # Find all installed WWDRCA certificates. A single `security
138
+ # find-certificate` call can only match one common name, so query the
139
+ # keychain once per common name used by the certificates
130
140
  installed_certs = []
131
- Helper.backticks("security find-certificate -a -c '#{certificate_name}' -p #{keychain.shellescape}", print: false)
132
- .lines
133
- .each do |line|
134
- if line.start_with?('-----BEGIN CERTIFICATE-----')
135
- installed_certs << line
136
- else
137
- installed_certs.last << line
141
+ WWDRCA_CERTIFICATE_NAMES.each do |certificate_name|
142
+ Helper.backticks("security find-certificate -a -c '#{certificate_name}' -p #{keychain.shellescape}", print: false)
143
+ .lines
144
+ .each do |line|
145
+ if line.start_with?('-----BEGIN CERTIFICATE-----')
146
+ installed_certs << line
147
+ else
148
+ installed_certs.last << line
149
+ end
138
150
  end
139
151
  end
140
152
 
@@ -145,6 +157,7 @@ module FastlaneCore
145
157
  WWDRCA_CERTIFICATES.find { |c| c[:sha256].casecmp?(sha256) }&.fetch(:alias)
146
158
  end
147
159
  .compact
160
+ .uniq
148
161
  end
149
162
 
150
163
  def self.install_missing_wwdr_certificates(in_keychain: nil)
@@ -91,9 +91,10 @@ module FastlaneCore
91
91
  end
92
92
 
93
93
  def self.operating_system
94
- return "macOS" if RUBY_PLATFORM.downcase.include?("darwin")
95
- return "Windows" if RUBY_PLATFORM.downcase.include?("mswin")
96
- return "Linux" if RUBY_PLATFORM.downcase.include?("linux")
94
+ return "macOS" if self.mac?
95
+ return "Windows" if self.windows?
96
+ return "Linux" if self.linux?
97
+ return "Java" if RUBY_PLATFORM =~ /java/
97
98
  return "Unknown"
98
99
  end
99
100
 
@@ -14,8 +14,12 @@ module FastlaneCore
14
14
 
15
15
  def generate(app_id: nil, ipa_path: nil, package_path: nil, platform: nil, app_identifier: nil, short_version: nil, bundle_version: nil)
16
16
  unless Helper.is_mac?
17
- # .itmsp packages are not supported for ipa uploads starting Transporter 4.1, for non-macOS
18
- self.package_path = package_path
17
+ # .itmsp packages are not supported for ipa uploads starting Transporter 4.1, for non-macOS.
18
+ # Use a unique subdirectory anyway: callers pass a shared parent (deliver passes "/tmp"),
19
+ # and ItunesTransporter#upload runs FileUtils.rm_rf on whatever this returns.
20
+ self.package_path = File.join(package_path, "#{app_id}-#{SecureRandom.uuid}")
21
+ FileUtils.mkdir_p(self.package_path)
22
+
19
23
  copy_ipa(ipa_path)
20
24
 
21
25
  # copy any AppStoreInfo.plist file that's next to the ipa file
@@ -404,6 +404,8 @@ module FastlaneCore
404
404
  private
405
405
 
406
406
  def file_upload_option(source)
407
+ # altool's --upload-app and --validate-app only support the -f flag,
408
+ # unlike iTMSTransporter which requires -assetFile for .ipa/.pkg since 2026.
407
409
  "-f #{source.shellescape}"
408
410
  end
409
411
 
@@ -482,7 +484,7 @@ module FastlaneCore
482
484
  '"' + Helper.transporter_path + '"',
483
485
  '-m verify',
484
486
  build_credential_params(username, password, jwt),
485
- "-f #{source.shellescape}",
487
+ file_upload_option(source),
486
488
  ("-WONoPause true" if Helper.windows?), # Windows only: process instantly returns instead of waiting for key press
487
489
  ("-itc_provider #{provider_short_name}" if jwt.nil? && !provider_short_name.to_s.empty?)
488
490
  ].compact.join(' ')
@@ -10,7 +10,7 @@ module FastlaneCore
10
10
  password_part = " -P #{certificate_password.shellescape}"
11
11
  certificate_format_part = certificate_format.to_s.strip.empty? ? "" : " -f #{certificate_format.shellescape}"
12
12
 
13
- command = "security import #{path.shellescape} -k '#{keychain_path.shellescape}'"
13
+ command = "security import #{path.shellescape} -k #{keychain_path.shellescape}"
14
14
  command << password_part
15
15
  command << certificate_format_part
16
16
  command << " -T /usr/bin/codesign" # to not be asked for permission when running a tool like `gym` (before Sierra)
@@ -22,7 +22,7 @@ module FastlaneCore
22
22
  apple_id: app_id,
23
23
  file_size: File.size(pkg_path),
24
24
  ipa_path: File.basename(pkg_path), # this is only the base name as the ipa is inside the package
25
- md5: Digest::MD5.hexdigest(File.read(pkg_path)),
25
+ md5: Digest::MD5.file(pkg_path).hexdigest,
26
26
  archive_type: 'product-archive',
27
27
  platform: platform
28
28
  }
@@ -1,22 +1,6 @@
1
1
  require_relative 'configuration/configuration'
2
2
  require_relative 'helper'
3
3
 
4
- # Monkey patch Terminal::Table until this is merged
5
- # https://github.com/tj/terminal-table/pull/131
6
- # solves https://github.com/fastlane/fastlane/issues/21852
7
- # loads Terminal::Table first to be able to monkey patch it.
8
- require 'terminal-table'
9
- module Terminal
10
- class Table
11
- class Cell
12
- def lines
13
- # @value.to_s.split(/\n/)
14
- @value.to_s.encode("utf-8", invalid: :replace).split(/\n/)
15
- end
16
- end
17
- end
18
- end
19
-
20
4
  module FastlaneCore
21
5
  class PrintTable
22
6
  class << self
@@ -395,7 +395,9 @@ module FastlaneCore
395
395
  xcode_at_least_13 = FastlaneCore::Helper.xcode_at_least?("13")
396
396
  if xcode_at_least_13 && options[:destination]
397
397
  begin
398
- destination_parameter = " " + "-destination #{options[:destination].shellescape}"
398
+ destination = options[:destination]
399
+ destination = destination.first if destination.kind_of?(Array)
400
+ destination_parameter = " " + "-destination #{destination.shellescape}"
399
401
  rescue => ex
400
402
  # xcodebuild command can continue without destination parameter, so
401
403
  # we really don't care about this exception if something goes wrong with shellescape
@@ -33,17 +33,26 @@ module FastlaneCore
33
33
  @queue.close
34
34
 
35
35
  threads = []
36
+ results = Queue.new
36
37
  @concurrency.times do
37
38
  threads << Thread.new do
39
+ # Exceptions are re-joined (and raised) below, so this just causes duplicate raised exceptions
40
+ Thread.current.report_on_exception = false
41
+
38
42
  job = @queue.pop
39
43
  while job
40
- @block.call(job)
44
+ results << @block.call(job)
41
45
  job = @queue.pop
42
46
  end
43
47
  end
44
48
  end
45
49
 
46
50
  threads.each(&:join)
51
+
52
+ # Convert Queue to Array
53
+ real_results = []
54
+ real_results << results.pop until results.empty?
55
+ real_results
47
56
  end
48
57
  end
49
58
  end
@@ -3,6 +3,12 @@ require_relative 'fastlane_exception'
3
3
  module FastlaneCore
4
4
  class Interface
5
5
  class FastlaneShellError < FastlaneException
6
+ attr_reader :show_github_issues
7
+
8
+ def initialize(options = {})
9
+ @show_github_issues = options[:show_github_issues].nil? ? false : options[:show_github_issues]
10
+ end
11
+
6
12
  def prefix
7
13
  '[SHELL_ERROR]'
8
14
  end
@@ -167,7 +167,7 @@ module Commander
167
167
  end
168
168
  rescue FastlaneCore::Interface::FastlaneCommonException => e # these are exceptions that we don't count as crashes
169
169
  display_user_error!(e, e.to_s)
170
- rescue FastlaneCore::Interface::FastlaneError => e # user_error!
170
+ rescue FastlaneCore::Interface::FastlaneError, FastlaneCore::Interface::FastlaneShellError => e # user_error! or shell_error!
171
171
  rescue_fastlane_error(e)
172
172
  rescue Errno::ENOENT => e
173
173
  rescue_file_error(e)
@@ -60,7 +60,9 @@ module FastlaneCore
60
60
  nil
61
61
  end
62
62
 
63
- # Reads the moov box and returns [w, h] if found in any trak/tkhd, else nil.
63
+ # Reads the moov box and returns [w, h] from the first trak/tkhd with non-zero dimensions, else nil.
64
+ # Non-video tracks (e.g. audio) carry a legitimate tkhd width/height of 0x0,
65
+ # so zero-sized results are skipped to keep scanning for the video track.
64
66
  # Input: io (IO) positioned at moov contents; moov_end (Integer) absolute end offset.
65
67
  # Output: [width, height] or nil.
66
68
  def self.extract_resolution_from_moov(io, moov_end)
@@ -71,7 +73,7 @@ module FastlaneCore
71
73
  if atom_type == "trak"
72
74
  trak_end = io.pos + (atom_size - 8)
73
75
  resolution = extract_resolution_from_trak(io, trak_end)
74
- return resolution if resolution
76
+ return resolution if resolution && resolution[0] > 0 && resolution[1] > 0
75
77
  io.seek(trak_end, IO::SEEK_SET)
76
78
  else
77
79
  skip(io, atom_size - 8)
@@ -151,11 +151,21 @@ module Gym
151
151
  # `xcodebuild` doesn't properly mark lines as failure reason or important information
152
152
  # so we assume that the last few lines show the error message that's relevant
153
153
  # (at least that's what was correct during testing)
154
- log_content = File.read(log_path).split("\n").last(5)
154
+ log_lines = File.read(log_path).split("\n")
155
+ log_content = log_lines.last(5)
155
156
  log_content.each do |row|
156
157
  UI.command_output(row)
157
158
  end
158
159
 
160
+ # If we didn't find the error in the last 5 lines, let's search for it
161
+ # This is specifically for errors that might be buried by subsequent warnings
162
+ last_5_lines = log_content.join("\n")
163
+ unless last_5_lines.include?("error:")
164
+ UI.verbose("Error not found in last 5 lines, searching for it in the log...")
165
+ error_line = log_lines.reverse.find { |l| l.include?("error:") }
166
+ UI.command_output(error_line) if error_line
167
+ end
168
+
159
169
  UI.message("")
160
170
  UI.error("⬆️ Check out the few lines of raw `xcodebuild` output above for potential hints on how to solve this error")
161
171
  UI.important("📋 For the complete and more detailed error log, check the full log at:")
@@ -119,6 +119,8 @@ module Gym
119
119
  end
120
120
 
121
121
  def pipe_xcpretty
122
+ UI.important("Using xcpretty can result in missing some build errors and is slower than the preferred xcbeautify. See https://docs.fastlane.tools/best-practices/xcodebuild-formatters/ for more information.")
123
+
122
124
  pipe = []
123
125
 
124
126
  formatter = Gym.config[:xcpretty_formatter]
@@ -56,11 +56,29 @@ module Gym
56
56
  end
57
57
 
58
58
  def building_mac_catalyst_for_ios?
59
- Gym.project.supports_mac_catalyst? && Gym.config[:catalyst_platform] == "ios"
59
+ return false unless Gym.project.supports_mac_catalyst?
60
+
61
+ # If catalyst_platform is explicitly set, use it
62
+ if Gym.config[:catalyst_platform]
63
+ return Gym.config[:catalyst_platform] == "ios"
64
+ end
65
+
66
+ # If catalyst_platform is not set, use SDK to determine
67
+ # Mac Catalyst apps with iOS SDK build for iOS (IPA), not macOS
68
+ return Gym.config[:sdk] == "iphoneos" || Gym.config[:sdk] == "iphonesimulator"
60
69
  end
61
70
 
62
71
  def building_mac_catalyst_for_mac?
63
- Gym.project.supports_mac_catalyst? && Gym.config[:catalyst_platform] == "macos"
72
+ return false unless Gym.project.supports_mac_catalyst?
73
+
74
+ # If catalyst_platform is explicitly set, use it
75
+ if Gym.config[:catalyst_platform]
76
+ return Gym.config[:catalyst_platform] == "macos"
77
+ end
78
+
79
+ # If catalyst_platform is not set, use SDK to determine
80
+ # Mac Catalyst apps with SDK macosx build for Mac (PKG), otherwise for iOS (IPA)
81
+ return Gym.config[:sdk] == "macosx"
64
82
  end
65
83
 
66
84
  def building_multiplatform_for_ios?
@@ -297,6 +297,14 @@ module Match
297
297
  description: "Renew the provisioning profiles if the certificate count on the developer portal has changed. Works only for the 'development' provisioning profile type. Requires 'include_all_certificates' option to be 'true'",
298
298
  type: Boolean,
299
299
  default_value: false),
300
+ FastlaneCore::ConfigItem.new(key: :renew_expired_certs,
301
+ env_name: "MATCH_RENEW_EXPIRED_CERTS",
302
+ description: "Automatically renew expired certificates. Note: to renew `developer_id` and `developer_id_installer` certificates " \
303
+ "you must log in with the Account Holder account by using username and password; App Store Connect API key doesn't work in this case. " \
304
+ "The expired certificate is removed from the match storage and a new one is created, but the old certificate is not revoked on the " \
305
+ "Apple Developer Portal — run `fastlane match nuke` if you need to free up certificate slots",
306
+ type: Boolean,
307
+ default_value: false),
300
308
  FastlaneCore::ConfigItem.new(key: :skip_confirmation,
301
309
  env_name: "MATCH_SKIP_CONFIRMATION",
302
310
  description: "Disables confirmation prompts during nuke, answering them with yes",
@@ -85,12 +85,12 @@ module Match
85
85
  end
86
86
 
87
87
  # Certificate
88
- cert_id = fetch_certificate(params: params, renew_expired_certs: false)
88
+ cert_id = fetch_certificate(params: params, renew_expired_certs: params[:renew_expired_certs])
89
89
 
90
90
  # Mac Installer Distribution Certificate
91
91
  additional_cert_types = params[:additional_cert_types] || []
92
92
  cert_ids = additional_cert_types.map do |additional_cert_type|
93
- fetch_certificate(params: params, renew_expired_certs: false, specific_cert_type: additional_cert_type)
93
+ fetch_certificate(params: params, renew_expired_certs: params[:renew_expired_certs], specific_cert_type: additional_cert_type)
94
94
  end
95
95
 
96
96
  profile_type = Sigh.profile_type_for_distribution_type(
@@ -174,6 +174,8 @@ module Match
174
174
  is_cert_renewable = is_authenticated_with_login || is_cert_renewable_via_api
175
175
 
176
176
  # Validate existing certificate first.
177
+ # NOTE: only a single certificate is considered here — the one matching
178
+ # `certificate_id`, or otherwise the last one found for this type.
177
179
  if renew_expired_certs && is_cert_renewable && storage_has_certs && !params[:readonly]
178
180
  cert_path = select_cert_or_key(paths: certs, certificate_id: certificate_id)
179
181
 
@@ -111,7 +111,7 @@ module Match
111
111
 
112
112
  begin
113
113
  # GIT_TERMINAL_PROMPT will fail the `git clone` command if user credentials are missing
114
- Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
114
+ Helper.with_env_values(git_env_values) do
115
115
  FastlaneCore::CommandExecutor.execute(command: command,
116
116
  print_all: FastlaneCore::Globals.verbose?,
117
117
  print_command: FastlaneCore::Globals.verbose?)
@@ -252,7 +252,7 @@ module Match
252
252
  commands << git_push_command
253
253
 
254
254
  UI.message("Pushing changes to remote git repo...")
255
- Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
255
+ Helper.with_env_values(git_env_values) do
256
256
  commands.each do |command|
257
257
  FastlaneCore::CommandExecutor.execute(command: command,
258
258
  print_all: FastlaneCore::Globals.verbose?,
@@ -263,6 +263,22 @@ module Match
263
263
  UI.error("Couldn't commit or push changes back to git...")
264
264
  UI.error(ex)
265
265
  end
266
+
267
+ def git_env_values
268
+ env_values = { 'GIT_TERMINAL_PROMPT' => '0' }
269
+ env_values['GIT_SSH_COMMAND'] = non_interactive_git_ssh_command
270
+ env_values
271
+ end
272
+
273
+ def non_interactive_git_ssh_command
274
+ ssh_command = ENV['GIT_SSH_COMMAND']
275
+
276
+ if ssh_command.nil? || ssh_command.empty?
277
+ return "ssh -o BatchMode=yes"
278
+ end
279
+ return ssh_command if ssh_command.include?('BatchMode=yes')
280
+ "#{ssh_command} -o BatchMode=yes"
281
+ end
266
282
  end
267
283
  end
268
284
  end
@@ -172,7 +172,7 @@ module Pilot
172
172
  # Verify the build has all the includes that we need
173
173
  # and fetch a new build if not
174
174
  if build && (!build.app || !build.build_beta_detail || !build.pre_release_version)
175
- UI.important("Build did include information for app, build beta detail and pre release version")
175
+ UI.important("Build did not include information for app, build beta detail and pre release version")
176
176
  UI.important("Fetching a new build with all the information needed")
177
177
  build = Spaceship::ConnectAPI::Build.get(build_id: build.id)
178
178
  end
@@ -459,7 +459,7 @@ module Pilot
459
459
  if options[:groups]
460
460
  app = uploaded_build.app
461
461
  beta_groups = app.get_beta_groups.select do |group|
462
- options[:groups].include?(group.name)
462
+ group.matches_identifiers?(options[:groups])
463
463
  end
464
464
 
465
465
  unless beta_groups.empty?
@@ -267,7 +267,7 @@ module Pilot
267
267
  FastlaneCore::ConfigItem.new(key: :groups,
268
268
  short_option: "-g",
269
269
  env_name: "PILOT_GROUPS",
270
- description: "Associate tester to one group or more by group name / group id. E.g. `-g \"Team 1\",\"Team 2\"` This is required when `distribute_external` option is set to true or when we want to add a tester to one or more external testing groups ",
270
+ description: "Associate tester to one group or more by group name / group id. E.g. `-g \"Team 1\",\"a06cf5b5-95a9-4beb-88c6-f22bd6b3f7a2\"` This is required when `distribute_external` option is set to true or when we want to add a tester to one or more external testing groups ",
271
271
  optional: true,
272
272
  type: Array,
273
273
  verify_block: proc do |value|
@@ -312,7 +312,7 @@ module Pilot
312
312
  # rubocop:disable Layout/LineLength
313
313
  FastlaneCore::ConfigItem.new(key: :itc_provider,
314
314
  env_name: "PILOT_ITC_PROVIDER",
315
- 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",
315
+ 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 `xcrun 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",
316
316
  optional: true),
317
317
  FastlaneCore::ConfigItem.new(key: :provider_public_id,
318
318
  env_name: "PILOT_PROVIDER_PUBLIC_ID",
@@ -13,7 +13,7 @@ module Pilot
13
13
  UI.user_error!("You must provide 1 or more groups (with the `:groups` option)") unless groups_param
14
14
 
15
15
  app.get_beta_groups.select do |group|
16
- next unless groups_param.include?(group.name)
16
+ next unless group.matches_identifiers?(groups_param)
17
17
  user = {
18
18
  email: config[:email],
19
19
  firstName: config[:first_name],
@@ -54,7 +54,7 @@ module Pilot
54
54
  UI.success("Successfully removed tester #{tester.email} from app: #{app.name}")
55
55
  else
56
56
  groups = tester.beta_groups.select do |group|
57
- config[:groups].include?(group.name)
57
+ group.matches_identifiers?(config[:groups])
58
58
  end
59
59
  tester.delete_from_beta_groups(beta_groups: groups)
60
60
 
@@ -11,27 +11,33 @@ module Precheck
11
11
  end
12
12
 
13
13
  def self.friendly_name
14
- "Incorrect, or missing copyright date"
14
+ "No incorrect or missing copyright date"
15
15
  end
16
16
 
17
17
  def self.description
18
- "using a copyright date that is any different from this current year, or missing a date"
19
- end
20
-
21
- def pass_if_empty?
22
- return false
18
+ "using a copyright year in the future, or missing a copyright year"
23
19
  end
24
20
 
25
21
  def supported_fields_symbol_set
26
22
  [:copyright].to_set
27
23
  end
28
24
 
29
- def word_search_type
30
- WORD_SEARCH_TYPES[:fail_on_exclusion]
25
+ def rule_block
26
+ return lambda { |text|
27
+ year = copyright_year(text)
28
+ if year.nil?
29
+ RuleReturn.new(validation_state: VALIDATION_STATES[:failed], failure_data: "missing copyright year")
30
+ elsif year > DateTime.now.year
31
+ RuleReturn.new(validation_state: VALIDATION_STATES[:failed], failure_data: "copyright year is in the future: #{year}")
32
+ else
33
+ RuleReturn.new(validation_state: VALIDATION_STATES[:passed])
34
+ end
35
+ }
31
36
  end
32
37
 
33
- def lowercased_words_to_look_for
34
- [DateTime.now.year.to_s]
38
+ def copyright_year(text)
39
+ match = text.to_s.match(/\b(?:19|20)\d{2}\b/)
40
+ match && match[0].to_i
35
41
  end
36
42
  end
37
43
  end
@@ -1,5 +1,5 @@
1
1
  require 'addressable'
2
- require 'faraday_middleware'
2
+ require 'faraday/follow_redirects'
3
3
 
4
4
  require_relative '../rule'
5
5
 
@@ -30,7 +30,7 @@ module Precheck
30
30
  uri = Addressable::URI.parse(url)
31
31
  uri.fragment = nil
32
32
  request = Faraday.new(uri.normalize.to_s) do |connection|
33
- connection.use(FaradayMiddleware::FollowRedirects)
33
+ connection.response(:follow_redirects)
34
34
  connection.adapter(:net_http)
35
35
  end
36
36
  return RuleReturn.new(validation_state: Precheck::VALIDATION_STATES[:failed], failure_data: "HTTP #{request.head.status}: #{url}") unless request.head.status == 200
@@ -273,6 +273,11 @@ module Scan
273
273
  description: "Remove retry attempts from test results table and the JUnit report (if not using xcpretty)",
274
274
  type: Boolean,
275
275
  default_value: false),
276
+ FastlaneCore::ConfigItem.new(key: :force_legacy_xcresulttool,
277
+ env_names: ["SCAN_FORCE_LEGACY_XCRESULTTOOL", "TRAINER_FORCE_LEGACY_XCRESULTTOOL"],
278
+ description: "Force the use of the '--legacy' flag for xcresulttool instead of using the new commands",
279
+ type: Boolean,
280
+ default_value: false),
276
281
 
277
282
  # xcpretty
278
283
  FastlaneCore::ConfigItem.new(key: :disable_xcpretty,
@@ -216,6 +216,7 @@ module Scan
216
216
  params = {
217
217
  path: result_bundle_path,
218
218
  output_remove_retry_attempts: Scan.config[:output_remove_retry_attempts],
219
+ force_legacy_xcresulttool: Scan.config[:force_legacy_xcresulttool],
219
220
  silent: !FastlaneCore::Globals.verbose?
220
221
  }
221
222