fastlane 1.62.0 → 1.63.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b38f8789384c96fbbf2a8135629f4ac47326912a
4
- data.tar.gz: 6d57b912e744ee6d4eeccab52a0c3bbaec11cada
3
+ metadata.gz: 6a1d03886641cb6e37a2e1050678327f3ff8ffa4
4
+ data.tar.gz: aabe7a99dcbb2568bab3c949ac944bdf192f9546
5
5
  SHA512:
6
- metadata.gz: 831c8ab0c35ce8cdf8c289e973eee995653ff92268a31ffa58d8fa1e769f13a0a8d23825e47d098e328c7c1d5b5cec72dc0c2fcff5701419ae9da520eceb6d31
7
- data.tar.gz: eeb059e37cb59f69d8c15e9e8480a09d6476ab15bab4f36610f74f76c7ef975c5da3d2d1ad9729359c78bbc5dfe1b0a31d7096a34b9e2f312f69e57f3509f397
6
+ metadata.gz: c7163d0dda0c6c01f15a6a3007fc616b90cc9293df083d2c172fb0013057f92c36ff990b0429ec32daa5a7099ca9a61a7a8dc6ec2ca83766c7c578ba3d44a0f3
7
+ data.tar.gz: 6ba39cddb16a1f3bcec3bcd9ee3feabc29f58e1a32ab2a94bbc72d0abcc7df40930869c2ee7ee5e620b5348565940532960e811c751575630aaff42a25608d27
data/README.md CHANGED
@@ -89,7 +89,7 @@ fastlane appstore
89
89
  :mountain_cableway: | Implement a fully working Continuous Delivery process
90
90
  :ghost: | [Jenkins Integration](https://github.com/fastlane/fastlane/blob/master/docs/Jenkins.md): Show the output directly in the Jenkins test results
91
91
  :book: | Automatically generate a markdown documentation of your lane config
92
- :hatching_chick: | Over 140 built-in integrations available
92
+ :hatching_chick: | Over 150 built-in integrations available
93
93
  :computer: | Support for both iOS, Mac OS and Android apps
94
94
  :octocat: | Full git and mercurial support
95
95
 
@@ -54,7 +54,7 @@
54
54
 
55
55
  <div class="oneRow">
56
56
  <span class="download" id="ios">
57
- <a href="itms-services://?action=download-manifest&url=itms-services://?action=download-manifest&url=<%= url %>" id="text" class="btn btn-lg btn-default" onclick="document.getElementById('finished').id = '';">
57
+ <a href="itms-services://?action=download-manifest&url=itms-services://?action=download-manifest&url=<%= plist_url %>" id="text" class="btn btn-lg btn-default" onclick="document.getElementById('finished').id = '';">
58
58
  Install <%= title %> <%= bundle_version %>
59
59
  </a>
60
60
  </span>
@@ -62,7 +62,7 @@
62
62
  <!-- <span class="download" id="android">
63
63
  </span> -->
64
64
  </div>
65
-
65
+
66
66
  <h3 id="desktop">Please open this page on your iPhone!</h3>
67
67
 
68
68
  <p id="finished">
@@ -70,7 +70,7 @@
70
70
  </p>
71
71
 
72
72
  <p id="footnote">
73
- This is a beta version and is not meant to share with the public.
73
+ This is a beta version and is not meant to share with the public.
74
74
  </p>
75
75
  <img src="https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane_medium.png" id="fastlaneLogo" />
76
76
  </body>
@@ -92,4 +92,4 @@
92
92
  // document.getElementById("android").remove()
93
93
  }
94
94
  </script>
95
- </html>
95
+ </html>
@@ -11,7 +11,7 @@
11
11
  <key>kind</key>
12
12
  <string>software-package</string>
13
13
  <key>url</key>
14
- <string><%= url %></string>
14
+ <string><%= ipa_url %></string>
15
15
  </dict>
16
16
  </array>
17
17
  <key>metadata</key>
@@ -28,4 +28,4 @@
28
28
  </dict>
29
29
  </array>
30
30
  </dict>
