fastlane 2.238.0 → 2.241.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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -177
  3. data/bin/fastlane +4 -2
  4. data/credentials_manager/README.md +1 -1
  5. data/credentials_manager/lib/credentials_manager/account_manager.rb +6 -2
  6. data/deliver/lib/deliver/app_screenshot.rb +0 -4
  7. data/deliver/lib/deliver/commands_generator.rb +1 -1
  8. data/deliver/lib/deliver/detect_values.rb +3 -1
  9. data/deliver/lib/deliver/download_screenshots.rb +8 -1
  10. data/deliver/lib/deliver/html_generator.rb +1 -1
  11. data/deliver/lib/deliver/options.rb +8 -0
  12. data/deliver/lib/deliver/runner.rb +5 -4
  13. data/deliver/lib/deliver/sync_screenshots.rb +27 -28
  14. data/deliver/lib/deliver/upload_app_clip_default_experience_header_images.rb +3 -3
  15. data/deliver/lib/deliver/upload_metadata.rb +34 -16
  16. data/deliver/lib/deliver/upload_screenshots.rb +24 -18
  17. data/docs/README.md +25 -0
  18. data/fastlane/lib/assets/TeamTable.md.erb +28 -0
  19. data/fastlane/lib/fastlane/actions/appaloosa.rb +2 -1
  20. data/fastlane/lib/fastlane/actions/appium.rb +1 -1
  21. data/fastlane/lib/fastlane/actions/apteligent.rb +8 -1
  22. data/fastlane/lib/fastlane/actions/build_and_upload_to_appetize.rb +3 -1
  23. data/fastlane/lib/fastlane/actions/changelog_from_git_commits.rb +7 -2
  24. data/fastlane/lib/fastlane/actions/clean_build_artifacts.rb +3 -0
  25. data/fastlane/lib/fastlane/actions/danger.rb +5 -5
  26. data/fastlane/lib/fastlane/actions/docs/build_app.md +1 -2
  27. data/fastlane/lib/fastlane/actions/docs/create_app_online.md +1 -2
  28. data/fastlane/lib/fastlane/actions/docs/get_certificates.md +0 -2
  29. data/fastlane/lib/fastlane/actions/docs/get_push_certificate.md +7 -3
  30. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +13 -3
  31. data/fastlane/lib/fastlane/actions/ensure_no_debug_code.rb +2 -2
  32. data/fastlane/lib/fastlane/actions/get_managed_play_store_publishing_rights.rb +1 -0
  33. data/fastlane/lib/fastlane/actions/github_api.rb +1 -2
  34. data/fastlane/lib/fastlane/actions/hg_commit_version_bump.rb +1 -1
  35. data/fastlane/lib/fastlane/actions/increment_build_number.rb +1 -1
  36. data/fastlane/lib/fastlane/actions/increment_version_number.rb +1 -1
  37. data/fastlane/lib/fastlane/actions/install_xcode_plugin.rb +16 -6
  38. data/fastlane/lib/fastlane/actions/lcov.rb +11 -2
  39. data/fastlane/lib/fastlane/actions/nexus_upload.rb +16 -4
  40. data/fastlane/lib/fastlane/actions/notification.rb +3 -2
  41. data/fastlane/lib/fastlane/actions/oclint.rb +8 -8
  42. data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +9 -4
  43. data/fastlane/lib/fastlane/actions/register_device.rb +1 -1
  44. data/fastlane/lib/fastlane/actions/say.rb +2 -4
  45. data/fastlane/lib/fastlane/actions/set_pod_key.rb +3 -3
  46. data/fastlane/lib/fastlane/actions/sonar.rb +16 -16
  47. data/fastlane/lib/fastlane/actions/sourcedocs.rb +8 -8
  48. data/fastlane/lib/fastlane/actions/spaceship_logs.rb +4 -2
  49. data/fastlane/lib/fastlane/actions/splunkmint.rb +8 -1
  50. data/fastlane/lib/fastlane/actions/swiftlint.rb +6 -3
  51. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +4 -2
  52. data/fastlane/lib/fastlane/actions/upload_to_app_store.rb +5 -2
  53. data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +5 -2
  54. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -4
  55. data/fastlane/lib/fastlane/actions/verify_pod_keys.rb +3 -1
  56. data/fastlane/lib/fastlane/actions/xcode_server_get_assets.rb +20 -19
  57. data/fastlane/lib/fastlane/actions/xcodebuild.rb +5 -6
  58. data/fastlane/lib/fastlane/actions/zip.rb +2 -1
  59. data/fastlane/lib/fastlane/auto_complete.rb +1 -1
  60. data/fastlane/lib/fastlane/cli_tools_distributor.rb +14 -0
  61. data/fastlane/lib/fastlane/documentation/markdown_docs_generator.rb +23 -0
  62. data/fastlane/lib/fastlane/environment_printer.rb +12 -25
  63. data/fastlane/lib/fastlane/fast_file.rb +2 -1
  64. data/fastlane/lib/fastlane/fastlane_require.rb +5 -1
  65. data/fastlane/lib/fastlane/helper/git_helper.rb +4 -2
  66. data/fastlane/lib/fastlane/helper/sh_helper.rb +25 -7
  67. data/fastlane/lib/fastlane/new_action.rb +1 -1
  68. data/fastlane/lib/fastlane/plugins/plugin_fetcher.rb +1 -1
  69. data/fastlane/lib/fastlane/plugins/plugin_info_collector.rb +1 -1
  70. data/fastlane/lib/fastlane/plugins/plugin_manager.rb +36 -4
  71. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +1 -1
  72. data/fastlane/lib/fastlane/plugins/template/.github/workflows/test.yml +1 -1
  73. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +7 -7
  74. data/fastlane/lib/fastlane/swift_lane_manager.rb +22 -4
  75. data/fastlane/lib/fastlane/version.rb +3 -2
  76. data/fastlane/swift/Deliverfile.swift +1 -1
  77. data/fastlane/swift/DeliverfileProtocol.swift +8 -1
  78. data/fastlane/swift/Fastlane.swift +129 -11
  79. data/fastlane/swift/Gymfile.swift +1 -1
  80. data/fastlane/swift/GymfileProtocol.swift +8 -1
  81. data/fastlane/swift/MainProcess.swift +190 -20
  82. data/fastlane/swift/Matchfile.swift +1 -1
  83. data/fastlane/swift/MatchfileProtocol.swift +10 -3
  84. data/fastlane/swift/Precheckfile.swift +1 -1
  85. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  86. data/fastlane/swift/Scanfile.swift +1 -1
  87. data/fastlane/swift/ScanfileProtocol.swift +15 -1
  88. data/fastlane/swift/Screengrabfile.swift +1 -1
  89. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  90. data/fastlane/swift/Snapshotfile.swift +1 -1
  91. data/fastlane/swift/SnapshotfileProtocol.swift +8 -1
  92. data/fastlane/swift/formatting/Rakefile +1 -0
  93. data/fastlane/swift/main.swift +4 -1
  94. data/fastlane_core/README.md +1 -1
  95. data/fastlane_core/lib/fastlane_core/analytics/analytics_event_builder.rb +1 -17
  96. data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +23 -8
  97. data/fastlane_core/lib/fastlane_core/device_manager.rb +2 -2
  98. data/fastlane_core/lib/fastlane_core/fastlane_pty.rb +20 -7
  99. data/fastlane_core/lib/fastlane_core/helper.rb +34 -5
  100. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +1 -1
  101. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +16 -27
  102. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +31 -28
  103. data/fastlane_core/lib/fastlane_core/languages.rb +0 -1
  104. data/fastlane_core/lib/fastlane_core/print_table.rb +5 -2
  105. data/fastlane_core/lib/fastlane_core/project.rb +19 -0
  106. data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +10 -19
  107. data/fastlane_core/lib/fastlane_core/secrets.rb +77 -0
  108. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +3 -3
  109. data/fastlane_core/lib/fastlane_core/ui/github_issue_inspector_reporter.rb +1 -1
  110. data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +2 -1
  111. data/fastlane_core/lib/fastlane_core.rb +1 -0
  112. data/frameit/lib/frameit/device.rb +1 -1
  113. data/frameit/lib/frameit/frame_downloader.rb +2 -0
  114. data/frameit/lib/frameit/runner.rb +3 -2
  115. data/frameit/lib/frameit/screenshot.rb +0 -1
  116. data/gym/lib/gym/generators/build_command_generator.rb +4 -4
  117. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +3 -3
  118. data/gym/lib/gym/options.rb +5 -0
  119. data/gym/lib/gym/runner.rb +1 -1
  120. data/internal/README.md +1 -1
  121. data/match/lib/match/encryption/openssl.rb +6 -2
  122. data/match/lib/match/generator.rb +1 -0
  123. data/match/lib/match/nuke.rb +2 -2
  124. data/match/lib/match/options.rb +13 -5
  125. data/match/lib/match/runner.rb +30 -3
  126. data/match/lib/match/storage/git_storage.rb +44 -11
  127. data/match/lib/match/storage/gitlab/secure_file.rb +2 -1
  128. data/match/lib/match/storage/google_cloud_storage.rb +6 -1
  129. data/match/lib/match/storage/interface.rb +8 -5
  130. data/pem/lib/pem/manager.rb +5 -1
  131. data/pem/lib/pem/module.rb +0 -4
  132. data/pem/lib/pem/options.rb +6 -0
  133. data/pilot/lib/pilot/build_manager.rb +32 -0
  134. data/pilot/lib/pilot/options.rb +8 -0
  135. data/precheck/lib/precheck/rule_processor.rb +1 -0
  136. data/produce/lib/produce/developer_center.rb +49 -2
  137. data/produce/lib/produce/itunes_connect.rb +1 -1
  138. data/produce/lib/produce/module.rb +21 -0
  139. data/produce/lib/produce/options.rb +17 -0
  140. data/produce/lib/produce/service.rb +3 -5
  141. data/produce/lib/produce.rb +0 -1
  142. data/scan/lib/scan/options.rb +12 -0
  143. data/scan/lib/scan/runner.rb +1 -1
  144. data/scan/lib/scan/test_command_generator.rb +12 -9
  145. data/scan/lib/scan/test_result_parser.rb +1 -0
  146. data/scan/lib/scan/xcpretty_reporter_options_generator.rb +2 -2
  147. data/screengrab/lib/screengrab/page.html.erb +12 -7
  148. data/screengrab/lib/screengrab/reports_generator.rb +1 -1
  149. data/sigh/lib/assets/resign.sh +38 -9
  150. data/sigh/lib/sigh/local_manage.rb +1 -1
  151. data/sigh/lib/sigh/options.rb +5 -0
  152. data/sigh/lib/sigh/resign.rb +3 -3
  153. data/sigh/lib/sigh/runner.rb +6 -1
  154. data/snapshot/lib/snapshot/options.rb +5 -0
  155. data/snapshot/lib/snapshot/page.html.erb +17 -12
  156. data/snapshot/lib/snapshot/reports_generator.rb +1 -1
  157. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +1 -1
  158. data/snapshot/lib/snapshot.rb +0 -2
  159. data/spaceship/README.md +2 -3
  160. data/spaceship/lib/spaceship/base.rb +3 -1
  161. data/spaceship/lib/spaceship/client.rb +159 -19
  162. data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +16 -0
  163. data/spaceship/lib/spaceship/connect_api/models/bundle_id.rb +13 -2
  164. data/spaceship/lib/spaceship/connect_api/models/bundle_id_capability.rb +6 -0
  165. data/spaceship/lib/spaceship/connect_api/models/profile.rb +7 -5
  166. data/spaceship/lib/spaceship/connect_api/models/routing_app_coverage.rb +75 -0
  167. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +25 -0
  168. data/spaceship/lib/spaceship/connect_api/token.rb +6 -1
  169. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +45 -0
  170. data/spaceship/lib/spaceship/connect_api.rb +1 -0
  171. data/spaceship/lib/spaceship/du/upload_file.rb +4 -3
  172. data/spaceship/lib/spaceship/errors.rb +7 -1
  173. data/spaceship/lib/spaceship/globals.rb +2 -2
  174. data/spaceship/lib/spaceship/portal/merchant.rb +127 -2
  175. data/spaceship/lib/spaceship/portal/portal_client.rb +58 -0
  176. data/spaceship/lib/spaceship/portal/ui/select_team.rb +0 -2
  177. data/spaceship/lib/spaceship/portal/website_push.rb +8 -2
  178. data/spaceship/lib/spaceship/test_flight/client.rb +0 -22
  179. data/spaceship/lib/spaceship/test_flight.rb +0 -1
  180. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +11 -0
  181. data/supply/lib/supply/client.rb +6 -0
  182. data/supply/lib/supply/options.rb +1 -0
  183. data/supply/lib/supply/reader.rb +15 -0
  184. data/supply/lib/supply.rb +0 -1
  185. data/trainer/lib/trainer/xcresult/helper.rb +27 -13
  186. metadata +80 -64
  187. data/produce/lib/produce/available_default_languages.rb +0 -62
  188. data/snapshot/lib/snapshot/screenshot_flatten.rb +0 -20
  189. data/snapshot/lib/snapshot/screenshot_rotate.rb +0 -36
  190. data/spaceship/lib/spaceship/test_flight/app_test_info.rb +0 -40
  191. data/supply/lib/supply/apk_listing.rb +0 -14
  192. /data/fastlane/lib/assets/{custom_action_template.rb → custom_action_template.rb.template} +0 -0
