fastlane-plugin-appcenter 1.5.0 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +42 -14
- data/lib/fastlane/plugin/appcenter/actions/appcenter_fetch_version_number.rb +84 -0
- data/lib/fastlane/plugin/appcenter/actions/appcenter_upload_action.rb +190 -54
- data/lib/fastlane/plugin/appcenter/helper/appcenter_helper.rb +245 -80
- data/lib/fastlane/plugin/appcenter/version.rb +1 -1
- metadata +34 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3383c94635e8212980d620bf95d5e61eca5a3bbacfeea4875e8128884670d66
|
4
|
+
data.tar.gz: 12d16a1669fb1f2c0239d3d0e5feba0e7a076d48c109047ba474b7e59672be5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b8e7acb6b7dc353a0aa443e9d0737b08a634f88e8f311cd78be834d38ceb23d15e81038d8cfba88aca7e09be3f83431fb2681829b969c3f074fe21601b67fdb
|
7
|
+
data.tar.gz: 0f143cb13db8034eca974c0c7a4a6c773c1a9faa3b11c29cf9419397957124f331bfa642ecaad475c4fa6cd41d5d11a53145035de16157b10e239c39573683bd
|
data/README.md
CHANGED
@@ -12,6 +12,8 @@ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To
|
|
12
12
|
fastlane add_plugin appcenter
|
13
13
|
```
|
14
14
|
|
15
|
+
fastlane v2.96.0 or higher is required for all plugin-actions to function properly.
|
16
|
+
|
15
17
|
## About App Center
|
16
18
|
With [App Center](https://appcenter.ms) you can continuously build, test, release, and monitor your apps. This plugin provides a set of actions to interact with App Center.
|
17
19
|
|
@@ -19,6 +21,8 @@ With [App Center](https://appcenter.ms) you can continuously build, test, releas
|
|
19
21
|
|
20
22
|
`appcenter_upload` allows you to upload and [distribute](https://docs.microsoft.com/en-us/appcenter/distribution/uploading) apps to your testers on App Center as well as to upload .dSYM files to [collect detailed crash reports](https://docs.microsoft.com/en-us/appcenter/crashes/ios) in App Center.
|
21
23
|
|
24
|
+
`appcenter_fetch_version_number` allows you to obtain the latest version number (short or full) for an app. This is useful for tasks such as getting the latest version of an app so that an increment action can take place on CI, or checking that an upload has been successful.
|
25
|
+
|
22
26
|
## Usage
|
23
27
|
|
24
28
|
To get started, first, [obtain an API token](https://appcenter.ms/settings/apitokens) in App Center. The API Token is used to authenticate with the App Center API in each call.
|
@@ -36,13 +40,28 @@ appcenter_fetch_devices(
|
|
36
40
|
```ruby
|
37
41
|
appcenter_upload(
|
38
42
|
api_token: "<appcenter token>",
|
39
|
-
owner_name: "<appcenter
|
43
|
+
owner_name: "<appcenter account name of the owner of the app (username or organization URL name)>",
|
44
|
+
owner_type: "user", # Default is user - set to organization for appcenter organizations
|
40
45
|
app_name: "<appcenter app name (as seen in app URL)>",
|
41
46
|
file: "<path to android build binary>",
|
42
47
|
notify_testers: true # Set to false if you don't want to notify testers of your new release (default: `false`)
|
43
48
|
)
|
44
49
|
```
|
45
50
|
|
51
|
+
```ruby
|
52
|
+
appcenter_fetch_version_number(
|
53
|
+
api_token: "<appcenter token>",
|
54
|
+
owner_name: "<appcenter account name of the owner of the app (username or organization URL name)>",
|
55
|
+
app_name: "<appcenter app name (as seen in app URL)>"
|
56
|
+
)
|
57
|
+
```
|
58
|
+
|
59
|
+
The `appcenter_fetch_version_number` returns a hash that contains the id, the version number, and the build number. The version corresponds to the `short_version` and the build number to the `version` known by App Center for a given release:
|
60
|
+
```ruby
|
61
|
+
{"id"=>1, "version"=>"1.0.0", "build_number"=>"1.0.0.1234"} # iOS apps contain the full version plus build number due to the way that Apple use CFBundleVersion for this value
|
62
|
+
{"id"=>588, "version"=>"1.2.0", "build_number"=>"1615"}
|
63
|
+
```
|
64
|
+
|
46
65
|
### Help
|
47
66
|
|
48
67
|
Once installed, information and help for an action can be printed out with this command:
|
@@ -70,7 +89,6 @@ Here is the list of all existing parameters:
|
|
70
89
|
| Key & Env Var | Description |
|
71
90
|
|-----------------|--------------------|
|
72
91
|
| `api_token` <br/> `APPCENTER_API_TOKEN` | API Token for App Center |
|
73
|
-
| `owner_type` <br/> `APPCENTER_OWNER_TYPE` | Owner type, either 'user' or 'organization' (default: `user`) |
|
74
92
|
| `owner_name` <br/> `APPCENTER_OWNER_NAME` | Owner name, as found in the App's URL in App Center |
|
75
93
|
| `destinations` <br/> `APPCENTER_DISTRIBUTE_DESTINATIONS` | Comma separated list of distribution group names. Default is 'Collaborators', use '*' for all distribution groups |
|
76
94
|
| `devices_file` <br/> `FL_REGISTER_DEVICES_FILE` | File to save the devices list to. Same environment variable as _fastlane_'s `register_devices` action |
|
@@ -81,31 +99,37 @@ Here is the list of all existing parameters:
|
|
81
99
|
|-----------------|--------------------|
|
82
100
|
| `api_token` <br/> `APPCENTER_API_TOKEN` | API Token for App Center |
|
83
101
|
| `owner_type` <br/> `APPCENTER_OWNER_TYPE` | Owner type, either 'user' or 'organization' (default: `user`) |
|
84
|
-
| `owner_name` <br/> `APPCENTER_OWNER_NAME` | Owner name
|
85
|
-
| `app_name` <br/> `APPCENTER_APP_NAME` | App name as found in the App's URL in App Center
|
102
|
+
| `owner_name` <br/> `APPCENTER_OWNER_NAME` | Owner name as found in the App's URL in App Center |
|
103
|
+
| `app_name` <br/> `APPCENTER_APP_NAME` | App name as found in the App's URL in App Center. If there is no app with such name, you will be prompted to create one |
|
86
104
|
| `app_display_name` <br/> `APPCENTER_APP_DISPLAY_NAME` | App display name to use when creating a new app |
|
87
|
-
| `app_os` <br/> `APPCENTER_APP_OS` | App OS. Used for new app creation, if app 'app_name' was not found |
|
105
|
+
| `app_os` <br/> `APPCENTER_APP_OS` | App OS can be Android, iOS, macOS, Windows, Custom. Used for new app creation, if app 'app_name' was not found |
|
88
106
|
| `app_platform` <br/> `APPCENTER_APP_PLATFORM` | App Platform. Used for new app creation, if app 'app_name' was not found |
|
89
|
-
| `
|
90
|
-
| `
|
91
|
-
| `ipa` <br/> `APPCENTER_DISTRIBUTE_IPA` | Build release path for iOS builds |
|
92
|
-
| `file` <br/> `APPCENTER_DISTRIBUTE_FILE` | Build release path for generic builds (.aab, .app, .app.zip, .apk, .dmg, .ipa, .pkg) |
|
107
|
+
| `file` <br/> `APPCENTER_DISTRIBUTE_FILE` | File path to the release build to publish |
|
108
|
+
| `upload_build_only` <br/> `APPCENTER_DISTRIBUTE_UPLOAD_BUILD_ONLY` | Flag to upload only the build to App Center. Skips uploading symbols or mapping (default: `false`) |
|
93
109
|
| `dsym` <br/> `APPCENTER_DISTRIBUTE_DSYM` | Path to your symbols file. For iOS provide path to app.dSYM.zip |
|
94
110
|
| `upload_dsym_only` <br/> `APPCENTER_DISTRIBUTE_UPLOAD_DSYM_ONLY` | Flag to upload only the dSYM file to App Center (default: `false`) |
|
95
111
|
| `mapping` <br/> `APPCENTER_DISTRIBUTE_ANDROID_MAPPING` | Path to your Android mapping.txt |
|
96
112
|
| `upload_mapping_only` <br/> `APPCENTER_DISTRIBUTE_UPLOAD_ANDROID_MAPPING_ONLY` | Flag to upload only the mapping.txt file to App Center (default: `false`) |
|
97
|
-
| `destinations` <br/> `APPCENTER_DISTRIBUTE_DESTINATIONS` | Comma separated list of destination names. Both distribution groups and stores are supported. All names are required to be of the same destination type (default: `Collaborators`) |
|
113
|
+
| `destinations` <br/> `APPCENTER_DISTRIBUTE_DESTINATIONS` | Comma separated list of destination names, use '*' for all distribution groups if destination type is 'group'. Both distribution groups and stores are supported. All names are required to be of the same destination type (default: `Collaborators`) |
|
98
114
|
| `destination_type` <br/> `APPCENTER_DISTRIBUTE_DESTINATION_TYPE` | Destination type of distribution destination. 'group' and 'store' are supported (default: `group`) |
|
99
115
|
| `mandatory_update` <br/> `APPCENTER_DISTRIBUTE_MANDATORY_UPDATE` | Require users to update to this release. Ignored if destination type is 'store' (default: `false`) |
|
100
116
|
| `notify_testers` <br/> `APPCENTER_DISTRIBUTE_NOTIFY_TESTERS` | Send email notification about release. Ignored if destination type is 'store' (default: `false`) |
|
101
117
|
| `release_notes` <br/> `APPCENTER_DISTRIBUTE_RELEASE_NOTES` | Release notes (default: `No changelog given`) |
|
102
118
|
| `should_clip` <br/> `APPCENTER_DISTRIBUTE_RELEASE_NOTES_CLIPPING` | Clip release notes if its length is more then 5000, true by default (default: `true`) |
|
103
119
|
| `release_notes_link` <br/> `APPCENTER_DISTRIBUTE_RELEASE_NOTES_LINK` | Additional release notes link |
|
104
|
-
| `build_number` <br/> `APPCENTER_DISTRIBUTE_BUILD_NUMBER` | The build number, required for
|
105
|
-
| `version` <br/> `APPCENTER_DISTRIBUTE_VERSION` | The build version, required for
|
120
|
+
| `build_number` <br/> `APPCENTER_DISTRIBUTE_BUILD_NUMBER` | The build number, required for macOS .pkg and .dmg builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only` |
|
121
|
+
| `version` <br/> `APPCENTER_DISTRIBUTE_VERSION` | The build version, required for .pkg, .dmg, .zip and .msi builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only` |
|
106
122
|
| `timeout` <br/> `APPCENTER_DISTRIBUTE_TIMEOUT` | Request timeout in seconds |
|
107
|
-
| `dsa_signature` <br/> `APPCENTER_DISTRIBUTE_DSA_SIGNATURE` | DSA signature of the macOS or Windows
|
123
|
+
| `dsa_signature` <br/> `APPCENTER_DISTRIBUTE_DSA_SIGNATURE` | DSA signature of the macOS or Windows release for Sparkle update feed |
|
124
|
+
| `strict` <br/> `APPCENTER_STRICT_MODE` | Strict mode, set to 'true' to fail early in case a potential error was detected |
|
108
125
|
|
126
|
+
#### `appcenter_fetch_version_number`
|
127
|
+
|
128
|
+
| Key & Env Var | Description |
|
129
|
+
|-----------------|--------------------|
|
130
|
+
| `api_token` <br/> `APPCENTER_API_TOKEN` | API Token for App Center |
|
131
|
+
| `owner_name` <br/> `APPCENTER_OWNER_NAME` | Owner name, as found in the App's URL in App Center |
|
132
|
+
| `app_name` <br/> `APPCENTER_APP_NAME` | App name as found in the App's URL in App Center. If there is no app with such name, you will be prompted to create one |
|
109
133
|
|
110
134
|
## Example
|
111
135
|
|
@@ -153,6 +177,10 @@ _fastlane_ is the easiest way to automate beta deployments and releases for your
|
|
153
177
|
|
154
178
|
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.
|
155
179
|
|
180
|
+
## Security
|
181
|
+
|
182
|
+
Check out [SECURITY.md](SECURITY.md) for any security concern with this project.
|
183
|
+
|
156
184
|
## Contact
|
157
185
|
|
158
|
-
We're on Twitter as [@vsappcenter](https://www.twitter.com/vsappcenter). Additionally you can reach out to us on the [App Center](https://appcenter.ms/apps) portal
|
186
|
+
We're on Twitter as [@vsappcenter](https://www.twitter.com/vsappcenter). Additionally you can reach out to us on the [App Center](https://appcenter.ms/apps) portal. Open the "?" menu on the top right corner of screen, then use "Contact support" to file a support ticket. Our support team is there to answer your questions and help you solve your problems.
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'fastlane_core/ui/ui'
|
4
|
+
|
5
|
+
module Fastlane
|
6
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
7
|
+
|
8
|
+
module Actions
|
9
|
+
class AppcenterFetchVersionNumberAction < Action
|
10
|
+
def self.description
|
11
|
+
"Fetches the latest version number of an app from App Center"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.authors
|
15
|
+
["jspargo", "ShopKeep"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.run(params)
|
19
|
+
api_token = params[:api_token]
|
20
|
+
app_name = params[:app_name]
|
21
|
+
owner_name = params[:owner_name]
|
22
|
+
|
23
|
+
releases = Helper::AppcenterHelper.fetch_releases(
|
24
|
+
api_token: api_token,
|
25
|
+
owner_name: owner_name,
|
26
|
+
app_name: app_name
|
27
|
+
)
|
28
|
+
|
29
|
+
UI.abort_with_message!("No versions found for '#{app_name}' owned by #{owner_name}") unless releases
|
30
|
+
sorted_release = releases.sort_by { |release| release['id'] }.reverse!
|
31
|
+
latest_release = sorted_release.first
|
32
|
+
|
33
|
+
if latest_release.nil?
|
34
|
+
UI.user_error!("This app has no releases yet")
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
|
38
|
+
return {
|
39
|
+
"id" => latest_release['id'],
|
40
|
+
"version" => latest_release['short_version'],
|
41
|
+
"build_number" => latest_release['version']
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.available_options
|
46
|
+
[
|
47
|
+
FastlaneCore::ConfigItem.new(key: :api_token,
|
48
|
+
env_name: "APPCENTER_API_TOKEN",
|
49
|
+
description: "API Token for App Center Access",
|
50
|
+
verify_block: proc do |value|
|
51
|
+
UI.user_error!("No API token for App Center given, pass using `api_token: 'token'`") unless value && !value.empty?
|
52
|
+
end),
|
53
|
+
FastlaneCore::ConfigItem.new(key: :owner_name,
|
54
|
+
env_name: "APPCENTER_OWNER_NAME",
|
55
|
+
description: "Name of the owner of the application on App Center",
|
56
|
+
verify_block: proc do |value|
|
57
|
+
UI.user_error!("No owner name for App Center given, pass using `owner_name: 'owner name'`") unless value && !value.empty?
|
58
|
+
end),
|
59
|
+
FastlaneCore::ConfigItem.new(key: :app_name,
|
60
|
+
env_name: "APPCENTER_APP_NAME",
|
61
|
+
description: "Name of the application on App Center",
|
62
|
+
verify_block: proc do |value|
|
63
|
+
UI.user_error!("No app name for App Center given, pass using `app_name: 'app name'`") unless value && !value.empty?
|
64
|
+
end)
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.is_supported?(platform)
|
69
|
+
[:ios, :android].include?(platform)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.get_apps(api_token)
|
73
|
+
host_uri = URI.parse('https://api.appcenter.ms')
|
74
|
+
http = Net::HTTP.new(host_uri.host, host_uri.port)
|
75
|
+
http.use_ssl = true
|
76
|
+
apps_request = Net::HTTP::Get.new("/v0.1/apps")
|
77
|
+
apps_request['X-API-Token'] = api_token
|
78
|
+
apps_response = http.request(apps_request)
|
79
|
+
return [] unless apps_response.kind_of?(Net::HTTPOK)
|
80
|
+
return JSON.parse(apps_response.body)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -2,6 +2,18 @@ module Fastlane
|
|
2
2
|
module Actions
|
3
3
|
module Constants
|
4
4
|
MAX_RELEASE_NOTES_LENGTH = 5000
|
5
|
+
SUPPORTED_EXTENSIONS = {
|
6
|
+
android: %w(.aab .apk),
|
7
|
+
ios: %w(.ipa),
|
8
|
+
mac: %w(.app .app.zip .dmg .pkg),
|
9
|
+
windows: %w(.appx .appxbundle .appxupload .msix .msixbundle .msixupload .zip .msi),
|
10
|
+
custom: %w(.zip)
|
11
|
+
}
|
12
|
+
ALL_SUPPORTED_EXTENSIONS = SUPPORTED_EXTENSIONS.values.flatten.sort!.uniq!
|
13
|
+
STORE_ONLY_EXTENSIONS = %w(.aab)
|
14
|
+
STORE_SUPPORTED_EXTENSIONS = %w(.aab .apk .ipa)
|
15
|
+
VERSION_REQUIRED_EXTENSIONS = %w(.msi .zip)
|
16
|
+
FULL_VERSION_REQUIRED_EXTENSIONS = %w(.dmg .pkg)
|
5
17
|
end
|
6
18
|
|
7
19
|
module SharedValues
|
@@ -10,21 +22,29 @@ module Fastlane
|
|
10
22
|
end
|
11
23
|
|
12
24
|
class AppcenterUploadAction < Action
|
25
|
+
def self.is_apple_build(file)
|
26
|
+
return false unless file
|
27
|
+
|
28
|
+
file_ext = Helper::AppcenterHelper.file_extname_full(file)
|
29
|
+
((Constants::SUPPORTED_EXTENSIONS[:ios] + Constants::SUPPORTED_EXTENSIONS[:mac])).include? file_ext
|
30
|
+
end
|
31
|
+
|
13
32
|
# run whole upload process for dSYM files
|
14
33
|
def self.run_dsym_upload(params)
|
15
34
|
values = params.values
|
16
35
|
api_token = params[:api_token]
|
17
36
|
owner_name = params[:owner_name]
|
18
37
|
app_name = params[:app_name]
|
19
|
-
file = params[:ipa]
|
38
|
+
file = params[:file] || params[:ipa]
|
20
39
|
dsym = params[:dsym]
|
21
40
|
build_number = params[:build_number]
|
22
41
|
version = params[:version]
|
23
42
|
|
24
43
|
dsym_path = nil
|
25
44
|
if dsym
|
26
|
-
# we can use dsym parameter
|
27
|
-
|
45
|
+
# we can use dsym parameter for all apple builds
|
46
|
+
self.optional_error("dsym parameter can only be used with Apple builds (ios, mac)") unless !file || self.is_apple_build(file)
|
47
|
+
dsym_path = dsym
|
28
48
|
else
|
29
49
|
# if dsym is not set, but build is ipa - check default path
|
30
50
|
if file && File.exist?(file) && File.extname(file) == '.ipa'
|
@@ -64,7 +84,6 @@ module Fastlane
|
|
64
84
|
end
|
65
85
|
|
66
86
|
def self.run_mapping_upload(params)
|
67
|
-
values = params.values
|
68
87
|
api_token = params[:api_token]
|
69
88
|
owner_name = params[:owner_name]
|
70
89
|
app_name = params[:app_name]
|
@@ -121,40 +140,47 @@ module Fastlane
|
|
121
140
|
end
|
122
141
|
|
123
142
|
file = [
|
143
|
+
params[:file],
|
124
144
|
params[:ipa],
|
125
145
|
params[:apk],
|
126
146
|
params[:aab],
|
127
|
-
params[:file]
|
128
147
|
].detect { |e| !e.to_s.empty? }
|
129
148
|
|
130
149
|
UI.user_error!("Couldn't find build file at path '#{file}'") unless file && File.exist?(file)
|
131
150
|
|
132
151
|
file_ext = Helper::AppcenterHelper.file_extname_full(file)
|
133
152
|
if destination_type == "group"
|
134
|
-
|
153
|
+
self.optional_error("Can't distribute #{file_ext} to groups, please use `destination_type: 'store'`") if Constants::STORE_ONLY_EXTENSIONS.include? file_ext
|
135
154
|
else
|
136
|
-
|
155
|
+
self.optional_error("Can't distribute #{file_ext} to stores, please use `destination_type: 'group'`") unless Constants::STORE_SUPPORTED_EXTENSIONS.include? file_ext
|
156
|
+
UI.user_error!("The combination of `destinations: '*'` and `destination_type: 'store'` is invalid, please use `destination_type: 'group'` or explicitly specify the destinations") if destinations == "*"
|
137
157
|
end
|
138
158
|
|
159
|
+
release_upload_body = nil
|
139
160
|
unless params[:file].to_s.empty?
|
140
|
-
if
|
141
|
-
|
142
|
-
|
161
|
+
if Constants::FULL_VERSION_REQUIRED_EXTENSIONS.include? file_ext
|
162
|
+
self.optional_error("Fields `version` and `build_number` must be specified to upload a #{file_ext} file") if build_number.to_s.empty? || version.to_s.empty?
|
163
|
+
elsif Constants::VERSION_REQUIRED_EXTENSIONS.include? file_ext
|
164
|
+
self.optional_error("Field `version` must be specified to upload a #{file_ext} file") if version.to_s.empty?
|
143
165
|
else
|
144
|
-
|
166
|
+
self.optional_error("Fields `version` and `build_number` are not supported for files of type #{file_ext}") unless build_number.to_s.empty? && version.to_s.empty?
|
145
167
|
end
|
168
|
+
|
169
|
+
release_upload_body = { build_version: version } unless version.nil?
|
170
|
+
release_upload_body = { build_version: version, build_number: build_number } if !version.nil? && !build_number.nil?
|
146
171
|
end
|
147
172
|
|
148
173
|
if file_ext == ".app" && File.directory?(file)
|
149
174
|
UI.message("App path is a directory, zipping it before upload")
|
150
175
|
zip_file = file + ".zip"
|
151
|
-
if File.
|
176
|
+
if File.exist? zip_file
|
152
177
|
override = UI.interactive? ? UI.confirm("File '#{zip_file}' already exists, do you want to override it?") : true
|
153
178
|
UI.abort_with_message!("Not overriding, aborting publishing operation") unless override
|
154
|
-
UI.message("Deleting zip
|
179
|
+
UI.message("Deleting zip archive: #{zip_file}")
|
155
180
|
File.delete zip_file
|
156
181
|
end
|
157
|
-
|
182
|
+
UI.message("Creating zip archive: #{zip_file}")
|
183
|
+
file = Actions::ZipAction.run(path: file, output_path: zip_file, symlinks: true)
|
158
184
|
end
|
159
185
|
|
160
186
|
UI.message("Starting release upload...")
|
@@ -171,10 +197,25 @@ module Fastlane
|
|
171
197
|
release_url = Helper::AppcenterHelper.get_release_url(owner_type, owner_name, app_name, release_id)
|
172
198
|
UI.message("Release '#{release_id}' committed: #{release_url}")
|
173
199
|
|
174
|
-
Helper::AppcenterHelper.update_release(api_token, owner_name, app_name, release_id, release_notes)
|
200
|
+
release = Helper::AppcenterHelper.update_release(api_token, owner_name, app_name, release_id, release_notes)
|
175
201
|
Helper::AppcenterHelper.update_release_metadata(api_token, owner_name, app_name, release_id, dsa_signature)
|
176
202
|
|
177
|
-
destinations_array =
|
203
|
+
destinations_array = []
|
204
|
+
if destinations == '*'
|
205
|
+
UI.message("Looking up all distribution groups for #{owner_name}/#{app_name}")
|
206
|
+
distribution_groups = Helper::AppcenterHelper.fetch_distribution_groups(
|
207
|
+
api_token: api_token,
|
208
|
+
owner_name: owner_name,
|
209
|
+
app_name: app_name
|
210
|
+
)
|
211
|
+
|
212
|
+
UI.abort_with_message!("Failed to list distribution groups for #{owner_name}/#{app_name}") unless distribution_groups
|
213
|
+
|
214
|
+
destinations_array = distribution_groups.map {|h| h['name'] }
|
215
|
+
else
|
216
|
+
destinations_array = destinations.split(',').map(&:strip)
|
217
|
+
end
|
218
|
+
|
178
219
|
destinations_array.each do |destination_name|
|
179
220
|
destination = Helper::AppcenterHelper.get_destination(api_token, owner_name, app_name, destination_type, destination_name)
|
180
221
|
if destination
|
@@ -196,6 +237,8 @@ module Fastlane
|
|
196
237
|
UI.user_error!("Failed to upload release")
|
197
238
|
end
|
198
239
|
end
|
240
|
+
|
241
|
+
release
|
199
242
|
end
|
200
243
|
|
201
244
|
# checks app existance, if ther is no such - creates it
|
@@ -209,13 +252,20 @@ module Fastlane
|
|
209
252
|
app_platform = params[:app_platform]
|
210
253
|
|
211
254
|
platforms = {
|
212
|
-
Android: %w[Java React-Native Xamarin],
|
213
|
-
iOS: %w[Objective-C-Swift React-Native Xamarin],
|
214
|
-
macOS: %w[Objective-C-Swift]
|
255
|
+
Android: %w[Java React-Native Xamarin Unity],
|
256
|
+
iOS: %w[Objective-C-Swift React-Native Xamarin Unity],
|
257
|
+
macOS: %w[Objective-C-Swift],
|
258
|
+
Windows: %w[UWP WPF WinForms Unity],
|
259
|
+
Custom: %w[Custom]
|
215
260
|
}
|
216
261
|
|
217
|
-
|
218
|
-
|
262
|
+
begin
|
263
|
+
if Helper::AppcenterHelper.get_app(api_token, owner_name, app_name)
|
264
|
+
return true
|
265
|
+
end
|
266
|
+
rescue URI::InvalidURIError
|
267
|
+
UI.user_error!("Provided app_name: '#{app_name}' is not in a valid format. Please ensure no special characters or spaces in the app_name.")
|
268
|
+
return false
|
219
269
|
end
|
220
270
|
|
221
271
|
should_create_app = !app_display_name.to_s.empty? || !app_os.to_s.empty? || !app_platform.to_s.empty?
|
@@ -223,9 +273,9 @@ module Fastlane
|
|
223
273
|
if Helper.test? || should_create_app || UI.confirm("App with name #{app_name} not found, create one?")
|
224
274
|
app_display_name = app_name if app_display_name.to_s.empty?
|
225
275
|
os = app_os.to_s.empty? && (Helper.test? ? "Android" : UI.select("Select OS", platforms.keys)) || app_os.to_s
|
226
|
-
platform = app_platform.to_s.empty? && (Helper.test? ?
|
276
|
+
platform = app_platform.to_s.empty? && (Helper.test? ? platforms[os.to_sym][0] : app_platform.to_s) || app_platform.to_s
|
227
277
|
if platform.to_s.empty?
|
228
|
-
platform = platforms[os].length == 1 ? platforms[os][0] : UI.select("Select Platform", platforms[os])
|
278
|
+
platform = platforms[os.to_sym].length == 1 ? platforms[os.to_sym][0] : UI.select("Select Platform", platforms[os.to_sym])
|
229
279
|
end
|
230
280
|
|
231
281
|
Helper::AppcenterHelper.create_app(api_token, owner_type, owner_name, app_name, app_display_name, os, platform)
|
@@ -235,16 +285,47 @@ module Fastlane
|
|
235
285
|
end
|
236
286
|
end
|
237
287
|
|
288
|
+
def self.add_app_to_distribution_group_if_needed(params)
|
289
|
+
return unless params[:destination_type] == 'group' && params[:owner_type] == 'organization' && params[:destinations] != '*'
|
290
|
+
|
291
|
+
app_distribution_groups = Helper::AppcenterHelper.fetch_distribution_groups(
|
292
|
+
api_token: params[:api_token],
|
293
|
+
owner_name: params[:owner_name],
|
294
|
+
app_name: params[:app_name]
|
295
|
+
)
|
296
|
+
|
297
|
+
group_names = app_distribution_groups.map { |g| g['name'] }
|
298
|
+
destination_names = params[:destinations].split(',').map(&:strip)
|
299
|
+
|
300
|
+
destination_names.each do |destination_name|
|
301
|
+
unless group_names.include? destination_name
|
302
|
+
Helper::AppcenterHelper.add_new_app_to_distribution_group(
|
303
|
+
api_token: params[:api_token],
|
304
|
+
owner_name: params[:owner_name],
|
305
|
+
app_name: params[:app_name],
|
306
|
+
destination_name: destination_name
|
307
|
+
)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
238
312
|
def self.run(params)
|
239
313
|
values = params.values
|
314
|
+
upload_build_only = params[:upload_build_only]
|
240
315
|
upload_dsym_only = params[:upload_dsym_only]
|
241
316
|
upload_mapping_only = params[:upload_mapping_only]
|
242
317
|
|
318
|
+
Options.strict_mode(params[:strict])
|
319
|
+
|
243
320
|
# if app found or successfully created
|
244
321
|
if self.get_or_create_app(params)
|
245
|
-
self.
|
246
|
-
self.
|
247
|
-
|
322
|
+
self.add_app_to_distribution_group_if_needed(params)
|
323
|
+
release = self.run_release_upload(params) unless upload_dsym_only || upload_mapping_only
|
324
|
+
params[:version] = release['short_version'] if release
|
325
|
+
params[:build_number] = release['version'] if release
|
326
|
+
|
327
|
+
self.run_dsym_upload(params) unless upload_mapping_only || upload_build_only
|
328
|
+
self.run_mapping_upload(params) unless upload_dsym_only || upload_build_only
|
248
329
|
end
|
249
330
|
|
250
331
|
return values if Helper.test?
|
@@ -282,7 +363,7 @@ module Fastlane
|
|
282
363
|
type: String,
|
283
364
|
verify_block: proc do |value|
|
284
365
|
accepted_formats = ["user", "organization"]
|
285
|
-
UI.user_error!("Only \"user\" and \"organization\" types are allowed, you provided \"#{
|
366
|
+
UI.user_error!("Only \"user\" and \"organization\" types are allowed, you provided \"#{value}\"") unless accepted_formats.include? value
|
286
367
|
end),
|
287
368
|
|
288
369
|
FastlaneCore::ConfigItem.new(key: :owner_name,
|
@@ -313,7 +394,7 @@ module Fastlane
|
|
313
394
|
|
314
395
|
FastlaneCore::ConfigItem.new(key: :app_os,
|
315
396
|
env_name: "APPCENTER_APP_OS",
|
316
|
-
description: "App OS. Used for new app creation, if app 'app_name' was not found",
|
397
|
+
description: "App OS can be Android, iOS, macOS, Windows, Custom. Used for new app creation, if app 'app_name' was not found",
|
317
398
|
optional: true,
|
318
399
|
type: String),
|
319
400
|
|
@@ -328,6 +409,7 @@ module Fastlane
|
|
328
409
|
description: "Build release path for android build",
|
329
410
|
default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH],
|
330
411
|
optional: true,
|
412
|
+
deprecated: true,
|
331
413
|
type: String,
|
332
414
|
conflicting_options: [:ipa, :aab, :file],
|
333
415
|
conflict_block: proc do |value|
|
@@ -336,7 +418,7 @@ module Fastlane
|
|
336
418
|
verify_block: proc do |value|
|
337
419
|
accepted_formats = [".apk"]
|
338
420
|
file_extname_full = Helper::AppcenterHelper.file_extname_full(value)
|
339
|
-
|
421
|
+
self.optional_error("Only \".apk\" formats are allowed, you provided \"#{file_extname_full}\"") unless accepted_formats.include? file_extname_full
|
340
422
|
end),
|
341
423
|
|
342
424
|
FastlaneCore::ConfigItem.new(key: :aab,
|
@@ -344,6 +426,7 @@ module Fastlane
|
|
344
426
|
description: "Build release path for android app bundle build",
|
345
427
|
default_value: Actions.lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH],
|
346
428
|
optional: true,
|
429
|
+
deprecated: true,
|
347
430
|
type: String,
|
348
431
|
conflicting_options: [:ipa, :apk, :file],
|
349
432
|
conflict_block: proc do |value|
|
@@ -351,7 +434,7 @@ module Fastlane
|
|
351
434
|
end,
|
352
435
|
verify_block: proc do |value|
|
353
436
|
accepted_formats = [".aab"]
|
354
|
-
|
437
|
+
self.optional_error("Only \".aab\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
|
355
438
|
end),
|
356
439
|
|
357
440
|
FastlaneCore::ConfigItem.new(key: :ipa,
|
@@ -359,6 +442,7 @@ module Fastlane
|
|
359
442
|
description: "Build release path for iOS builds",
|
360
443
|
default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH],
|
361
444
|
optional: true,
|
445
|
+
deprecated: true,
|
362
446
|
type: String,
|
363
447
|
conflicting_options: [:apk, :aab, :file],
|
364
448
|
conflict_block: proc do |value|
|
@@ -366,12 +450,12 @@ module Fastlane
|
|
366
450
|
end,
|
367
451
|
verify_block: proc do |value|
|
368
452
|
accepted_formats = [".ipa"]
|
369
|
-
|
453
|
+
self.optional_error("Only \".ipa\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
|
370
454
|
end),
|
371
455
|
|
372
456
|
FastlaneCore::ConfigItem.new(key: :file,
|
373
457
|
env_name: "APPCENTER_DISTRIBUTE_FILE",
|
374
|
-
description: "
|
458
|
+
description: "File path to the release build to publish",
|
375
459
|
optional: true,
|
376
460
|
type: String,
|
377
461
|
conflicting_options: [:apk, :aab, :ipa],
|
@@ -379,11 +463,29 @@ module Fastlane
|
|
379
463
|
UI.user_error!("You can't use 'file' and '#{value.key}' options in one run")
|
380
464
|
end,
|
381
465
|
verify_block: proc do |value|
|
382
|
-
|
383
|
-
|
384
|
-
|
466
|
+
platform = Actions.lane_context[SharedValues::PLATFORM_NAME]
|
467
|
+
if platform
|
468
|
+
accepted_formats = Constants::SUPPORTED_EXTENSIONS[platform.to_sym]
|
469
|
+
unless accepted_formats
|
470
|
+
UI.important("Unknown platform '#{platform}', Supported are #{Constants::SUPPORTED_EXTENSIONS.keys}")
|
471
|
+
accepted_formats = Constants::ALL_SUPPORTED_EXTENSIONS
|
472
|
+
end
|
473
|
+
file_ext = Helper::AppcenterHelper.file_extname_full(value)
|
474
|
+
self.optional_error("Extension not supported: '#{file_ext}'. Supported formats for platform '#{platform}': #{accepted_formats.join ' '}") unless accepted_formats.include? file_ext
|
475
|
+
end
|
385
476
|
end),
|
386
477
|
|
478
|
+
FastlaneCore::ConfigItem.new(key: :upload_build_only,
|
479
|
+
env_name: "APPCENTER_DISTRIBUTE_UPLOAD_BUILD_ONLY",
|
480
|
+
description: "Flag to upload only the build to App Center. Skips uploading symbols or mapping",
|
481
|
+
optional: true,
|
482
|
+
is_string: false,
|
483
|
+
default_value: false,
|
484
|
+
conflicting_options: [:upload_dsym_only, :upload_mapping_only],
|
485
|
+
conflict_block: proc do |value|
|
486
|
+
UI.user_error!("You can't use 'upload_build_only' and '#{value.key}' options in one run")
|
487
|
+
end),
|
488
|
+
|
387
489
|
FastlaneCore::ConfigItem.new(key: :dsym,
|
388
490
|
env_name: "APPCENTER_DISTRIBUTE_DSYM",
|
389
491
|
description: "Path to your symbols file. For iOS provide path to app.dSYM.zip",
|
@@ -408,6 +510,7 @@ module Fastlane
|
|
408
510
|
FastlaneCore::ConfigItem.new(key: :mapping,
|
409
511
|
env_name: "APPCENTER_DISTRIBUTE_ANDROID_MAPPING",
|
410
512
|
description: "Path to your Android mapping.txt",
|
513
|
+
default_value: (defined? SharedValues::GRADLE_MAPPING_TXT_OUTPUT_PATH) && Actions.lane_context[SharedValues::GRADLE_MAPPING_TXT_OUTPUT_PATH] || nil,
|
411
514
|
optional: true,
|
412
515
|
type: String,
|
413
516
|
verify_block: proc do |value|
|
@@ -437,12 +540,11 @@ module Fastlane
|
|
437
540
|
|
438
541
|
FastlaneCore::ConfigItem.new(key: :destinations,
|
439
542
|
env_name: "APPCENTER_DISTRIBUTE_DESTINATIONS",
|
440
|
-
description: "Comma separated list of destination names. Both distribution groups and stores are supported. All names are required to be of the same destination type",
|
543
|
+
description: "Comma separated list of destination names, use '*' for all distribution groups if destination type is 'group'. Both distribution groups and stores are supported. All names are required to be of the same destination type",
|
441
544
|
default_value: Actions.lane_context[SharedValues::APPCENTER_DISTRIBUTE_DESTINATIONS] || "Collaborators",
|
442
545
|
optional: true,
|
443
546
|
type: String),
|
444
547
|
|
445
|
-
|
446
548
|
FastlaneCore::ConfigItem.new(key: :destination_type,
|
447
549
|
env_name: "APPCENTER_DISTRIBUTE_DESTINATION_TYPE",
|
448
550
|
description: "Destination type of distribution destination. 'group' and 'store' are supported",
|
@@ -489,13 +591,13 @@ module Fastlane
|
|
489
591
|
|
490
592
|
FastlaneCore::ConfigItem.new(key: :build_number,
|
491
593
|
env_name: "APPCENTER_DISTRIBUTE_BUILD_NUMBER",
|
492
|
-
description: "The build number, required for
|
594
|
+
description: "The build number, required for macOS .pkg and .dmg builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only`",
|
493
595
|
optional: true,
|
494
596
|
type: String),
|
495
597
|
|
496
598
|
FastlaneCore::ConfigItem.new(key: :version,
|
497
599
|
env_name: "APPCENTER_DISTRIBUTE_VERSION",
|
498
|
-
description: "The build version, required for
|
600
|
+
description: "The build version, required for .pkg, .dmg, .zip and .msi builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only`",
|
499
601
|
optional: true,
|
500
602
|
type: String),
|
501
603
|
|
@@ -507,7 +609,13 @@ module Fastlane
|
|
507
609
|
|
508
610
|
FastlaneCore::ConfigItem.new(key: :dsa_signature,
|
509
611
|
env_name: "APPCENTER_DISTRIBUTE_DSA_SIGNATURE",
|
510
|
-
description: "DSA signature of the macOS or Windows
|
612
|
+
description: "DSA signature of the macOS or Windows release for Sparkle update feed",
|
613
|
+
optional: true,
|
614
|
+
type: String),
|
615
|
+
|
616
|
+
FastlaneCore::ConfigItem.new(key: :strict,
|
617
|
+
env_name: "APPCENTER_STRICT_MODE",
|
618
|
+
description: "Strict mode, set to 'true' to fail early in case a potential error was detected",
|
511
619
|
optional: true,
|
512
620
|
type: String)
|
513
621
|
]
|
@@ -521,6 +629,8 @@ module Fastlane
|
|
521
629
|
end
|
522
630
|
|
523
631
|
def self.is_supported?(platform)
|
632
|
+
return Constants::SUPPORTED_EXTENSIONS.keys.include?(platform) if Options.strict
|
633
|
+
|
524
634
|
true
|
525
635
|
end
|
526
636
|
|
@@ -529,12 +639,10 @@ module Fastlane
|
|
529
639
|
'appcenter_upload(
|
530
640
|
api_token: "...",
|
531
641
|
owner_name: "appcenter_owner",
|
532
|
-
app_name: "
|
533
|
-
|
642
|
+
app_name: "testing_android_app",
|
643
|
+
file: "./app-release.apk",
|
534
644
|
destinations: "Testers",
|
535
645
|
destination_type: "group",
|
536
|
-
build_number: "3",
|
537
|
-
version: "1.0.0",
|
538
646
|
mapping: "./mapping.txt",
|
539
647
|
release_notes: "release notes",
|
540
648
|
notify_testers: false
|
@@ -542,9 +650,9 @@ module Fastlane
|
|
542
650
|
'appcenter_upload(
|
543
651
|
api_token: "...",
|
544
652
|
owner_name: "appcenter_owner",
|
545
|
-
app_name: "
|
546
|
-
|
547
|
-
destinations: "Testers,
|
653
|
+
app_name: "testing_ios_app",
|
654
|
+
file: "./app-release.ipa",
|
655
|
+
destinations: "Testers,Public",
|
548
656
|
destination_type: "group",
|
549
657
|
dsym: "./app.dSYM.zip",
|
550
658
|
release_notes: "release notes",
|
@@ -553,18 +661,46 @@ module Fastlane
|
|
553
661
|
'appcenter_upload(
|
554
662
|
api_token: "...",
|
555
663
|
owner_name: "appcenter_owner",
|
556
|
-
app_name: "
|
557
|
-
|
558
|
-
destinations: "
|
559
|
-
destination_type: "
|
560
|
-
build_number: "3",
|
561
|
-
version: "1.0.0",
|
562
|
-
mapping: "./mapping.txt",
|
664
|
+
app_name: "testing_ios_app",
|
665
|
+
file: "./app-release.ipa",
|
666
|
+
destinations: "*",
|
667
|
+
destination_type: "group",
|
563
668
|
release_notes: "release notes",
|
564
669
|
notify_testers: false
|
670
|
+
)',
|
671
|
+
'appcenter_upload(
|
672
|
+
api_token: "...",
|
673
|
+
owner_name: "appcenter_owner",
|
674
|
+
app_name: "testing_google_play_app",
|
675
|
+
file: "./app.aab",
|
676
|
+
destinations: "Alpha",
|
677
|
+
destination_type: "store",
|
678
|
+
release_notes: "this is a store release"
|
565
679
|
)'
|
566
680
|
]
|
567
681
|
end
|
682
|
+
|
683
|
+
class Options
|
684
|
+
include Singleton
|
685
|
+
|
686
|
+
def self.strict_mode(mode)
|
687
|
+
@strict = mode.to_s == "true"
|
688
|
+
UI.message("Enabled strict mode") if @strict
|
689
|
+
end
|
690
|
+
|
691
|
+
def self.strict
|
692
|
+
@strict
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
def self.optional_error(message)
|
697
|
+
if Options.strict
|
698
|
+
UI.user_error!(message)
|
699
|
+
else
|
700
|
+
UI.important(message)
|
701
|
+
UI.important("The current operation might fail, trying anyway...")
|
702
|
+
end
|
703
|
+
end
|
568
704
|
end
|
569
705
|
end
|
570
706
|
end
|