31
- </plist>
31
+ </plist>
@@ -1,4 +1,4 @@
1
- {
1
+ {
2
2
  "latestVersion": "<%= full_version %>",
3
- "updateUrl": "itms-services://?action=download-manifest&url=<%= url %>"
3
+ "updateUrl": "itms-services://?action=download-manifest&url=<%= plist_url %>"
4
4
  }
@@ -1,27 +1,16 @@
1
1
  module Fastlane
2
2
  module Actions
3
3
  class AppaloosaAction < Action
4
- APPALOOSA_SERVER = 'https://www.appaloosa-store.com/api/v1'
4
+ APPALOOSA_SERVER = 'https://www.appaloosa-store.com/api/v2'.freeze
5
5
  def self.run(params)
6
- require 'http'
7
-
8
6
  api_key = params[:api_token]
9
7
  store_id = params[:store_id]
10
-
11
- if request_email?(api_key, store_id)
12
- auth = create_an_account params[:email]
13
- api_key = auth['api_key']
14
- store_id = auth['store_id']
15
- return if error_detected(auth['errors'])
16
- end
17
-
18
- binary = normalize_binary_name(params[:binary])
8
+ binary = params[:binary]
19
9
  remove_extra_screenshots_file(params[:screenshots])
20
10
  binary_url = get_binary_link(binary, api_key, store_id, params[:group_ids])
21
11
  return if binary_url.nil?
22
12
  screenshots_url = get_screenshots_links(api_key, store_id, params[:screenshots], params[:locale], params[:device])
23
13
  upload_on_appaloosa(api_key, store_id, binary_url, screenshots_url, params[:group_ids])
24
- reset_original_binary_names(binary, params[:binary])
25
14
  end
26
15
 
27
16
  def self.get_binary_link(binary, api_key, store_id, group_ids)
@@ -32,15 +21,15 @@ module Fastlane
32
21
 
33
22
  def self.upload_on_s3(file, api_key, store_id, group_ids = '')
34
23
  file_name = file.split('/').last
35
- response = HTTP.get("#{APPALOOSA_SERVER}/upload_services/presign_form",
36
- json: { file: file_name,
37
- store_id: store_id,
38
- group_ids: group_ids })
39
- json_res = JSON.parse(response)
24
+ uri = URI("#{APPALOOSA_SERVER}/upload_services/presign_form")
25
+ params = { file: file_name, store_id: store_id, group_ids: group_ids }
26
+ uri.query = URI.encode_www_form(params)
27
+ presign_form_response = Net::HTTP.get_response(uri)
28
+ json_res = JSON.parse(presign_form_response.body)
40
29
  return if error_detected json_res['errors']
41
- url = json_res['s3_sign']
30
+ s3_sign = json_res['s3_sign']
42
31
  path = json_res['path']
43
- uri = URI.parse(Base64.decode64(url))
32
+ uri = URI.parse(Base64.decode64(s3_sign))
44
33
  File.open(file, 'rb') do |f|
45
34
  Net::HTTP.start(uri.host) do |http|
46
35
  http.send_request('PUT', uri.request_uri, f.read, 'content-type' => '')
@@ -50,42 +39,23 @@ module Fastlane
50
39
  end
51
40
 
52
41
  def self.get_s3_url(api_key, store_id, path)
53
- binary_path = HTTP.get("#{APPALOOSA_SERVER}/#{store_id}/upload_services/url_for_download",
54
- json: { store_id: store_id,
55
- api_key: api_key,
56
- key: path })
57
- if binary_path.status == 404
58
- return nil if error_detected("A problem occurred with your API token and your store id. Please try again.")
42
+ uri = URI("#{APPALOOSA_SERVER}/#{store_id}/upload_services/url_for_download")
43
+ params = { store_id: store_id, api_key: api_key, key: path }
44
+ uri.query = URI.encode_www_form(params)
45
+ url_for_download_response = Net::HTTP.get_response(uri)
46
+ if url_for_download_response.kind_of?(Net::HTTPNotFound)
47
+ UI.user_error!("ERROR: A problem occurred with your API token and your store id. Please try again.")
59
48
  end
60
- json_res = JSON.parse(binary_path)
49
+ json_res = JSON.parse(url_for_download_response.body)
61
50
  return if error_detected(json_res['errors'])
62
51
  json_res['binary_url']
63
52
  end
64
53
 
65
- def self.reset_original_binary_names(current_name, original_name)
66
- File.rename("#{current_name}", "#{original_name}")
67
- end
68
-
69
54
  def self.remove_extra_screenshots_file(screenshots_env)
70
55
  extra_file = "#{screenshots_env}/screenshots.html"
71
56
  File.unlink(extra_file) if File.exist?(extra_file)
72
57
  end
73
58
 
74
- def self.normalize_binary_name(binary)
75
- binary_rename = binary.delete(' ')
76
- File.rename("#{binary}", "#{binary_rename}")
77
- binary_rename
78
- end
79
-
80
- def self.create_an_account(email)
81
- response = HTTP.post("#{APPALOOSA_SERVER}/upload_services/create_an_account", form: { email: email })
82
- JSON.parse(response)
83
- end
84
-
85
- def self.request_email?(api_key, store_id)
86
- api_key.size == 0 && store_id.size == 0
87
- end
88
-
89
59
  def self.upload_screenshots(screenshots, api_key, store_id)
90
60
  return if screenshots.nil?
91
61
  list = []
@@ -113,7 +83,7 @@ module Fastlane
113
83
  def self.get_screenshots(screenshots_path, locale, device)
114
84
  get_env_value('screenshots').nil? ? locale = '' : locale.concat('/')
115
85
  device.nil? ? device = '' : device.concat('-')
116
- screenshots_path.strip.size > 0 ? screenshots_list(screenshots_path, locale, device) : nil
86
+ !screenshots_path.strip.empty? ? screenshots_list(screenshots_path, locale, device) : nil
117
87
  end
118
88
 
119
89
  def self.screenshots_list(path, locale, device)
@@ -127,21 +97,24 @@ module Fastlane
127
97
 
128
98
  def self.upload_on_appaloosa(api_key, store_id, binary_path, screenshots, group_ids)
129
99
  screenshots = all_screenshots_links(screenshots)
130
- response = HTTP.post("#{APPALOOSA_SERVER}/#{store_id}/applications/upload",
131
- json: { store_id: store_id,
132
- api_key: api_key,
133
- application: {
134
- binary_path: binary_path,
135
- screenshot1: screenshots[0],
136
- screenshot2: screenshots[1],
137
- screenshot3: screenshots[2],
138
- screenshot4: screenshots[3],
139
- screenshot5: screenshots[4],
140
- group_ids: group_ids,
141
- provider: 'fastlane'
142
- }
143
- })
144
- json_res = JSON.parse(response)
100
+ uri = URI("#{APPALOOSA_SERVER}/#{store_id}/mobile_application_updates/upload")
101
+ http = Net::HTTP.new(uri.host, uri.port)
102
+ req = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json' })
103
+ req.body = { store_id: store_id,
104
+ api_key: api_key,
105
+ mobile_application_update: {
106
+ binary_path: binary_path,
107
+ screenshot1: screenshots[0],
108
+ screenshot2: screenshots[1],
109
+ screenshot3: screenshots[2],
110
+ screenshot4: screenshots[3],
111
+ screenshot5: screenshots[4],
112
+ group_ids: group_ids,
113
+ provider: 'fastlane'
114
+ }
115
+ }.to_json
116
+ uoa_response = http.request(req)
117
+ json_res = JSON.parse(uoa_response.body)
145
118
  if json_res['errors']
146
119
  UI.error "App: #{json_res['errors']}"
147
120
  else
@@ -172,8 +145,7 @@ module Fastlane
172
145
 
173
146
  def self.error_detected(errors)
174
147
  if errors
175
- UI.error("ERROR: #{errors}")
176
- true
148
+ UI.user_error!("ERROR: #{errors}")
177
149
  else
178
150
  false
179
151
  end
@@ -194,34 +166,28 @@ module Fastlane
194
166
 
195
167
  def self.details
196
168
  [
197
- "Appaloosa is a private mobile application store. This action offers a quick deployment on the platform.",
198
- "You can create an account, push to your existing account, or manage your user groups. We accept iOS, Mac and Android applications."
199
- ].join(" ")
169
+ "Appaloosa is a private mobile application store. This action ",
170
+ "offers a quick deployment on the platform. You can create an ",
171
+ "account, push to your existing account, or manage your user ",
172
+ "groups. We accept iOS and Android applications."
173
+ ].join("\n")
200
174
  end
