fastlane-plugin-appcenter 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +84 -0
- data/lib/fastlane/plugin/appcenter.rb +16 -0
- data/lib/fastlane/plugin/appcenter/actions/appcenter_upload_action.rb +535 -0
- data/lib/fastlane/plugin/appcenter/helper/appcenter_helper.rb +12 -0
- data/lib/fastlane/plugin/appcenter/version.rb +5 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 48df8a17edb752b7e11b38125b5e205971e9e2be
|
4
|
+
data.tar.gz: 7a0672dd1a6250ce6225a42c23cb30ac74e82177
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29d4b4696c8cacdee4e2ad44ea52b806a77ff60fabee8ef4da0516688016f855caa2895a367348cdc8fffb9c750603b89098c45e77d295dcd7fd5295a0859b26
|
7
|
+
data.tar.gz: 69e9c5a5bf119ad4afbced987b4522384ab9c56469e52d3c49d043d5d00e3bc3508ed6bbe955877830ae00512e930376165f73950b2ef252124bbf26328dec54
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# appcenter plugin
|
2
|
+
|
3
|
+
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-appcenter)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/fastlane-plugin-appcenter.svg)](https://badge.fury.io/rb/fastlane-plugin-appcenter)
|
5
|
+
[![Build Status](https://travis-ci.org/Microsoft/fastlane-plugin-appcenter.svg?branch=master)](https://travis-ci.org/Microsoft/fastlane-plugin-appcenter)
|
6
|
+
|
7
|
+
## Getting Started
|
8
|
+
|
9
|
+
This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-appcenter`, add it to your project by running:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
fastlane add_plugin appcenter
|
13
|
+
```
|
14
|
+
|
15
|
+
## About appcenter
|
16
|
+
|
17
|
+
Plugin for [App Center](https://appcenter.ms). Provides `appcenter_upload` action for [release distribution](https://docs.microsoft.com/en-us/appcenter/distribution/uploading) and [dSYM uploads](https://docs.microsoft.com/en-us/appcenter/crashes/ios)
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Obtain an [API token](https://docs.microsoft.com/en-us/appcenter/api-docs/). API Token is used for authentication for all App Center API calls.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
appcenter_upload(
|
25
|
+
api_token: "<appcenter token>",
|
26
|
+
owner_name: "<your appcenter account name>",
|
27
|
+
app_name: "<your app name>",
|
28
|
+
apk: "<path to android build binary>"
|
29
|
+
)
|
30
|
+
```
|
31
|
+
|
32
|
+
## Example
|
33
|
+
|
34
|
+
Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
35
|
+
|
36
|
+
Sample uses `.env` for setting private variables like API token, owner name, .etc. You need to replace it in `Fastfile` by your own values.
|
37
|
+
|
38
|
+
There are three examples in `test` lane:
|
39
|
+
- upload release for android with minimum required parameters
|
40
|
+
- upload release for ios with all set parameters
|
41
|
+
- upload only dSYM file for ios
|
42
|
+
|
43
|
+
## Run tests for this plugin
|
44
|
+
|
45
|
+
To run both the tests, and code style validation, run
|
46
|
+
|
47
|
+
```
|
48
|
+
rake
|
49
|
+
```
|
50
|
+
|
51
|
+
To automatically fix many of the styling issues, use
|
52
|
+
```
|
53
|
+
rubocop -a
|
54
|
+
```
|
55
|
+
|
56
|
+
## Issues and Feedback
|
57
|
+
|
58
|
+
For any other issues and feedback about this plugin, please submit it to this repository.
|
59
|
+
|
60
|
+
## Troubleshooting
|
61
|
+
|
62
|
+
If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
|
63
|
+
|
64
|
+
## Using _fastlane_ Plugins
|
65
|
+
|
66
|
+
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
|
67
|
+
|
68
|
+
## About _fastlane_
|
69
|
+
|
70
|
+
_fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
75
|
+
|
76
|
+
## Contact
|
77
|
+
|
78
|
+
### Intercom
|
79
|
+
|
80
|
+
If you have further questions, want to provide feedback or you are running into issues, log in to the [App Center](https://appcenter.ms) portal and use the blue Intercom button on the bottom right to start a conversation with us.
|
81
|
+
|
82
|
+
### Twitter
|
83
|
+
|
84
|
+
We're on Twitter as [@appcenter](https://www.twitter.com/vsappcenter).
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'fastlane/plugin/appcenter/version'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Appcenter
|
5
|
+
# Return all .rb files inside the "actions" and "helper" directory
|
6
|
+
def self.all_classes
|
7
|
+
Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# By default we want to import all available actions and helpers
|
13
|
+
# A plugin can contain any number of actions and plugins
|
14
|
+
Fastlane::Appcenter.all_classes.each do |current|
|
15
|
+
require current
|
16
|
+
end
|
@@ -0,0 +1,535 @@
|
|
1
|
+
# rubocop:disable Metrics/ClassLength
|
2
|
+
module Fastlane
|
3
|
+
module Actions
|
4
|
+
module SharedValues
|
5
|
+
APPCENTER_DOWNLOAD_LINK = :APPCENTER_DOWNLOAD_LINK
|
6
|
+
APPCENTER_BUILD_INFORMATION = :APPCENTER_BUILD_INFORMATION
|
7
|
+
end
|
8
|
+
|
9
|
+
class AppcenterUploadAction < Action
|
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
|
+
# add release to distribution group
|
195
|
+
def self.add_to_group(api_token, release_url, group_name, release_notes = '')
|
196
|
+
connection = self.connection
|
197
|
+
|
198
|
+
response = connection.patch do |req|
|
199
|
+
req.url("/#{release_url}")
|
200
|
+
req.headers['X-API-Token'] = api_token
|
201
|
+
req.headers['internal-request-source'] = "fastlane"
|
202
|
+
req.body = {
|
203
|
+
"distribution_group_name" => group_name,
|
204
|
+
"release_notes" => release_notes
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
case response.status
|
209
|
+
when 200...300
|
210
|
+
release = response.body
|
211
|
+
download_url = release['download_url']
|
212
|
+
|
213
|
+
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
214
|
+
|
215
|
+
Actions.lane_context[SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
216
|
+
Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
217
|
+
|
218
|
+
UI.message("Public Download URL: #{download_url}") if download_url
|
219
|
+
UI.success("Release #{release['short_version']} was successfully distributed")
|
220
|
+
|
221
|
+
release
|
222
|
+
when 404
|
223
|
+
UI.error("Not found, invalid distribution group name")
|
224
|
+
false
|
225
|
+
else
|
226
|
+
UI.error("Error adding to group #{response.status}: #{response.body}")
|
227
|
+
false
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# run whole upload process for dSYM files
|
232
|
+
def self.run_dsym_upload(params)
|
233
|
+
values = params.values
|
234
|
+
api_token = params[:api_token]
|
235
|
+
owner_name = params[:owner_name]
|
236
|
+
app_name = params[:app_name]
|
237
|
+
file = params[:ipa]
|
238
|
+
dsym = params[:dsym]
|
239
|
+
|
240
|
+
dsym_path = nil
|
241
|
+
if dsym
|
242
|
+
# we can use dsym parameter only if build file is ipa
|
243
|
+
dsym_path = dsym if !file || File.extname(file) == '.ipa'
|
244
|
+
else
|
245
|
+
# if dsym is note set, but build is ipa - check default path
|
246
|
+
if file && File.exist?(file) && File.extname(file) == '.ipa'
|
247
|
+
dsym_path = file.to_s.gsub('.ipa', '.dSYM.zip')
|
248
|
+
UI.message("dSYM is found")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# if we provided valid dsym path, or <ipa_path>.dSYM.zip was found, start dSYM upload
|
253
|
+
if dsym_path && File.exist?(dsym_path)
|
254
|
+
if File.directory?(dsym_path)
|
255
|
+
UI.message("dSYM path is folder, zipping...")
|
256
|
+
dsym_path = Actions::ZipAction.run(path: dsym, output_path: dsym + ".zip")
|
257
|
+
UI.message("dSYM files zipped")
|
258
|
+
end
|
259
|
+
|
260
|
+
values[:dsym_path] = dsym_path
|
261
|
+
|
262
|
+
UI.message("Starting dSYM upload...")
|
263
|
+
dsym_upload_details = self.create_dsym_upload(api_token, owner_name, app_name)
|
264
|
+
|
265
|
+
if dsym_upload_details
|
266
|
+
symbol_upload_id = dsym_upload_details['symbol_upload_id']
|
267
|
+
upload_url = dsym_upload_details['upload_url']
|
268
|
+
|
269
|
+
UI.message("Uploading dSYM...")
|
270
|
+
self.upload_dsym(api_token, owner_name, app_name, dsym_path, symbol_upload_id, upload_url)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# run whole upload process for release
|
276
|
+
def self.run_release_upload(params)
|
277
|
+
api_token = params[:api_token]
|
278
|
+
owner_name = params[:owner_name]
|
279
|
+
app_name = params[:app_name]
|
280
|
+
group = params[:group]
|
281
|
+
|
282
|
+
file = [
|
283
|
+
params[:ipa],
|
284
|
+
params[:apk]
|
285
|
+
].detect { |e| !e.to_s.empty? }
|
286
|
+
|
287
|
+
UI.user_error!("Couldn't find build file at path '#{file}'") unless file && File.exist?(file)
|
288
|
+
UI.user_error!("No Distribute Group given, pass using `group: 'group name'`") unless group && !group.empty?
|
289
|
+
|
290
|
+
UI.message("Starting release upload...")
|
291
|
+
upload_details = self.create_release_upload(api_token, owner_name, app_name)
|
292
|
+
if upload_details
|
293
|
+
upload_id = upload_details['upload_id']
|
294
|
+
upload_url = upload_details['upload_url']
|
295
|
+
|
296
|
+
UI.message("Uploading release binary...")
|
297
|
+
uploaded = self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url)
|
298
|
+
|
299
|
+
if uploaded
|
300
|
+
release_url = uploaded['release_url']
|
301
|
+
UI.message("Release committed")
|
302
|
+
self.add_to_group(api_token, release_url, group, params[:release_notes])
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# returns true if app exists, false in case of 404 and error otherwise
|
308
|
+
def self.get_app(api_token, owner_name, app_name)
|
309
|
+
connection = self.connection
|
310
|
+
|
311
|
+
response = connection.get do |req|
|
312
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}")
|
313
|
+
req.headers['X-API-Token'] = api_token
|
314
|
+
req.headers['internal-request-source'] = "fastlane"
|
315
|
+
end
|
316
|
+
|
317
|
+
case response.status
|
318
|
+
when 200...300
|
319
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
320
|
+
true
|
321
|
+
when 404
|
322
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
323
|
+
false
|
324
|
+
else
|
325
|
+
UI.error("Error #{response.status}: #{response.body}")
|
326
|
+
false
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# checks app existance, if ther is no such - creates it
|
331
|
+
def self.get_or_create_app(params)
|
332
|
+
api_token = params[:api_token]
|
333
|
+
owner_name = params[:owner_name]
|
334
|
+
app_name = params[:app_name]
|
335
|
+
|
336
|
+
platforms = {
|
337
|
+
"Android" => ['Java', 'React-Native', 'Xamarin'],
|
338
|
+
"iOS" => ['Objective-C-Swift', 'React-Native', 'Xamarin']
|
339
|
+
}
|
340
|
+
|
341
|
+
if self.get_app(api_token, owner_name, app_name)
|
342
|
+
true
|
343
|
+
else
|
344
|
+
if Helper.test? || UI.confirm("App with name #{app_name} not found, create one?")
|
345
|
+
connection = self.connection
|
346
|
+
|
347
|
+
os = Helper.test? ? "Android" : UI.select("Select OS", ["Android", "iOS"])
|
348
|
+
platform = Helper.test? ? "Java" : UI.select("Select Platform", platforms[os])
|
349
|
+
|
350
|
+
response = connection.post do |req|
|
351
|
+
req.url("/v0.1/apps")
|
352
|
+
req.headers['X-API-Token'] = api_token
|
353
|
+
req.headers['internal-request-source'] = "fastlane"
|
354
|
+
req.body = {
|
355
|
+
"display_name" => app_name,
|
356
|
+
"name" => app_name,
|
357
|
+
"os" => os,
|
358
|
+
"platform" => platform
|
359
|
+
}
|
360
|
+
end
|
361
|
+
|
362
|
+
case response.status
|
363
|
+
when 200...300
|
364
|
+
created = response.body
|
365
|
+
UI.message("DEBUG: #{JSON.pretty_generate(created)}") if ENV['DEBUG']
|
366
|
+
UI.success("Created #{os}/#{platform} app with name \"#{created['name']}\"")
|
367
|
+
true
|
368
|
+
else
|
369
|
+
UI.error("Error creating app #{response.status}: #{response.body}")
|
370
|
+
false
|
371
|
+
end
|
372
|
+
else
|
373
|
+
UI.error("Lane aborted")
|
374
|
+
false
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def self.run(params)
|
380
|
+
values = params.values
|
381
|
+
upload_dsym_only = params[:upload_dsym_only]
|
382
|
+
|
383
|
+
# if app found or successfully created
|
384
|
+
if self.get_or_create_app(params)
|
385
|
+
self.run_release_upload(params) unless upload_dsym_only
|
386
|
+
self.run_dsym_upload(params)
|
387
|
+
end
|
388
|
+
|
389
|
+
return values if Helper.test?
|
390
|
+
end
|
391
|
+
|
392
|
+
def self.description
|
393
|
+
"Distribute new release to App Center"
|
394
|
+
end
|
395
|
+
|
396
|
+
def self.authors
|
397
|
+
["Microsoft"]
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.details
|
401
|
+
[
|
402
|
+
"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."
|
403
|
+
]
|
404
|
+
end
|
405
|
+
|
406
|
+
def self.available_options
|
407
|
+
[
|
408
|
+
FastlaneCore::ConfigItem.new(key: :api_token,
|
409
|
+
env_name: "APPCENTER_API_TOKEN",
|
410
|
+
description: "API Token for App Center",
|
411
|
+
optional: false,
|
412
|
+
type: String,
|
413
|
+
verify_block: proc do |value|
|
414
|
+
UI.user_error!("No API token for App Center given, pass using `api_token: 'token'`") unless value && !value.empty?
|
415
|
+
end),
|
416
|
+
|
417
|
+
FastlaneCore::ConfigItem.new(key: :owner_name,
|
418
|
+
env_name: "APPCENTER_OWNER_NAME",
|
419
|
+
description: "Owner name",
|
420
|
+
optional: false,
|
421
|
+
type: String,
|
422
|
+
verify_block: proc do |value|
|
423
|
+
UI.user_error!("No Owner name for App Center given, pass using `owner_name: 'name'`") unless value && !value.empty?
|
424
|
+
end),
|
425
|
+
|
426
|
+
FastlaneCore::ConfigItem.new(key: :app_name,
|
427
|
+
env_name: "APPCENTER_APP_NAME",
|
428
|
+
description: "App name. If there is no app with such name, you will be prompted to create one",
|
429
|
+
optional: false,
|
430
|
+
type: String,
|
431
|
+
verify_block: proc do |value|
|
432
|
+
UI.user_error!("No App name given, pass using `app_name: 'app name'`") unless value && !value.empty?
|
433
|
+
end),
|
434
|
+
|
435
|
+
FastlaneCore::ConfigItem.new(key: :apk,
|
436
|
+
env_name: "APPCENTER_DISTRIBUTE_APK",
|
437
|
+
description: "Build release path for android build",
|
438
|
+
default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH],
|
439
|
+
optional: true,
|
440
|
+
type: String,
|
441
|
+
conflicting_options: [:ipa],
|
442
|
+
conflict_block: proc do |value|
|
443
|
+
UI.user_error!("You can't use 'apk' and '#{value.key}' options in one run")
|
444
|
+
end,
|
445
|
+
verify_block: proc do |value|
|
446
|
+
accepted_formats = [".apk"]
|
447
|
+
UI.user_error!("Only \".apk\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
|
448
|
+
end),
|
449
|
+
|
450
|
+
FastlaneCore::ConfigItem.new(key: :ipa,
|
451
|
+
env_name: "APPCENTER_DISTRIBUTE_IPA",
|
452
|
+
description: "Build release path for ios build",
|
453
|
+
default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH],
|
454
|
+
optional: true,
|
455
|
+
type: String,
|
456
|
+
conflicting_options: [:apk],
|
457
|
+
conflict_block: proc do |value|
|
458
|
+
UI.user_error!("You can't use 'ipa' and '#{value.key}' options in one run")
|
459
|
+
end,
|
460
|
+
verify_block: proc do |value|
|
461
|
+
accepted_formats = [".ipa"]
|
462
|
+
UI.user_error!("Only \".ipa\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
|
463
|
+
end),
|
464
|
+
|
465
|
+
FastlaneCore::ConfigItem.new(key: :dsym,
|
466
|
+
env_name: "APPCENTER_DISTRIBUTE_DSYM",
|
467
|
+
description: "Path to your symbols file. For iOS provide path to app.dSYM.zip",
|
468
|
+
default_value: Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH],
|
469
|
+
optional: true,
|
470
|
+
type: String,
|
471
|
+
verify_block: proc do |value|
|
472
|
+
if value
|
473
|
+
UI.user_error!("Couldn't find dSYM file at path '#{value}'") unless File.exist?(value)
|
474
|
+
end
|
475
|
+
end),
|
476
|
+
|
477
|
+
FastlaneCore::ConfigItem.new(key: :upload_dsym_only,
|
478
|
+
env_name: "APPCENTER_DISTRIBUTE_UPLOAD_DSYM_ONLY",
|
479
|
+
description: "Flag to upload only the dSYM file to App Center",
|
480
|
+
optional: true,
|
481
|
+
is_string: false,
|
482
|
+
default_value: false),
|
483
|
+
|
484
|
+
FastlaneCore::ConfigItem.new(key: :group,
|
485
|
+
env_name: "APPCENTER_DISTRIBUTE_GROUP",
|
486
|
+
description: "Distribute group name",
|
487
|
+
default_value: "Collaborators",
|
488
|
+
optional: true,
|
489
|
+
type: String),
|
490
|
+
|
491
|
+
FastlaneCore::ConfigItem.new(key: :release_notes,
|
492
|
+
env_name: "APPCENTER_DISTRIBUTE_RELEASE_NOTES",
|
493
|
+
description: "Release notes",
|
494
|
+
default_value: Actions.lane_context[SharedValues::FL_CHANGELOG] || "No changelog given",
|
495
|
+
optional: true,
|
496
|
+
type: String)
|
497
|
+
]
|
498
|
+
end
|
499
|
+
|
500
|
+
def self.output
|
501
|
+
[
|
502
|
+
['APPCENTER_DOWNLOAD_LINK', 'The newly generated download link for this build'],
|
503
|
+
['APPCENTER_BUILD_INFORMATION', 'contains all keys/values from the App Center API']
|
504
|
+
]
|
505
|
+
end
|
506
|
+
|
507
|
+
def self.is_supported?(platform)
|
508
|
+
true
|
509
|
+
end
|
510
|
+
|
511
|
+
def self.example_code
|
512
|
+
[
|
513
|
+
'appcenter_upload(
|
514
|
+
api_token: "...",
|
515
|
+
owner_name: "appcenter_owner",
|
516
|
+
app_name: "testing_app",
|
517
|
+
apk: "./app-release.apk",
|
518
|
+
group: "Testers",
|
519
|
+
release_notes: "release notes"
|
520
|
+
)',
|
521
|
+
'appcenter_upload(
|
522
|
+
api_token: "...",
|
523
|
+
owner_name: "appcenter_owner",
|
524
|
+
app_name: "testing_app",
|
525
|
+
apk: "./app-release.ipa",
|
526
|
+
group: "Testers",
|
527
|
+
dsym: "./app.dSYM.zip",
|
528
|
+
release_notes: "release notes"
|
529
|
+
)'
|
530
|
+
]
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Helper
|
3
|
+
class AppcenterHelper
|
4
|
+
# class methods that you define here become available in your action
|
5
|
+
# as `Helper::AppcenterHelper.your_method`
|
6
|
+
#
|
7
|
+
def self.show_message
|
8
|
+
UI.message("Hello from the appcenter plugin helper!")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane-plugin-appcenter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Microsoft Corporation
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fastlane
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.29.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.29.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- LICENSE
|
104
|
+
- README.md
|
105
|
+
- lib/fastlane/plugin/appcenter.rb
|
106
|
+
- lib/fastlane/plugin/appcenter/actions/appcenter_upload_action.rb
|
107
|
+
- lib/fastlane/plugin/appcenter/helper/appcenter_helper.rb
|
108
|
+
- lib/fastlane/plugin/appcenter/version.rb
|
109
|
+
homepage: https://github.com/Microsoft/fastlane-plugin-appcenter
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.6.12
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: Fastlane plugin for App Center
|
133
|
+
test_files: []
|