fastlane 2.232.2 → 2.233.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +98 -98
  3. data/credentials_manager/lib/credentials_manager/appfile_config.rb +4 -0
  4. data/deliver/lib/deliver/detect_values.rb +2 -0
  5. data/deliver/lib/deliver/options.rb +23 -0
  6. data/deliver/lib/deliver/runner.rb +17 -12
  7. data/deliver/lib/deliver/sync_app_previews.rb +204 -0
  8. data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +5 -1
  9. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +20 -4
  10. data/fastlane/lib/fastlane/actions/docs/upload_to_testflight.md +3 -0
  11. data/fastlane/lib/fastlane/actions/resign.rb +13 -2
  12. data/fastlane/lib/fastlane/actions/swiftlint.rb +8 -1
  13. data/fastlane/lib/fastlane/actions/upload_to_app_store.rb +1 -1
  14. data/fastlane/lib/fastlane/helper/s3_client_helper.rb +5 -2
  15. data/fastlane/lib/fastlane/version.rb +1 -1
  16. data/fastlane/swift/Deliverfile.swift +1 -1
  17. data/fastlane/swift/DeliverfileProtocol.swift +29 -1
  18. data/fastlane/swift/Fastlane.swift +105 -9
  19. data/fastlane/swift/Gymfile.swift +1 -1
  20. data/fastlane/swift/GymfileProtocol.swift +8 -1
  21. data/fastlane/swift/Matchfile.swift +1 -1
  22. data/fastlane/swift/MatchfileProtocol.swift +8 -1
  23. data/fastlane/swift/Precheckfile.swift +1 -1
  24. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  25. data/fastlane/swift/Scanfile.swift +1 -1
  26. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  27. data/fastlane/swift/Screengrabfile.swift +1 -1
  28. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  29. data/fastlane/swift/Snapshotfile.swift +1 -1
  30. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  31. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +48 -17
  32. data/fastlane_core/lib/fastlane_core/video_utils.rb +202 -0
  33. data/frameit/lib/frameit/device_types.rb +2 -2
  34. data/gym/lib/gym/generators/build_command_generator.rb +2 -1
  35. data/gym/lib/gym/options.rb +5 -0
  36. data/match/lib/match/generator.rb +3 -1
  37. data/match/lib/match/options.rb +5 -0
  38. data/match/lib/match/runner.rb +12 -7
  39. data/match/lib/match/storage/s3_storage.rb +4 -1
  40. data/match/lib/match/storage.rb +1 -0
  41. data/pilot/lib/pilot/build_manager.rb +4 -12
  42. data/pilot/lib/pilot/options.rb +4 -0
  43. data/precheck/lib/precheck/rules/rules_data/curse_word_hashes/README.md +54 -0
  44. data/precheck/lib/precheck/rules/rules_data/curse_word_hashes/en_us.txt +2 -1
  45. data/scan/lib/scan/detect_values.rb +11 -3
  46. data/sigh/lib/assets/resign.sh +17 -5
  47. data/sigh/lib/sigh/commands_generator.rb +1 -0
  48. data/sigh/lib/sigh/manager.rb +6 -6
  49. data/sigh/lib/sigh/resign.rb +9 -6
  50. data/spaceship/lib/spaceship/connect_api/models/app_preview_set.rb +54 -17
  51. data/spaceship/lib/spaceship/connect_api/models/app_store_version_localization.rb +1 -2
  52. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +2 -2
  53. data/trainer/lib/trainer/legacy_xcresult.rb +27 -20
  54. data/trainer/lib/trainer/xcresult/test_suite.rb +4 -1
  55. metadata +25 -22
