fastlane 2.241.0 → 2.241.2

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -37
  3. data/deliver/lib/deliver/app_screenshot.rb +8 -0
  4. data/fastlane/lib/fastlane/commands_generator.rb +7 -1
  5. data/fastlane/lib/fastlane/server/socket_server.rb +57 -7
  6. data/fastlane/lib/fastlane/swift_lane_manager.rb +32 -11
  7. data/fastlane/lib/fastlane/version.rb +1 -1
  8. data/fastlane/swift/ArgumentProcessor.swift +10 -2
  9. data/fastlane/swift/Deliverfile.swift +1 -1
  10. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  11. data/fastlane/swift/Fastlane.swift +2 -18
  12. data/fastlane/swift/Gymfile.swift +1 -1
  13. data/fastlane/swift/GymfileProtocol.swift +1 -1
  14. data/fastlane/swift/MainProcess.swift +27 -0
  15. data/fastlane/swift/Matchfile.swift +1 -1
  16. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  17. data/fastlane/swift/Precheckfile.swift +1 -1
  18. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  19. data/fastlane/swift/RubyCommand.swift +1 -9
  20. data/fastlane/swift/RubyCommandable.swift +3 -2
  21. data/fastlane/swift/Scanfile.swift +1 -1
  22. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  23. data/fastlane/swift/Screengrabfile.swift +1 -1
  24. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  25. data/fastlane/swift/Snapshotfile.swift +1 -1
  26. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  27. data/fastlane/swift/main.swift +1 -1
  28. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +19 -5
  29. data/match/lib/match/runner.rb +2 -0
  30. data/match/lib/match/storage/gitlab/secure_file.rb +2 -1
  31. data/match/lib/match/storage/google_cloud_storage.rb +1 -1
  32. data/match/lib/match/storage/interface.rb +10 -0
  33. data/match/lib/match/storage/s3_storage.rb +1 -1
  34. data/produce/lib/produce/developer_center.rb +2 -49
  35. data/produce/lib/produce/module.rb +0 -21
  36. data/produce/lib/produce/options.rb +0 -17
  37. data/produce/lib/produce/service.rb +4 -3
  38. data/sigh/lib/sigh/manager.rb +5 -1
  39. data/snapshot/lib/snapshot/reports_generator.rb +2 -0
  40. data/spaceship/lib/spaceship/client.rb +7 -1
  41. data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +2 -0
  42. data/spaceship/lib/spaceship/connect_api/models/bundle_id.rb +2 -13
  43. data/spaceship/lib/spaceship/connect_api/models/bundle_id_capability.rb +0 -6
  44. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +0 -16
  45. metadata +19 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 696cd16eb3defc78305bdebccbc787402841c1105199cfd437d15f019e617024
4
- data.tar.gz: ab772bcd92b21da5da03746f3e7bdab86e60f5ff6771696e2beb5cbd5c673b4d
3
+ metadata.gz: de93b89428a3de2726024a5c924723c7fcb3f3096402884e4139749e8d22cb39
4
+ data.tar.gz: 25aa9615e76bb0f0e503ebf6cad2170178938f249027994b8b463ce45bd94403
5
5
  SHA512:
6
- metadata.gz: fb16eaacf91b754e72424d9780054f13b49040aadb738c86746dba6fa75ff3e275af98621aeda98794b47a1a3ad982d91071b6c9b1642da7e243a0d187a86739
7
- data.tar.gz: e116e4c1c5f59831e65a20e676f9f8735807ae86929efb06bc5d97d97e11182361ea2d783c9da2ced56276f6d2183bfbfd863e5fcb7e7add57f5f1961543e84f
6
+ metadata.gz: a3ae511471c7729e7854ac3aa91ea06d2ceb306e87b7e968aab9be77ce05d0ba6e3b127e3e720598380084686158c2b40b2e587cf78d402b5ec1a6aebcf4e42c
7
+ data.tar.gz: 84016a1687602afdde1ab12f6c6dacb1f22550c6ab3c3724e79638435dea908cdcf58735519523ba670074190caddb6bb4b0fb792c359b56b181751acb0347eb
data/README.md CHANGED
@@ -36,17 +36,23 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
36
36
  </a>
37
37
  <h4 align='center'><a href='https://twitter.com/Sujan'>Jan Piotrowski</a></h4>
38
38
  </td>
39
- <td id='jérôme-lacoste'>
40
- <a href='https://github.com/lacostej'>
41
- <img src='https://github.com/lacostej.png?size=200' width='140'>
39
+ <td id='connor-tumbleson'>
40
+ <a href='https://github.com/ibotpeaches'>
41
+ <img src='https://github.com/ibotpeaches.png?size=200' width='140'>
42
42
  </a>
43
- <h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
43
+ <h4 align='center'><a href='https://twitter.com/ibotpeaches'>Connor Tumbleson</a></h4>
44
44
  </td>
45
- <td id='joshua-liebowitz'>
46
- <a href='https://github.com/taquitos'>
47
- <img src='https://github.com/taquitos.png?size=200' width='140'>
45
+ <td id='olivier-halligon'>
46
+ <a href='https://github.com/AliSoftware'>
47
+ <img src='https://github.com/AliSoftware.png?size=200' width='140'>
48
48
  </a>
49
- <h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
49
+ <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
50
+ </td>
51
+ <td id='felix-krause'>
52
+ <a href='https://github.com/KrauseFx'>
53
+ <img src='https://github.com/KrauseFx.png?size=200' width='140'>
54
+ </a>
55
+ <h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
50
56
  </td>
51
57
  <td id='josh-holtz'>
52
58
  <a href='https://github.com/joshdholtz'>
@@ -54,31 +60,25 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
54
60
  </a>
55
61
  <h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
56
62
  </td>
57
- <td id='stefan-natchev'>
58
- <a href='https://github.com/snatchev'>
59
- <img src='https://github.com/snatchev.png?size=200' width='140'>
60
- </a>
61
- <h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
62
- </td>
63
63
  </tr>
64
64
  <tr>
65
- <td id='olivier-halligon'>
66
- <a href='https://github.com/AliSoftware'>
67
- <img src='https://github.com/AliSoftware.png?size=200' width='140'>
65
+ <td id='joshua-liebowitz'>
66
+ <a href='https://github.com/taquitos'>
67
+ <img src='https://github.com/taquitos.png?size=200' width='140'>
68
68
  </a>
69
- <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
69
+ <h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
70
70
  </td>
71
- <td id='felix-krause'>
72
- <a href='https://github.com/KrauseFx'>
73
- <img src='https://github.com/KrauseFx.png?size=200' width='140'>
71
+ <td id='jérôme-lacoste'>
72
+ <a href='https://github.com/lacostej'>
73
+ <img src='https://github.com/lacostej.png?size=200' width='140'>
74
74
  </a>
75
- <h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
75
+ <h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
76
76
  </td>
77
- <td id='connor-tumbleson'>
78
- <a href='https://github.com/ibotpeaches'>
79
- <img src='https://github.com/ibotpeaches.png?size=200' width='140'>
77
+ <td id='stefan-natchev'>
78
+ <a href='https://github.com/snatchev'>
79
+ <img src='https://github.com/snatchev.png?size=200' width='140'>
80
80
  </a>
