fastlane 2.240.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 (166) 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/sync_screenshots.rb +27 -28
  11. data/deliver/lib/deliver/upload_app_clip_default_experience_header_images.rb +3 -3
  12. data/deliver/lib/deliver/upload_metadata.rb +12 -16
  13. data/deliver/lib/deliver/upload_screenshots.rb +12 -14
  14. data/docs/README.md +25 -0
  15. data/fastlane/lib/assets/TeamTable.md.erb +28 -0
  16. data/fastlane/lib/fastlane/actions/appaloosa.rb +2 -1
  17. data/fastlane/lib/fastlane/actions/appium.rb +1 -1
  18. data/fastlane/lib/fastlane/actions/apteligent.rb +8 -1
  19. data/fastlane/lib/fastlane/actions/build_and_upload_to_appetize.rb +1 -1
  20. data/fastlane/lib/fastlane/actions/clean_build_artifacts.rb +3 -0
  21. data/fastlane/lib/fastlane/actions/danger.rb +5 -5
  22. data/fastlane/lib/fastlane/actions/docs/build_app.md +1 -2
  23. data/fastlane/lib/fastlane/actions/docs/create_app_online.md +1 -2
  24. data/fastlane/lib/fastlane/actions/docs/get_certificates.md +0 -2
  25. data/fastlane/lib/fastlane/actions/docs/get_push_certificate.md +1 -3
  26. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +13 -3
  27. data/fastlane/lib/fastlane/actions/ensure_no_debug_code.rb +2 -2
  28. data/fastlane/lib/fastlane/actions/get_managed_play_store_publishing_rights.rb +1 -0
  29. data/fastlane/lib/fastlane/actions/github_api.rb +1 -2
  30. data/fastlane/lib/fastlane/actions/hg_commit_version_bump.rb +1 -1
  31. data/fastlane/lib/fastlane/actions/increment_build_number.rb +1 -1
  32. data/fastlane/lib/fastlane/actions/increment_version_number.rb +1 -1
  33. data/fastlane/lib/fastlane/actions/install_xcode_plugin.rb +16 -6
  34. data/fastlane/lib/fastlane/actions/lcov.rb +9 -2
  35. data/fastlane/lib/fastlane/actions/nexus_upload.rb +16 -4
  36. data/fastlane/lib/fastlane/actions/notification.rb +3 -2
  37. data/fastlane/lib/fastlane/actions/oclint.rb +8 -8
  38. data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +9 -4
  39. data/fastlane/lib/fastlane/actions/register_device.rb +1 -1
  40. data/fastlane/lib/fastlane/actions/say.rb +2 -4
  41. data/fastlane/lib/fastlane/actions/set_pod_key.rb +3 -3
  42. data/fastlane/lib/fastlane/actions/sonar.rb +16 -16
  43. data/fastlane/lib/fastlane/actions/sourcedocs.rb +8 -8
  44. data/fastlane/lib/fastlane/actions/splunkmint.rb +8 -1
  45. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +1 -1
  46. data/fastlane/lib/fastlane/actions/upload_to_app_store.rb +5 -2
  47. data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +5 -2
  48. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -4
  49. data/fastlane/lib/fastlane/actions/verify_pod_keys.rb +3 -1
  50. data/fastlane/lib/fastlane/actions/xcode_server_get_assets.rb +20 -19
  51. data/fastlane/lib/fastlane/actions/xcodebuild.rb +5 -6
  52. data/fastlane/lib/fastlane/actions/zip.rb +2 -1
  53. data/fastlane/lib/fastlane/auto_complete.rb +1 -1
  54. data/fastlane/lib/fastlane/cli_tools_distributor.rb +14 -0
  55. data/fastlane/lib/fastlane/documentation/markdown_docs_generator.rb +23 -0
  56. data/fastlane/lib/fastlane/environment_printer.rb +12 -25
  57. data/fastlane/lib/fastlane/fast_file.rb +2 -1
  58. data/fastlane/lib/fastlane/fastlane_require.rb +5 -1
  59. data/fastlane/lib/fastlane/helper/sh_helper.rb +16 -6
  60. data/fastlane/lib/fastlane/new_action.rb +1 -1
  61. data/fastlane/lib/fastlane/plugins/plugin_fetcher.rb +1 -1
  62. data/fastlane/lib/fastlane/plugins/plugin_info_collector.rb +1 -1
  63. data/fastlane/lib/fastlane/plugins/plugin_manager.rb +36 -4
  64. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +1 -1
  65. data/fastlane/lib/fastlane/plugins/template/.github/workflows/test.yml +1 -1
  66. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +3 -7
  67. data/fastlane/lib/fastlane/swift_lane_manager.rb +22 -4
  68. data/fastlane/lib/fastlane/version.rb +3 -2
  69. data/fastlane/swift/Deliverfile.swift +1 -1
  70. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  71. data/fastlane/swift/Fastlane.swift +55 -9
  72. data/fastlane/swift/Gymfile.swift +1 -1
  73. data/fastlane/swift/GymfileProtocol.swift +1 -1
  74. data/fastlane/swift/MainProcess.swift +190 -20
  75. data/fastlane/swift/Matchfile.swift +1 -1
  76. data/fastlane/swift/MatchfileProtocol.swift +10 -3
  77. data/fastlane/swift/Precheckfile.swift +1 -1
  78. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  79. data/fastlane/swift/Scanfile.swift +1 -1
  80. data/fastlane/swift/ScanfileProtocol.swift +8 -1
  81. data/fastlane/swift/Screengrabfile.swift +1 -1
  82. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  83. data/fastlane/swift/Snapshotfile.swift +1 -1
  84. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  85. data/fastlane/swift/formatting/Rakefile +1 -0
  86. data/fastlane/swift/main.swift +4 -1
  87. data/fastlane_core/README.md +1 -1
  88. data/fastlane_core/lib/fastlane_core/analytics/analytics_event_builder.rb +1 -17
  89. data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +23 -8
  90. data/fastlane_core/lib/fastlane_core/device_manager.rb +2 -2
  91. data/fastlane_core/lib/fastlane_core/helper.rb +34 -5
  92. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +0 -14
  93. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +31 -28
  94. data/fastlane_core/lib/fastlane_core/languages.rb +0 -1
  95. data/fastlane_core/lib/fastlane_core/print_table.rb +5 -2
  96. data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +10 -19
  97. data/fastlane_core/lib/fastlane_core/secrets.rb +77 -0
  98. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +3 -3
  99. data/fastlane_core/lib/fastlane_core/ui/github_issue_inspector_reporter.rb +1 -1
  100. data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +2 -1
  101. data/fastlane_core/lib/fastlane_core.rb +1 -0
  102. data/frameit/lib/frameit/device.rb +1 -1
  103. data/frameit/lib/frameit/frame_downloader.rb +2 -0
  104. data/frameit/lib/frameit/runner.rb +3 -2
  105. data/frameit/lib/frameit/screenshot.rb +0 -1
  106. data/gym/lib/gym/generators/build_command_generator.rb +4 -4
  107. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +3 -3
  108. data/gym/lib/gym/runner.rb +1 -1
  109. data/internal/README.md +1 -1
  110. data/match/lib/match/encryption/openssl.rb +6 -2
  111. data/match/lib/match/generator.rb +1 -0
  112. data/match/lib/match/options.rb +13 -5
  113. data/match/lib/match/runner.rb +30 -3
  114. data/match/lib/match/storage/git_storage.rb +43 -7
  115. data/match/lib/match/storage/gitlab/secure_file.rb +2 -1
  116. data/match/lib/match/storage/interface.rb +8 -5
  117. data/pem/lib/pem/module.rb +0 -4
  118. data/precheck/lib/precheck/rule_processor.rb +1 -0
  119. data/produce/lib/produce/developer_center.rb +49 -2
  120. data/produce/lib/produce/itunes_connect.rb +1 -1
  121. data/produce/lib/produce/module.rb +21 -0
  122. data/produce/lib/produce/options.rb +17 -0
  123. data/produce/lib/produce/service.rb +3 -5
  124. data/produce/lib/produce.rb +0 -1
  125. data/scan/lib/scan/options.rb +7 -0
  126. data/scan/lib/scan/runner.rb +1 -1
  127. data/scan/lib/scan/test_command_generator.rb +12 -9
  128. data/scan/lib/scan/test_result_parser.rb +1 -0
  129. data/scan/lib/scan/xcpretty_reporter_options_generator.rb +2 -2
  130. data/screengrab/lib/screengrab/page.html.erb +12 -7
  131. data/screengrab/lib/screengrab/reports_generator.rb +1 -1
  132. data/sigh/lib/assets/resign.sh +17 -0
  133. data/sigh/lib/sigh/local_manage.rb +1 -1
  134. data/sigh/lib/sigh/options.rb +5 -0
  135. data/sigh/lib/sigh/resign.rb +3 -3
  136. data/sigh/lib/sigh/runner.rb +6 -1
  137. data/snapshot/lib/snapshot/page.html.erb +17 -12
  138. data/snapshot/lib/snapshot/reports_generator.rb +1 -1
  139. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +1 -1
  140. data/snapshot/lib/snapshot.rb +0 -2
  141. data/spaceship/README.md +2 -3
  142. data/spaceship/lib/spaceship/base.rb +3 -1
  143. data/spaceship/lib/spaceship/client.rb +65 -47
  144. data/spaceship/lib/spaceship/connect_api/models/bundle_id.rb +13 -2
  145. data/spaceship/lib/spaceship/connect_api/models/bundle_id_capability.rb +6 -0
  146. data/spaceship/lib/spaceship/connect_api/models/profile.rb +7 -5
  147. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +25 -0
  148. data/spaceship/lib/spaceship/du/upload_file.rb +1 -1
  149. data/spaceship/lib/spaceship/errors.rb +7 -1
  150. data/spaceship/lib/spaceship/globals.rb +2 -2
  151. data/spaceship/lib/spaceship/portal/merchant.rb +127 -2
  152. data/spaceship/lib/spaceship/portal/portal_client.rb +58 -0
  153. data/spaceship/lib/spaceship/portal/ui/select_team.rb +0 -2
  154. data/spaceship/lib/spaceship/portal/website_push.rb +8 -2
  155. data/spaceship/lib/spaceship/test_flight/client.rb +0 -22
  156. data/spaceship/lib/spaceship/test_flight.rb +0 -1
  157. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +11 -0
  158. data/supply/lib/supply/options.rb +1 -0
  159. data/supply/lib/supply.rb +0 -1
  160. metadata +75 -43
  161. data/produce/lib/produce/available_default_languages.rb +0 -62
  162. data/snapshot/lib/snapshot/screenshot_flatten.rb +0 -20
  163. data/snapshot/lib/snapshot/screenshot_rotate.rb +0 -36
  164. data/spaceship/lib/spaceship/test_flight/app_test_info.rb +0 -40
  165. data/supply/lib/supply/apk_listing.rb +0 -14
  166. /data/fastlane/lib/assets/{custom_action_template.rb → custom_action_template.rb.template} +0 -0
