fastlane-plugin-appcenter 0.1.7 → 0.2.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.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b7aee7355c0b29ee7bf79054cf8a1036b59ccf37adb847b9dc0dc62cf421f25f
|
4
|
+
data.tar.gz: bb7c314a2e16662fd9aef3d820fb6e31e097f7ccff7807ab2d4e990b8c0d8e72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c24839b70a4b7366b8966a57adabf13b66e6f04e94c494f1f851d49e7ca0620cbca701c217af7990abf1f1591b16ca46950c37a0ad2ad4072c4b34d195e7e4c
|
7
|
+
data.tar.gz: 5667213b8132cc43957cd3dc5124b579104e5c3cac9eaeeb012440a3c54bfe13b3618a38646edc31f7b99debe0a2b5f2b748fa649eb2e890d3dec9866f548f5d
|
data/README.md
CHANGED
@@ -38,7 +38,11 @@ The action parameters `api_token` and `owner_name` can also be omitted when thei
|
|
38
38
|
- `APPCENTER_DISTRIBUTE_DSYM` - Path to your symbols file. For iOS provide path to app.dSYM.zip
|
39
39
|
- `APPCENTER_DISTRIBUTE_UPLOAD_DSYM_ONLY` - Flag to upload only the dSYM file to App Center
|
40
40
|
- `APPCENTER_DISTRIBUTE_GROUP` - Comma separated list of Distribution Group names
|
41
|
+
- `APPCENTER_DISTRIBUTE_MANDATORY_UPDATE` - Require users to update to this release
|
42
|
+
- `APPCENTER_DISTRIBUTE_NOTIFY_TESTERS` - Send email notification about release
|
41
43
|
- `APPCENTER_DISTRIBUTE_RELEASE_NOTES` - Release notes
|
44
|
+
- `APPCENTER_DISTRIBUTE_RELEASE_NOTES_CLIPPING` - Clip release notes if its length is more then 5000, `true` by default
|
45
|
+
- `APPCENTER_DISTRIBUTE_RELEASE_NOTES_LINK` - Additional release notes link
|
42
46
|
|
43
47
|
## Example
|
44
48
|
|
@@ -1,262 +1,10 @@
|
|
1
|
-
# rubocop:disable Metrics/ClassLength
|
2
1
|
module Fastlane
|
3
2
|
module Actions
|
4
|
-
module SharedValues
|
5
|
-
APPCENTER_DOWNLOAD_LINK = :APPCENTER_DOWNLOAD_LINK
|
6
|
-
APPCENTER_BUILD_INFORMATION = :APPCENTER_BUILD_INFORMATION
|
7
|
-
end
|
8
|
-
|
9
3
|
module Constants
|
10
4
|
MAX_RELEASE_NOTES_LENGTH = 5000
|
11
5
|
end
|
12
6
|
|
13
7
|
class AppcenterUploadAction < Action
|
14
|
-
# create request
|
15
|
-
def self.connection(upload_url = false, dsym = false)
|
16
|
-
require 'faraday'
|
17
|
-
require 'faraday_middleware'
|
18
|
-
|
19
|
-
options = {
|
20
|
-
url: upload_url ? upload_url : "https://api.appcenter.ms"
|
21
|
-
}
|
22
|
-
|
23
|
-
Faraday.new(options) do |builder|
|
24
|
-
if upload_url
|
25
|
-
builder.request :multipart unless dsym
|
26
|
-
builder.request :url_encoded unless dsym
|
27
|
-
else
|
28
|
-
builder.request :json
|
29
|
-
end
|
30
|
-
builder.response :json, content_type: /\bjson$/
|
31
|
-
builder.use FaradayMiddleware::FollowRedirects
|
32
|
-
builder.adapter :net_http
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# creates new release upload
|
37
|
-
# returns:
|
38
|
-
# upload_id
|
39
|
-
# upload_url
|
40
|
-
def self.create_release_upload(api_token, owner_name, app_name)
|
41
|
-
connection = self.connection
|
42
|
-
|
43
|
-
response = connection.post do |req|
|
44
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads")
|
45
|
-
req.headers['X-API-Token'] = api_token
|
46
|
-
req.headers['internal-request-source'] = "fastlane"
|
47
|
-
req.body = {}
|
48
|
-
end
|
49
|
-
|
50
|
-
case response.status
|
51
|
-
when 200...300
|
52
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
53
|
-
response.body
|
54
|
-
when 401
|
55
|
-
UI.user_error!("Auth Error, provided invalid token")
|
56
|
-
false
|
57
|
-
when 404
|
58
|
-
UI.error("Not found, invalid owner or application name")
|
59
|
-
false
|
60
|
-
else
|
61
|
-
UI.error("Error #{response.status}: #{response.body}")
|
62
|
-
false
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# creates new dSYM upload in appcenter
|
67
|
-
# returns:
|
68
|
-
# symbol_upload_id
|
69
|
-
# upload_url
|
70
|
-
# expiration_date
|
71
|
-
def self.create_dsym_upload(api_token, owner_name, app_name)
|
72
|
-
connection = self.connection
|
73
|
-
|
74
|
-
response = connection.post do |req|
|
75
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads")
|
76
|
-
req.headers['X-API-Token'] = api_token
|
77
|
-
req.headers['internal-request-source'] = "fastlane"
|
78
|
-
req.body = {
|
79
|
-
symbol_type: 'Apple'
|
80
|
-
}
|
81
|
-
end
|
82
|
-
|
83
|
-
case response.status
|
84
|
-
when 200...300
|
85
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
86
|
-
response.body
|
87
|
-
when 401
|
88
|
-
UI.user_error!("Auth Error, provided invalid token")
|
89
|
-
false
|
90
|
-
when 404
|
91
|
-
UI.error("Not found, invalid owner or application name")
|
92
|
-
false
|
93
|
-
else
|
94
|
-
UI.error("Error #{response.status}: #{response.body}")
|
95
|
-
false
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# committs or aborts dsym upload
|
100
|
-
def self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, status)
|
101
|
-
connection = self.connection
|
102
|
-
|
103
|
-
response = connection.patch do |req|
|
104
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads/#{symbol_upload_id}")
|
105
|
-
req.headers['X-API-Token'] = api_token
|
106
|
-
req.headers['internal-request-source'] = "fastlane"
|
107
|
-
req.body = {
|
108
|
-
"status" => status
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
|
-
case response.status
|
113
|
-
when 200...300
|
114
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
115
|
-
response.body
|
116
|
-
else
|
117
|
-
UI.error("Error #{response.status}: #{response.body}")
|
118
|
-
false
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# upload dSYM files to specified upload url
|
123
|
-
# if succeed, then commits the upload
|
124
|
-
# otherwise aborts
|
125
|
-
def self.upload_dsym(api_token, owner_name, app_name, dsym, symbol_upload_id, upload_url)
|
126
|
-
connection = self.connection(upload_url, true)
|
127
|
-
|
128
|
-
response = connection.put do |req|
|
129
|
-
req.headers['x-ms-blob-type'] = "BlockBlob"
|
130
|
-
req.headers['Content-Length'] = File.size(dsym).to_s
|
131
|
-
req.headers['internal-request-source'] = "fastlane"
|
132
|
-
req.body = Faraday::UploadIO.new(dsym, 'application/octet-stream') if dsym && File.exist?(dsym)
|
133
|
-
end
|
134
|
-
|
135
|
-
case response.status
|
136
|
-
when 200...300
|
137
|
-
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'committed')
|
138
|
-
UI.success("dSYM uploaded")
|
139
|
-
else
|
140
|
-
UI.error("Error uploading dSYM #{response.status}: #{response.body}")
|
141
|
-
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'aborted')
|
142
|
-
UI.error("dSYM upload aborted")
|
143
|
-
false
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# upload binary for specified upload_url
|
148
|
-
# if succeed, then commits the release
|
149
|
-
# otherwise aborts
|
150
|
-
def self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url)
|
151
|
-
connection = self.connection(upload_url)
|
152
|
-
|
153
|
-
options = {}
|
154
|
-
options[:upload_id] = upload_id
|
155
|
-
# ipa field is used both for .apk and .ipa files
|
156
|
-
options[:ipa] = Faraday::UploadIO.new(file, 'application/octet-stream') if file && File.exist?(file)
|
157
|
-
|
158
|
-
response = connection.post do |req|
|
159
|
-
req.headers['internal-request-source'] = "fastlane"
|
160
|
-
req.body = options
|
161
|
-
end
|
162
|
-
|
163
|
-
case response.status
|
164
|
-
when 200...300
|
165
|
-
UI.message("Binary uploaded")
|
166
|
-
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'committed')
|
167
|
-
else
|
168
|
-
UI.error("Error uploading binary #{response.status}: #{response.body}")
|
169
|
-
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'aborted')
|
170
|
-
UI.error("Release aborted")
|
171
|
-
false
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
# Commits or aborts the upload process for a release
|
176
|
-
def self.update_release_upload(api_token, owner_name, app_name, upload_id, status)
|
177
|
-
connection = self.connection
|
178
|
-
|
179
|
-
response = connection.patch do |req|
|
180
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads/#{upload_id}")
|
181
|
-
req.headers['X-API-Token'] = api_token
|
182
|
-
req.headers['internal-request-source'] = "fastlane"
|
183
|
-
req.body = {
|
184
|
-
"status" => status
|
185
|
-
}
|
186
|
-
end
|
187
|
-
|
188
|
-
case response.status
|
189
|
-
when 200...300
|
190
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
191
|
-
response.body
|
192
|
-
else
|
193
|
-
UI.error("Error #{response.status}: #{response.body}")
|
194
|
-
false
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
# get existing release
|
199
|
-
def self.get_release(api_token, release_url)
|
200
|
-
connection = self.connection
|
201
|
-
response = connection.get do |req|
|
202
|
-
req.url("/#{release_url}")
|
203
|
-
req.headers['X-API-Token'] = api_token
|
204
|
-
req.headers['internal-request-source'] = "fastlane"
|
205
|
-
end
|
206
|
-
|
207
|
-
case response.status
|
208
|
-
when 200...300
|
209
|
-
release = response.body
|
210
|
-
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
211
|
-
release
|
212
|
-
when 404
|
213
|
-
UI.error("Not found, invalid release url")
|
214
|
-
false
|
215
|
-
else
|
216
|
-
UI.error("Error fetching information about release #{response.status}: #{response.body}")
|
217
|
-
false
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# add release to distribution group
|
222
|
-
def self.add_to_group(api_token, release_url, group_name, release_notes = '')
|
223
|
-
connection = self.connection
|
224
|
-
|
225
|
-
response = connection.patch do |req|
|
226
|
-
req.url("/#{release_url}")
|
227
|
-
req.headers['X-API-Token'] = api_token
|
228
|
-
req.headers['internal-request-source'] = "fastlane"
|
229
|
-
req.body = {
|
230
|
-
"distribution_group_name" => group_name,
|
231
|
-
"release_notes" => release_notes
|
232
|
-
}
|
233
|
-
end
|
234
|
-
|
235
|
-
case response.status
|
236
|
-
when 200...300
|
237
|
-
# get full release info
|
238
|
-
release = self.get_release(api_token, release_url)
|
239
|
-
return false unless release
|
240
|
-
download_url = release['download_url']
|
241
|
-
|
242
|
-
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
243
|
-
|
244
|
-
Actions.lane_context[SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
245
|
-
Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
246
|
-
|
247
|
-
UI.message("Public Download URL: #{download_url}") if download_url
|
248
|
-
UI.success("Release #{release['short_version']} was successfully distributed to group \"#{group_name}\"")
|
249
|
-
|
250
|
-
release
|
251
|
-
when 404
|
252
|
-
UI.error("Not found, invalid distribution group name")
|
253
|
-
false
|
254
|
-
else
|
255
|
-
UI.error("Error adding to group #{response.status}: #{response.body}")
|
256
|
-
false
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
8
|
# run whole upload process for dSYM files
|
261
9
|
def self.run_dsym_upload(params)
|
262
10
|
values = params.values
|
@@ -289,14 +37,14 @@ module Fastlane
|
|
289
37
|
values[:dsym_path] = dsym_path
|
290
38
|
|
291
39
|
UI.message("Starting dSYM upload...")
|
292
|
-
dsym_upload_details =
|
40
|
+
dsym_upload_details = Helper::AppcenterHelper.create_dsym_upload(api_token, owner_name, app_name)
|
293
41
|
|
294
42
|
if dsym_upload_details
|
295
43
|
symbol_upload_id = dsym_upload_details['symbol_upload_id']
|
296
44
|
upload_url = dsym_upload_details['upload_url']
|
297
45
|
|
298
46
|
UI.message("Uploading dSYM...")
|
299
|
-
|
47
|
+
Helper::AppcenterHelper.upload_dsym(api_token, owner_name, app_name, dsym_path, symbol_upload_id, upload_url)
|
300
48
|
end
|
301
49
|
end
|
302
50
|
end
|
@@ -308,6 +56,8 @@ module Fastlane
|
|
308
56
|
owner_name = params[:owner_name]
|
309
57
|
app_name = params[:app_name]
|
310
58
|
group = params[:group]
|
59
|
+
mandatory_update = params[:mandatory_update]
|
60
|
+
notify_testers = params[:notify_testers]
|
311
61
|
release_notes = params[:release_notes]
|
312
62
|
should_clip = params[:should_clip]
|
313
63
|
release_notes_link = params[:release_notes_link]
|
@@ -333,48 +83,39 @@ module Fastlane
|
|
333
83
|
UI.user_error!("No Distribute Group given, pass using `group: 'group name'`") unless group && !group.empty?
|
334
84
|
|
335
85
|
UI.message("Starting release upload...")
|
336
|
-
upload_details =
|
86
|
+
upload_details = Helper::AppcenterHelper.create_release_upload(api_token, owner_name, app_name)
|
337
87
|
if upload_details
|
338
88
|
upload_id = upload_details['upload_id']
|
339
89
|
upload_url = upload_details['upload_url']
|
340
90
|
|
341
91
|
UI.message("Uploading release binary...")
|
342
|
-
uploaded =
|
92
|
+
uploaded = Helper::AppcenterHelper.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url)
|
343
93
|
|
344
94
|
if uploaded
|
345
|
-
|
346
|
-
UI.message("Release committed")
|
95
|
+
release_id = uploaded['release_id']
|
96
|
+
UI.message("Release '#{release_id}' committed")
|
97
|
+
|
98
|
+
Helper::AppcenterHelper.update_release(api_token, owner_name, app_name, release_id, release_notes)
|
99
|
+
|
347
100
|
groups = group.split(',')
|
348
101
|
groups.each do |group_name|
|
349
|
-
|
102
|
+
group = Helper::AppcenterHelper.get_group(api_token, owner_name, app_name, group_name)
|
103
|
+
if group
|
104
|
+
group_id = group['id']
|
105
|
+
distributed_release = Helper::AppcenterHelper.add_to_group(api_token, owner_name, app_name, release_id, group_id, mandatory_update, notify_testers)
|
106
|
+
if distributed_release
|
107
|
+
UI.success("Release #{distributed_release['short_version']} was successfully distributed to group \"#{group_name}\"")
|
108
|
+
else
|
109
|
+
UI.error("Release '#{release_id}' was not found")
|
110
|
+
end
|
111
|
+
else
|
112
|
+
UI.error("Group '#{group_name}' was not found")
|
113
|
+
end
|
350
114
|
end
|
351
115
|
end
|
352
116
|
end
|
353
117
|
end
|
354
118
|
|
355
|
-
# returns true if app exists, false in case of 404 and error otherwise
|
356
|
-
def self.get_app(api_token, owner_name, app_name)
|
357
|
-
connection = self.connection
|
358
|
-
|
359
|
-
response = connection.get do |req|
|
360
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}")
|
361
|
-
req.headers['X-API-Token'] = api_token
|
362
|
-
req.headers['internal-request-source'] = "fastlane"
|
363
|
-
end
|
364
|
-
|
365
|
-
case response.status
|
366
|
-
when 200...300
|
367
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
368
|
-
true
|
369
|
-
when 404
|
370
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
371
|
-
false
|
372
|
-
else
|
373
|
-
UI.error("Error #{response.status}: #{response.body}")
|
374
|
-
false
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
119
|
# checks app existance, if ther is no such - creates it
|
379
120
|
def self.get_or_create_app(params)
|
380
121
|
api_token = params[:api_token]
|
@@ -386,37 +127,14 @@ module Fastlane
|
|
386
127
|
"iOS" => ['Objective-C-Swift', 'React-Native', 'Xamarin']
|
387
128
|
}
|
388
129
|
|
389
|
-
if
|
130
|
+
if Helper::AppcenterHelper.get_app(api_token, owner_name, app_name)
|
390
131
|
true
|
391
132
|
else
|
392
133
|
if Helper.test? || UI.confirm("App with name #{app_name} not found, create one?")
|
393
|
-
connection = self.connection
|
394
|
-
|
395
134
|
os = Helper.test? ? "Android" : UI.select("Select OS", ["Android", "iOS"])
|
396
135
|
platform = Helper.test? ? "Java" : UI.select("Select Platform", platforms[os])
|
397
136
|
|
398
|
-
|
399
|
-
req.url("/v0.1/apps")
|
400
|
-
req.headers['X-API-Token'] = api_token
|
401
|
-
req.headers['internal-request-source'] = "fastlane"
|
402
|
-
req.body = {
|
403
|
-
"display_name" => app_name,
|
404
|
-
"name" => app_name,
|
405
|
-
"os" => os,
|
406
|
-
"platform" => platform
|
407
|
-
}
|
408
|
-
end
|
409
|
-
|
410
|
-
case response.status
|
411
|
-
when 200...300
|
412
|
-
created = response.body
|
413
|
-
UI.message("DEBUG: #{JSON.pretty_generate(created)}") if ENV['DEBUG']
|
414
|
-
UI.success("Created #{os}/#{platform} app with name \"#{created['name']}\"")
|
415
|
-
true
|
416
|
-
else
|
417
|
-
UI.error("Error creating app #{response.status}: #{response.body}")
|
418
|
-
false
|
419
|
-
end
|
137
|
+
Helper::AppcenterHelper.create_app(api_token, owner_name, app_name, os, platform)
|
420
138
|
else
|
421
139
|
UI.error("Lane aborted")
|
422
140
|
false
|
@@ -446,9 +164,7 @@ module Fastlane
|
|
446
164
|
end
|
447
165
|
|
448
166
|
def self.details
|
449
|
-
|
450
|
-
"Symbols will also be uploaded automatically if a `app.dSYM.zip` file is found next to `app.ipa`. In case it is located in a different place you can specify the path explicitly in `:dsym` parameter."
|
451
|
-
]
|
167
|
+
"Symbols will also be uploaded automatically if a `app.dSYM.zip` file is found next to `app.ipa`. In case it is located in a different place you can specify the path explicitly in `:dsym` parameter."
|
452
168
|
end
|
453
169
|
|
454
170
|
def self.available_options
|
@@ -536,6 +252,20 @@ module Fastlane
|
|
536
252
|
optional: true,
|
537
253
|
type: String),
|
538
254
|
|
255
|
+
FastlaneCore::ConfigItem.new(key: :mandatory_update,
|
256
|
+
env_name: "APPCENTER_DISTRIBUTE_MANDATORY_UPDATE",
|
257
|
+
description: "Require users to update to this release",
|
258
|
+
optional: true,
|
259
|
+
is_string: false,
|
260
|
+
default_value: false),
|
261
|
+
|
262
|
+
FastlaneCore::ConfigItem.new(key: :notify_testers,
|
263
|
+
env_name: "APPCENTER_DISTRIBUTE_NOTIFY_TESTERS",
|
264
|
+
description: "Send email notification about release",
|
265
|
+
optional: true,
|
266
|
+
is_string: false,
|
267
|
+
default_value: false),
|
268
|
+
|
539
269
|
FastlaneCore::ConfigItem.new(key: :release_notes,
|
540
270
|
env_name: "APPCENTER_DISTRIBUTE_RELEASE_NOTES",
|
541
271
|
description: "Release notes",
|
@@ -545,7 +275,7 @@ module Fastlane
|
|
545
275
|
|
546
276
|
FastlaneCore::ConfigItem.new(key: :should_clip,
|
547
277
|
env_name: "APPCENTER_DISTRIBUTE_RELEASE_NOTES_CLIPPING",
|
548
|
-
description: "Clip release notes if its
|
278
|
+
description: "Clip release notes if its length is more then #{Constants::MAX_RELEASE_NOTES_LENGTH}, true by default",
|
549
279
|
optional: true,
|
550
280
|
is_string: false,
|
551
281
|
default_value: true),
|
@@ -593,4 +323,3 @@ module Fastlane
|
|
593
323
|
end
|
594
324
|
end
|
595
325
|
end
|
596
|
-
# rubocop:enable Metrics/ClassLength
|
@@ -1,11 +1,370 @@
|
|
1
1
|
module Fastlane
|
2
2
|
module Helper
|
3
|
+
module SharedValues
|
4
|
+
APPCENTER_DOWNLOAD_LINK = :APPCENTER_DOWNLOAD_LINK
|
5
|
+
APPCENTER_BUILD_INFORMATION = :APPCENTER_BUILD_INFORMATION
|
6
|
+
end
|
7
|
+
|
3
8
|
class AppcenterHelper
|
4
|
-
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
|
10
|
+
# create request
|
11
|
+
def self.connection(upload_url = false, dsym = false)
|
12
|
+
require 'faraday'
|
13
|
+
require 'faraday_middleware'
|
14
|
+
|
15
|
+
options = {
|
16
|
+
url: upload_url ? upload_url : "https://api.appcenter.ms"
|
17
|
+
}
|
18
|
+
|
19
|
+
Faraday.new(options) do |builder|
|
20
|
+
if upload_url
|
21
|
+
builder.request :multipart unless dsym
|
22
|
+
builder.request :url_encoded unless dsym
|
23
|
+
else
|
24
|
+
builder.request :json
|
25
|
+
end
|
26
|
+
builder.response :json, content_type: /\bjson$/
|
27
|
+
builder.use FaradayMiddleware::FollowRedirects
|
28
|
+
builder.adapter :net_http
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# creates new release upload
|
33
|
+
# returns:
|
34
|
+
# upload_id
|
35
|
+
# upload_url
|
36
|
+
def self.create_release_upload(api_token, owner_name, app_name)
|
37
|
+
connection = self.connection
|
38
|
+
|
39
|
+
response = connection.post do |req|
|
40
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads")
|
41
|
+
req.headers['X-API-Token'] = api_token
|
42
|
+
req.headers['internal-request-source'] = "fastlane"
|
43
|
+
req.body = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
case response.status
|
47
|
+
when 200...300
|
48
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
49
|
+
response.body
|
50
|
+
when 401
|
51
|
+
UI.user_error!("Auth Error, provided invalid token")
|
52
|
+
false
|
53
|
+
when 404
|
54
|
+
UI.error("Not found, invalid owner or application name")
|
55
|
+
false
|
56
|
+
else
|
57
|
+
UI.error("Error #{response.status}: #{response.body}")
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# creates new dSYM upload in appcenter
|
63
|
+
# returns:
|
64
|
+
# symbol_upload_id
|
65
|
+
# upload_url
|
66
|
+
# expiration_date
|
67
|
+
def self.create_dsym_upload(api_token, owner_name, app_name)
|
68
|
+
connection = self.connection
|
69
|
+
|
70
|
+
response = connection.post do |req|
|
71
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads")
|
72
|
+
req.headers['X-API-Token'] = api_token
|
73
|
+
req.headers['internal-request-source'] = "fastlane"
|
74
|
+
req.body = {
|
75
|
+
symbol_type: 'Apple'
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
case response.status
|
80
|
+
when 200...300
|
81
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
82
|
+
response.body
|
83
|
+
when 401
|
84
|
+
UI.user_error!("Auth Error, provided invalid token")
|
85
|
+
false
|
86
|
+
when 404
|
87
|
+
UI.error("Not found, invalid owner or application name")
|
88
|
+
false
|
89
|
+
else
|
90
|
+
UI.error("Error #{response.status}: #{response.body}")
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# committs or aborts dsym upload
|
96
|
+
def self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, status)
|
97
|
+
connection = self.connection
|
98
|
+
|
99
|
+
response = connection.patch do |req|
|
100
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads/#{symbol_upload_id}")
|
101
|
+
req.headers['X-API-Token'] = api_token
|
102
|
+
req.headers['internal-request-source'] = "fastlane"
|
103
|
+
req.body = {
|
104
|
+
"status" => status
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
case response.status
|
109
|
+
when 200...300
|
110
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
111
|
+
response.body
|
112
|
+
else
|
113
|
+
UI.error("Error #{response.status}: #{response.body}")
|
114
|
+
false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# upload dSYM files to specified upload url
|
119
|
+
# if succeed, then commits the upload
|
120
|
+
# otherwise aborts
|
121
|
+
def self.upload_dsym(api_token, owner_name, app_name, dsym, symbol_upload_id, upload_url)
|
122
|
+
connection = self.connection(upload_url, true)
|
123
|
+
|
124
|
+
response = connection.put do |req|
|
125
|
+
req.headers['x-ms-blob-type'] = "BlockBlob"
|
126
|
+
req.headers['Content-Length'] = File.size(dsym).to_s
|
127
|
+
req.headers['internal-request-source'] = "fastlane"
|
128
|
+
req.body = Faraday::UploadIO.new(dsym, 'application/octet-stream') if dsym && File.exist?(dsym)
|
129
|
+
end
|
130
|
+
|
131
|
+
case response.status
|
132
|
+
when 200...300
|
133
|
+
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'committed')
|
134
|
+
UI.success("dSYM uploaded")
|
135
|
+
else
|
136
|
+
UI.error("Error uploading dSYM #{response.status}: #{response.body}")
|
137
|
+
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'aborted')
|
138
|
+
UI.error("dSYM upload aborted")
|
139
|
+
false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# upload binary for specified upload_url
|
144
|
+
# if succeed, then commits the release
|
145
|
+
# otherwise aborts
|
146
|
+
def self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url)
|
147
|
+
connection = self.connection(upload_url)
|
148
|
+
|
149
|
+
options = {}
|
150
|
+
options[:upload_id] = upload_id
|
151
|
+
# ipa field is used both for .apk and .ipa files
|
152
|
+
options[:ipa] = Faraday::UploadIO.new(file, 'application/octet-stream') if file && File.exist?(file)
|
153
|
+
|
154
|
+
response = connection.post do |req|
|
155
|
+
req.headers['internal-request-source'] = "fastlane"
|
156
|
+
req.body = options
|
157
|
+
end
|
158
|
+
|
159
|
+
case response.status
|
160
|
+
when 200...300
|
161
|
+
UI.message("Binary uploaded")
|
162
|
+
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'committed')
|
163
|
+
else
|
164
|
+
UI.error("Error uploading binary #{response.status}: #{response.body}")
|
165
|
+
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'aborted')
|
166
|
+
UI.error("Release aborted")
|
167
|
+
false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Commits or aborts the upload process for a release
|
172
|
+
def self.update_release_upload(api_token, owner_name, app_name, upload_id, status)
|
173
|
+
connection = self.connection
|
174
|
+
|
175
|
+
response = connection.patch do |req|
|
176
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads/#{upload_id}")
|
177
|
+
req.headers['X-API-Token'] = api_token
|
178
|
+
req.headers['internal-request-source'] = "fastlane"
|
179
|
+
req.body = {
|
180
|
+
"status" => status
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
case response.status
|
185
|
+
when 200...300
|
186
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
187
|
+
response.body
|
188
|
+
else
|
189
|
+
UI.error("Error #{response.status}: #{response.body}")
|
190
|
+
false
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# get existing release
|
195
|
+
def self.get_release(api_token, owner_name, app_name, release_id)
|
196
|
+
connection = self.connection
|
197
|
+
response = connection.get do |req|
|
198
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}")
|
199
|
+
req.headers['X-API-Token'] = api_token
|
200
|
+
req.headers['internal-request-source'] = "fastlane"
|
201
|
+
end
|
202
|
+
|
203
|
+
case response.status
|
204
|
+
when 200...300
|
205
|
+
release = response.body
|
206
|
+
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
207
|
+
release
|
208
|
+
when 404
|
209
|
+
UI.error("Not found, invalid release url")
|
210
|
+
false
|
211
|
+
else
|
212
|
+
UI.error("Error fetching information about release #{response.status}: #{response.body}")
|
213
|
+
false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# get distribution group
|
218
|
+
def self.get_group(api_token, owner_name, app_name, group_name)
|
219
|
+
connection = self.connection
|
220
|
+
|
221
|
+
response = connection.get do |req|
|
222
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups/#{group_name}")
|
223
|
+
req.headers['X-API-Token'] = api_token
|
224
|
+
req.headers['internal-request-source'] = "fastlane"
|
225
|
+
end
|
226
|
+
|
227
|
+
case response.status
|
228
|
+
when 200...300
|
229
|
+
group = response.body
|
230
|
+
UI.message("DEBUG: received group #{JSON.pretty_generate(group)}") if ENV['DEBUG']
|
231
|
+
group
|
232
|
+
when 404
|
233
|
+
UI.error("Not found, invalid distribution group name")
|
234
|
+
false
|
235
|
+
else
|
236
|
+
UI.error("Error getting group #{response.status}: #{response.body}")
|
237
|
+
false
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# add release to destination
|
242
|
+
def self.update_release(api_token, owner_name, app_name, release_id, release_notes = '')
|
243
|
+
connection = self.connection
|
244
|
+
|
245
|
+
response = connection.put do |req|
|
246
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}")
|
247
|
+
req.headers['X-API-Token'] = api_token
|
248
|
+
req.headers['internal-request-source'] = "fastlane"
|
249
|
+
req.body = {
|
250
|
+
"release_notes" => release_notes
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
case response.status
|
255
|
+
when 200...300
|
256
|
+
# get full release info
|
257
|
+
release = self.get_release(api_token, owner_name, app_name, release_id)
|
258
|
+
return false unless release
|
259
|
+
download_url = release['download_url']
|
260
|
+
|
261
|
+
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
262
|
+
|
263
|
+
Actions.lane_context[SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
264
|
+
Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
265
|
+
|
266
|
+
UI.message("Release #{release['short_version']} was successfully updated")
|
267
|
+
|
268
|
+
release
|
269
|
+
when 404
|
270
|
+
UI.error("Not found, invalid release id")
|
271
|
+
false
|
272
|
+
else
|
273
|
+
UI.error("Error adding updating release #{response.status}: #{response.body}")
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# add release to distribution group
|
279
|
+
def self.add_to_group(api_token, owner_name, app_name, release_id, group_id, mandatory_update = false, notify_testers = false)
|
280
|
+
connection = self.connection
|
281
|
+
|
282
|
+
UI.message("DEBUG: getting #{release_id}") if ENV['DEBUG']
|
283
|
+
|
284
|
+
response = connection.post do |req|
|
285
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}/groups")
|
286
|
+
req.headers['X-API-Token'] = api_token
|
287
|
+
req.headers['internal-request-source'] = "fastlane"
|
288
|
+
req.body = {
|
289
|
+
"id" => group_id,
|
290
|
+
"mandatory_update" => mandatory_update,
|
291
|
+
"notify_testers" => notify_testers
|
292
|
+
}
|
293
|
+
end
|
294
|
+
|
295
|
+
case response.status
|
296
|
+
when 200...300
|
297
|
+
# get full release info
|
298
|
+
release = self.get_release(api_token, owner_name, app_name, release_id)
|
299
|
+
return false unless release
|
300
|
+
download_url = release['download_url']
|
301
|
+
|
302
|
+
UI.message("DEBUG: received release #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
303
|
+
|
304
|
+
Actions.lane_context[SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
305
|
+
Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
306
|
+
|
307
|
+
UI.message("Public Download URL: #{download_url}") if download_url
|
308
|
+
|
309
|
+
release
|
310
|
+
when 404
|
311
|
+
UI.error("Not found, invalid distribution group name")
|
312
|
+
false
|
313
|
+
else
|
314
|
+
UI.error("Error adding to group #{response.status}: #{response.body}")
|
315
|
+
false
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# returns true if app exists, false in case of 404 and error otherwise
|
320
|
+
def self.get_app(api_token, owner_name, app_name)
|
321
|
+
connection = self.connection
|
322
|
+
|
323
|
+
response = connection.get do |req|
|
324
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}")
|
325
|
+
req.headers['X-API-Token'] = api_token
|
326
|
+
req.headers['internal-request-source'] = "fastlane"
|
327
|
+
end
|
328
|
+
|
329
|
+
case response.status
|
330
|
+
when 200...300
|
331
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
332
|
+
true
|
333
|
+
when 404
|
334
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
335
|
+
false
|
336
|
+
else
|
337
|
+
UI.error("Error #{response.status}: #{response.body}")
|
338
|
+
false
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# returns true if app exists, false in case of 404 and error otherwise
|
343
|
+
def self.create_app(api_token, owner_name, app_name, os, platform)
|
344
|
+
connection = self.connection
|
345
|
+
|
346
|
+
response = connection.post do |req|
|
347
|
+
req.url("/v0.1/apps")
|
348
|
+
req.headers['X-API-Token'] = api_token
|
349
|
+
req.headers['internal-request-source'] = "fastlane"
|
350
|
+
req.body = {
|
351
|
+
"display_name" => app_name,
|
352
|
+
"name" => app_name,
|
353
|
+
"os" => os,
|
354
|
+
"platform" => platform
|
355
|
+
}
|
356
|
+
end
|
357
|
+
|
358
|
+
case response.status
|
359
|
+
when 200...300
|
360
|
+
created = response.body
|
361
|
+
UI.message("DEBUG: #{JSON.pretty_generate(created)}") if ENV['DEBUG']
|
362
|
+
UI.success("Created #{os}/#{platform} app with name \"#{created['name']}\"")
|
363
|
+
true
|
364
|
+
else
|
365
|
+
UI.error("Error creating app #{response.status}: #{response.body}")
|
366
|
+
false
|
367
|
+
end
|
9
368
|
end
|
10
369
|
end
|
11
370
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-appcenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Microsoft Corporation
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -126,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
126
|
version: '0'
|
127
127
|
requirements: []
|
128
128
|
rubyforge_project:
|
129
|
-
rubygems_version: 2.6
|
129
|
+
rubygems_version: 2.7.6
|
130
130
|
signing_key:
|
131
131
|
specification_version: 4
|
132
132
|
summary: Fastlane plugin for App Center
|