81
- <h4 align='center'><a href='https://twitter.com/ibotpeaches'>Connor Tumbleson</a></h4>
81
+ <h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
82
82
  </td>
83
83
  <td id='roger-oba'>
84
84
  <a href='https://github.com/rogerluan'>
@@ -95,23 +95,23 @@ _fastlane_ wouldn't be what it is today without the countless hours these former
95
95
 
96
96
  <p id='alumni'>
97
97
  <a href='https://github.com/endocrimes'>Danielle Tomlinson</a>,
98
+ <a href='https://github.com/max-ott'>Max Ott</a>,
98
99
  <a href='https://github.com/crazymanish'>Manish Rathi</a>,
100
+ <a href='https://github.com/lucgrabowski'>Łukasz Grabowski</a>,
101
+ <a href='https://github.com/mollyIV'>Daniel Jankowski</a>,
99
102
  <a href='https://github.com/ainame'>Satoshi Namai</a>,
100
- <a href='https://github.com/giginet'>Kohki Miki</a>,
103
+ <a href='https://github.com/getaaron'>Aaron Brager</a>,
104
+ <a href='https://github.com/jdee'>Jimmy Dee</a>,
105
+ <a href='https://github.com/revolter'>Iulian Onofrei</a>,
106
+ <a href='https://github.com/mgrebenets'>Maksym Grebenets</a>,
107
+ <a href='https://github.com/armcburney'>Andrew McBurney</a>,
101
108
  <a href='https://github.com/lmirosevic'>Luka Mirosevic</a>,
109
+ <a href='https://github.com/milch'>Manu Wallner</a>,
102
110
  <a href='https://github.com/minuscorp'>Jorge Revuelta H</a>,
103
- <a href='https://github.com/mollyIV'>Daniel Jankowski</a>,
104
- <a href='https://github.com/armcburney'>Andrew McBurney</a>,
105
- <a href='https://github.com/max-ott'>Max Ott</a>,
106
- <a href='https://github.com/lucgrabowski'>Łukasz Grabowski</a>,
107
111
  <a href='https://github.com/matthewellis'>Matthew Ellis</a>,
108
- <a href='https://github.com/jdee'>Jimmy Dee</a>,
109
- <a href='https://github.com/nafu'>Fumiya Nakamura</a>,
110
112
  <a href='https://github.com/hjanuschka'>Helmut Januschka</a>,
111
- <a href='https://github.com/milch'>Manu Wallner</a>,
112
- <a href='https://github.com/getaaron'>Aaron Brager</a>,
113
- <a href='https://github.com/mgrebenets'>Maksym Grebenets</a>,
114
- <a href='https://github.com/revolter'>Iulian Onofrei</a>
113
+ <a href='https://github.com/nafu'>Fumiya Nakamura</a>,
114
+ <a href='https://github.com/giginet'>Kohki Miki</a>
115
115
  </p>
116
116
  <!-- team:end -->
117
117
 
@@ -19,6 +19,7 @@ module Deliver
19
19
  DisplayType::APP_IPHONE_61 => "iPhone 14 Pro",
20
20
  DisplayType::APP_IPHONE_65 => "iPhone XS Max",
21
21
  DisplayType::APP_IPHONE_67 => "iPhone 14 Pro Max",
22
+ DisplayType::APP_IPHONE_DUO => "iPhone Duo",
22
23
  DisplayType::APP_IPAD_97 => "iPad",
23
24
  DisplayType::APP_IPAD_105 => "iPad 10.5",
24
25
  DisplayType::APP_IPAD_PRO_3GEN_11 => "iPad 11",
@@ -100,6 +101,13 @@ module Deliver
100
101
  [960, 600],
101
102
  [960, 640]
102
103
  ],
104
+ # iPhone Duo outer and inner displays
105
+ DisplayType::APP_IPHONE_DUO => [
106
+ [1398, 2034],
107
+ [2034, 1398],
108
+ [2007, 2853],
109
+ [2853, 2007]
110
+ ],
103
111
  DisplayType::APP_IPAD_97 => [
104
112
  [1024, 748],
105
113
  [1024, 768],
@@ -144,11 +144,16 @@ module Fastlane
144
144
  c.option('-s', '--stay_alive', 'Keeps socket server up even after error or disconnects, requires CTRL-C to kill.')
145
145
  c.option('-c seconds', '--connection_timeout', 'Sets connection established timeout')
146
146
  c.option('-p port', '--port', "Sets the port on localhost for the socket connection")
147
+ c.option('--token_file path', "File holding the token every command must carry (default: FASTLANE_SOCKET_SERVER_TOKEN_FILE)")
147
148
  c.action do |args, options|
148
149
  default_connection_timeout = 5
149
150
  stay_alive = options.stay_alive || false
150
151
  connection_timeout = options.connection_timeout || default_connection_timeout
151
152
  port = options.port || 2000
153
+ token_file = options.token_file || ENV["FASTLANE_SOCKET_SERVER_TOKEN_FILE"]
154
+ token = File.read(token_file).strip if token_file
155
+ UI.user_error!("The token file #{token_file} is empty") if token_file && token.empty?
156
+ UI.important("No --token_file given: any local process that connects first can run actions") unless token
152
157
 
153
158
  if stay_alive && options.connection_timeout.nil?
154
159
  UI.important("stay_alive is set, but the connection timeout is not, this will give you #{default_connection_timeout} seconds to (re)connect")
@@ -162,7 +167,8 @@ module Fastlane
162
167
  command_executor: command_executor,
163
168
  connection_timeout: connection_timeout,
164
169
  stay_alive: stay_alive,
165
- port: port
170
+ port: port,
171
+ token: token
166
172
  )
167
173
  result = server.start
168
174
  UI.success("Result: #{result}") if result
@@ -3,6 +3,7 @@ require 'fastlane/server/command_parser.rb'
3
3
  require 'fastlane/server/json_return_value_processor.rb'
4
4
  require 'socket'
5
5
  require 'json'
6
+ require 'openssl'
6
7
 
7
8
  module Fastlane
8
9
  class SocketServer
@@ -20,7 +21,8 @@ module Fastlane
20
21
  return_value_processor: nil,
21
22
  connection_timeout: 5,
22
23
  stay_alive: false,
23
- port: 2000
24
+ port: 2000,
25
+ token: nil
24
26
  )
25
27
  if return_value_processor.nil?
26
28
  return_value_processor = JSONReturnValueProcessor.new
@@ -31,6 +33,8 @@ module Fastlane
31
33
  @connection_timeout = connection_timeout.to_i
32
34
  @stay_alive = stay_alive
33
35
  @port = port.to_i
36
+ # When set, every command must carry it: the runner reads it from a file only this user can read
37
+ @token = token
34
38
  end
35
39
 
36
40
  # this is the public API, don't call anything else
@@ -78,6 +82,11 @@ module Fastlane
78
82
  end
79
83
 
80
84
  def parse_and_execute_command(command_string: nil)
85
+ unless token_matches?(command_string)
86
+ UI.error("Refusing a command without this run's token: it does not come from the FastlaneRunner fastlane started")
87
+ return COMMAND_EXECUTION_STATE[:error]
88
+ end
89
+
81
90
  command = CommandParser.parse(json: command_string)
82
91
  case command
83
92
  when ControlCommand