@@ -1,6 +1,7 @@
1
1
  require_relative '../../helper'
2
2
  require_relative '../../globals'
3
3
  require_relative '../../env'
4
+ require_relative '../../secrets'
4
5
 
5
6
  require_relative '../interface'
6
7
 
@@ -23,7 +24,7 @@ module FastlaneCore
23
24
  end
24
25
 
25
26
  @log.formatter = proc do |severity, datetime, progname, msg|
26
- "#{format_string(datetime, severity)}#{msg}\n"
27
+ "#{format_string(datetime, severity)}#{Secrets.mask(msg.to_s)}\n"
27
28
  end
28
29
 
29
30
  @log
@@ -2,6 +2,7 @@ require_relative 'fastlane_core/globals'
2
2
  # Ruby monkey-patches - should be before almost all else
3
3
  require_relative 'fastlane_core/core_ext/string'
4
4
  require_relative 'fastlane_core/core_ext/shellwords'
5
+ require_relative 'fastlane_core/secrets'
5
6
 
6
7
  require_relative 'fastlane_core/analytics/action_completion_context'
7
8
  require_relative 'fastlane_core/analytics/action_launch_context'
@@ -15,7 +15,7 @@ module Frameit
15
15
  attr_reader :priority_config_key
16
16
 
17
17
  def initialize(id, formatted_name, priority, resolutions, density_ppi, default_color, platform = Platform::IOS, deliver_screen_id = nil, priority_config_key = nil)