201
175
 
202
176
  def self.available_options
203
177
  [
204
178
  FastlaneCore::ConfigItem.new(key: :binary,
205
179
  env_name: 'FL_APPALOOSA_BINARY',
206
- description: 'Path to your IPA or APK file. Optional for ipa if you use the `ipa` or `xcodebuild` action. For Mac zip the .app',
180
+ description: 'Binary path. Optional for ipa if you use the `ipa` or `xcodebuild` action',
207
181
  default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH],
208
182
  verify_block: proc do |value|
209
- fail "Couldn't find ipa || apk file at path '#{value}'".red unless File.exist?(value)
183
+ raise "Couldn't find ipa || apk file at path '#{value}'".red unless File.exist?(value)
210
184
  end),
211
185
  FastlaneCore::ConfigItem.new(key: :api_token,
212
186
  env_name: 'FL_APPALOOSA_API_TOKEN',
213
- description: "Your API Token, if you don\'t have an account hit [enter]",
214
- verify_block: proc do
215
- end),
187
+ description: "Your API token"),
216
188
  FastlaneCore::ConfigItem.new(key: :store_id,
217
189
  env_name: 'FL_APPALOOSA_STORE_ID',
218
- description: "Your Store id, if you don\'t have an account hit [enter]",
219
- verify_block: proc do |_value|
220
- end),
221
- FastlaneCore::ConfigItem.new(key: :email,
222
- env_name: 'FL_APPALOOSA_EMAIL',
223
- description: "It's your first time? Give your email address",
224
- optional: false),
190
+ description: "Your Store id"),
225
191
  FastlaneCore::ConfigItem.new(key: :group_ids,
226
192
  env_name: 'FL_APPALOOSA_GROUPS',
227
193
  description: 'Your app is limited to special users? Give us the group ids',
@@ -235,13 +201,11 @@ module Fastlane
235
201
  env_name: 'FL_APPALOOSA_LOCALE',
236
202
  description: 'Select the folder locale for yours screenshots',
237
203
  default_value: 'en-US',
238
- optional: true
239
- ),
204
+ optional: true),
240
205
  FastlaneCore::ConfigItem.new(key: :device,
241
206
  env_name: 'FL_APPALOOSA_DEVICE',
242
207
  description: 'Select the device format for yours screenshots',
243
- optional: true
244
- )
208
+ optional: true)
245
209
  ]
