fastlane 2.194.0 → 2.198.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +90 -90
  3. data/cert/lib/cert/runner.rb +5 -2
  4. data/deliver/lib/assets/ScreenshotsHelp +29 -6
  5. data/deliver/lib/deliver/app_screenshot.rb +4 -4
  6. data/deliver/lib/deliver/runner.rb +1 -1
  7. data/deliver/lib/deliver/upload_screenshots.rb +1 -1
  8. data/fastlane/lib/fastlane/actions/app_store_build_number.rb +12 -6
  9. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +1 -1
  10. data/fastlane/lib/fastlane/actions/download_dsyms.rb +47 -30
  11. data/fastlane/lib/fastlane/actions/get_push_certificate.rb +1 -1
  12. data/fastlane/lib/fastlane/actions/get_version_number.rb +6 -2
  13. data/fastlane/lib/fastlane/actions/gradle.rb +15 -2
  14. data/fastlane/lib/fastlane/actions/last_git_commit.rb +1 -1
  15. data/fastlane/lib/fastlane/actions/latest_testflight_build_number.rb +2 -3
  16. data/fastlane/lib/fastlane/actions/notarize.rb +29 -11
  17. data/fastlane/lib/fastlane/actions/prompt.rb +1 -1
  18. data/fastlane/lib/fastlane/actions/set_github_release.rb +11 -5
  19. data/fastlane/lib/fastlane/version.rb +1 -1
  20. data/fastlane/swift/Deliverfile.swift +1 -1
  21. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  22. data/fastlane/swift/Fastlane.swift +77 -82
  23. data/fastlane/swift/Gymfile.swift +1 -1
  24. data/fastlane/swift/GymfileProtocol.swift +1 -1
  25. data/fastlane/swift/LaneFileProtocol.swift +1 -1
  26. data/fastlane/swift/Matchfile.swift +1 -1
  27. data/fastlane/swift/MatchfileProtocol.swift +9 -1
  28. data/fastlane/swift/Precheckfile.swift +1 -1
  29. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  30. data/fastlane/swift/Runner.swift +1 -1
  31. data/fastlane/swift/Scanfile.swift +1 -1
  32. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  33. data/fastlane/swift/Screengrabfile.swift +1 -1
  34. data/fastlane/swift/ScreengrabfileProtocol.swift +3 -3
  35. data/fastlane/swift/Snapshotfile.swift +1 -1
  36. data/fastlane/swift/SnapshotfileProtocol.swift +2 -2
  37. data/fastlane/swift/formatting/Brewfile.lock.json +27 -22
  38. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +31 -7
  39. data/frameit/lib/frameit/editor.rb +16 -18
  40. data/frameit/lib/frameit/trim_box.rb +6 -0
  41. data/match/lib/match/generator.rb +2 -1
  42. data/match/lib/match/nuke.rb +79 -1
  43. data/match/lib/match/options.rb +10 -0
  44. data/match/lib/match/runner.rb +94 -10
  45. data/match/lib/match/spaceship_ensure.rb +1 -0
  46. data/pem/lib/pem/manager.rb +31 -7
  47. data/pem/lib/pem/options.rb +10 -1
  48. data/pilot/lib/pilot/build_manager.rb +1 -1
  49. data/scan/lib/scan/runner.rb +1 -1
  50. data/screengrab/lib/screengrab/options.rb +2 -2
  51. data/sigh/lib/assets/resign.sh +8 -5
  52. data/sigh/lib/sigh/options.rb +5 -0
  53. data/sigh/lib/sigh/runner.rb +2 -2
  54. data/snapshot/lib/snapshot/options.rb +1 -1
  55. data/snapshot/lib/snapshot/reports_generator.rb +8 -0
  56. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +7 -0
  57. data/spaceship/lib/spaceship/connect_api/models/build.rb +4 -2
  58. data/spaceship/lib/spaceship/connect_api/models/build_bundle.rb +59 -0
  59. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +3 -0
  60. data/{fastlane/lib/fastlane/actions/.notarize.rb.swp → spaceship/lib/spaceship/connect_api/testflight/.testflight.rb.swp} +0 -0
  61. data/spaceship/lib/spaceship/connect_api/token.rb +1 -1
  62. data/spaceship/lib/spaceship/connect_api.rb +1 -0
  63. data/supply/lib/supply/client.rb +3 -3
  64. data/supply/lib/supply/uploader.rb +1 -1
  65. metadata +38 -35
  66. data/fastlane/lib/fastlane/actions/crashlytics.rb +0 -207
  67. data/fastlane/lib/fastlane/helper/crashlytics_helper.rb +0 -157