18
- Raise("Priority mustn't be higher than #{REQUIRED_PRIORITY}") if priority > REQUIRED_PRIORITY
18
+ raise "Priority mustn't be higher than #{REQUIRED_PRIORITY}" if priority > REQUIRED_PRIORITY
19
19
  @id = id
20
20
  @deliver_screen_id = deliver_screen_id
21
21
  @formatted_name = formatted_name
@@ -20,6 +20,8 @@ module Frameit
20
20
 
21
21
  files = JSON.parse(download_file("files.json"))
22
22
  files.each_with_index do |current, index|
23
+ # files.json is downloaded: only accept plain file names, so nothing is written outside templates_path
24
+ UI.user_error!("Unexpected frame file name '#{current}'") if current.include?("/") || current.include?("\\") || current.start_with?(".")
23
25
  content = download_file(current, txt: "#{index + 1} of #{files.count} files")
24
26
  File.binwrite(File.join(templates_path, current), content)
25
27
  end
@@ -38,8 +38,9 @@ module Frameit
38
38
  if editor.should_skip?
39
39
  UI.message("Skipping framing of screenshot #{screenshot.path}. No title provided in your Framefile.json or title.strings.")
40
40
  else
41
- Helper.show_loading_indicator("Framing screenshot '#{full_path}'")
42
- editor.frame!
41
+ Helper.with_loading_indicator("Framing screenshot '#{full_path}'") do
42
+ editor.frame!
43
+ end
43
44
  end