@@ -321,6 +321,11 @@ module Gym
321
321
  description: "Skips resolution of Swift Package Manager dependencies",
322
322
  type: Boolean,
323
323
  default_value: false),
324
+ FastlaneCore::ConfigItem.new(key: :disallow_xcodebuild_settings_lookup,
325
+ env_name: "GYM_DISALLOW_XCODEBUILD_SETTINGS_LOOKUP",
326
+ description: "Raises an error instead of fetching build settings by running `xcodebuild -showBuildSettings`, which can take a long time on large projects. The error names the required build setting, so the corresponding option can be specified manually",
327
+ type: Boolean,
328
+ default_value: false),
324
329
  FastlaneCore::ConfigItem.new(key: :disable_package_automatic_updates,
325
330
  env_name: "GYM_DISABLE_PACKAGE_AUTOMATIC_UPDATES",
326
331
  description: "Prevents packages from automatically being resolved to versions other than those recorded in the `Package.resolved` file. This translates in the option `-disableAutomaticPackageResolution` being passed to xcodebuild",
@@ -223,7 +223,7 @@ module Gym
223
223
  UI.message("Compressing #{available_dsyms.count} dSYM(s)") unless Gym.config[:silent]
224
224
 
225
225
  output_path = File.expand_path(File.join(Gym.config[:output_directory], Gym.config[:output_name] + ".app.dSYM.zip"))
226
- command = "cd '#{containing_directory}' && zip -r '#{output_path}' *.dSYM"
226
+ command = "cd #{containing_directory.shellescape} && zip -r #{output_path.shellescape} *.dSYM"
227
227
  Helper.backticks(command, print: !Gym.config[:silent])
228
228
  puts("") # new line
229
229
 
data/internal/README.md CHANGED
@@ -8,4 +8,4 @@
8
8
 
9
9
  ------
10
10
 
11
- Internal scripts for Rubocop linting & Rake tasks.
11
+ Internal scripts for Rubocop linting, Rake tasks, and the docs checks and generation the Fastfile runs (`docs/`).
@@ -93,8 +93,12 @@ module Match
93
93
  def fetch_password!
94
94
  password = ENV["MATCH_PASSWORD"]
95
95
  unless password
96
- item = Security::InternetPassword.find(server: server_name(self.keychain_name))
97
- password = item.password if item
96
+ begin
97
+ item = Security::InternetPassword.find(server: server_name(self.keychain_name))
98
+ password = item.password if item
99
+ rescue Security::Error => ex
100
+ UI.important("Could not read the local keychain: #{ex.message}")
101
+ end
98
102
  end
99
103
 
100
104
  unless password
@@ -92,6 +92,7 @@ module Match
92
92
  fail_on_name_taken: params[:fail_on_name_taken],
93
93
  include_all_certificates: params[:include_all_certificates],
94
94
  include_mac_in_profiles: params[:include_mac_in_profiles],
95
+ offline_profile: params[:offline_profile],
95
96
  }