@@ -137,15 +146,17 @@ module Fastlane
137
146
  end
138
147
 
139
148
  def listen
140
- @server = TCPServer.open('localhost', @port) # Socket to listen on port 2000
141
- UI.verbose("Waiting for #{@connection_timeout} seconds for a connection from FastlaneRunner")
149
+ @servers = listening_sockets
150
+ @port = @servers.first.local_address.ip_port
151
+ UI.verbose("Waiting for #{@connection_timeout} seconds for a connection from FastlaneRunner on port #{@port}")
142
152
 
143
- # set thread local to ready so we can check it
153
+ # set thread locals so the caller can tell the runner where to connect
154
+ Thread.current[:port] = @port
144
155
  Thread.current[:ready] = true
145
156
  @client = nil
146
157
  begin
147
158
  Timeout.timeout(@connection_timeout) do
148
- @client = @server.accept # Wait for a client to connect
159
+ @client = accept_first_connection
149
160
  end
150
161
  rescue Timeout::Error
151
162
  UI.user_error!("fastlane failed to receive a connection from the FastlaneRunner binary after #{@connection_timeout} seconds, shutting down")
@@ -166,8 +177,47 @@ module Fastlane
166
177
  @client.close
167
178
  @client = nil
168
179
 
169
- @server.close
170
- @server = nil
180
+ close_servers
181
+ end
182
+
183
+ # A socket on every address localhost resolves to. The runner connects to
184
+ # `localhost` and tries those addresses in turn, so another process listening
185
+ # on one this server did not take would receive its commands.
186
+ def listening_sockets
187
+ # Port 0: a free port, the same on every address
188
+ return Socket.tcp_server_sockets('localhost', 0) if @port.zero?
189
+
190
+ sockets = []
191
+ Addrinfo.getaddrinfo('localhost', @port, nil, :STREAM).uniq(&:ip_address).each do |address|
192
+ sockets << address.listen
193
+ rescue SystemCallError => e
194
+ sockets.each(&:close)
195
+ UI.user_error!("fastlane could not listen on #{address.inspect_sockaddr} for the FastlaneRunner binary: #{e.message}")
196
+ end
197
+ sockets
198
+ end
199
+
200
+ # The first connection on any of the sockets. They are closed once it is
201
+ # accepted, so nothing else can connect while the runner is talking to us.
202
+ def accept_first_connection
203
+ ready, = IO.select(@servers)
204
+ ready.first.accept.first
205
+ ensure
206
+ close_servers
207
+ end
208
+
209
+ def token_matches?(command_string)
210
+ return true if @token.nil?
211
+
212
+ token = JSON.parse(command_string)['token']
213
+ token.kind_of?(String) && OpenSSL.secure_compare(token, @token)
214
+ rescue JSON::ParserError, TypeError
215
+ false
216
+ end
217
+
218
+ def close_servers
219
+ (@servers || []).each { |server| server.close unless server.closed? }
220
+ @servers = nil
171
221
  end
172
222
 
173
223
  # record fastlane action command and then execute it
@@ -1,10 +1,12 @@
1
1
  require_relative 'lane_manager_base.rb'
2
2
  require_relative 'swift_runner_upgrader.rb'
3
+ require 'securerandom'
4
+ require 'tmpdir'
3
5
 
4
6
  module Fastlane
5
7
  class SwiftLaneManager < LaneManagerBase
6
8
  # The runner protocol version this fastlane ships, see main.swift
7
- RUNNER_PROTOCOL_VERSION = 1
9
+ RUNNER_PROTOCOL_VERSION = 2
8
10
  RUNNER_PROTOCOL_VERSION_REGEX = /FastlaneRunnerProtocolVersion \[(\d+)\]/
9
11
 
10
12
  # @param lane_name The name of the lane to execute
@@ -31,15 +33,25 @@ module Fastlane
31
33
 
32
34
  self.ensure_runner_built!
33
35
  self.warn_if_runner_outdated
34
- swift_server_port ||= 2000
35
- socket_thread = self.start_socket_thread(port: swift_server_port)
36
- sleep(0.250) while socket_thread[:ready].nil?
36
+ # A secret only this user can read, which the runner sends back with every command
37
+ token_dir = Dir.mktmpdir('fastlane-runner')
38
+ token_file = File.join(token_dir, 'token')
39
+ token = SecureRandom.hex(32)
40
+ File.write(token_file, token, perm: 0o600)
41
+ token = nil unless self.runner_sends_token?
42
+ # Port 0 lets the server pick a free port, which no other process can take beforehand
43
+ socket_thread = self.start_socket_thread(port: swift_server_port || 0, token: token)
44
+ sleep(0.250) while socket_thread[:ready].nil? && socket_thread.alive?
45
+ # The thread ended without listening: join raises its error rather than waiting forever
46
+ socket_thread.join unless socket_thread[:ready]
37
47
  # wait on socket_thread to be in ready state, then start the runner thread
38
- self.cruise_swift_lane_in_thread(lane, parameters, swift_server_port)
48
+ self.cruise_swift_lane_in_thread(lane, parameters, socket_thread[:port], token_file)
39
49
 
40
50
  socket_thread.value
41
51
  rescue Exception => ex # rubocop:disable Lint/RescueException
42
52
  e = ex
53
+ ensure
54
+ FileUtils.remove_entry(token_dir) if token_dir
43
55
  end
44
56
  # If we have a thread exception, drop that in the exception
45
57
  # won't ever have a situation where e is non-nil, and socket_thread[:exception] is also non-nil
@@ -84,14 +96,14 @@ module Fastlane
84
96
  end
85
97
  end
86
98
 
87
- def self.cruise_swift_lane_in_thread(lane, parameters = nil, swift_server_port)
99
+ def self.cruise_swift_lane_in_thread(lane, parameters = nil, swift_server_port, token_file)
88
100
  if parameters.nil?
89
101
  parameters = {}
90
102
  end
91
103
 
92
104
  parameter_string = ""
93
105
  parameters.each do |key, value|
94
- parameter_string += " #{key} #{value}"
106
+ parameter_string += " #{key.to_s.shellescape} #{value.to_s.shellescape}"
95
107
  end
96
108
 
97
109
  if FastlaneCore::Globals.verbose?
@@ -99,13 +111,16 @@ module Fastlane
99
111
  end
100
112
 
101
113
  parameter_string += " swiftServerPort #{swift_server_port}"
114
+ parameter_string += " swiftServerTokenFile #{token_file.shellescape}"
115
+
116
+ command = "#{FastlaneCore::FastlaneFolder.swift_runner_path.shellescape} lane #{lane.to_s.shellescape}#{parameter_string}"
102
117
 
103
118
  return Thread.new do
104
119
  if FastlaneCore::Globals.verbose?