@@ -47,7 +47,7 @@ public protocol ScreengrabfileProtocol: class {
47
47
  /// The path to the APK for the app under test
48
48
  var appApkPath: String? { get }
49
49
 
50
- /// The path to the APK for the the tests bundle
50
+ /// The path to the APK for the tests bundle
51
51
  var testsApkPath: String? { get }
52
52
 
53
53
  /// Use the device or emulator with the given serial number or qualifier
@@ -56,7 +56,7 @@ public protocol ScreengrabfileProtocol: class {
56
56
  /// Type of device used for screenshots. Matches Google Play Types (phone, sevenInch, tenInch, tv, wear)
57
57
  var deviceType: String { get }
58
58
 
59
- /// Whether or not to exit Screengrab on test failure. Exiting on failure will not copy sceenshots to local machine nor open sceenshots summary
59
+ /// Whether or not to exit Screengrab on test failure. Exiting on failure will not copy screenshots to local machine nor open screenshots summary
60
60
  var exitOnTestFailure: Bool { get }
61
61
 
62
62
  /// Enabling this option will automatically uninstall the application before running it
@@ -96,4 +96,4 @@ public extension ScreengrabfileProtocol {
96
96
 
97
97
  // Please don't remove the lines below
98
98
  // They are used to detect outdated files
99
- // FastlaneRunnerAPIVersion [0.9.81]
99
+ // FastlaneRunnerAPIVersion [0.9.85]
@@ -17,4 +17,4 @@ public class Snapshotfile: SnapshotfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.194.0
20
+ // Generated with fastlane 2.198.0
@@ -50,7 +50,7 @@ public protocol SnapshotfileProtocol: class {
50
50
  /// Enabling this option will prevent displaying the simulator window
51
51
  var headless: Bool { get }
52
52
 
53
- /// Enabling this option will automatically override the status bar to show 9:41 AM, full battery, and full reception
53
+ /// Enabling this option will automatically override the status bar to show 9:41 AM, full battery, and full reception (Adjust 'SNAPSHOT_SIMULATOR_WAIT_FOR_BOOT_TIMEOUT' environment variable if override status bar is not working. Might be because simulator is not fully booted. Defaults to 10 seconds)
54
54
  var overrideStatusBar: Bool { get }
55
55
 
56
56
  /// Fully customize the status bar by setting each option here. See `xcrun simctl status_bar --help`
@@ -200,4 +200,4 @@ public extension SnapshotfileProtocol {
200
200
 
201
201
  // Please don't remove the lines below
202
202
  // They are used to detect outdated files
203
- // FastlaneRunnerAPIVersion [0.9.75]
203
+ // FastlaneRunnerAPIVersion [0.9.79]
@@ -2,30 +2,35 @@
2
2
  "entries": {
3
3
  "brew": {
4
4
  "swiftformat": {
5
- "version": "0.48.11",
5
+ "version": "0.48.18",
6
6
  "bottle": {
7
7
  "rebuild": 0,
8
8
  "root_url": "https://ghcr.io/v2/homebrew/core",
9
9
  "files": {
10
+ "arm64_monterey": {
11
+ "cellar": ":any_skip_relocation",
12
+ "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:adbb11cdda0596a1f40e920f826a2e1ffe6aeb1643c43ddb108f25f30755be39",
13
+ "sha256": "adbb11cdda0596a1f40e920f826a2e1ffe6aeb1643c43ddb108f25f30755be39"
14
+ },
10
15
  "arm64_big_sur": {
11
16
  "cellar": ":any_skip_relocation",
12
- "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:e0a851cfa2ff5d04f0fc98a9e624d1411f1b5b1e55e3cbc0901f4913c02e716a",
13
- "sha256": "e0a851cfa2ff5d04f0fc98a9e624d1411f1b5b1e55e3cbc0901f4913c02e716a"
17
+ "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:74691b21c40d34459c5825306828039bdbdfdd02c80d1cea5c449c3f59760ab1",
18
+ "sha256": "74691b21c40d34459c5825306828039bdbdfdd02c80d1cea5c449c3f59760ab1"
14
19
  },
15
- "big_sur": {
20
+ "monterey": {
16
21
  "cellar": ":any_skip_relocation",
17
- "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:a5327283fe32b2ef2c6f264e14c966a9a60cb291415d3d05ed659c92a93c4987",
18
- "sha256": "a5327283fe32b2ef2c6f264e14c966a9a60cb291415d3d05ed659c92a93c4987"
22
+ "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:14e4b90f29b55b06c92f90b6e81e8dd55c54ff258ee8b0bab2bb479ce0cc8daf",
23
+ "sha256": "14e4b90f29b55b06c92f90b6e81e8dd55c54ff258ee8b0bab2bb479ce0cc8daf"
19
24
  },
20
- "catalina": {
25
+ "big_sur": {
21
26
  "cellar": ":any_skip_relocation",
22
- "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:ba95e49ecc71bb19734698dee565e3b0ced6470729206cb434675cfa051f2755",
23
- "sha256": "ba95e49ecc71bb19734698dee565e3b0ced6470729206cb434675cfa051f2755"
27
+ "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:73f0497b504f87159bc2d133371014e0372799d89f62bc78068b3b5427ae614f",
28
+ "sha256": "73f0497b504f87159bc2d133371014e0372799d89f62bc78068b3b5427ae614f"
24
29
  },
25
- "mojave": {
30
+ "catalina": {
26
31
  "cellar": ":any_skip_relocation",
27
- "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:c7e00eae9d46dddf040999f0f2832d08110f093c7a403aaaaaa18d8830213967",
28
- "sha256": "c7e00eae9d46dddf040999f0f2832d08110f093c7a403aaaaaa18d8830213967"
32
+ "url": "https://ghcr.io/v2/homebrew/core/swiftformat/blobs/sha256:11543d157b589acdf9de93133a68fa32aa00372b574a64c40292aead20f6f6cb",
33
+ "sha256": "11543d157b589acdf9de93133a68fa32aa00372b574a64c40292aead20f6f6cb"
29
34
  }
30
35
  }
31
36
  }
@@ -43,20 +48,20 @@
43
48
  "macOS": "10.15.7"
44
49
  },
45
50
  "big_sur": {
46
- "HOMEBREW_VERSION": "3.2.10-50-ge3f851d",
51
+ "HOMEBREW_VERSION": "3.2.17",
47
52
  "HOMEBREW_PREFIX": "/opt/homebrew",
48
- "Homebrew/homebrew-core": "73588fb5f5edccfe62f1b290a3298b402fbd71d5",
49
- "CLT": "12.5.1.0.1.1623191612",
53
+ "Homebrew/homebrew-core": "d975bb4c6f50e8cafd6df9fc7f2ebf04d22ffa41",
54
+ "CLT": "13.0.0.0.1.1630607135",
50
55
  "Xcode": "13.0",
51
- "macOS": "11.5.2"
56
+ "macOS": "11.6"
52
57
  },
53
58
  "monterey": {
54
- "HOMEBREW_VERSION": "3.2.12-9-gb19fcef",
55
- "HOMEBREW_PREFIX": "/usr/local",
56
- "Homebrew/homebrew-core": "e79d8126370187be3bd7be6bdbffacf803732113",
57
- "CLT": "13.0.0.0.1.1628499445",
58
- "Xcode": "12.5.1",
59
- "macOS": "12.0"
59
+ "HOMEBREW_VERSION": "3.3.4",
60
+ "HOMEBREW_PREFIX": "/opt/homebrew",
61
+ "Homebrew/homebrew-core": "446005a8fbbc1a51d0bd8efd8e03bf82f1f78fab",
62
+ "CLT": "13.1.0.0.1.1633545042",
63
+ "Xcode": "13.1",
64
+ "macOS": "12.0.1"
60
65
  }
61
66
  }
62
67
  }
@@ -1,4 +1,5 @@
1
1
  require 'shellwords'
2
+ require 'tmpdir'
2
3
  require 'fileutils'
3
4
  require 'credentials_manager/account_manager'
4
5
 
@@ -148,6 +149,14 @@ module FastlaneCore
148
149
  end
149
150
  end
150
151
 
152
+ def file_upload_option(source)
153
+ if File.extname(source) == ".itmsp"
154
+ return "-f #{source.shellescape}"
155
+ else
156
+ return "-assetFile #{source.shellescape}"
157
+ end
158
+ end
159
+
151
160
  def additional_upload_parameters
152
161
  # As Apple recommends in Transporter User Guide we shouldn't specify the -t transport parameter
153
162
  # and instead allow Transporter to use automatic transport discovery
@@ -177,7 +186,7 @@ module FastlaneCore
177
186
  ("-u #{username.shellescape}" unless use_jwt),
178
187
  ("-p #{shell_escaped_password(password)}" unless use_jwt),
179
188
  ("-jwt #{jwt}" if use_jwt),
180
- "-f \"#{source}\"",
189
+ file_upload_option(source),
181
190
  additional_upload_parameters, # that's here, because the user might overwrite the -t option
182
191
  "-k 100000",
183
192
  ("-WONoPause true" if Helper.windows?), # Windows only: process instantly returns instead of waiting for key press
@@ -261,7 +270,7 @@ module FastlaneCore
261
270
  ("-u #{username.shellescape}" unless use_jwt),
262
271
  ("-p @env:ITMS_TRANSPORTER_PASSWORD" unless use_jwt),
263
272
  ("-jwt #{jwt}" if use_jwt),
264
- "-f #{source.shellescape}",
273
+ file_upload_option(source),
265
274
  additional_upload_parameters, # that's here, because the user might overwrite the -t option
266
275
  '-k 100000',
267
276
  ("-itc_provider #{provider_short_name}" if jwt.nil? && !provider_short_name.to_s.empty?),
@@ -282,7 +291,7 @@ module FastlaneCore
282
291
  ("-u #{username.shellescape}" unless use_jwt),
283
292
  ("-p #{password.shellescape}" unless use_jwt),
284
293
  ("-jwt #{jwt}" if use_jwt),
285
- "-f #{source.shellescape}",
294
+ file_upload_option(source),
286
295
  additional_upload_parameters, # that's here, because the user might overwrite the -t option
287
296
  '-k 100000',
288
297
  ("-itc_provider #{provider_short_name}" if jwt.nil? && !provider_short_name.to_s.empty?),
@@ -472,12 +481,27 @@ module FastlaneCore
472
481
  # @param app_id [Integer] The unique App ID
473
482
  # @param dir [String] the path in which the package file is located
474
483
  # @param package_path [String] the path to the package file (used instead of app_id and dir)
484
+ # @param asset_path [String] the path to the ipa/dmg/pkg file (used instead of package_path if running on macOS)
475
485
  # @return (Bool) True if everything worked fine
476
486
  # @raise [Deliver::TransporterTransferError] when something went wrong
477
487
  # when transferring
478
- def upload(app_id = nil, dir = nil, package_path: nil)
479
- raise "app_id and dir are required or package_path is required" if (app_id.nil? || dir.nil?) && package_path.nil?
480
- actual_dir = package_path || File.join(dir, "#{app_id}.itmsp")
488
+ def upload(app_id = nil, dir = nil, package_path: nil, asset_path: nil)
489
+ raise "app_id and dir are required or package_path or asset_path is required" if (app_id.nil? || dir.nil?) && package_path.nil? && asset_path.nil?
490
+
491
+ # Transport can upload .ipa, .dmg, and .pkg files directly with -assetFile
492
+ # However, -assetFile requires -assetDescription if Linux or Windows
493
+ # This will give the asset directly if macOS and asset_path exists
494
+ # otherwise it will use the .itmsp package
495
+ actual_dir = if Helper.is_mac? && asset_path
496
+ # The asset gets deleted upon completion so copying to a temp directory
497
+ tmp_asset_path = File.join(Dir.tmpdir, File.basename(asset_path))
498
+ FileUtils.cp(asset_path, tmp_asset_path)
499
+ tmp_asset_path
500
+ elsif package_path
501
+ package_path
502
+ else
503
+ File.join(dir, "#{app_id}.itmsp")
504
+ end
481
505
 
482
506
  UI.message("Going to upload updated app to App Store Connect")
483
507
  UI.success("This might take a few minutes. Please don't interrupt the script.")
@@ -492,7 +516,7 @@ module FastlaneCore
492
516
  result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
493
517
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
494
518
  handle_two_step_failure(ex)
495
- return upload(app_id, dir, package_path: package_path)
519
+ return upload(app_id, dir, package_path: package_path, asset_path: asset_path)
496
520
  end
497
521
 
498
522
  if result
@@ -467,26 +467,24 @@ module Frameit
467
467
  # Get matching trim box:
468
468
  trim_box = trim_boxes[key]
469
469
 
470
- # For side-by-side text images (e.g. stack_title is false) adjust the trim box based on top_vertical_trim_offset and bottom_vertical_trim_offset to maintain the text baseline:
471
- unless stack_title
472
- # Determine the trim area by maintaining the same vertical top offset based on the smallest value from all trim boxes (top_vertical_trim_offset).
473
- # When the vertical top offset is larger than the smallest vertical top offset, the trim box needs to be adjusted:
474
- if trim_box.offset_y > top_vertical_trim_offset
475
- # Increase the height of the trim box with the difference in vertical top offset:
476
- trim_box.height += trim_box.offset_y - top_vertical_trim_offset
477
- # Change the vertical top offset to match that of the others:
478
- trim_box.offset_y = top_vertical_trim_offset
479
-
480
- UI.verbose("Trim box for key \"#{key}\" is adjusted to align top: #{trim_box}\n")
481
- end
470
+ # Adjust the trim box based on top_vertical_trim_offset and bottom_vertical_trim_offset to maintain the text baseline:
471
+ # Determine the trim area by maintaining the same vertical top offset based on the smallest value from all trim boxes (top_vertical_trim_offset).
472
+ # When the vertical top offset is larger than the smallest vertical top offset, the trim box needs to be adjusted:
473
+ if trim_box.offset_y > top_vertical_trim_offset
474
+ # Increase the height of the trim box with the difference in vertical top offset:
475
+ trim_box.height += trim_box.offset_y - top_vertical_trim_offset
476
+ # Change the vertical top offset to match that of the others:
477
+ trim_box.offset_y = top_vertical_trim_offset
478
+
479
+ UI.verbose("Trim box for key \"#{key}\" is adjusted to align top: #{trim_box.json_string_format}")
480
+ end
482
481
 
483
- # Check if the height needs to be adjusted to reach the bottom offset:
484
- if (trim_box.offset_y + trim_box.height) < bottom_vertical_trim_offset
485
- # Set the height of the trim box to the difference between vertical bottom and top offset:
486
- trim_box.height = bottom_vertical_trim_offset - trim_box.offset_y
482
+ # Check if the height needs to be adjusted to reach the bottom offset:
483
+ if (trim_box.offset_y + trim_box.height) < bottom_vertical_trim_offset
484
+ # Set the height of the trim box to the difference between vertical bottom and top offset:
485
+ trim_box.height = bottom_vertical_trim_offset - trim_box.offset_y
487
486
 
488
- UI.verbose("Trim box for key \"#{key}\" is adjusted to align bottom: #{trim_box}\n")
489
- end
487
+ UI.verbose("Trim box for key \"#{key}\" is adjusted to align bottom: #{trim_box.json_string_format}")
490
488
  end
491
489
 
492
490
  # Crop image with (adjusted) trim box parameters in MiniMagick string format:
@@ -31,5 +31,11 @@ module Frameit
31
31
  # Convert trim box parameters to string with syntax: "<width>x<height>+<offset_x>+<offset_y>":
32
32
  return "#{@width}x#{@height}+#{@offset_x}+#{@offset_y}"
33
33
  end
34
+
35
+ # Get the trimbox parameters in a human readable JSON string format
36
+ def json_string_format
37
+ # Create a JSON string from the trim box parameters:
38
+ return "{\"width\" : #{@width}, \"height\" : #{@height} , \"offset_x\" : #{@offset_x}, \"offset_y\" : #{@offset_y}}"
39
+ end
34
40
  end
35
41
  end
@@ -88,7 +88,8 @@ module Match
88
88
  team_id: params[:team_id],
89
89
  team_name: params[:team_name],
90
90
  template_name: params[:template_name],
91
- fail_on_name_taken: params[:fail_on_name_taken]
91
+ fail_on_name_taken: params[:fail_on_name_taken],
92
+ include_all_certificates: params[:include_all_certificates],
92
93
  }
93
94
 
94
95
  values[:platform] = params[:platform]
@@ -9,7 +9,11 @@ require_relative 'module'
9
9
  require_relative 'storage'
10
10
  require_relative 'encryption'
11
11
 
12
+ require 'tempfile'
13
+ require 'base64'
14
+
12
15
  module Match
16
+ # rubocop:disable Metrics/ClassLength
13
17
  class Nuke
14
18
  attr_accessor :params
15
19
  attr_accessor :type
@@ -67,6 +71,7 @@ module Match
67
71
  title: "Summary for match nuke #{Fastlane::VERSION}")
68
72
 
69
73
  prepare_list
74
+ filter_by_cert
70
75
  print_tables
71
76
 
72
77
  if params[:readonly]
@@ -139,7 +144,7 @@ module Match
139
144
  types = profile_types(prov_type)
140
145
  # Filtering on 'profileType' seems to be undocumented as of 2020-07-30
141
146
  # but works on both web session and official API
142
- self.profiles += Spaceship::ConnectAPI::Profile.all(filter: { profileType: types.join(",") })
147
+ self.profiles += Spaceship::ConnectAPI::Profile.all(filter: { profileType: types.join(",") }, includes: "certificates")
143
148
  end
144
149
 
145
150
  # Gets the main and additional cert types
@@ -173,6 +178,78 @@ module Match
173
178
  self.files = certs + keys + profiles
174
179
  end
175
180
 
181
+ def filter_by_cert
182
+ # Force will continue to revoke and delete all certificates and profiles
183
+ return if self.params[:force] || !UI.interactive?
184
+ return if self.certs.count < 2
185
+
186
+ # Print table showing certificates that can be revoked
187
+ puts("")
188
+ rows = self.certs.each_with_index.collect do |cert, i|
189
+ cert_expiration = cert.expiration_date.nil? ? "Unknown" : Time.parse(cert.expiration_date).strftime("%Y-%m-%d")
190
+ [i + 1, cert.name, cert.id, cert.class.to_s.split("::").last, cert_expiration]
191
+ end
192
+ puts(Terminal::Table.new({
193
+ title: "Certificates that can be revoked".green,
194
+ headings: ["Option", "Name", "ID", "Type", "Expires"],
195
+ rows: FastlaneCore::PrintTable.transform_output(rows)
196
+ }))
197
+ puts("")
198
+
199
+ UI.important("By default, all listed certificates and profiles will be nuked")
200
+ if UI.confirm("Do you want to only nuke specific certificates and their associated profiles?")
201
+ input_indexes = UI.input("Enter the \"Option\" number(s) from the table above? (comma-separated)").split(',')
202
+
203
+ # Get certificates from option indexes
204
+ self.certs = input_indexes.map do |index|
205
+ self.certs[index.to_i - 1]
206
+ end.compact
207
+
208
+ if self.certs.empty?
209
+ UI.user_error!("No certificates were selected based on option number(s) entered")
210
+ end
211
+
212
+ # Do profile selection logic
213
+ cert_ids = self.certs.map(&:id)
214
+ self.profiles = self.profiles.select do |profile|
215
+ profile_cert_ids = profile.certificates.map(&:id)
216
+ (cert_ids & profile_cert_ids).any?
217
+ end
218
+
219
+ # Do file selection logic
220
+ self.files = self.files.select do |f|
221
+ found = false
222
+
223
+ ext = File.extname(f)
224
+ filename = File.basename(f, ".*")
225
+
226
+ # Attempt to find cert based on filename
227
+ if ext == ".cer" || ext == ".p12"
228
+ found ||= self.certs.any? do |cert|
229
+ filename == cert.id.to_s
230
+ end
231
+ end
232
+
233
+ # Attempt to find profile matched on UUIDs in profile
234
+ if ext == ".mobileprovision" || ext == ".provisionprofile"
235
+ storage_uuid = FastlaneCore::ProvisioningProfile.uuid(f)
236
+
237
+ found ||= self.profiles.any? do |profile|
238
+ tmp_file = Tempfile.new
239
+ tmp_file.write(Base64.decode64(profile.profile_content))
240
+ tmp_file.close
241
+
242
+ # Compare profile uuid in storage to profile uuid on developer portal
243
+ portal_uuid = FastlaneCore::ProvisioningProfile.uuid(tmp_file.path)
244
+ storage_uuid == portal_uuid
245
+ end
246
+ end
247
+
248
+ found
249
+ end
250
+ end
251
+ end
252
+
176
253
  # Print tables to ask the user
177
254
  def print_tables
178
255
  puts("")
@@ -351,4 +428,5 @@ module Match
351
428
  end
352
429
  end
353
430
  end
431
+ # rubocop:disable Metrics/ClassLength
354
432
  end
@@ -242,6 +242,16 @@ module Match
242
242
  description: "Renew the provisioning profiles if the device count on the developer portal has changed. Ignored for profile types 'appstore' and 'developer_id'",
243
243
  type: Boolean,
244
244
  default_value: false),
245
+ FastlaneCore::ConfigItem.new(key: :include_all_certificates,
246
+ env_name: "MATCH_INCLUDE_ALL_CERTIFICATES",
247
+ description: "Include all matching certificates in the provisioning profile. Works only for the 'development' provisioning profile type",
248
+ type: Boolean,
249
+ default_value: false),
250
+ FastlaneCore::ConfigItem.new(key: :force_for_new_certificates,
251
+ env_name: "MATCH_FORCE_FOR_NEW_CERTIFICATES",
252
+ description: "Renew the provisioning profiles if the device count on the developer portal has changed. Works only for the 'development' provisioning profile type. Requires 'include_all_certificates' option to be 'true'",
253
+ type: Boolean,
254
+ default_value: false),
245
255
  FastlaneCore::ConfigItem.new(key: :skip_confirmation,
246
256
  env_name: "MATCH_SKIP_CONFIRMATION",
247
257
  description: "Disables confirmation prompts during nuke, answering them with yes",
@@ -12,6 +12,7 @@ require_relative 'storage'
12
12
  require_relative 'encryption'
13
13
 
14
14
  module Match
15
+ # rubocop:disable Metrics/ClassLength
15
16
  class Runner
16
17
  attr_accessor :files_to_commit
17
18
  attr_accessor :spaceship
@@ -242,16 +243,14 @@ module Match
242
243
  profile = profiles.last
243
244
  force = params[:force]
244
245
 
245
- if params[:force_for_new_devices] && !params[:readonly]
246
- prov_types_without_devices = [:appstore, :developer_id]
247
- if !prov_types_without_devices.include?(prov_type) && !params[:force]
248
- force = device_count_different?(profile: profile, keychain_path: keychain_path, platform: params[:platform].to_sym)
249
- else
250
- # App Store provisioning profiles don't contain device identifiers and
251
- # thus shouldn't be renewed if the device count has changed.
252
- UI.important("Warning: `force_for_new_devices` is set but is ignored for App Store & Developer ID provisioning profiles.")
253
- UI.important("You can safely stop specifying `force_for_new_devices` when running Match for type 'appstore' or 'developer_id'.")
254
- end
246
+ if params[:force_for_new_devices]
247
+ force = should_force_include_all_devices(params: params, prov_type: prov_type, profile: profile, keychain_path: keychain_path) unless force
248
+ end
249
+
250
+ if params[:include_all_certificates]
251
+ # Clearing specified certificate id which will prevent a profile being created with only one certificate
252
+ certificate_id = nil
253
+ force = should_force_include_all_certificates(params: params, prov_type: prov_type, profile: profile, keychain_path: keychain_path) unless force
255
254
  end
256
255
 
257
256
  if profile.nil? || force
@@ -319,6 +318,24 @@ module Match
319
318
  end
320
319
  # rubocop:enable Metrics/PerceivedComplexity
321
320
 
321
+ def should_force_include_all_devices(params: nil, prov_type: nil, profile: nil, keychain_path: nil)
322
+ return false unless params[:force_for_new_devices] && !params[:readonly]
323
+
324
+ force = false
325
+
326
+ prov_types_without_devices = [:appstore, :developer_id]
327
+ if !prov_types_without_devices.include?(prov_type) && !params[:force]
328
+ force = device_count_different?(profile: profile, keychain_path: keychain_path, platform: params[:platform].to_sym)
329
+ else
330
+ # App Store provisioning profiles don't contain device identifiers and
331
+ # thus shouldn't be renewed if the device count has changed.
332
+ UI.important("Warning: `force_for_new_devices` is set but is ignored for App Store & Developer ID provisioning profiles.")
333
+ UI.important("You can safely stop specifying `force_for_new_devices` when running Match for type 'appstore' or 'developer_id'.")
334
+ end
335
+
336
+ return force
337
+ end
338
+
322
339
  def device_count_different?(profile: nil, keychain_path: nil, platform: nil)
323
340
  return false unless profile
324
341
 
@@ -365,5 +382,72 @@ module Match
365
382
  end
366
383
  return false
367
384
  end
385
+
386
+ def should_force_include_all_certificates(params: nil, prov_type: nil, profile: nil, keychain_path: nil)
387
+ unless params[:include_all_certificates]
388
+ if params[:force_for_new_certificates]
389
+ UI.important("You specified 'force_for_new_certificates: true', but new certificates will not be added, cause 'include_all_certificates' is 'false'")
390
+ end
391
+ return false
392
+ end
393
+
394
+ force = false
395
+
396
+ if params[:force_for_new_certificates] && !params[:readonly]
397
+ if prov_type == :development && !params[:force]
398
+ force = certificate_count_different?(profile: profile, keychain_path: keychain_path, platform: params[:platform].to_sym)
399
+ else
400
+ # All other (not development) provisioning profiles don't contain
401
+ # multiple certificates, thus shouldn't be renewed
402
+ # if the certificates count has changed.
403
+ UI.important("Warning: `force_for_new_certificates` is set but is ignored for non-'development' provisioning profiles.")
404
+ UI.important("You can safely stop specifying `force_for_new_certificates` when running Match for '#{prov_type}' provisioning profiles.")
405
+ end
406
+ end
407
+
408
+ return force
409
+ end
410
+
411
+ def certificate_count_different?(profile: nil, keychain_path: nil, platform: nil)
412
+ return false unless profile
413
+
414
+ parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
415
+ uuid = parsed["UUID"]
416
+
417
+ all_profiles = Spaceship::ConnectAPI::Profile.all(includes: "certificates")
418
+ portal_profile = all_profiles.detect { |i| i.uuid == uuid }
419
+
420
+ return false unless portal_profile
421
+
422
+ profile_certs_count = portal_profile.fetch_all_certificates.count
423
+
424
+ certificate_types =
425
+ case platform
426
+ when :ios, :tvos
427
+ [
428
+ Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPMENT,
429
+ Spaceship::ConnectAPI::Certificate::CertificateType::IOS_DEVELOPMENT
430
+ ]
431
+ when :macos, :catalyst
432
+ [
433
+ Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPMENT,
434
+ Spaceship::ConnectAPI::Certificate::CertificateType::MAC_APP_DEVELOPMENT
435
+ ]
436
+ else
437
+ []
438
+ end
439
+
440
+ certificates = Spaceship::ConnectAPI::Certificate.all
441
+ unless certificate_types.empty?
442
+ certificates = certificates.select do |certificate|
443
+ certificate_types.include?(certificate.certificateType) && certificate.valid?
444
+ end
445
+ end
446
+
447
+ portal_certs_count = certificates.size
448
+
449
+ return portal_certs_count != profile_certs_count
450
+ end
368
451
  end
452
+ # rubocop:enable Metrics/ClassLength
369
453
  end
@@ -70,6 +70,7 @@ module Match
70
70
  UI.error("for the user #{username}")
71
71
  UI.error("Make sure to use the same user and team every time you run 'match' for this")
72
72
  UI.error("Git repository. This might be caused by revoking the certificate on the Dev Portal")
73
+ UI.error("If missing certificate is a Developer ID Installer, you may need to auth with Apple ID instead of App Store API Key")
73
74
  UI.user_error!("To reset the certificates of your Apple account, you can use the `fastlane match nuke` feature, more information on https://docs.fastlane.tools/actions/match/")
74
75
  end
75
76
 
@@ -18,7 +18,13 @@ module PEM
18
18
 
19
19
  if existing_certificate
20
20
  remaining_days = (existing_certificate.expires - Time.now) / 60 / 60 / 24
21
- UI.message("Existing push notification profile for '#{existing_certificate.owner_name}' is valid for #{remaining_days.round} more days.")
21
+
22
+ display_platform = ''
23
+ unless PEM.config[:website_push]
24
+ display_platform = "#{PEM.config[:platform]} "
25
+ end
26
+
27
+ UI.message("Existing #{display_platform}push notification profile for '#{existing_certificate.owner_name}' is valid for #{remaining_days.round} more days.")
22
28
  if remaining_days > PEM.config[:active_days_limit]
23
29
  if PEM.config[:force]
24
30
  UI.success("You already have an existing push certificate, but a new one will be created since the --force option has been set.")
@@ -59,7 +65,7 @@ module PEM
59
65
 
60
66
  x509_certificate = cert.download
61
67
 
62
- filename_base = PEM.config[:pem_name] || "#{certificate_type}_#{PEM.config[:app_identifier]}"
68
+ filename_base = PEM.config[:pem_name] || "#{certificate_type}_#{PEM.config[:app_identifier]}_#{PEM.config[:platform]}"
63
69
  filename_base = File.basename(filename_base, ".pem") # strip off the .pem if it was provided.
64
70
 
65
71
  output_path = File.expand_path(PEM.config[:output_path])
@@ -73,7 +79,8 @@ module PEM
73
79
 
74
80
  if PEM.config[:generate_p12]
75
81
  p12_cert_path = File.join(output_path, "#{filename_base}.p12")
76
- p12 = OpenSSL::PKCS12.create(PEM.config[:p12_password], certificate_type, pkey, x509_certificate)
82
+ p12_password = PEM.config[:p12_password] == "" ? nil : PEM.config[:p12_password]
83
+ p12 = OpenSSL::PKCS12.create(p12_password, certificate_type, pkey, x509_certificate)
77
84
  File.write(p12_cert_path, p12.to_der)
78
85
  UI.message("p12 certificate: ".green + Pathname.new(p12_cert_path).realpath.to_s)
79
86
  end
@@ -85,12 +92,29 @@ module PEM
85
92
  end
86
93
 
87
94
  def certificate
88
- if PEM.config[:development]
89
- Spaceship.certificate.development_push
90
- elsif PEM.config[:website_push]
95
+ if PEM.config[:website_push]
91
96
  Spaceship.certificate.website_push
92
97
  else
93
- Spaceship.certificate.production_push
98
+ platform = PEM.config[:platform]
99
+ UI.user_error!('platform parameter is unspecified.') unless platform
100
+
101
+ case platform
102
+ when 'ios'
103
+ if PEM.config[:development]
104
+ Spaceship.certificate.development_push
105
+ else
106
+ Spaceship.certificate.production_push
107
+ end
108
+ when 'macos'
109
+ if PEM.config[:development]
110
+ Spaceship.certificate.mac_development_push
111
+ else
112
+ Spaceship.certificate.mac_production_push
113
+ end
114
+ else
115
+ UI.user_error!("Unsupported platform '#{platform}'. Supported platforms for development and production certificates are 'ios' & 'macos'")
116
+ end
117
+
94
118
  end
95
119
  end
96
120
 
@@ -1,5 +1,6 @@
1
1
  require 'fastlane_core/configuration/config_item'
2
2
  require 'credentials_manager/appfile_config'
3
+ require 'fastlane/helper/lane_helper'
3
4
 
4
5
  require_relative 'module'
5
6
 
@@ -10,6 +11,14 @@ module PEM
10
11
  user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
11
12
 
12
13
  [
14
+ FastlaneCore::ConfigItem.new(key: :platform,
15
+ description: "Set certificate's platform. Used for creation of production & development certificates. Supported platforms: ios, macos",
16
+ short_option: "-m",
17
+ env_name: "PEM_PLATFORM",
18
+ default_value: "ios",
19
+ verify_block: proc do |value|
20
+ UI.user_error!("The platform can only be ios or macos") unless ['ios', 'macos'].include?(value)
21
+ end),
13
22
  FastlaneCore::ConfigItem.new(key: :development,
14
23
  env_name: "PEM_DEVELOPMENT",
15
24
  description: "Renew the development push certificate instead of the production one",
@@ -86,7 +95,7 @@ module PEM
86
95
  env_name: "PEM_P12_PASSWORD",
87
96
  sensitive: true,
88
97
  description: "The password that is used for your p12 file",
89
- default_value: ""),
98
+ optional: true),
90
99
  FastlaneCore::ConfigItem.new(key: :pem_name,
91
100
  short_option: "-o",
92
101
  env_name: "PEM_FILE_NAME",