96
97
 
97
98
  values[:platform] = params[:platform]
@@ -341,9 +341,9 @@ module Match
341
341
  def filter_profiles_and_files_by_selected_certificates
342
342
  # Do profile selection logic
343
343
  cert_ids = self.certs.map { |cert| cert.id.to_s }
344
- self.profiles = self.profiles.reject do |profile|
344
+ self.profiles = self.profiles.select do |profile|
345
345
  profile_cert_ids = profile.certificates.map { |cert| cert.id.to_s }
346
- (cert_ids & profile_cert_ids).empty?
346
+ cert_ids.intersect?(profile_cert_ids)
347
347
  end
348
348
 
349
349
  profile_uuids = self.profiles.map do |profile|
@@ -160,14 +160,14 @@ module Match
160
160
  FastlaneCore::ConfigItem.new(key: :git_basic_authorization,
161
161
  env_name: "MATCH_GIT_BASIC_AUTHORIZATION",
162
162
  sensitive: true,
163
- description: "Use a basic authorization header to access the git repo (e.g.: access via HTTPS, GitHub Actions, etc), usually a string in Base64",
163
+ description: "Use a basic authorization header to access the git repo (e.g.: access via HTTPS, GitHub Actions, etc), usually a string in Base64. Needs git 2.31 or later",
164
164
  conflicting_options: [:git_bearer_authorization, :git_private_key],
165
165
  optional: true,
166
166
  default_value: nil),