44
45
  rescue => ex
45
46
  UI.error(ex.to_s)
@@ -48,7 +48,6 @@ module Frameit
48
48
  # Device name for a given screen size. Used to use the correct template
49
49
  def device_name
50
50
  @device.formatted_name
51
- # rubocop:enable Require/MissingRequireStatement
52
51
  end
53
52
 
54
53
  def default_color
@@ -34,11 +34,11 @@ module Gym
34
34
 
35
35
  options = []
36
36
  options += project_path_array
37
- options << "-sdk '#{config[:sdk]}'" if config[:sdk]
38
- options << "-toolchain '#{config[:toolchain]}'" if config[:toolchain]
39
- options << "-destination '#{config[:destination]}'" if config[:destination]
37
+ options << "-sdk #{config[:sdk].shellescape}" if config[:sdk]
38
+ options << "-toolchain #{config[:toolchain].shellescape}" if config[:toolchain]
39
+ options << "-destination #{config[:destination].shellescape}" if config[:destination]
40
40
  options << "-archivePath #{archive_path.shellescape}" unless config[:skip_archive]
41
- options << "-resultBundlePath '#{result_bundle_path}'" if config[:result_bundle]
41
+ options << "-resultBundlePath #{result_bundle_path.shellescape}" if config[:result_bundle]
42
42
  options << "-showBuildTimingSummary" if config[:build_timing_summary]
43
43
  if config[:use_system_scm] && !options.include?("-scmProvider system")
44
44
  options << "-scmProvider system"
@@ -33,10 +33,10 @@ module Gym
33
33
  config = Gym.config
34
34
 
35
35
  options = []
36
- options << "-exportOptionsPlist '#{config_path}'"
36
+ options << "-exportOptionsPlist #{config_path.shellescape}"
37
37
  options << "-archivePath #{BuildCommandGenerator.archive_path.shellescape}"
38
- options << "-exportPath '#{temporary_output_path}'"
39
- options << "-toolchain '#{config[:toolchain]}'" if config[:toolchain]
38
+ options << "-exportPath #{temporary_output_path.shellescape}"
39
+ options << "-toolchain #{config[:toolchain].shellescape}" if config[:toolchain]
40
40
  options << config[:export_xcargs] if config[:export_xcargs]