246
210
  end
247
211
 
@@ -11,7 +11,8 @@ module Fastlane
11
11
  shield: params[:shield],
12
12
  alpha: params[:alpha],
13
13
  shield_io_timeout: params[:shield_io_timeout],
14
- glob: params[:glob]
14
+ glob: params[:glob],
15
+ alpha_channel: params[:alpha_channel]
15
16
  }
16
17
  Badge::Runner.new.run(params[:path], options)
17
18
  end
@@ -91,7 +92,15 @@ module Fastlane
91
92
  env_name: "FL_BADGE_GLOB",
92
93
  description: "Glob pattern for finding image files",
93
94
  optional: true,
94
- is_string: true)
95
+ is_string: true),
96
+ FastlaneCore::ConfigItem.new(key: :alpha_channel,
97
+ env_name: "FL_BADGE_ALPHA_CHANNEL",
98
+ description: "Keeps/adds an alpha channel to the icon (useful for android icons)",
99
+ optional: true,
100
+ is_string: false,
101
+ verify_block: proc do |value|
102
+ raise "alpha_channel is only a flag and should always be true".red unless value == true
103
+ end)
95
104
  ]
96
105
  end
97
106
 
@@ -10,11 +10,11 @@ module Fastlane
10
10
  #####################################################
11
11
 
12
12
  def self.description
13
- "Return last git commit message and author"
13
+ "Return last git commit hash, abbreviated commit hash, commit message and author"
14
14
  end