167
167
  FastlaneCore::ConfigItem.new(key: :git_bearer_authorization,
168
168
  env_name: "MATCH_GIT_BEARER_AUTHORIZATION",
169
169
  sensitive: true,
170
- description: "Use a bearer authorization header to access the git repo (e.g.: access to an Azure DevOps repository), usually a string in Base64",
170
+ description: "Use a bearer authorization header to access the git repo (e.g.: access to an Azure DevOps repository), usually a string in Base64. Needs git 2.31 or later",
171
171
  conflicting_options: [:git_basic_authorization, :git_private_key],
172
172
  optional: true,
173
173
  default_value: nil),
@@ -209,7 +209,8 @@ module Match
209
209
  FastlaneCore::ConfigItem.new(key: :s3_access_key,
210
210
  env_name: "MATCH_S3_ACCESS_KEY",
211
211
  description: "S3 access key",
212
- optional: true),
212
+ optional: true,
213
+ sensitive: true),
213
214
  FastlaneCore::ConfigItem.new(key: :s3_secret_access_key,
214
215
  env_name: "MATCH_S3_SECRET_ACCESS_KEY",
215
216
  description: "S3 secret access key",
@@ -247,11 +248,13 @@ module Match
247
248
  FastlaneCore::ConfigItem.new(key: :job_token,
248
249
  env_name: "CI_JOB_TOKEN",
249
250
  description: "GitLab CI_JOB_TOKEN",
250
- optional: true),
251
+ optional: true,
252
+ sensitive: true),
251
253
  FastlaneCore::ConfigItem.new(key: :private_token,
252
254
  env_name: "PRIVATE_TOKEN",
253
255
  description: "GitLab Access Token",
254
- optional: true),
256
+ optional: true,
257
+ sensitive: true),
255
258
 
256
259
  # Keychain
257
260
  FastlaneCore::ConfigItem.new(key: :keychain_name,
@@ -374,6 +377,11 @@ module Match
374
377
  description: "Force encryption to use legacy cbc algorithm for backwards compatibility with older match versions",
375
378
  type: Boolean,
376
379
  default_value: false),
380
+ FastlaneCore::ConfigItem.new(key: :offline_profile,
381
+ env_name: "MATCH_OFFLINE_PROFILE",
382
+ description: "Enable profile with 'Offline Support' (7 day validity). Requires Apple ID login, not supported with App Store Connect API key authentication",
383
+ type: Boolean,
384
+ default_value: false),
377
385
 
378
386
  # other