41
41
  options << config[:xcargs] if config[:xcargs]
42
42
 
@@ -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]
@@ -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"
@@ -165,8 +164,10 @@ module Match
165
164
  def generate_matchfile_content
166
165
  UI.important("Please create a new, private git repository to store the certificates and profiles there")
167
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?
168
169
 
169
- return "git_url(\"#{url}\")"
170
+ return "git_url(\"#{url}\")\ngit_branch(\"#{branch}\")"
170
171
  end
171
172
 
172
173
  def list_files(file_name: "", file_ext: "")
@@ -178,7 +179,8 @@ module Match
178
179
  ssh_add = File.expand_path(self.git_private_key).shellescape.to_s
179
180
  else
180
181
  UI.message("Private key file does not exist, will continue by using it as a raw key.")
181
- 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}\""
182
184
  end
183
185
  return "ssh-agent bash -c 'ssh-add #{ssh_add}; #{command}'"
184
186
  end
@@ -264,9 +266,43 @@ module Match
264
266
  def git_env_values
265
267
  env_values = { 'GIT_TERMINAL_PROMPT' => '0' }
266
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
267
293
  env_values
268
294
  end
269
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
+
270
306
  def non_interactive_git_ssh_command
271
307
  ssh_command = ENV['GIT_SSH_COMMAND']
272
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
 
@@ -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)
@@ -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
 
@@ -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",
@@ -88,7 +88,6 @@ module Produce
88
88
  end
89
89
 
90
90
  # rubocop:disable Metrics/PerceivedComplexity
91
- # rubocop:disable Require/MissingRequireStatement
92
91
  def update(on, options)
93
92
  updated = valid_services_for(options).count
94
93
 
@@ -420,10 +419,9 @@ module Produce
420
419
 
421
420
  def bundle_id
422
421
  return @bundle_id if @bundle_id
423
- UI.message("Starting login with user '#{Produce.config[:username]}'")
424
- Spaceship.login(Produce.config[:username], nil)
425
- Spaceship.select_team
426
- UI.message("Successfully logged in")
422
+
423
+ Produce.authenticate_connect_api!
424
+
427
425
  @bundle_id ||= Spaceship::ConnectAPI::BundleId.find(Produce.config[:app_identifier].to_s)
428
426
  end
429
427
  end
@@ -2,5 +2,4 @@ require_relative 'produce/manager'
2
2
  require_relative 'produce/options'
3
3
  require_relative 'produce/developer_center'
4
4
  require_relative 'produce/itunes_connect'
5
- require_relative 'produce/available_default_languages'
6
5
  require_relative 'produce/module'
@@ -207,6 +207,13 @@ module Scan
207
207
  conflict_block: proc do |value|
208
208
  UI.user_error!("You can't use 'thread_sanitizer' and 'address_sanitizer' options in one run")
209
209
  end),
210
+ FastlaneCore::ConfigItem.new(key: :collect_test_diagnostics,
211
+ env_name: "SCAN_COLLECT_TEST_DIAGNOSTICS",
212
+ description: "Whether verbose and long-running diagnostics (like sysdiagnoses or log archives) are collected when testing. Valid values are: on-failure or never. If not specified, the value in the test plan is used. Equivalent to -collect-test-diagnostics (Xcode 14 and up)",
213
+ optional: true,
214
+ verify_block: proc do |value|
215
+ UI.user_error!("Invalid collect_test_diagnostics #{value}") unless ['on-failure', 'never'].include?(value)
216
+ end),
210
217
 
211
218
  # output
212
219
  FastlaneCore::ConfigItem.new(key: :open_report,
@@ -327,7 +327,7 @@ module Scan
327
327
 
328
328
  def open_report
329
329
  if !Helper.ci? && Scan.cache[:open_html_report_path]
330
- `open --hide '#{Scan.cache[:open_html_report_path]}'`
330
+ system("open", "--hide", Scan.cache[:open_html_report_path])
331
331
  end
332
332
  end
333
333