105
- return_value = Actions.sh(%(#{FastlaneCore::FastlaneFolder.swift_runner_path} lane #{lane}#{parameter_string}))
120
+ return_value = Actions.sh(command)
106
121
  UI.message("runner output: ".yellow + return_value)
107
122
  else
108
- Actions.sh(%(#{FastlaneCore::FastlaneFolder.swift_runner_path} lane #{lane}#{parameter_string} > /dev/null))
123
+ Actions.sh("#{command} > /dev/null")
109
124
  end
110
125
  end
111
126
  end
@@ -223,13 +238,13 @@ module Fastlane
223
238
  return project_modified
224
239
  end
225
240
 
226
- def self.start_socket_thread(port: nil)
241
+ def self.start_socket_thread(port: nil, token: nil)
227
242
  require 'fastlane/server/socket_server'
228
243
  require 'fastlane/server/socket_server_action_command_executor'
229
244
 
230
245
  return Thread.new do
231
246
  command_executor = SocketServerActionCommandExecutor.new
232
- server = Fastlane::SocketServer.new(command_executor: command_executor, port: port)
247
+ server = Fastlane::SocketServer.new(command_executor: command_executor, port: port, token: token)
233
248
  server.start
234
249
  end
235
250
  end
@@ -245,6 +260,12 @@ module Fastlane
245
260
  UI.important("Your FastlaneRunner is outdated for this version of fastlane. Run a lane outside CI, without `disable_runner_upgrades`, to update it, then commit the changes.")
246
261
  end
247
262
 
263
+ # Runners from protocol version 2 send the run's token with every command; older ones get warn_if_runner_outdated.
264
+ # Version 1 was released before the token existed.
265
+ def self.runner_sends_token?
266
+ runner_protocol_version >= 2
267
+ end
268
+
248
269
  def self.ensure_runner_built!
249
270
  UI.verbose("Checking for new user-provided tool configuration files")
250
271
  # if self.link_user_configs_to_project returns true, that means we need to rebuild the runner
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
- VERSION = '2.241.0'.freeze
2
+ VERSION = '2.241.2'.freeze
3
3
  SUMMARY = "The easiest way to build and release mobile apps.".freeze
4
4
  DESCRIPTION = "The easiest way to automate beta deployments and releases for your iOS and Android apps".freeze
5
5
  MINIMUM_XCODE_RELEASE = "7.0".freeze
@@ -11,6 +11,10 @@
11
11
  import Foundation
12
12
 
13
13
  struct ArgumentProcessor {
14
+ /// The secret fastlane wrote to a file only this user can read: sent with every command so that
15
+ /// fastlane can tell this runner from another process that connected to its port
16
+ static var serverToken: String?
17
+
14
18
  let args: [RunnerArgument]
15
19
  let currentLane: String
16
20
  let commandTimeout: Int
@@ -33,6 +37,10 @@ struct ArgumentProcessor {
33
37
 
34
38
  port = UInt32(fastlaneArgsMinusLanes.first(where: { $0.name == "swiftServerPort" })?.value ?? "") ?? 2000
35
39
 
40
+ if let tokenFile = fastlaneArgsMinusLanes.first(where: { $0.name == "swiftServerTokenFile" })?.value {
41
+ ArgumentProcessor.serverToken = (try? String(contentsOfFile: tokenFile, encoding: .utf8))?.trimmingCharacters(in: .whitespacesAndNewlines)
42
+ }
43
+
36
44
  // Configure logMode since we might need to use it before we finish parsing
37
45
  if let logModeArg = potentialLogMode.first {
38
46
  let logModeString = logModeArg.value
@@ -74,7 +82,7 @@ struct ArgumentProcessor {
74
82
  func laneParameters() -> [String: String] {
75
83
  let laneParametersArgs = args.filter { arg in
76
84
  let lowercasedName = arg.name.lowercased()
77
- return lowercasedName != "timeoutseconds" && lowercasedName != "lane" && lowercasedName != "logmode"
85
+ return lowercasedName != "timeoutseconds" && lowercasedName != "lane" && lowercasedName != "logmode" && lowercasedName != "swiftservertokenfile"
78
86
  }
79
87
  var laneParameters = [String: String]()
80
88
  for arg in laneParametersArgs {
@@ -86,4 +94,4 @@ struct ArgumentProcessor {
86
94
 
87
95
  // Please don't remove the lines below
88
96
  // They are used to detect outdated files
89
- // FastlaneRunnerAPIVersion [0.9.2]
97
+ // FastlaneRunnerAPIVersion [0.9.3]
@@ -17,4 +17,4 @@ public class Deliverfile: DeliverfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.0
20
+ // Generated with fastlane 2.241.2
@@ -539,4 +539,4 @@ public extension DeliverfileProtocol {
539
539
 
540
540
  // Please don't remove the lines below
541
541
  // They are used to detect outdated files
542
- // FastlaneRunnerAPIVersion [0.9.156]
542
+ // FastlaneRunnerAPIVersion [0.9.158]
@@ -3437,8 +3437,6 @@ public func createAppOnManagedPlayStore(jsonKey: OptionalConfigValue<String?> =
3437
3437
 
3438
3438
  - parameters:
3439
3439
  - username: Your Apple ID Username
3440
- - apiKeyPath: Path to your App Store Connect API Key JSON file (https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file)
3441
- - apiKey: Your App Store Connect API Key information (https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-hash-option)
3442
3440
  - appIdentifier: App Identifier (Bundle ID, e.g. com.krausefx.app)
3443
3441
  - bundleIdentifierSuffix: App Identifier Suffix (Ignored if App Identifier does not end with .*)
3444
3442
  - appName: App Name
@@ -3463,8 +3461,6 @@ public func createAppOnManagedPlayStore(jsonKey: OptionalConfigValue<String?> =
3463
3461
  For more information about _produce_, visit its documentation page: [https://docs.fastlane.tools/actions/produce/](https://docs.fastlane.tools/actions/produce/).
3464
3462
  */
3465
3463
  public func createAppOnline(username: String,
3466
- apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault(nil),
3467
- apiKey: OptionalConfigValue<[String: Any]?> = .fastlaneDefault(nil),
3468
3464
  appIdentifier: String,
3469
3465
  bundleIdentifierSuffix: OptionalConfigValue<String?> = .fastlaneDefault(nil),
3470
3466
  appName: String,
@@ -3485,8 +3481,6 @@ public func createAppOnline(username: String,
3485
3481
  itcTeamName: OptionalConfigValue<String?> = .fastlaneDefault(nil))
3486
3482
  {
3487
3483
  let usernameArg = RubyCommand.Argument(name: "username", value: username, type: nil)
3488
- let apiKeyPathArg = apiKeyPath.asRubyArgument(name: "api_key_path", type: nil)
3489
- let apiKeyArg = apiKey.asRubyArgument(name: "api_key", type: nil)
3490
3484
  let appIdentifierArg = RubyCommand.Argument(name: "app_identifier", value: appIdentifier, type: nil)
3491
3485
  let bundleIdentifierSuffixArg = bundleIdentifierSuffix.asRubyArgument(name: "bundle_identifier_suffix", type: nil)
3492
3486
  let appNameArg = RubyCommand.Argument(name: "app_name", value: appName, type: nil)
@@ -3506,8 +3500,6 @@ public func createAppOnline(username: String,
3506
3500
  let itcTeamIdArg = RubyCommand.Argument(name: "itc_team_id", value: itcTeamId, type: nil)
3507
3501
  let itcTeamNameArg = itcTeamName.asRubyArgument(name: "itc_team_name", type: nil)
3508
3502
  let array: [RubyCommand.Argument?] = [usernameArg,
3509
- apiKeyPathArg,
3510
- apiKeyArg,
3511
3503
  appIdentifierArg,
3512
3504
  bundleIdentifierSuffixArg,
3513
3505
  appNameArg,
@@ -8437,8 +8429,6 @@ public func println(message: OptionalConfigValue<String?> = .fastlaneDefault(nil
8437
8429
 
8438
8430
  - parameters:
8439
8431
  - username: Your Apple ID Username
8440
- - apiKeyPath: Path to your App Store Connect API Key JSON file (https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file)
8441
- - apiKey: Your App Store Connect API Key information (https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-hash-option)
8442
8432
  - appIdentifier: App Identifier (Bundle ID, e.g. com.krausefx.app)
8443
8433
  - bundleIdentifierSuffix: App Identifier Suffix (Ignored if App Identifier does not end with .*)
8444
8434
  - appName: App Name
@@ -8463,8 +8453,6 @@ public func println(message: OptionalConfigValue<String?> = .fastlaneDefault(nil
8463
8453
  For more information about _produce_, visit its documentation page: [https://docs.fastlane.tools/actions/produce/](https://docs.fastlane.tools/actions/produce/).
8464
8454
  */
8465
8455
  public func produce(username: String,
8466
- apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault(nil),
8467
- apiKey: OptionalConfigValue<[String: Any]?> = .fastlaneDefault(nil),
8468
8456
  appIdentifier: String,
8469
8457
  bundleIdentifierSuffix: OptionalConfigValue<String?> = .fastlaneDefault(nil),
8470
8458
  appName: String,
@@ -8485,8 +8473,6 @@ public func produce(username: String,
8485
8473
  itcTeamName: OptionalConfigValue<String?> = .fastlaneDefault(nil))
8486
8474
  {
8487
8475
  let usernameArg = RubyCommand.Argument(name: "username", value: username, type: nil)
8488
- let apiKeyPathArg = apiKeyPath.asRubyArgument(name: "api_key_path", type: nil)
8489
- let apiKeyArg = apiKey.asRubyArgument(name: "api_key", type: nil)
8490
8476
  let appIdentifierArg = RubyCommand.Argument(name: "app_identifier", value: appIdentifier, type: nil)
8491
8477
  let bundleIdentifierSuffixArg = bundleIdentifierSuffix.asRubyArgument(name: "bundle_identifier_suffix", type: nil)
8492
8478
  let appNameArg = RubyCommand.Argument(name: "app_name", value: appName, type: nil)
@@ -8506,8 +8492,6 @@ public func produce(username: String,
8506
8492
  let itcTeamIdArg = RubyCommand.Argument(name: "itc_team_id", value: itcTeamId, type: nil)
8507
8493
  let itcTeamNameArg = itcTeamName.asRubyArgument(name: "itc_team_name", type: nil)
8508
8494
  let array: [RubyCommand.Argument?] = [usernameArg,
8509
- apiKeyPathArg,
8510
- apiKeyArg,
8511
8495
  appIdentifierArg,
8512
8496
  bundleIdentifierSuffixArg,
8513
8497
  appNameArg,
@@ -12621,7 +12605,7 @@ public func updateProjectProvisioning(xcodeproj: OptionalConfigValue<String?> =
12621
12605
  targetFilter: OptionalConfigValue<String?> = .fastlaneDefault(nil),
12622
12606
  buildConfigurationFilter: OptionalConfigValue<String?> = .fastlaneDefault(nil),
12623
12607
  buildConfiguration: OptionalConfigValue<String?> = .fastlaneDefault(nil),
12624
- certificate: String = "/var/folders/36/tjdph2t965j8snz9_vkdnw0r0000gn/T/AppleIncRootCertificate.cer",
12608
+ certificate: String = "/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/AppleIncRootCertificate.cer",
12625
12609
  codeSigningIdentity: OptionalConfigValue<String?> = .fastlaneDefault(nil))
12626
12610
  {
12627
12611
  let xcodeprojArg = xcodeproj.asRubyArgument(name: "xcodeproj", type: nil)
@@ -14261,4 +14245,4 @@ public let snapshotfile: Snapshotfile = .init()
14261
14245
 
14262
14246
  // Please don't remove the lines below
14263
14247
  // They are used to detect outdated files
14264
- // FastlaneRunnerAPIVersion [0.9.209]
14248
+ // FastlaneRunnerAPIVersion [0.9.211]
@@ -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.241.0
20
+ // Generated with fastlane 2.241.2
@@ -399,4 +399,4 @@ public extension GymfileProtocol {
399
399
 
400
400
  // Please don't remove the lines below
401
401
  // They are used to detect outdated files
402
- // FastlaneRunnerAPIVersion [0.9.159]
402
+ // FastlaneRunnerAPIVersion [0.9.161]
@@ -42,12 +42,21 @@ class MainProcess {
42
42
  killExistingSocketServerProcesses(port: argumentProcessor.port)
43
43
  abortIfPortIsInUse(argumentProcessor.port)
44
44
 
45
+ let serverToken = createServerToken()
46
+ ArgumentProcessor.serverToken = serverToken.token
47
+
45
48
  rubySocketCommand = Process()
46
49
  rubySocketCommand.launchPath = "/usr/bin/env"
47
50
  rubySocketCommand.arguments = fastlaneLaunchArguments() + ["socket_server", "-c", String(timeout), "-p", String(argumentProcessor.port)]
51
+ // An environment variable rather than --token_file, so that an older fastlane still starts
52
+ var environment = ProcessInfo.processInfo.environment
53
+ environment["FASTLANE_SOCKET_SERVER_TOKEN_FILE"] = serverToken.file
54
+ rubySocketCommand.environment = environment
48
55
  rubySocketCommand.launch()
49
56
 
50
57
  waitUntilSocketServerIsListening(port: argumentProcessor.port, serverProcess: rubySocketCommand)
58
+ // socket_server read the token when it started
59
+ try? FileManager.default.removeItem(atPath: serverToken.directory)
51
60
  thread.start()
52
61
  #endif
53
62
  }
@@ -78,6 +87,24 @@ class MainProcess {
78
87
  return ["bundle", "exec", "fastlane"]
79
88
  }
80
89
 
90
+ /// A random token in a file only this user can read, which socket_server requires on every command and the runner sends
91
+ func createServerToken() -> (token: String, directory: String, file: String) {
92
+ var template = Array((NSTemporaryDirectory() as NSString).appendingPathComponent("fastlane-runner.XXXXXX").utf8CString)
93
+ guard let created = mkdtemp(&template), let random = FileHandle(forReadingAtPath: "/dev/urandom") else {
94
+ log(message: "Could not create the token for fastlane socket_server")
95
+ exit(1)
96
+ }
97
+ let directory = String(cString: created)
98
+ let token = random.readData(ofLength: 32).map { String(format: "%02x", $0) }.joined()
99
+ random.closeFile()
100
+ let file = directory + "/token"
101
+ guard token.count == 64, FileManager.default.createFile(atPath: file, contents: token.data(using: .utf8), attributes: [.posixPermissions: 0o600]) else {
102
+ log(message: "Could not create the token for fastlane socket_server")
103
+ exit(1)
104
+ }
105
+ return (token, directory, file)
106
+ }
107
+
81
108
  /// The directory of the Gemfile Bundler would use: BUNDLE_GEMFILE, else the
82
109
  /// nearest Gemfile or gems.rb from the working directory up.
83
110
  func bundleRootDirectory() -> String? {
@@ -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.241.0
20
+ // Generated with fastlane 2.241.2
@@ -420,4 +420,4 @@ public extension MatchfileProtocol {
420
420
 
421
421
  // Please don't remove the lines below
422
422
  // They are used to detect outdated files
423
- // FastlaneRunnerAPIVersion [0.9.153]
423
+ // FastlaneRunnerAPIVersion [0.9.155]
@@ -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.241.0
20
+ // Generated with fastlane 2.241.2
@@ -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.152]
87
+ // FastlaneRunnerAPIVersion [0.9.154]
@@ -81,15 +81,7 @@ struct RubyCommand: RubyCommandable {
81
81
 
82
82
  var closure: ((String) -> Void)? {
83
83
  let callbacks = args.filter { ($0.type != nil) && $0.type == .stringClosure }
84
- guard let callback = callbacks.first else {
85
- return nil
86
- }
87
-
88
- guard let callbackArgValue = callback.value else {
89
- return nil
90
- }
91
-
92
- guard let callbackClosure = callbackArgValue as? ((String) -> Void) else {
84
+ guard let callback = callbacks.first, let callbackArgValue = callback.value, let callbackClosure = callbackArgValue as? ((String) -> Void) else {
93
85
  return nil
94
86
  }
95
87
  return callbackClosure
@@ -32,12 +32,13 @@ protocol RubyCommandable {
32
32
 
33
33
  extension RubyCommandable {
34
34
  var json: String {
35
+ let token = ArgumentProcessor.serverToken.map { ", \"token\": \"\($0)\"" } ?? ""
35
36
  return """
36
- { "commandType": "\(type.token)", "command": \(commandJson) }
37
+ { "commandType": "\(type.token)", "command": \(commandJson)\(token) }
37
38
  """
38
39
  }
39
40
  }
40
41
 
41
42
  // Please don't remove the lines below
42
43
  // They are used to detect outdated files
43
- // FastlaneRunnerAPIVersion [0.9.2]
44
+ // FastlaneRunnerAPIVersion [0.9.3]
@@ -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.241.0
20
+ // Generated with fastlane 2.241.2
@@ -595,4 +595,4 @@ public extension ScanfileProtocol {
595
595
 
596
596
  // Please don't remove the lines below
597
597
  // They are used to detect outdated files
598
- // FastlaneRunnerAPIVersion [0.9.164]
598
+ // FastlaneRunnerAPIVersion [0.9.166]
@@ -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.241.0
20
+ // Generated with fastlane 2.241.2
@@ -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.154]
164
+ // FastlaneRunnerAPIVersion [0.9.156]
@@ -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.241.0
20
+ // Generated with fastlane 2.241.2
@@ -385,4 +385,4 @@ public extension SnapshotfileProtocol {
385
385
 
386
386
  // Please don't remove the lines below
387
387
  // They are used to detect outdated files
388
- // FastlaneRunnerAPIVersion [0.9.148]
388
+ // FastlaneRunnerAPIVersion [0.9.150]
@@ -12,7 +12,7 @@ import Foundation
12
12
 
13
13
  let argumentProcessor = ArgumentProcessor(args: CommandLine.arguments)
14
14
  /// fastlane reads this from the built runner, to tell whether it is outdated. Increase it when the runner changes how it talks to fastlane.
15
- let runnerProtocolVersion = "FastlaneRunnerProtocolVersion [1]"
15
+ let runnerProtocolVersion = "FastlaneRunnerProtocolVersion [2]"
16
16
  verbose(message: runnerProtocolVersion)
17
17
  let timeout = argumentProcessor.commandTimeout
18
18
 
@@ -58,30 +58,44 @@ module FastlaneCore
58
58
  # Runs preparations before executing any command from the executor.
59
59
  #
60
60
  # @param original_api_key [Hash] api key containing the issuer id and private key
61
- # @return [Hash] copy of `api_key` which includes an extra `key_dir` with the location of the .p8 file on disk
61
+ # @return [Hash] copy of `api_key` which includes an extra `key_dir` with the location of the .p8 file on disk,
62
+ # and `cleanup_path`: what #cleanup removes afterwards
62
63
  def prepare(original_api_key:)
63
64
  return if original_api_key.nil?
64
65
  # Create .p8 file from api_key and provide api key info which contains .p8 file path
65
66
  api_key = original_api_key.dup
67
+ p8_name = "AuthKey_#{api_key[:key_id]}.p8"
66
68
  if self.kind_of?(ShellScriptTransporterExecutor)
67
69
  # as of Transporter v3.3.0, the app is unable to detect the private keys under the 'private_keys' folder in current directory
68
70
  # so we must rely on the other search paths in the Home dir:
69
71
  # https://help.apple.com/itc/transporteruserguide/en.lproj/static.html#itc803b7be80
70
72
  private_keys_dir = File.join(Dir.home, ".appstoreconnect/private_keys")
71
73
  unless Dir.exist?(private_keys_dir)
72
- FileUtils.mkdir_p(private_keys_dir)
74
+ FileUtils.mkdir_p(private_keys_dir, mode: 0o700)
73
75
  end
74
76
  api_key[:key_dir] = private_keys_dir
77
+ # Keys already there belong to the user: only remove the one written below
78
+ p8_path = File.join(private_keys_dir, p8_name)
79
+ api_key[:cleanup_path] = p8_path unless File.exist?(p8_path)
75
80
  else
76
81
  api_key[:key_dir] = Dir.mktmpdir("deliver-")
82
+ api_key[:cleanup_path] = api_key[:key_dir]
77
83
  end
78
84
  # Specified p8 needs to be generated to call altool or iTMSTransporter
79
- File.open(File.join(api_key[:key_dir], "AuthKey_#{api_key[:key_id]}.p8"), "wb") do |p8|
85
+ p8_path = File.join(api_key[:key_dir], p8_name)
86
+ File.open(p8_path, "wb", 0o600) do |p8|
80
87
  p8.write(api_key[:key])
81
88
  end
89
+ File.chmod(0o600, p8_path)
82
90
  api_key
83
91
  end
84
92
 
93
+ # Removes what #prepare wrote: its temporary directory, or the key file it added to ~/.appstoreconnect/private_keys
94
+ def cleanup(api_key)
95
+ return if api_key.nil? || api_key[:cleanup_path].nil?
96
+ FileUtils.rm_rf(api_key[:cleanup_path])
97
+ end
98
+
85
99
  def execute(command, hide_output)
86
100
  if Helper.test?
87
101
  yield(nil) if block_given?
@@ -869,7 +883,7 @@ module FastlaneCore
869
883
  return upload(app_id, dir, package_path: package_path, asset_path: asset_path)
870
884
  ensure
871
885
  if use_api_key
872
- FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
886
+ @transporter_executor.cleanup(api_key)
873
887
  end
874
888
  end
875
889
 
@@ -967,7 +981,7 @@ module FastlaneCore
967
981
  return provider_ids
968
982
  ensure
969
983
  if use_api_key
970
- FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
984
+ @transporter_executor.cleanup(api_key)
971
985
  end
972
986
  end
973
987
 
@@ -387,6 +387,8 @@ module Match
387
387
 
388
388
  if Helper.mac?
389
389
  installed_profile = FastlaneCore::ProvisioningProfile.install(stored_profile_path, keychain_path)
390
+ else
391
+ UI.message("Skipping installation of provisioning profile as it would not work on this operating system.")
390
392
  end
391
393
 
392
394
  if params[:output_path]
@@ -2,6 +2,7 @@ require 'open-uri'
2
2
  require 'ostruct'
3
3
 
4
4
  require_relative '../../module'
5
+ require_relative '../interface'
5
6
 
6
7
  module Match
7
8
  module Storage
@@ -36,7 +37,7 @@ module Match
36
37
  url = URI("#{file_url}/download")
37
38
 
38
39
  begin
39
- destination_file = "#{working_directory}/#{@file.name}"
40
+ destination_file = Storage.download_path(working_directory, @file.name)
40
41
 
41
42
  create_subfolders(working_directory)
42
43
  File.open(destination_file, "wb") do |saved_file|
@@ -158,7 +158,7 @@ module Match
158
158
 
159
159
  bucket.files.each do |current_file|
160
160
  file_path = current_file.name # e.g. "N8X438SEU2/certs/distribution/XD9G7QCACF.cer"
161
- download_path = File.join(self.working_directory, file_path)
161
+ download_path = Storage.download_path(self.working_directory, file_path)
162
162
 
163
163
  FileUtils.mkdir_p(File.expand_path("..", download_path))
164
164
  UI.verbose("Downloading file from Google Cloud Storage '#{file_path}' on bucket #{self.bucket_name}")
@@ -2,6 +2,16 @@ require_relative '../module'
2
2
 
3
3
  module Match
4
4
  module Storage
5
+ # Where a file named `remote_name` by the storage is written in `working_directory`.
6
+ # Fails when the name would place it outside, e.g. with `..` segments.
7
+ def self.download_path(working_directory, remote_name)
8
+ root = File.expand_path(working_directory)
9
+ path = File.expand_path(File.join(root, remote_name))
10
+ return path if path.start_with?(root + File::SEPARATOR)
11
+
12
+ UI.user_error!("Refusing to download #{remote_name.inspect}: it would be written outside #{root}")
13
+ end
14
+
5
15
  class Interface
6
16
  MATCH_VERSION_FILE_NAME = "match_version.txt"
7
17
  # The working directory in which we download all the profiles
@@ -114,7 +114,7 @@ module Match
114
114
  file_path = strip_s3_object_prefix(object.key) # :s3_object_prefix:team_id/path/to/file
115
115
 
116
116
  # strip s3_prefix from file_path
117
- download_path = File.join(self.working_directory, file_path)
117
+ download_path = Storage.download_path(self.working_directory, file_path)
118
118
 
119
119
  FileUtils.mkdir_p(File.expand_path("..", download_path))
120
120
  UI.verbose("Downloading file from S3 '#{file_path}' on bucket #{self.s3_bucket}")
@@ -1,7 +1,5 @@
1
- require 'ostruct'
2
1
  require 'spaceship'
3
2
  require_relative 'module'
4
- require_relative 'service'
5
3
 
6
4
  module Produce
7
5
  class DeveloperCenter
@@ -88,8 +86,6 @@ module Produce
88
86
  end
89
87
 
90
88
  def create_new_app
91
- return create_new_app_via_connect_api if using_connect_api?
92
-
93
89
  ENV["CREATED_NEW_APP_ID"] = Time.now.to_i.to_s
94
90
  if app_exists?
95
91
  UI.success("[DevCenter] App '#{Produce.config[:app_identifier]}' already exists, nothing to do on the Dev Center")
@@ -122,31 +118,6 @@ module Produce
122
118
  return true
123
119
  end
124
120
 
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
-
150
121
  def enable_services
151
122
  app_service = Spaceship.app_service
152
123
  enabled_clean_options = {}
@@ -190,17 +161,6 @@ module Produce
190
161
 
191
162
  private
192
163
 
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
-
204
164
  def platform
205
165
  # This was added to support creation of multiple platforms
206
166
  # Produce::ItunesConnect can take an array of platforms to create for App Store Connect
@@ -214,16 +174,9 @@ module Produce
214
174
  Spaceship.app.find(app_identifier, mac: platform == "osx") != nil
215
175
  end
216
176
 
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
-
225
177
  def login
226
- Produce.authenticate_connect_api!
178
+ Spaceship.login(Produce.config[:username], nil)
179
+ Spaceship.select_team
227
180
  end
228
181
  end
229
182
  end
@@ -1,5 +1,4 @@
1
1
  require 'fastlane_core/helper'
2
- require 'spaceship'
3
2
 
4
3
  module Produce
5
4
  class << self
@@ -12,24 +11,4 @@ module Produce
12
11
 
13
12
  ENV['FASTLANE_TEAM_ID'] ||= ENV["PRODUCE_TEAM_ID"]
14
13
  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
35
14
  end
@@ -17,23 +17,6 @@ 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]),
37
20
  FastlaneCore::ConfigItem.new(key: :app_identifier,
38
21
  env_name: "PRODUCE_APP_IDENTIFIER",
39
22
  short_option: "-a",
@@ -419,9 +419,10 @@ module Produce
419
419
 
420
420
  def bundle_id
421
421
  return @bundle_id if @bundle_id
422
-
423
- Produce.authenticate_connect_api!
424
-
422
+ UI.message("Starting login with user '#{Produce.config[:username]}'")
423
+ Spaceship.login(Produce.config[:username], nil)
424
+ Spaceship.select_team
425
+ UI.message("Successfully logged in")
425
426
  @bundle_id ||= Spaceship::ConnectAPI::BundleId.find(Produce.config[:app_identifier].to_s)
426
427
  end
427
428
  end
@@ -41,7 +41,11 @@ module Sigh
41
41
  ENV["SIGH_UDID"] = ENV["SIGH_UUID"] = uuid if uuid
42
42
  ENV["SIGH_NAME"] = name if name
43
43
 
44
- FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
44
+ if Helper.mac?
45
+ FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
46
+ else
47
+ UI.message("Skipping installation of provisioning profile as it would not work on this operating system.")
48
+ end
45
49
  end
46
50
  end
47
51
  end
@@ -84,6 +84,8 @@ module Snapshot
84
84
  {
85
85
  # snapshot in Xcode 9 saves screenshots with the SIMULATOR_DEVICE_NAME
86
86
  # which includes spaces
87
+ 'iPhone 18 Pro Max' => "iPhone 18 Pro Max",
88
+ 'iPhone 18 Pro' => "iPhone 18 Pro",
87
89
  'iPhone 17 Pro Max' => "iPhone 17 Pro Max",
88
90
  'iPhone 17 Pro' => "iPhone 17 Pro",
89
91
  'iPhone 17' => "iPhone 17",
@@ -252,6 +252,8 @@ module Spaceship
252
252
  else
253
253
  # Log to file by default
254
254
  path = File.join(Dir.tmpdir, "spaceship#{Time.now.to_i}_#{Process.pid}_#{Thread.current.object_id}.log")
255
+ # The log holds every request and response body: create it readable by its owner only
256
+ File.open(path, File::WRONLY | File::CREAT | File::APPEND, 0o600) {}
255
257
  @logger = Logger.new(path)
256
258
  end
257
259
 
@@ -279,10 +281,12 @@ module Spaceship
279
281
  def store_cookie(path: nil)
280
282
  path ||= persistent_cookie_path
281
283
  FileUtils.mkdir_p(File.expand_path("..", path))
284
+ File.chmod(0o700, File.expand_path("..", path))
282
285
 
283
286
  # really important to specify the session to true
284
287
  # otherwise myacinfo and more won't be stored
285
288
  @cookie.save(path, :yaml, session: true)
289
+ File.chmod(0o600, path)
286
290
  return File.read(path)
287
291
  end
288
292
 
@@ -880,6 +884,8 @@ module Spaceship
880
884
  begin
881
885
  if File.exist?(persistent_cookie_path)
882
886
  puts("Loading session from '#{persistent_cookie_path}'") if Spaceship::Globals.verbose?
887
+ File.chmod(0o700, File.expand_path("..", persistent_cookie_path))
888
+ File.chmod(0o600, persistent_cookie_path)
883
889
  @cookie.load(persistent_cookie_path)
884
890
  return true
885
891
  end
@@ -1144,7 +1150,7 @@ module Spaceship
1144
1150
 
1145
1151
  def log_response(method, url, response, headers = nil, &block)
1146
1152
  url ||= extract_key_from_block('url', &block)
1147
- body = response.body.kind_of?(String) ? response.body.force_encoding(Encoding::UTF_8) : response.body
1153
+ body = response.body.kind_of?(String) ? response.body.dup.force_encoding(Encoding::UTF_8) : response.body
1148
1154
  logger.debug("<< #{method.upcase} #{url}: #{response.status} #{body}")
1149
1155
  end
1150
1156
 
@@ -19,6 +19,7 @@ module Spaceship
19
19
  APP_IPHONE_61 = "APP_IPHONE_61"
20
20
  APP_IPHONE_65 = "APP_IPHONE_65"
21
21
  APP_IPHONE_67 = "APP_IPHONE_67"
22
+ APP_IPHONE_DUO = "APP_IPHONE_DUO"
22
23
 
23
24
  APP_IPAD_97 = "APP_IPAD_97"
24
25
  APP_IPAD_105 = "APP_IPAD_105"
@@ -77,6 +78,7 @@ module Spaceship
77
78
  APP_IPHONE_61,
78
79
  APP_IPHONE_65,
79
80
  APP_IPHONE_67,
81
+ APP_IPHONE_DUO,
80
82
 
81
83
  APP_IPAD_97,
82
84
  APP_IPAD_105,
@@ -81,23 +81,12 @@ module Spaceship
81
81
  return resp.to_models.first
82
82
  end
83
83
 
84
- # `patch_bundle_id_capability` patches the wrong resource (bundleIds, with
85
- # attributes nested inside a relationship's `data` entry) — invalid JSON:API,
86
- # rejected by Apple. This uses the real per-capability endpoints instead:
87
- # create to enable, PATCH to update settings, delete to disable.
88
84
  def update_capability(capability_type, enabled: false, settings: [], client: nil)
89
85
  raise "capability_type is required " if capability_type.nil?
90
86
 
91
87
  client ||= Spaceship::ConnectAPI
92
- existing = get_capabilities(client: client).find { |capability| capability.is_type?(capability_type) }
93
-
94
- if enabled
95
- return create_capability(capability_type, settings: settings, client: client) unless existing
96
- return existing.update!(client: client, settings: settings)
97
- else
98
- existing&.delete!(client: client)
99
- return nil
100
- end
88
+ resp = client.patch_bundle_id_capability(bundle_id_id: id, seed_id: seed_id, enabled: enabled, capability_type: capability_type, settings: settings)
89
+ return resp.to_models.first
101
90
  end
102
91
  end
103
92
  end
@@ -133,12 +133,6 @@ module Spaceship
133
133
  client ||= Spaceship::ConnectAPI
134
134
  client.delete_bundle_id_capability(bundle_id_capability_id: id)
135
135
  end
136
-
137
- def update!(client: nil, enabled: true, settings: [])
138
- client ||= Spaceship::ConnectAPI
139
- resp = client.patch_bundle_id_capability_configuration(bundle_id_capability_id: id, enabled: enabled, settings: settings)
140
- return resp.to_models.first
141
- end
142
136
  end
143
137
  end
144
138
  end
@@ -89,22 +89,6 @@ module Spaceship
89
89
  provisioning_request_client.post("#{Version::V1}/bundleIdCapabilities", body)
90
90
  end
91
91
 
92
- # Modify a Capability Configuration — patches the bundleIdCapability resource
93
- # directly (attributes at the top level), not the bundleId it belongs to.
94
- def patch_bundle_id_capability_configuration(bundle_id_capability_id:, enabled: false, settings: [])
95
- body = {
96
- data: {
97
- type: "bundleIdCapabilities",
98
- id: bundle_id_capability_id,
99
- attributes: {
100
- enabled: enabled,
101
- settings: settings
102
- }
103
- }
104
- }
105
- provisioning_request_client.patch("#{Version::V1}/bundleIdCapabilities/#{bundle_id_capability_id}", body)
106
- end
107
-
108
92
  def patch_bundle_id_capability(bundle_id_id:, seed_id:, enabled: false, capability_type:, settings: [])
109
93
  body = {
110
94
  data: {
metadata CHANGED
@@ -1,36 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.241.0
4
+ version: 2.241.2
5
5
  platform: ruby
6
6
  authors:
7
- - Maksym Grebenets
8
- - Manu Wallner
9
- - Jan Piotrowski
10
- - Fumiya Nakamura
11
- - Satoshi Namai
7
+ - Josh Holtz
12
8
  - Jimmy Dee
13
- - Matthew Ellis
9
+ - Joshua Liebowitz
14
10
  - Max Ott
15
- - Łukasz Grabowski
16
- - Iulian Onofrei
17
11
  - Olivier Halligon
18
- - Jérôme Lacoste
19
12
  - Helmut Januschka
20
- - Daniel Jankowski
21
- - Andrew McBurney
13
+ - Satoshi Namai
14
+ - Danielle Tomlinson
15
+ - Manish Rathi
16
+ - Iulian Onofrei
17
+ - Kohki Miki
22
18
  - Jorge Revuelta H
23
- - Aaron Brager
24
- - Josh Holtz
19
+ - Andrew McBurney
25
20
  - Connor Tumbleson
21
+ - Jan Piotrowski
22
+ - Maksym Grebenets
23
+ - Manu Wallner
26
24
  - Stefan Natchev
27
- - Luka Mirosevic
28
- - Manish Rathi
25
+ - Łukasz Grabowski
26
+ - Matthew Ellis
29
27
  - Roger Oba
30
- - Kohki Miki
28
+ - Luka Mirosevic
29
+ - Aaron Brager
30
+ - Fumiya Nakamura
31
+ - Jérôme Lacoste
32
+ - Daniel Jankowski
31
33
  - Felix Krause
32
- - Joshua Liebowitz
33
- - Danielle Tomlinson
34
34
  bindir: bin
35
35
  cert_chain: []
36
36
  date: 1980-01-02 00:00:00.000000000 Z