379
387
  FastlaneCore::ConfigItem.new(key: :verbose,
@@ -24,6 +24,8 @@ module Match
24
24
 
25
25
  attr_accessor :storage
26
26
 
27
+ attr_accessor :encryption
28
+
27
29
  attr_accessor :cache
28
30
 
29
31
  # rubocop:disable Metrics/PerceivedComplexity
@@ -45,14 +47,14 @@ module Match
45
47
  storage.download
46
48
 
47
49
  # Init the encryption only after the `storage.download` was called to have the right working directory
48
- encryption = Encryption.for_storage_mode(params[:storage_mode], {
50
+ self.encryption = Encryption.for_storage_mode(params[:storage_mode], {
49
51
  git_url: params[:git_url],
50
52
  s3_bucket: params[:s3_bucket],
51
53
  s3_skip_encryption: params[:s3_skip_encryption],
52
54
  working_directory: storage.working_directory,
53
55
  force_legacy_encryption: params[:force_legacy_encryption]
54
56
  })
55
- encryption.decrypt_files if encryption
57
+ self.encryption.decrypt_files if self.encryption
56
58
 
57
59
  unless params[:readonly]
58
60
  self.spaceship = SpaceshipEnsure.new(params[:username], params[:team_id], params[:team_name], api_token(params))
@@ -117,7 +119,7 @@ module Match
117
119
 
118
120
  has_file_changes = self.files_to_commit.count > 0 || self.files_to_delete.count > 0
119
121
  if has_file_changes && !params[:readonly]
120
- encryption.encrypt_files if encryption
122
+ self.encryption.encrypt_files if self.encryption
121
123
  storage.save_changes!(files_to_commit: self.files_to_commit, files_to_delete: self.files_to_delete)
122
124
  end
123
125
 
@@ -209,6 +211,10 @@ module Match
209
211
  self.files_to_commit << cert_path
210
212
  self.files_to_commit << private_key_path
211
213
 
214
+ # We pause to upload certs immediatly (or renewals) in case later logic fails, leaving
215
+ # AppStore with a set of files de-synced from match storage. See fastlane/fastlane#30140
216
+ save_changes_immediately!(params)
217
+
212
218
  # Reset certificates cache since we have a new cert.
213
219
  self.cache.reset_certificates
214
220
  else
@@ -255,6 +261,27 @@ module Match
255
261
  return File.basename(cert_path).gsub(".cer", "") # Certificate ID
256
262
  end
257
263
 
264
+ # Saves all pending file changes to storage right away, keeping the
265
+ # working directory around so the current run can continue using it
266
+ def save_changes_immediately!(params)
267
+ return if params[:readonly]
268
+ return if self.files_to_commit.empty? && self.files_to_delete.empty?
269
+
270
+ self.encryption.encrypt_files if self.encryption
271
+ begin
272
+ self.storage.save_changes!(files_to_commit: self.files_to_commit,
273
+ files_to_delete: self.files_to_delete,
274
+ clear_working_directory: false)
275
+ ensure
276
+ # Always hand back a decrypted working directory, 2nd upload will hopefully work.
277
+ self.encryption.decrypt_files if self.encryption
278
+ end
279
+
280
+ # We may have another upload at end (profiles) so reset our cert file(s).
281
+ self.files_to_commit = []
282
+ self.files_to_delete = []
283
+ end
284
+
258
285
  # @return [String] Path to certificate or P12 key
259
286
  def select_cert_or_key(paths:, certificate_id: nil)
260
287
  return paths.last if certificate_id.to_s.strip.empty?
@@ -1,4 +1,5 @@
1
1
  require 'fastlane_core/command_executor'
2
+ require 'open3'
2
3
 
3
4
  require_relative '../module'
4
5
  require_relative './interface'
@@ -7,6 +8,9 @@ module Match
7
8
  module Storage
8
9
  # Store the code signing identities in a git repo
9
10
  class GitStorage < Interface
11
+ DEFAULT_BRANCH = "main"
12
+ PRIVATE_KEY_ENV = "MATCH_SSH_ADD_PRIVATE_KEY"
13
+
10
14
  # User provided values
11
15
  attr_accessor :git_url
12
16
  attr_accessor :shallow_clone
@@ -85,11 +89,6 @@ module Match
85
89
  self.working_directory = Dir.mktmpdir
86
90
 
87
91
  command = "git clone #{self.git_url.shellescape} #{self.working_directory.shellescape}"
88
- # HTTP headers are supposed to be case-insensitive but
89
- # Bitbucket requires `Authorization: Basic` and `Authorization Bearer` to work
90
- # https://github.com/fastlane/fastlane/pull/15928
91
- command << " -c http.extraheader='Authorization: Basic #{self.git_basic_authorization}'" unless self.git_basic_authorization.nil?
92
- command << " -c http.extraheader='Authorization: Bearer #{self.git_bearer_authorization}'" unless self.git_bearer_authorization.nil?
93
92
 
94
93
  if self.shallow_clone
95
94
  command << " --depth 1"
@@ -117,13 +116,10 @@ module Match
117
116
  print_command: FastlaneCore::Globals.verbose?)
118
117
  end
119
118
  rescue
120
- UI.error("Error cloning certificates repo, please make sure you have read access to the repository you want to use")
121
119
  if self.branch && self.clone_branch_directly
122
120
  UI.error("You passed '#{self.branch}' as branch in combination with the `clone_branch_directly` flag. Please remove `clone_branch_directly` flag on the first run for _match_ to create the branch.")
123
121
  end
124
- UI.error("Run the following command manually to make sure you're properly authenticated:")
125
- UI.command(command)
126
- UI.user_error!("Error cloning certificates git repo, please make sure you have access to the repository - see instructions above")
122
+ UI.user_error!("Error cloning certificates repo, please make sure you have read access to the repository you want to use")
127
123
  end
128
124
 
129
125
  add_user_config(self.git_full_name, self.git_user_email)
@@ -168,8 +164,10 @@ module Match
168
164
  def generate_matchfile_content
169
165
  UI.important("Please create a new, private git repository to store the certificates and profiles there")
170
166
  url = UI.input("URL of the Git Repo: ")
167
+ branch = UI.input("Branch of the Git Repo (leave blank to use '#{DEFAULT_BRANCH}'): ").to_s.strip
168
+ branch = DEFAULT_BRANCH if branch.empty?
171
169
 
172
- return "git_url(\"#{url}\")"
170
+ return "git_url(\"#{url}\")\ngit_branch(\"#{branch}\")"
173
171
  end
174
172
 
175
173
  def list_files(file_name: "", file_ext: "")
@@ -181,7 +179,8 @@ module Match
181
179
  ssh_add = File.expand_path(self.git_private_key).shellescape.to_s
182
180
  else
183
181
  UI.message("Private key file does not exist, will continue by using it as a raw key.")
184
- ssh_add = "- <<< \"#{self.git_private_key}\""
182
+ # From the environment (git_env_values), so the key is not on bash's command line
183
+ ssh_add = "- <<< \"$#{PRIVATE_KEY_ENV}\""
185
184
  end
186
185
  return "ssh-agent bash -c 'ssh-add #{ssh_add}; #{command}'"
187
186
  end
@@ -267,9 +266,43 @@ module Match
267
266
  def git_env_values
268
267
  env_values = { 'GIT_TERMINAL_PROMPT' => '0' }
269
268
  env_values['GIT_SSH_COMMAND'] = non_interactive_git_ssh_command
269
+ env_values[PRIVATE_KEY_ENV] = self.git_private_key unless self.git_private_key.nil?
270
+ env_values.merge(authorization_env_values)
271
+ end
272
+
273
+ # Through git's environment (git 2.31+), so the header is neither on the command line nor stored in .git/config
274
+ def authorization_env_values
275
+ headers = []
276
+ # HTTP headers are supposed to be case-insensitive but
277
+ # Bitbucket requires `Authorization: Basic` and `Authorization Bearer` to work
278
+ # https://github.com/fastlane/fastlane/pull/15928
279
+ headers << "Authorization: Basic #{self.git_basic_authorization}" unless self.git_basic_authorization.nil?
280
+ headers << "Authorization: Bearer #{self.git_bearer_authorization}" unless self.git_bearer_authorization.nil?
281
+ return {} if headers.empty?
282
+
283
+ version = git_version
284
+ UI.user_error!("git_basic_authorization and git_bearer_authorization need git 2.31 or later, found git #{version}") if version && Gem::Version.new(version) < Gem::Version.new('2.31')
285
+
286
+ # Keep pairs the user already passes this way
287
+ first = ENV['GIT_CONFIG_COUNT'].to_i
288
+ env_values = { 'GIT_CONFIG_COUNT' => (first + headers.count).to_s }
289
+ headers.each_with_index do |header, i|
290
+ env_values["GIT_CONFIG_KEY_#{first + i}"] = 'http.extraheader'
291
+ env_values["GIT_CONFIG_VALUE_#{first + i}"] = header
292
+ end
270
293
  env_values
271
294
  end
272
295
 
296
+ # nil when git cannot be run: cloning reports that
297
+ def git_version
298
+ @git_version ||= begin
299
+ output, status = Open3.capture2('git', '--version')
300
+ output[/\d+\.\d+(\.\d+)?/] if status.success?
301
+ rescue Errno::ENOENT
302
+ nil
303
+ end
304
+ end
305
+
273
306
  def non_interactive_git_ssh_command
274
307
  ssh_command = ENV['GIT_SSH_COMMAND']
275
308
 
@@ -1,4 +1,5 @@
1
1
  require 'open-uri'
2
+ require 'ostruct'
2
3
 
3
4
  require_relative '../../module'
4
5
 
@@ -39,7 +40,7 @@ module Match
39
40
 
40
41
  create_subfolders(working_directory)
41
42
  File.open(destination_file, "wb") do |saved_file|
42
- URI.open(url, "rb", { @client.authentication_key => @client.authentication_value }) do |data|
43
+ url.open("rb", { @client.authentication_key => @client.authentication_value }) do |data|
43
44
  saved_file.write(data.read)
44
45
  end
45
46
 
@@ -82,7 +82,12 @@ module Match
82
82
  if self.google_cloud_keys_file.to_s.length > 0
83
83
  # Extract the Project ID from the `JSON` file
84
84
  # so the user doesn't have to provide it manually
85
- keys_file_content = JSON.parse(File.read(self.google_cloud_keys_file))
85
+ begin
86
+ keys_file_content = JSON.parse(File.read(self.google_cloud_keys_file))
87
+ rescue JSON::ParserError
88
+ # Don't dump secret into logs in case a secret (p8) is passed. See fastlane/fastlane#21017
89
+ UI.user_error!("Provided keys file on path #{File.expand_path(self.google_cloud_keys_file)} is not a valid JSON file")
90
+ end
86
91
  if google_cloud_project_id.to_s.length > 0 && google_cloud_project_id != keys_file_content["project_id"]
87
92
  UI.important("The google_cloud_keys_file's project ID ('#{keys_file_content['project_id']}') doesn't match the google_cloud_project_id ('#{google_cloud_project_id}'). This may be the wrong keys file.")
88
93
  end
@@ -47,11 +47,14 @@ module Match
47
47
  # given remote server
48
48
  # This method is blocking, meaning it might take multiple
49
49
  # seconds or longer to run
50
- # @parameter files_to_commit [Array] Array to paths to files
50
+ # @param files_to_commit [Array] Array to paths to files
51
51
  # that should be committed to the storage provider
52
- # @parameter custom_message: [String] Custom change message
53
- # that's optional, is used for commit title
54
- def save_changes!(files_to_commit: nil, files_to_delete: nil, custom_message: nil)
52
+ # @param custom_message: [String] Custom change message
53
+ # that's optional, is used for commit title
54
+ # @param clear_working_directory: [Bool] Pass `false` to keep
55
+ # the local working directory after saving, so more
56
+ # changes can be made and saved later in the same run
57
+ def save_changes!(files_to_commit: nil, files_to_delete: nil, custom_message: nil, clear_working_directory: true)
55
58
  # Custom init to `[]` in case `nil` is passed
56
59
  files_to_commit ||= []
57
60
  files_to_delete ||= []
@@ -85,7 +88,7 @@ module Match
85
88
  end
86
89
  end
87
90
  ensure # Always clear working_directory after save
88
- self.clear_changes
91
+ self.clear_changes if clear_working_directory
89
92
  end
90
93
 
91
94
  def upload_files(files_to_upload: [], custom_message: nil)
@@ -20,7 +20,7 @@ module PEM
20
20
  remaining_days = (existing_certificate.expires - Time.now) / 60 / 60 / 24
21
21
 
22
22
  display_platform = ''
23
- unless PEM.config[:website_push]
23
+ unless PEM.config[:website_push] || PEM.config[:voip_push]
24
24
  display_platform = "#{PEM.config[:platform]} "
25
25
  end
26
26
 
@@ -94,6 +94,8 @@ module PEM
94
94
  def certificate
95
95
  if PEM.config[:website_push]
96
96
  Spaceship.certificate.website_push
97
+ elsif PEM.config[:voip_push]
98
+ Spaceship.certificate.voip_push
97
99
  else
98
100
  platform = PEM.config[:platform]
99
101
  UI.user_error!('platform parameter is unspecified.') unless platform
@@ -123,6 +125,8 @@ module PEM
123
125
  'development'
124
126
  elsif PEM.config[:website_push]
125
127
  'website'
128
+ elsif PEM.config[:voip_push]
129
+ 'voip'
126
130
  else
127
131
  'production'
128
132
  end
@@ -6,10 +6,6 @@ module PEM
6
6
  attr_accessor :config
7
7
  end
8
8
 
9
- tmp_dir = Dir.tmpdir
10
- TMP_FOLDER = "#{tmp_dir}/fastlane/PEM/"
11
- FileUtils.mkdir_p(TMP_FOLDER)
12
-
13
9
  ENV['FASTLANE_TEAM_ID'] ||= ENV["PEM_TEAM_ID"]
14
10
  ENV['DELIVER_USER'] ||= ENV["PEM_USERNAME"]
15
11
 
@@ -30,6 +30,12 @@ module PEM
30
30
  is_string: false,
31
31
  conflicting_options: [:development],
32
32
  default_value: false),
33
+ FastlaneCore::ConfigItem.new(key: :voip_push,
34
+ env_name: "PEM_VOIP_PUSH",
35
+ description: "Create a VoIP Services certificate",
36
+ is_string: false,
37
+ conflicting_options: [:development, :website_push],
38
+ default_value: false),
33
39
  FastlaneCore::ConfigItem.new(key: :generate_p12,
34
40
  env_name: "PEM_GENERATE_P12_FILE",
35
41
  description: "Generate a p12 file additionally to a PEM file",
@@ -296,6 +296,9 @@ module Pilot
296
296
 
297
297
  # Handle beta app clip invocations
298
298
  update_app_clip_invocations(options: options, build: build)
299
+
300
+ # Handle routing app coverage file
301
+ update_routing_app_coverage(options)
299
302
  end
300
303
 
301
304
  def self.truncate_changelog(changelog)
@@ -679,6 +682,35 @@ module Pilot
679
682
  end
680
683
  end
681
684
  end
685
+
686
+ def update_routing_app_coverage(options)
687
+ # Skip logic entirely if no coverage file passed. So status-quo stays the same.
688
+ return unless options[:routing_app_coverage_file]
689
+
690
+ # We can only attach a coverage file to build upload - so pick the most recent edit.
691
+ platform = Spaceship::ConnectAPI::Platform.map(fetch_app_platform)
692
+ version = app.get_edit_app_store_version(platform: platform)
693
+ unless version
694
+ UI.important("Skipping upload of routing app coverage file - could not find an editable app store version")
695
+ return
696
+ end
697
+
698
+ routing_app_coverage =
699
+ begin
700
+ version.fetch_routing_app_coverage
701
+ rescue => error
702
+ UI.important("Error fetching routing app coverage - #{error.message}")
703
+ nil
704
+ end
705
+
706
+ if routing_app_coverage
707
+ UI.message("Removing previous routing app coverage file from App Store Connect")
708
+ routing_app_coverage.delete!
709
+ end
710
+
711
+ UI.message("Uploading routing app coverage file to App Store Connect")
712
+ version.upload_routing_app_coverage(path: options[:routing_app_coverage_file])
713
+ end
682
714
  end
683
715
  # rubocop:enable Metrics/ClassLength
684
716
  end
@@ -201,6 +201,14 @@ module Pilot
201
201
  optional: true,
202
202
  type: Boolean,
203
203
  default_value: false),
204
+ FastlaneCore::ConfigItem.new(key: :routing_app_coverage_file,
205
+ env_name: "PILOT_ROUTING_APP_COVERAGE_FILE",
206
+ description: "Path to the routing app coverage file (`.geojson`) that is required for routing apps. It will be uploaded to the editable App Store version of the app",
207
+ optional: true,
208
+ verify_block: proc do |value|
209
+ UI.user_error!("Could not find routing app coverage file at path '#{File.expand_path(value)}'") unless File.exist?(value)
210
+ UI.user_error!("Routing app coverage file must be a .geojson file") unless File.extname(value).casecmp(".geojson").zero?
211
+ end),
204
212
 
205
213
  # distribution
206
214
  FastlaneCore::ConfigItem.new(key: :distribute_only,
@@ -1,3 +1,4 @@
1
+ require 'ostruct'
1
2
  require 'spaceship/tunes/language_item'
2
3
  require 'spaceship/tunes/iap_list'
3
4
  require 'fastlane/markdown_table_formatter'
@@ -1,5 +1,7 @@
1
+ require 'ostruct'
1
2
  require 'spaceship'
2
3
  require_relative 'module'
4
+ require_relative 'service'
3
5
 
4
6
  module Produce
5
7
  class DeveloperCenter
@@ -86,6 +88,8 @@ module Produce
86
88
  end
87
89
 
88
90
  def create_new_app
91
+ return create_new_app_via_connect_api if using_connect_api?
92
+
89
93
  ENV["CREATED_NEW_APP_ID"] = Time.now.to_i.to_s
90
94
  if app_exists?
91
95
  UI.success("[DevCenter] App '#{Produce.config[:app_identifier]}' already exists, nothing to do on the Dev Center")
@@ -118,6 +122,31 @@ module Produce
118
122
  return true
119
123
  end
120
124
 
125
+ # App Store Connect API key path — used instead of Spaceship.login.
126
+ def create_new_app_via_connect_api
127
+ config_enabled_services = Produce.config[:enable_services] || Produce.config[:enabled_features]
128
+
129
+ existing = Spaceship::ConnectAPI::BundleId.find(app_identifier)
130
+ if existing
131
+ UI.success("[DevCenter] App '#{Produce.config[:app_identifier]}' already exists, nothing to do on the Dev Center")
132
+ return true
133
+ end
134
+
135
+ app_name = Produce.config[:app_name]
136
+ UI.message("Creating new app '#{app_name}' on the Apple Dev Center")
137
+
138
+ connect_api_platform = Spaceship::ConnectAPI::BundleIdPlatform.map(platform)
139
+ bundle_id = Spaceship::ConnectAPI::BundleId.create(name: app_name, platform: connect_api_platform, identifier: app_identifier)
140
+
141
+ UI.crash!("Something went wrong when creating the new app - it's not listed in the apps list") unless bundle_id
142
+
143
+ UI.message("Created app #{bundle_id.id}")
144
+ enable_inline_services(config_enabled_services) if config_enabled_services && !config_enabled_services.empty?
145
+ UI.success("Finished creating new app '#{app_name}' on the Dev Center")
146
+
147
+ return true
148
+ end
149
+
121
150
  def enable_services
122
151
  app_service = Spaceship.app_service
123
152
  enabled_clean_options = {}
@@ -161,6 +190,17 @@ module Produce
161
190
 
162
191
  private
163
192
 
193
+ # Delegates to Service#update instead of duplicating its ~40-capability mapping.
194
+ # Skips any capability explicitly marked "off" — meaningless on a brand-new app.
195
+ def enable_inline_services(config_enabled_services)
196
+ to_enable = config_enabled_services.reject { |_, v| v == SERVICE_OFF }
197
+ return if to_enable.empty?
198
+
199
+ options = OpenStruct.new(to_enable)
200
+ options.define_singleton_method(:__hash__) { to_enable }
201
+ Produce::Service.new.update(true, options)
202
+ end
203
+
164
204
  def platform
165
205
  # This was added to support creation of multiple platforms
166
206
  # Produce::ItunesConnect can take an array of platforms to create for App Store Connect
@@ -174,9 +214,16 @@ module Produce
174
214
  Spaceship.app.find(app_identifier, mac: platform == "osx") != nil
175
215
  end
176
216
 
217
+ def using_connect_api?
218
+ !connect_api_token.nil?
219
+ end
220
+
221
+ def connect_api_token
222
+ Spaceship::ConnectAPI::Token.from(hash: Produce.config[:api_key], filepath: Produce.config[:api_key_path])
223
+ end
224
+
177
225
  def login
178
- Spaceship.login(Produce.config[:username], nil)
179
- Spaceship.select_team
226
+ Produce.authenticate_connect_api!
180
227
  end
181
228
  end
182
229
  end
@@ -38,7 +38,7 @@ module Produce
38
38
  end
39
39
 
40
40
  # Produce.config[:itc_users]
41
- application = Spaceship::ConnectAPI::App.create(
41
+ Spaceship::ConnectAPI::App.create(
42
42
  name: Produce.config[:app_name],
43
43
  version_string: Produce.config[:app_version] || "1.0",
44
44
  sku: Produce.config[:sku].to_s,
@@ -1,4 +1,5 @@
1
1
  require 'fastlane_core/helper'
2
+ require 'spaceship'
2
3
 
3
4
  module Produce
4
5
  class << self
@@ -11,4 +12,24 @@ module Produce
11
12
 
12
13
  ENV['FASTLANE_TEAM_ID'] ||= ENV["PRODUCE_TEAM_ID"]
13
14
  ENV['DELIVER_USER'] ||= ENV["PRODUCE_USERNAME"]
15
+
16
+ # Shared by DeveloperCenter#login and Service#bundle_id. Prefers a configured API key, falls
17
+ # back to a token already set by another lane (e.g. app_store_connect_api_key), then falls
18
+ # back to username-based session login.
19
+ def self.authenticate_connect_api!
20
+ if (token = Spaceship::ConnectAPI::Token.from(hash: Produce.config[:api_key], filepath: Produce.config[:api_key_path]))
21
+ UI.message("Authenticating with App Store Connect API Key")
22
+
23
+ # `current_team_id:` avoids a live, session-based team_id lookup elsewhere in
24
+ # ConnectAPI's provisioning client, which has no session to use here.
25
+ Spaceship::ConnectAPI.client = Spaceship::ConnectAPI::Client.new(token: token, current_team_id: Produce.config[:team_id])
26
+ elsif !Spaceship::ConnectAPI.token.nil?
27
+ UI.message("Using existing authorization token for App Store Connect API")
28
+ else
29
+ UI.message("Starting login with user '#{Produce.config[:username]}'")
30
+ Spaceship.login(Produce.config[:username], nil)
31
+ Spaceship.select_team
32
+ UI.message("Successfully logged in")
33
+ end
34
+ end
14
35
  end
@@ -17,6 +17,23 @@ module Produce
17
17
  code_gen_sensitive: true,
18
18
  default_value: user,
19
19
  default_value_dynamic: true),
20
+
21
+ # App Store Connect API
22
+ FastlaneCore::ConfigItem.new(key: :api_key_path,
23
+ env_names: ["PRODUCE_API_KEY_PATH", "APP_STORE_CONNECT_API_KEY_PATH"],
24
+ description: "Path to your App Store Connect API Key JSON file (https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file)",
25
+ optional: true,
26
+ conflicting_options: [:api_key],
27
+ verify_block: proc do |value|
28
+ UI.user_error!("Couldn't find API key JSON file at path '#{value}'") unless File.exist?(value)
29
+ end),
30
+ FastlaneCore::ConfigItem.new(key: :api_key,
31
+ env_names: ["PRODUCE_API_KEY", "APP_STORE_CONNECT_API_KEY"],
32
+ description: "Your App Store Connect API Key information (https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-hash-option)",
33
+ type: Hash,
34
+ optional: true,
35
+ sensitive: true,
36
+ conflicting_options: [:api_key_path]),
20
37
  FastlaneCore::ConfigItem.new(key: :app_identifier,
21
38
  env_name: "PRODUCE_APP_IDENTIFIER",
22
39
  short_option: "-a",