15
15
 
16
16
  def self.return_value
17
- "Returns the following dict: {author: \"Author\", message: \"commit message\"}"
17
+ "Returns the following dict: {commit_hash: \"commit hash\", abbreviated_commit_hash: \"abbreviated commit hash\" author: \"Author\", message: \"commit message\"}"
18
18
  end
19
19
 
20
20
  def self.author
@@ -42,7 +42,9 @@ module Fastlane
42
42
  end
43
43
 
44
44
  def self.output
45
- ['FL_CHANGELOG', 'The changelog generated by Jenkins']
45
+ [
46
+ ['FL_CHANGELOG', 'The changelog generated by Jenkins']
47
+ ]
46
48
  end
47
49
 
48
50
  def self.authors
@@ -135,6 +135,7 @@ module Fastlane
135
135
  end
136
136
  plist_render = eth.render(plist_template, {
137
137
  url: ipa_url,
138
+ ipa_url: ipa_url,
138
139
  build_num: build_num,
139
140
  bundle_id: bundle_id,
140
141
  bundle_version: bundle_version,
@@ -149,6 +150,8 @@ module Fastlane
149
150
  end
150
151
  html_render = eth.render(html_template, {
151
152
  url: plist_url,
153
+ plist_url: plist_url,
154
+ ipa_url: ipa_url,
152
155
  build_num: build_num,
153
156
  bundle_id: bundle_id,
154
157
  bundle_version: bundle_version,
@@ -163,6 +166,8 @@ module Fastlane
163
166
  end
164
167
  version_render = eth.render(version_template, {
165
168
  url: plist_url,
169
+ plist_url: plist_url,
170
+ ipa_url: ipa_url,
166
171
  build_num: build_num,
167
172
  bundle_version: bundle_version,
168
173
  full_version: full_version
@@ -282,7 +287,8 @@ module Fastlane
282
287
  env_name: "",
283
288
  description: "Upload relevant metadata for this build",
284
289
  optional: true,
285
- default_value: true),
290
+ default_value: true,
291
+ is_string: false),
286
292
  FastlaneCore::ConfigItem.new(key: :plist_template_path,
287
293
  env_name: "",
288
294
  description: "plist template path",
@@ -28,7 +28,9 @@ module Fastlane
28
28
 
29
29
  {
30
30
  author: last_git_commit_formatted_with('%an'),
31
- message: last_git_commit_formatted_with('%B')
31
+ message: last_git_commit_formatted_with('%B'),
32
+ commit_hash: last_git_commit_formatted_with('%H'),
33
+ abbreviated_commit_hash: last_git_commit_formatted_with('%h')
32
34
  }
33
35
  end
34
36
 
@@ -22,10 +22,15 @@ module Fastlane
22
22
  if Helper.test?
23
23
  result << command # only for the tests
24
24
  else
25
- if ENV['USE_SIMPLE_POPEN'] == 'true'
26
- exit_status = execute_with_simple_popen(command, log, result)
27
- else
28
- exit_status = execute_with_popen(command, log, result)
25
+ exit_status = nil
26
+ IO.popen(command, err: [:child, :out]) do |io|
27
+ io.sync = true
28
+ io.each do |line|
29
+ UI.command_output(line.strip) if log
30
+ result << line
31
+ end
32
+ io.close
33
+ exit_status = $?.exitstatus
29
34
  end
30
35
 
31
36
  if exit_status != 0
@@ -47,30 +52,5 @@ module Fastlane
47
52
  Encoding.default_external = previous_encoding.first
48
53
  Encoding.default_internal = previous_encoding.last
49
54
  end
50
-
51
- def self.execute_with_popen(command, log, result)
52
- exit_status = nil
53
- IO.popen(command, err: [:child, :out]) do |io|
54
- io.sync = true
55
- io.each do |line|
56
- UI.command_output(line.strip) if log
57
- result << line
58
- end
59
- io.close
60
- exit_status = $?.exitstatus
61
- end
62
- exit_status
63
- end
64
-
65
- def self.execute_with_simple_popen(command, log, result)
66
- puts 'Using simple popen'
67
- IO.popen(command, err: [:child, :out]) do |io|
68
- io.each do |line|
69
- UI.command_output(line.strip) if log
70
- result << line
71
- end
72
- end
73
- $?.exitstatus
74
- end
75
55
  end
76
56
  end
@@ -22,29 +22,48 @@ module Fastlane
22
22
 
23
23
  show_infos
24
24
 
25
+ FastlaneFolder.create_folder! unless Helper.is_test?
26
+ fastlane_actions_path = File.join(FastlaneFolder.path, 'actions')
27
+ is_manual_setup = false
28
+
25
29
  begin
26
- FastlaneFolder.create_folder! unless Helper.is_test?
27
30
  setup_project
28
31
  ask_for_apple_id
29
32
  detect_if_app_is_available
30
33
  print_config_table
31
- fastlane_actions_path = File.join(FastlaneFolder.path, 'actions')
32
34
  if UI.confirm("Please confirm the above values")
33
35
  default_setup(path: fastlane_actions_path)
34
36
  else
37
+ is_manual_setup = true
35
38
  manual_setup(path: fastlane_actions_path)
36
39
  end
37
40
  Helper.log.info 'Successfully finished setting up fastlane'.green
38
41
  rescue => ex # this will also be caused by Ctrl + C
39
- # Something went wrong with the setup, clear the folder again
40
- # and restore previous files
41
- Helper.log.fatal 'Error occurred with the setup program! Reverting changes now!'.red
42
- restore_previous_state
43
- raise ex
42
+ if is_manual_setup
43
+ handle_exception(exception: ex)
44
+ else
45
+ Helper.log.error ex.to_s
46
+ Helper.log.error 'An error occured during the setup process. Falling back to manual setup!'.yellow
47
+ try_manual_setup(path: fastlane_actions_path)
48
+ end
44
49
  end
45
50
  # rubocop:enable Lint/RescueException
46
51
  end
47
52
 
53
+ def handle_exception(exception: nil)
54
+ # Something went wrong with the setup, clear the folder again
55
+ # and restore previous files
56
+ Helper.log.fatal 'Error occurred with the setup program! Reverting changes now!'.red
57
+ restore_previous_state
58
+ raise exception
59
+ end
60
+
61
+ def try_manual_setup(path: nil)
62
+ manual_setup(path: path)
63
+ rescue => ex
64
+ handle_exception(exception: ex)
65
+ end
66
+
48
67
  def default_setup(path: nil)
49
68
  copy_existing_files
50
69
  generate_appfile(manually: false)
@@ -70,7 +89,7 @@ module Fastlane
70
89
 
71
90
  def ask_to_enable_other_tools
72
91
  if self.itc_ref.nil? && self.portal_ref.nil?
73
- wants_to_create_app = agree('Would you like to create your app on iTunes Connect and the Developer Portal?', true)
92
+ wants_to_create_app = agree('Would you like to create your app on iTunes Connect and the Developer Portal? (y/n)', true)
74
93
  if wants_to_create_app
75
94
  create_app_if_necessary
76
95
  detect_if_app_is_available # check if the app was, in fact, created.
@@ -1,3 +1,3 @@
1
1
  module Fastlane
2
- VERSION = '1.62.0'
2
+ VERSION = '1.63.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.62.0
4
+ version: 1.63.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felix Krause
@@ -47,7 +47,7 @@ dependencies:
47
47
  version: '0.20'
48
48
  - - "<"
49
49
  - !ruby/object:Gem::Version
50
- version: 1.0.0
50
+ version: 2.0.0
51
51
  type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
@@ -57,7 +57,7 @@ dependencies:
57
57
  version: '0.20'
58
58
  - - "<"
59
59
  - !ruby/object:Gem::Version
60
- version: 1.0.0
60
+ version: 2.0.0
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: xcpretty
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -827,7 +827,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
827
827
  version: '0'
828
828
  requirements: []
829
829
  rubyforge_project:
830
- rubygems_version: 2.2.2
830
+ rubygems_version: 2.5.0
831
831
  signing_key:
832
832
  specification_version: 4
833
833
  summary: Connect all iOS deployment tools into one streamlined workflow