@@ -17,4 +17,4 @@ public class Gymfile: GymfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.232.2
20
+ // Generated with fastlane 2.233.0
@@ -20,6 +20,9 @@ public protocol GymfileProtocol: AnyObject {
20
20
  /// The name of the resulting ipa file
21
21
  var outputName: String? { get }
22
22
 
23
+ /// App name to use in logfile name
24
+ var appName: String? { get }
25
+
23
26
  /// The configuration to use when building the app. Defaults to 'Release'
24
27
  var configuration: String? { get }
25
28
 
@@ -190,6 +193,10 @@ public extension GymfileProtocol {
190
193
  return nil
191
194
  }
192
195
 
196
+ var appName: String? {
197
+ return nil
198
+ }
199
+
193
200
  var configuration: String? {
194
201
  return nil
195
202
  }
@@ -385,4 +392,4 @@ public extension GymfileProtocol {
385
392
 
386
393
  // Please don't remove the lines below
387
394
  // They are used to detect outdated files
388
- // FastlaneRunnerAPIVersion [0.9.147]
395
+ // FastlaneRunnerAPIVersion [0.9.148]
@@ -17,4 +17,4 @@ public class Matchfile: MatchfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.232.2
20
+ // Generated with fastlane 2.233.0
@@ -86,6 +86,9 @@ public protocol MatchfileProtocol: AnyObject {
86
86
  /// S3 secret access key
87
87
  var s3SecretAccessKey: String? { get }
88
88
 
89
+ /// S3 session token
90
+ var s3SessionToken: String? { get }
91
+
89
92
  /// Name of the S3 bucket
90
93
  var s3Bucket: String? { get }
91
94
 
@@ -284,6 +287,10 @@ public extension MatchfileProtocol {
284
287
  return nil
285
288
  }
286
289
 
290
+ var s3SessionToken: String? {
291
+ return nil
292
+ }
293
+
287
294
  var s3Bucket: String? {
288
295
  return nil
289
296
  }
@@ -399,4 +406,4 @@ public extension MatchfileProtocol {
399
406
 
400
407
  // Please don't remove the lines below
401
408
  // They are used to detect outdated files
402
- // FastlaneRunnerAPIVersion [0.9.141]
409
+ // FastlaneRunnerAPIVersion [0.9.142]
@@ -17,4 +17,4 @@ public class Precheckfile: PrecheckfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.232.2
20
+ // Generated with fastlane 2.233.0
@@ -84,4 +84,4 @@ public extension PrecheckfileProtocol {
84
84
 
85
85
  // Please don't remove the lines below
86
86
  // They are used to detect outdated files
87
- // FastlaneRunnerAPIVersion [0.9.140]
87
+ // FastlaneRunnerAPIVersion [0.9.141]
@@ -17,4 +17,4 @@ public class Scanfile: ScanfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.232.2
20
+ // Generated with fastlane 2.233.0
@@ -574,4 +574,4 @@ public extension ScanfileProtocol {
574
574
 
575
575
  // Please don't remove the lines below
576
576
  // They are used to detect outdated files
577
- // FastlaneRunnerAPIVersion [0.9.152]
577
+ // FastlaneRunnerAPIVersion [0.9.153]
@@ -17,4 +17,4 @@ public class Screengrabfile: ScreengrabfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.232.2
20
+ // Generated with fastlane 2.233.0
@@ -161,4 +161,4 @@ public extension ScreengrabfileProtocol {
161
161
 
162
162
  // Please don't remove the lines below
163
163
  // They are used to detect outdated files
164
- // FastlaneRunnerAPIVersion [0.9.142]
164
+ // FastlaneRunnerAPIVersion [0.9.143]
@@ -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.232.2
20
+ // Generated with fastlane 2.233.0
@@ -371,4 +371,4 @@ public extension SnapshotfileProtocol {
371
371
 
372
372
  // Please don't remove the lines below
373
373
  // They are used to detect outdated files
374
- // FastlaneRunnerAPIVersion [0.9.136]
374
+ // FastlaneRunnerAPIVersion [0.9.137]
@@ -43,7 +43,7 @@ module FastlaneCore
43
43
  not_implemented(__method__)
44
44
  end
45
45
 
46
- def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
46
+ def build_upload_command(username, password, source = "/tmp", options = {})
47
47
  not_implemented(__method__)
48
48
  end
49
49
 
@@ -308,11 +308,23 @@ module FastlaneCore
308
308
  if !username.nil? && !password.nil? && api_key.nil?
309
309
  "-u #{username.shellescape} -p #{password.shellescape}"
310
310
  elsif !api_key.nil?
311
- "--apiKey #{api_key[:key_id]} --apiIssuer #{api_key[:issuer_id]}"
311
+ # Individual API keys have no issuer_id; altool requires --apiIssuer
312
+ # to be present, so we pass the key_id as a placeholder and add
313
+ # --api-key-subject user to signal individual key JWT generation.
314
+ if api_key[:issuer_id]
315
+ "--apiKey #{api_key[:key_id]} --apiIssuer #{api_key[:issuer_id]}"
316
+ else
317
+ "--apiKey #{api_key[:key_id]} --apiIssuer #{api_key[:key_id]} --api-key-subject user"
318
+ end
312
319
  end
313
320
  end
314
321
 
315
- def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
322
+ def build_upload_command(username, password, source = "/tmp", options = {})
323
+ provider_short_name = options.fetch(:provider_short_name, "")
324
+ provider_public_id = options.fetch(:provider_public_id, "")
325
+ jwt = options[:jwt]
326
+ platform = options[:platform]
327
+ api_key = options[:api_key]
316
328
  use_api_key = !api_key.nil?
317
329
  [
318
330
  ("API_PRIVATE_KEYS_DIR=#{api_key[:key_dir]}" if use_api_key),
@@ -320,6 +332,7 @@ module FastlaneCore
320
332
  "--upload-app",
321
333
  build_credential_params(username, password, jwt, api_key),
322
334
  ("--asc-provider #{provider_short_name}" unless use_api_key || provider_short_name.to_s.empty?),
335
+ ("--provider-public-id #{provider_public_id}" unless use_api_key || provider_public_id.to_s.empty?),
323
336
  platform_option(platform),
324
337
  file_upload_option(source),
325
338
  additional_upload_parameters,
@@ -342,9 +355,11 @@ module FastlaneCore
342
355
  raise "This feature has not been implemented yet with altool for Xcode 14"
343
356
  end
344
357
 
345
- def build_verify_command(username, password, source = "/tmp", provider_short_name = "", **kwargs)
346
- api_key = kwargs[:api_key]
347
- platform = kwargs[:platform]
358
+ def build_verify_command(username, password, source = "/tmp", options = {})
359
+ provider_short_name = options.fetch(:provider_short_name, "")
360
+ provider_public_id = options.fetch(:provider_public_id, "")
361
+ api_key = options[:api_key]
362
+ platform = options[:platform]
348
363
  use_api_key = !api_key.nil?
349
364
  [
350
365
  ("API_PRIVATE_KEYS_DIR=#{api_key[:key_dir]}" if use_api_key),
@@ -352,6 +367,7 @@ module FastlaneCore
352
367
  "--validate-app",
353
368
  build_credential_params(username, password, nil, api_key),
354
369
  ("--asc-provider #{provider_short_name}" unless use_api_key || provider_short_name.to_s.empty?),
370
+ ("--provider-public-id #{provider_public_id}" unless use_api_key || provider_public_id.to_s.empty?),
355
371
  platform_option(platform),
356
372
  file_upload_option(source)
357
373
  ].compact.join(' ')
@@ -424,7 +440,10 @@ module FastlaneCore
424
440
  end
425
441
  end
426
442
 
427
- def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
443
+ def build_upload_command(username, password, source = "/tmp", options = {})
444
+ provider_short_name = options.fetch(:provider_short_name, "")
445
+ jwt = options[:jwt]
446
+ api_key = options[:api_key]
428
447
  [
429
448
  '"' + Helper.transporter_path + '"',
430
449
  "-m upload",
@@ -456,8 +475,9 @@ module FastlaneCore
456
475
  ].compact.join(' ')
457
476
  end
458
477
 
459
- def build_verify_command(username, password, source = "/tmp", provider_short_name = "", **kwargs)
460
- jwt = kwargs[:jwt]
478
+ def build_verify_command(username, password, source = "/tmp", options = {})
479
+ provider_short_name = options.fetch(:provider_short_name, "")
480
+ jwt = options[:jwt]
461
481
  [
462
482
  '"' + Helper.transporter_path + '"',
463
483
  '-m verify',
@@ -546,7 +566,10 @@ module FastlaneCore
546
566
  end
547
567
  end
548
568
 
549
- def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
569
+ def build_upload_command(username, password, source = "/tmp", options = {})
570
+ provider_short_name = options.fetch(:provider_short_name, "")
571
+ jwt = options[:jwt]
572
+ api_key = options[:api_key]
550
573
  credential_params = build_credential_params(username, password, jwt, api_key, is_default_itms_on_xcode_11?)
551
574
  if is_default_itms_on_xcode_11?
552
575
  [
@@ -582,8 +605,9 @@ module FastlaneCore
582
605
  end
583
606
  end
584
607
 
585
- def build_verify_command(username, password, source = "/tmp", provider_short_name = "", **kwargs)
586
- jwt = kwargs[:jwt]
608
+ def build_verify_command(username, password, source = "/tmp", options = {})
609
+ provider_short_name = options.fetch(:provider_short_name, "")
610
+ jwt = options[:jwt]
587
611
  credential_params = build_credential_params(username, password, jwt, nil, is_default_itms_on_xcode_11?)
588
612
  if is_default_itms_on_xcode_11?
589
613
  [
@@ -731,7 +755,8 @@ module FastlaneCore
731
755
  # see: https://github.com/fastlane/fastlane/issues/1524#issuecomment-196370628
732
756
  # for more information about how to use the iTMSTransporter to list your provider
733
757
  # short names
734
- def initialize(user = nil, password = nil, use_shell_script = false, provider_short_name = nil, jwt = nil, altool_compatible_command: false, api_key: nil)
758
+ # @param provider_public_id The provider public ID to be given to altool via --provider-public-id.
759
+ def initialize(user = nil, password = nil, use_shell_script = false, provider_short_name = nil, jwt = nil, altool_compatible_command: false, api_key: nil, provider_public_id: nil)
735
760
  # Xcode 6.x doesn't have the same iTMSTransporter Java setup as later Xcode versions, so
736
761
  # we can't default to using the newer direct Java invocation strategy for those versions.
737
762
  use_shell_script ||= Helper.is_mac? && Helper.xcode_version.start_with?('6.')
@@ -749,9 +774,11 @@ module FastlaneCore
749
774
  if should_use_altool?(altool_compatible_command, use_shell_script)
750
775
  UI.verbose("Using altool as transporter.")
751
776
  @transporter_executor = AltoolTransporterExecutor.new
777
+ @provider_public_id = provider_public_id
752
778
  else
753
779
  UI.verbose("Using iTMSTransporter as transporter.")
754
780
  @transporter_executor = use_shell_script ? ShellScriptTransporterExecutor.new : JavaTransporterExecutor.new
781
+ @provider_public_id = nil
755
782
  end
756
783
 
757
784
  @provider_short_name = provider_short_name
@@ -837,8 +864,10 @@ module FastlaneCore
837
864
  api_key_placeholder = use_api_key ? { key_id: "YourKeyID", issuer_id: "YourIssuerID", key_dir: "YourTmpP8KeyDir" } : nil
838
865
  api_key = @transporter_executor.prepare(original_api_key: @api_key)
839
866
 
840
- command = @transporter_executor.build_upload_command(@user, @password, actual_dir, @provider_short_name, @jwt, platform, api_key)
841
- UI.verbose(@transporter_executor.build_upload_command(@user, password_placeholder, actual_dir, @provider_short_name, jwt_placeholder, platform, api_key_placeholder))
867
+ upload_options = { provider_short_name: @provider_short_name, provider_public_id: @provider_public_id, jwt: @jwt, platform: platform, api_key: api_key }
868
+ upload_options_placeholder = { provider_short_name: @provider_short_name, provider_public_id: @provider_public_id, jwt: jwt_placeholder, platform: platform, api_key: api_key_placeholder }
869
+ command = @transporter_executor.build_upload_command(@user, @password, actual_dir, upload_options)
870
+ UI.verbose(@transporter_executor.build_upload_command(@user, password_placeholder, actual_dir, upload_options_placeholder))
842
871
 
843
872
  begin
844
873
  result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
@@ -898,8 +927,10 @@ module FastlaneCore
898
927
  api_key_placeholder = use_api_key ? { key_id: "YourKeyID", issuer_id: "YourIssuerID", key_dir: "YourTmpP8KeyDir" } : nil
899
928
  api_key = @transporter_executor.prepare(original_api_key: @api_key)
900
929
 
901
- command = @transporter_executor.build_verify_command(@user, @password, actual_dir, @provider_short_name, jwt: @jwt, platform: platform, api_key: api_key)
902
- UI.verbose(@transporter_executor.build_verify_command(@user, password_placeholder, actual_dir, @provider_short_name, jwt: jwt_placeholder, platform: platform, api_key: api_key_placeholder))
930
+ verify_options = { provider_short_name: @provider_short_name, provider_public_id: @provider_public_id, jwt: @jwt, platform: platform, api_key: api_key }
931
+ verify_options_placeholder = { provider_short_name: @provider_short_name, provider_public_id: @provider_public_id, jwt: jwt_placeholder, platform: platform, api_key: api_key_placeholder }
932
+ command = @transporter_executor.build_verify_command(@user, @password, actual_dir, verify_options)
933
+ UI.verbose(@transporter_executor.build_verify_command(@user, password_placeholder, actual_dir, verify_options_placeholder))
903
934
 
904
935
  begin
905
936
  result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
@@ -0,0 +1,202 @@
1
+ require "fastlane_core/ui/ui"
2
+
3
+ # Reference for the MP4/QuickTime container (video atoms/boxes structure): ISO/IEC 14496-12:2022 (base media file format) - defines atoms like moov/trak/tkhd
4
+ # - https://observablehq.com/@benjamintoofer/iso-base-media-file-format
5
+ # - https://developer.apple.com/documentation/quicktime-file-format
6
+ module FastlaneCore
7
+ module VideoUtils
8
+ # Pure Ruby MP4/QuickTime MOV container header parser, reading tkhd to get width/height.
9
+ # Note: This reads container atoms/boxes only; it does NOT parse the video codec bitstream.
10
+ # Works regardless of codec as long as standard container headers exist.
11
+ # Input: path (String) to a local MP4/MOV file.
12
+ # Output: [width, height] (Array<Integer>) normalized to portrait (w <= h), or nil on failure.
13
+ def self.read_video_resolution(path)
14
+ File.open(path, "rb") do |f|
15
+ file_size = f.size
16
+ while f.pos < file_size
17
+ size, type = read_atom_header(f)
18
+ break unless size && type
19
+ return nil if size < 8
20
+ content = size - 8
21
+ if type == "moov"
22
+ moov_end = f.pos + content
23
+ resolution = extract_resolution_from_moov(f, moov_end)
24
+ return resolution if resolution
25
+ else
26
+ skip(f, content)
27
+ end
28
+ end
29
+ end
30
+ nil
31
+ rescue => e
32
+ FastlaneCore::UI.verbose("Failed to parse resolution for '#{path}': #{e.class} - #{e.message}")
33
+ nil
34
+ end
35
+
36
+ # Pure Ruby MP4/QuickTime MOV container header parser reading mvhd to get duration in seconds.
37
+ # Note: Uses movie header (mvhd) time scale/duration, not per-track edit lists; adequate for gating.
38
+ # Input: path (String) to a local MP4/MOV file.
39
+ # Output: duration in seconds (Float), or nil on failure.
40
+ def self.read_video_duration_seconds(path)
41
+ File.open(path, "rb") do |f|
42
+ file_size = f.size
43
+ while f.pos < file_size
44
+ size, type = read_atom_header(f)
45
+ break unless size && type
46
+ return nil if size < 8
47
+ content = size - 8
48
+ if type == "moov"
49
+ moov_end = f.pos + content
50
+ duration = extract_duration_from_moov(f, moov_end)
51
+ return duration if duration
52
+ else
53
+ skip(f, content)
54
+ end
55
+ end
56
+ end
57
+ nil
58
+ rescue => e
59
+ FastlaneCore::UI.verbose("Failed to parse duration for '#{path}': #{e.class} - #{e.message}")
60
+ nil
61
+ end
62
+
63
+ # Reads the moov box and returns [w, h] if found in any trak/tkhd, else nil.
64
+ # Input: io (IO) positioned at moov contents; moov_end (Integer) absolute end offset.
65
+ # Output: [width, height] or nil.
66
+ def self.extract_resolution_from_moov(io, moov_end)
67
+ while io.pos < moov_end
68
+ atom_size, atom_type = read_atom_header(io)
69
+ break unless atom_size && atom_type
70
+ return nil if atom_size < 8
71
+ if atom_type == "trak"
72
+ trak_end = io.pos + (atom_size - 8)
73
+ resolution = extract_resolution_from_trak(io, trak_end)
74
+ return resolution if resolution
75
+ io.seek(trak_end, IO::SEEK_SET)
76
+ else
77
+ skip(io, atom_size - 8)
78
+ end
79
+ end
80
+ nil
81
+ end
82
+
83
+ # Reads the moov box and returns duration in seconds if mvhd is found, else nil.
84
+ # Input: io (IO) positioned at moov contents; moov_end (Integer) absolute end offset.
85
+ # Output: Float seconds or nil.
86
+ def self.extract_duration_from_moov(io, moov_end)
87
+ while io.pos < moov_end
88
+ atom_size, atom_type = read_atom_header(io)
89
+ break unless atom_size && atom_type
90
+ return nil if atom_size < 8
91
+ if atom_type == "mvhd"
92
+ return parse_mvhd(io)
93
+ else
94
+ skip(io, atom_size - 8)
95
+ end
96
+ end
97
+ nil
98
+ end
99
+
100
+ # Reads a single trak box to locate tkhd and extract resolution.
101
+ # Input: io (IO) positioned at trak contents; trak_end (Integer) absolute end offset.
102
+ # Output: [width, height] or nil.
103
+ def self.extract_resolution_from_trak(io, trak_end)
104
+ while io.pos < trak_end
105
+ trak_size, trak_type = read_atom_header(io)
106
+ break unless trak_size && trak_type
107
+ return nil if trak_size < 8
108
+ if trak_type == "tkhd"
109
+ return parse_tkhd(io)
110
+ else
111
+ skip(io, trak_size - 8)
112
+ end
113
+ end
114
+ nil
115
+ end
116
+
117
+ # Parses mvhd box fields to read duration.
118
+ # Input: io (IO) positioned at the start of mvhd payload after its header.
119
+ # Output: Float seconds or nil.
120
+ def self.parse_mvhd(io)
121
+ version_flags = io.read(4)
122
+ return nil unless version_flags && version_flags.length == 4
123
+ version = version_flags.getbyte(0)
124
+ if version == 1
125
+ skip(io, 8) # creation_time (u64)
126
+ skip(io, 8) # modification_time (u64)
127
+ timescale = read_u32(io)
128
+ duration = read_u64(io) # u64
129
+ else
130
+ skip(io, 4) # creation_time (u32)
131
+ skip(io, 4) # modification_time (u32)
132
+ timescale = read_u32(io)
133
+ duration = read_u32(io) # u32
134
+ end
135
+ return nil unless timescale && timescale > 0
136
+ return nil unless duration
137
+ duration.to_f / timescale
138
+ end
139
+
140
+ # Parses tkhd box fields to read fixed-point width and height (16.16).
141
+ # Input: io (IO) positioned at the start of tkhd payload after its header.
142
+ # Output: [width, height] or nil.
143
+ def self.parse_tkhd(io)
144
+ video_flags = io.read(4)
145
+ return nil unless video_flags && video_flags.length == 4
146
+ version = video_flags.getbyte(0)
147
+ if version == 1
148
+ skip(io, 8 * 2) # creation, modification (u64)
149
+ skip(io, 4) # track id (u32)
150
+ skip(io, 4) # reserved
151
+ skip(io, 8) # duration (u64)
152
+ else
153
+ skip(io, 4 * 3) # creation, modification, track id (u32)
154
+ skip(io, 4) # reserved
155
+ skip(io, 4) # duration (u32)
156
+ end
157
+ skip(io, 8) # reserved
158
+ skip(io, 2) # layer (u16)
159
+ skip(io, 2) # alt group (u16)
160
+ skip(io, 2) # volume (u16)
161
+ skip(io, 2) # reserved
162
+ skip(io, 36) # matrix
163
+ # width/height (fixed point 16.16)
164
+ width_fixed = read_u32(io)
165
+ height_fixed = read_u32(io)
166
+ return nil unless width_fixed && height_fixed
167
+ width = (width_fixed >> 16)
168
+ height = (height_fixed >> 16)
169
+ [width, height]
170
+ end
171
+
172
+ # Read a big-endian 32-bit unsigned integer; returns Integer or nil if insufficient data.
173
+ def self.read_u32(io)
174
+ bytes = io.read(4)
175
+ return nil unless bytes && bytes.length == 4
176
+ bytes.unpack1("N") # 32-bit unsigned, network (big-endian) byte order
177
+ end
178
+
179
+ # Read a big-endian 64-bit unsigned integer; returns Integer or nil if insufficient data.
180
+ def self.read_u64(io)
181
+ bytes = io.read(8)
182
+ return nil unless bytes && bytes.length == 8
183
+ bytes.unpack1("Q>") # 64-bit unsigned, big-endian
184
+ end
185
+
186
+ # Read an atom/box header: returns [size(Integer), type(String)] or nil.
187
+ def self.read_atom_header(io)
188
+ size = read_u32(io)
189
+ return nil unless size
190
+ type = io.read(4)
191
+ return nil unless type && type.length == 4
192
+ [size, type]
193
+ end
194
+
195
+ # Advance the IO cursor by n bytes.
196
+ def self.skip(io, n)
197
+ io.seek(n, IO::SEEK_CUR)
198
+ end
199
+
200
+ private_class_method :extract_resolution_from_moov, :extract_duration_from_moov, :extract_resolution_from_trak, :parse_tkhd, :parse_mvhd, :read_u32, :read_u64, :read_atom_header, :skip
201
+ end
202
+ end
@@ -173,8 +173,8 @@ module Frameit
173
173
  IPHONE_13_MINI ||= Device.new("iphone-13-mini", "Apple iPhone 13 Mini", 11, [[1080, 2340], [2340, 1080]], 476, Color::MIDNIGHT, Platform::IOS)
174
174
  IPHONE_14 ||= Device.new("iphone-14", "Apple iPhone 14", 12, [[1170, 2532], [2532, 1170]], 460, Color::MIDNIGHT, Platform::IOS)
175
175
  IPHONE_14_PLUS ||= Device.new("iphone-14-plus", "Apple iPhone 14 Plus", 12, [[1284, 2778], [2778, 1284]], 458, Color::MIDNIGHT, Platform::IOS)
176
- IPHONE_14_PRO ||= Device.new("iphone-14-pro", "Apple iPhone 14 Pro", 12, [[1179, 2556], [2556, 1179]], 460, Color::PURPLE, Platform::IOS)
177
- IPHONE_14_PRO_MAX ||= Device.new("iphone14-pro-max", "Apple iPhone 14 Pro Max", 12, [[1290, 2796], [2796, 1290]], 458, Color::PURPLE, Platform::IOS)
176
+ IPHONE_14_PRO ||= Device.new("iphone-14-pro", "Apple iPhone 14 Pro", 12, [[1179, 2556], [2556, 1179]], 460, Color::BLACK, Platform::IOS)
177
+ IPHONE_14_PRO_MAX ||= Device.new("iphone14-pro-max", "Apple iPhone 14 Pro Max", 12, [[1290, 2796], [2796, 1290]], 458, Color::BLACK, Platform::IOS)
178
178
  IPAD_10_2 ||= Device.new("ipad-10-2", "Apple iPad 10.2", 1, [[1620, 2160], [2160, 1620]], 264, Color::SPACE_GRAY, Platform::IOS)
179
179
  IPAD_AIR_2 ||= Device.new("ipad-air-2", "Apple iPad Air 2", 1, [[1536, 2048], [2048, 1536]], 264, Color::SPACE_GRAY, Platform::IOS, DEVICE_SCREEN_IDS[DisplayType::APP_IPAD_97])
180
180
  IPAD_AIR_2019 ||= Device.new("ipad-air-2019", "Apple iPad Air (2019)", 2, [[1668, 2224], [2224, 1668]], 265, Color::SPACE_GRAY, Platform::IOS)
@@ -152,7 +152,8 @@ module Gym
152
152
  end
153
153
 
154
154
  def xcodebuild_log_path
155
- file_name = "#{Gym.project.app_name}-#{Gym.config[:scheme]}.log"
155
+ app_name = Gym.config[:app_name] || Gym.project.app_name
156
+ file_name = "#{app_name}-#{Gym.config[:scheme]}.log"
156
157
  containing = File.expand_path(Gym.config[:buildlog_path])
157
158
  FileUtils.mkdir_p(containing)
158
159
 
@@ -65,6 +65,11 @@ module Gym
65
65
  env_name: "GYM_OUTPUT_NAME",
66
66
  description: "The name of the resulting ipa file",
67
67
  optional: true),
68
+ FastlaneCore::ConfigItem.new(key: :app_name,
69
+ env_name: "GYM_APP_NAME",
70
+ optional: true,
71
+ description: "App name to use in logfile name",
72
+ type: String),
68
73
  FastlaneCore::ConfigItem.new(key: :configuration,
69
74
  short_option: "-q",
70
75
  env_name: "GYM_CONFIGURATION",
@@ -115,7 +115,9 @@ module Match
115
115
  arguments = FastlaneCore::Configuration.create(Sigh::Options.available_options, values)
116
116
 
117
117
  Sigh.config = arguments
118
- path = Sigh::Manager.start
118
+
119
+ keychain_path = FastlaneCore::Helper.keychain_path(params[:keychain_name]) if Helper.mac? && params[:keychain_name]
120
+ path = Sigh::Manager.start(keychain_path: keychain_path)
119
121
  return path
120
122
  end
121
123
 
@@ -215,6 +215,11 @@ module Match
215
215
  description: "S3 secret access key",
216
216
  sensitive: true,
217
217
  optional: true),
218
+ FastlaneCore::ConfigItem.new(key: :s3_session_token,
219
+ env_name: "MATCH_S3_SESSION_TOKEN",
220
+ description: "S3 session token",
221
+ sensitive: true,
222
+ optional: true),
218
223
  FastlaneCore::ConfigItem.new(key: :s3_bucket,
219
224
  env_name: "MATCH_S3_BUCKET",
220
225
  description: "Name of the S3 bucket",
@@ -160,6 +160,7 @@ module Match
160
160
 
161
161
  def fetch_certificate(params: nil, renew_expired_certs: false, specific_cert_type: nil)
162
162
  cert_type = Match.cert_type_sym(specific_cert_type || params[:type])
163
+ certificate_id = specific_cert_type.nil? ? params[:certificate_id] : nil
163
164
 
164
165
  certs = Dir[File.join(prefixed_working_directory, "certs", cert_type.to_s, "*.cer")]
165
166
  keys = Dir[File.join(prefixed_working_directory, "certs", cert_type.to_s, "*.p12")]
@@ -174,7 +175,7 @@ module Match
174
175
 
175
176
  # Validate existing certificate first.
176
177
  if renew_expired_certs && is_cert_renewable && storage_has_certs && !params[:readonly]
177
- cert_path = select_cert_or_key(paths: certs)
178
+ cert_path = select_cert_or_key(paths: certs, certificate_id: certificate_id)
178
179
 
179
180
  unless Utils.is_cert_valid?(cert_path)
180
181
  UI.important("Removing invalid certificate '#{File.basename(cert_path)}'")
@@ -209,7 +210,7 @@ module Match
209
210
  # Reset certificates cache since we have a new cert.
210
211
  self.cache.reset_certificates
211
212
  else
212
- cert_path = select_cert_or_key(paths: certs)
213
+ cert_path = select_cert_or_key(paths: certs, certificate_id: certificate_id)
213
214
 
214
215
  # Check validity of certificate
215
216
  if Utils.is_cert_valid?(cert_path)
@@ -233,7 +234,7 @@ module Match
233
234
  # Import the private key
234
235
  # there seems to be no good way to check if it's already installed - so just install it
235
236
  # Key will only be added to the partition list if it isn't already installed
236
- Utils.import(select_cert_or_key(paths: keys), params[:keychain_name], password: params[:keychain_password])
237
+ Utils.import(select_cert_or_key(paths: keys, certificate_id: certificate_id), params[:keychain_name], password: params[:keychain_password])
237
238
  end
238
239
  else
239
240
  UI.message("Skipping installation of certificate as it would not work on this operating system.")
@@ -241,7 +242,7 @@ module Match
241
242
 
242
243
  if params[:output_path]
243
244
  FileUtils.cp(cert_path, params[:output_path])
244
- FileUtils.cp(select_cert_or_key(paths: keys), params[:output_path])
245
+ FileUtils.cp(select_cert_or_key(paths: keys, certificate_id: certificate_id), params[:output_path])
245
246
  end
246
247
 
247
248
  # Get and print info of certificate
@@ -253,9 +254,13 @@ module Match
253
254
  end
254
255
 
255
256
  # @return [String] Path to certificate or P12 key
256
- def select_cert_or_key(paths:)
257
- cert_id_path = ENV['MATCH_CERTIFICATE_ID'] ? paths.find { |path| path.include?(ENV['MATCH_CERTIFICATE_ID']) } : nil
258
- cert_id_path || paths.last
257
+ def select_cert_or_key(paths:, certificate_id: nil)
258
+ return paths.last if certificate_id.to_s.strip.empty?
259
+
260
+ cert_id_path = paths.find { |path| File.basename(path, File.extname(path)) == certificate_id }
261
+ return cert_id_path if cert_id_path
262
+
263
+ UI.user_error!("No certificate or key found for certificate_id '#{certificate_id}'")
259
264
  end
260
265
 
261
266
  # rubocop:disable Metrics/PerceivedComplexity
@@ -26,6 +26,7 @@ module Match
26
26
  s3_region = params[:s3_region]
27
27
  s3_access_key = params[:s3_access_key]
28
28
  s3_secret_access_key = params[:s3_secret_access_key]
29
+ s3_session_token = params[:s3_session_token]
29
30
  s3_bucket = params[:s3_bucket]
30
31
  s3_object_prefix = params[:s3_object_prefix]
31
32
 
@@ -40,6 +41,7 @@ module Match
40
41
  s3_region: s3_region,
41
42
  s3_access_key: s3_access_key,
42
43
  s3_secret_access_key: s3_secret_access_key,
44
+ s3_session_token: s3_session_token,
43
45
  s3_bucket: s3_bucket,
44
46
  s3_object_prefix: s3_object_prefix,
45
47
  readonly: params[:readonly],
@@ -54,6 +56,7 @@ module Match
54
56
  def initialize(s3_region: nil,
55
57
  s3_access_key: nil,
56
58
  s3_secret_access_key: nil,
59
+ s3_session_token: nil,
57
60
  s3_bucket: nil,
58
61
  s3_object_prefix: nil,
59
62
  readonly: nil,
@@ -64,7 +67,7 @@ module Match
64
67
  api_key: nil)
65
68
  @s3_bucket = s3_bucket
66
69
  @s3_region = s3_region
67
- @s3_client = Fastlane::Helper::S3ClientHelper.new(access_key: s3_access_key, secret_access_key: s3_secret_access_key, region: s3_region)
70
+ @s3_client = Fastlane::Helper::S3ClientHelper.new(access_key: s3_access_key, secret_access_key: s3_secret_access_key, session_token: s3_session_token, region: s3_region)
68
71
  @s3_object_prefix = s3_object_prefix.to_s
69
72
  @readonly = readonly
70
73
  @username = username
@@ -46,6 +46,7 @@ module Match
46
46
  s3_region: params[:s3_region],
47
47
  s3_access_key: params[:s3_access_key],
48
48
  s3_secret_access_key: params[:s3_secret_access_key],
49
+ s3_session_token: params[:s3_session_token],
49
50
  s3_bucket: params[:s3_bucket],
50
51
  s3_object_prefix: params[:s3_object_prefix],
51
52
  readonly: params[:readonly],