fastlane-plugin-appcenter 1.4.0 → 1.7.1
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 +4 -4
- data/README.md +90 -25
- data/lib/fastlane/plugin/appcenter/actions/appcenter_fetch_devices_action.rb +138 -0
- data/lib/fastlane/plugin/appcenter/actions/appcenter_upload_action.rb +176 -58
- data/lib/fastlane/plugin/appcenter/helper/appcenter_helper.rb +138 -12
- data/lib/fastlane/plugin/appcenter/version.rb +1 -1
- metadata +20 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba75670e59a7a2ba066a788088aeb05d96991bcc1ab693633bb0fca27fc821c5
|
4
|
+
data.tar.gz: b11523450e225a2ec2b596d0a4db135cf1dd1a5acefd4a0a0bc0136cf42c8917
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffd5e3de2a289e28e264a679fb286bb25ecc830a8c36d15c0d044291bc891f9eb17e76e8e4ea76682bf84b19c18db86d475c20b6cdab5f223fd85d7e207b8d85
|
7
|
+
data.tar.gz: d8c07796377fb096fa566c35435e208d22de2dbd3bac1e5901fbff0b432ea7a7ecfdf77b60a5726ff30a87865062602544bd4f0f980c5a96dead0dd1d825855f
|
data/README.md
CHANGED
@@ -12,48 +12,107 @@ 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
|
-
With [App Center](https://appcenter.ms) you can continuously build, test, release, and monitor your apps. This plugin provides
|
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.
|
19
|
+
|
20
|
+
`appcenter_fetch_devices` allows you to obtain the list of iOS devices to distribute an app to (useful for automatic provisioning of testers' devices).
|
21
|
+
|
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.
|
17
23
|
|
18
24
|
## Usage
|
19
25
|
|
20
26
|
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.
|
21
27
|
|
22
28
|
```ruby
|
23
|
-
|
29
|
+
appcenter_fetch_devices(
|
24
30
|
api_token: "<appcenter token>",
|
25
31
|
owner_name: "<appcenter account name of the owner of the app (username or organization URL name)>",
|
32
|
+
owner_type: "user", # Default is user - set to organization for appcenter organizations
|
26
33
|
app_name: "<appcenter app name>",
|
27
|
-
|
34
|
+
destinations: "*", # Default is 'Collaborators', use '*' for all distribution groups
|
35
|
+
devices_file: "devices.txt" # Default. If you customize, the extension must be .txt
|
36
|
+
)
|
37
|
+
```
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
appcenter_upload(
|
41
|
+
api_token: "<appcenter token>",
|
42
|
+
owner_name: "<appcenter account name of the owner of the app (username or organization URL name)>",
|
43
|
+
owner_type: "user", # Default is user - set to organization for appcenter organizations
|
44
|
+
app_name: "<appcenter app name (as seen in app URL)>",
|
45
|
+
file: "<path to android build binary>",
|
28
46
|
notify_testers: true # Set to false if you don't want to notify testers of your new release (default: `false`)
|
29
47
|
)
|
30
48
|
```
|
31
49
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
### Help
|
51
|
+
|
52
|
+
Once installed, information and help for an action can be printed out with this command:
|
53
|
+
|
54
|
+
```bash
|
55
|
+
fastlane action appcenter_upload # or any action included with this plugin
|
56
|
+
```
|
57
|
+
|
58
|
+
### A note on App Name
|
59
|
+
|
60
|
+
The `app_name` and `owner_name` as set in the Fastfile come from the app's URL in App Center, in the below form:
|
61
|
+
```
|
62
|
+
https://appcenter.ms/users/{owner_name}/apps/{app_name}
|
63
|
+
```
|
64
|
+
They should not be confused with the displayed name on App Center pages, which is called `app_display_name ` instead.
|
65
|
+
|
66
|
+
### Parameters
|
67
|
+
|
68
|
+
The action parameters `api_token`, `owner_name`, `app_name`, and others can also be omitted when their values are [set as environment variables](https://docs.fastlane.tools/advanced/#environment-variables). By default, `appcenter_upload` will use the same `api_token`, `owner_name`, and `app_name` you used in `appcenter_fetch_devices`.
|
69
|
+
|
70
|
+
Here is the list of all existing parameters:
|
71
|
+
|
72
|
+
#### `appcenter_fetch_devices`
|
73
|
+
|
74
|
+
| Key & Env Var | Description |
|
75
|
+
|-----------------|--------------------|
|
76
|
+
| `api_token` <br/> `APPCENTER_API_TOKEN` | API Token for App Center |
|
77
|
+
| `owner_type` <br/> `APPCENTER_OWNER_TYPE` | Owner type, either 'user' or 'organization' (default: `user`) |
|
78
|
+
| `owner_name` <br/> `APPCENTER_OWNER_NAME` | Owner name, as found in the App's URL in App Center |
|
79
|
+
| `destinations` <br/> `APPCENTER_DISTRIBUTE_DESTINATIONS` | Comma separated list of distribution group names. Default is 'Collaborators', use '*' for all distribution groups |
|
80
|
+
| `devices_file` <br/> `FL_REGISTER_DEVICES_FILE` | File to save the devices list to. Same environment variable as _fastlane_'s `register_devices` action |
|
81
|
+
|
82
|
+
#### `appcenter_upload`
|
83
|
+
|
84
|
+
| Key & Env Var | Description |
|
85
|
+
|-----------------|--------------------|
|
86
|
+
| `api_token` <br/> `APPCENTER_API_TOKEN` | API Token for App Center |
|
87
|
+
| `owner_type` <br/> `APPCENTER_OWNER_TYPE` | Owner type, either 'user' or 'organization' (default: `user`) |
|
88
|
+
| `owner_name` <br/> `APPCENTER_OWNER_NAME` | Owner name as found in the App's URL in App Center |
|
89
|
+
| `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 |
|
90
|
+
| `app_display_name` <br/> `APPCENTER_APP_DISPLAY_NAME` | App display name to use when creating a new app |
|
91
|
+
| `app_os` <br/> `APPCENTER_APP_OS` | App OS. Used for new app creation, if app 'app_name' was not found |
|
92
|
+
| `app_platform` <br/> `APPCENTER_APP_PLATFORM` | App Platform. Used for new app creation, if app 'app_name' was not found |
|
93
|
+
| `file` <br/> `APPCENTER_DISTRIBUTE_FILE` | File path to the release build to publish |
|
94
|
+
| `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`) |
|
95
|
+
| `dsym` <br/> `APPCENTER_DISTRIBUTE_DSYM` | Path to your symbols file. For iOS provide path to app.dSYM.zip |
|
96
|
+
| `upload_dsym_only` <br/> `APPCENTER_DISTRIBUTE_UPLOAD_DSYM_ONLY` | Flag to upload only the dSYM file to App Center (default: `false`) |
|
97
|
+
| `mapping` <br/> `APPCENTER_DISTRIBUTE_ANDROID_MAPPING` | Path to your Android mapping.txt |
|
98
|
+
| `upload_mapping_only` <br/> `APPCENTER_DISTRIBUTE_UPLOAD_ANDROID_MAPPING_ONLY` | Flag to upload only the mapping.txt file to App Center (default: `false`) |
|
99
|
+
| `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`) |
|
100
|
+
| `destination_type` <br/> `APPCENTER_DISTRIBUTE_DESTINATION_TYPE` | Destination type of distribution destination. 'group' and 'store' are supported (default: `group`) |
|
101
|
+
| `mandatory_update` <br/> `APPCENTER_DISTRIBUTE_MANDATORY_UPDATE` | Require users to update to this release. Ignored if destination type is 'store' (default: `false`) |
|
102
|
+
| `notify_testers` <br/> `APPCENTER_DISTRIBUTE_NOTIFY_TESTERS` | Send email notification about release. Ignored if destination type is 'store' (default: `false`) |
|
103
|
+
| `release_notes` <br/> `APPCENTER_DISTRIBUTE_RELEASE_NOTES` | Release notes (default: `No changelog given`) |
|
104
|
+
| `should_clip` <br/> `APPCENTER_DISTRIBUTE_RELEASE_NOTES_CLIPPING` | Clip release notes if its length is more then 5000, true by default (default: `true`) |
|
105
|
+
| `release_notes_link` <br/> `APPCENTER_DISTRIBUTE_RELEASE_NOTES_LINK` | Additional release notes link |
|
106
|
+
| `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` |
|
107
|
+
| `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` |
|
108
|
+
| `timeout` <br/> `APPCENTER_DISTRIBUTE_TIMEOUT` | Request timeout in seconds |
|
109
|
+
| `dsa_signature` <br/> `APPCENTER_DISTRIBUTE_DSA_SIGNATURE` | DSA signature of the macOS or Windows release for Sparkle update feed |
|
110
|
+
| `strict` <br/> `APPCENTER_STRICT_MODE` | Strict mode, set to 'true' to fail early in case a potential error was detected |
|
111
|
+
|
53
112
|
|
54
113
|
## Example
|
55
114
|
|
56
|
-
Check out
|
115
|
+
Check out this [example `Fastfile`](fastlane/Fastfile) to see how to use the `appcenter_upload` action. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
57
116
|
|
58
117
|
Sample uses `.env` for setting private variables like API token, owner name, .etc. You need to replace it in `Fastfile` by your own values.
|
59
118
|
|
@@ -62,6 +121,8 @@ There are three examples in `test` lane:
|
|
62
121
|
- upload release for ios with all set parameters
|
63
122
|
- upload only dSYM file for ios
|
64
123
|
|
124
|
+
Check out this [example `Fastfile`](fastlane/fetch_devices/Fastfile) for a full example of fetching devices, registering them with Apple, provisioning the devices, and signing an app.
|
125
|
+
|
65
126
|
## Run tests for this plugin
|
66
127
|
|
67
128
|
To run both the tests, and code style validation, run
|
@@ -95,6 +156,10 @@ _fastlane_ is the easiest way to automate beta deployments and releases for your
|
|
95
156
|
|
96
157
|
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.
|
97
158
|
|
159
|
+
## Security
|
160
|
+
|
161
|
+
Check out [SECURITY.md](SECURITY.md) for any security concern with this project.
|
162
|
+
|
98
163
|
## Contact
|
99
164
|
|
100
165
|
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 by using the blue Intercom button on the bottom right to start a conversation.
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'fastlane/action'
|
2
|
+
require 'csv'
|
3
|
+
require 'faraday'
|
4
|
+
|
5
|
+
module Fastlane
|
6
|
+
module Actions
|
7
|
+
module SharedValues
|
8
|
+
APPCENTER_API_TOKEN = :APPCENTER_API_TOKEN
|
9
|
+
APPCENTER_OWNER_NAME = :APPCENTER_OWNER_NAME
|
10
|
+
APPCENTER_APP_NAME = :APPCENTER_APP_NAME
|
11
|
+
APPCENTER_DISTRIBUTE_DESTINATIONS = :APPCENTER_DISTRIBUTE_DESTINATIONS
|
12
|
+
end
|
13
|
+
|
14
|
+
class AppcenterFetchDevicesAction < Action
|
15
|
+
def self.run(params)
|
16
|
+
api_token = params[:api_token]
|
17
|
+
owner_name = params[:owner_name]
|
18
|
+
app_name = params[:app_name]
|
19
|
+
destinations = params[:destinations]
|
20
|
+
|
21
|
+
Actions.lane_context[SharedValues::APPCENTER_API_TOKEN] = api_token
|
22
|
+
Actions.lane_context[SharedValues::APPCENTER_OWNER_NAME] = owner_name
|
23
|
+
Actions.lane_context[SharedValues::APPCENTER_APP_NAME] = app_name
|
24
|
+
|
25
|
+
group_names = []
|
26
|
+
if destinations == '*'
|
27
|
+
UI.message("Looking up all distribution groups for #{owner_name}/#{app_name}")
|
28
|
+
distribution_groups = Helper::AppcenterHelper.fetch_distribution_groups(
|
29
|
+
api_token: api_token,
|
30
|
+
owner_name: owner_name,
|
31
|
+
app_name: app_name
|
32
|
+
)
|
33
|
+
UI.abort_with_message!("Failed to list distribution groups for #{owner_name}/#{app_name}") unless distribution_groups
|
34
|
+
distribution_groups.each do |group|
|
35
|
+
group_names << group['name']
|
36
|
+
end
|
37
|
+
else
|
38
|
+
group_names += destinations.split(',').map(&:strip)
|
39
|
+
end
|
40
|
+
|
41
|
+
Actions.lane_context[SharedValues::APPCENTER_DISTRIBUTE_DESTINATIONS] = group_names.join(',')
|
42
|
+
|
43
|
+
devices = []
|
44
|
+
group_names.each do |group_name|
|
45
|
+
group_devices = Helper::AppcenterHelper.fetch_devices(
|
46
|
+
api_token: api_token,
|
47
|
+
owner_name: owner_name,
|
48
|
+
app_name: app_name,
|
49
|
+
distribution_group: group_name
|
50
|
+
)
|
51
|
+
UI.abort_with_message!("Failed to get devices for group '#{group_name}'") unless group_devices
|
52
|
+
devices << group_devices
|
53
|
+
end
|
54
|
+
|
55
|
+
self.write_devices(devices: devices, devices_file: params[:devices_file])
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.write_devices(devices:, devices_file:)
|
59
|
+
CSV.open(devices_file, 'w',
|
60
|
+
write_headers: true,
|
61
|
+
headers: ['Device ID', 'Device Name'],
|
62
|
+
col_sep: "\t") do |csv|
|
63
|
+
|
64
|
+
devices.each do |device|
|
65
|
+
CSV.parse(device, { col_sep: "\t", headers: true }) do |row|
|
66
|
+
csv << row
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.description
|
73
|
+
"Fetches a list of devices from App Center to distribute an iOS app to"
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.authors
|
77
|
+
["benkane"]
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.return_value
|
81
|
+
"CSV file formatted for multi-device registration with Apple"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.details
|
85
|
+
"List is a tab-delimited CSV file containing every device from specified distribution groups for an app in App Center. " +
|
86
|
+
"Especially useful when combined with register_devices and match to automatically register and provision devices with Apple. " +
|
87
|
+
"By default, only the Collaborators group will be included, use `destination: '*'` to match all groups."
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.available_options
|
91
|
+
[
|
92
|
+
FastlaneCore::ConfigItem.new(key: :api_token,
|
93
|
+
env_name: "APPCENTER_API_TOKEN",
|
94
|
+
sensitive: true,
|
95
|
+
description: "API Token for App Center",
|
96
|
+
optional: false,
|
97
|
+
type: String,
|
98
|
+
verify_block: proc do |value|
|
99
|
+
UI.user_error!("No API token for App Center given, pass using `api_token: 'token'`") unless value && !value.empty?
|
100
|
+
end),
|
101
|
+
FastlaneCore::ConfigItem.new(key: :owner_name,
|
102
|
+
env_name: "APPCENTER_OWNER_NAME",
|
103
|
+
description: "Owner name",
|
104
|
+
optional: false,
|
105
|
+
type: String,
|
106
|
+
verify_block: proc do |value|
|
107
|
+
UI.user_error!("No Owner name for App Center given, pass using `owner_name: 'name'`") unless value && !value.empty?
|
108
|
+
end),
|
109
|
+
FastlaneCore::ConfigItem.new(key: :app_name,
|
110
|
+
env_name: "APPCENTER_APP_NAME",
|
111
|
+
description: "App name",
|
112
|
+
optional: false,
|
113
|
+
type: String,
|
114
|
+
verify_block: proc do |value|
|
115
|
+
UI.user_error!("No App name given, pass using `app_name: 'app name'`") unless value && !value.empty?
|
116
|
+
end),
|
117
|
+
FastlaneCore::ConfigItem.new(key: :devices_file,
|
118
|
+
env_name: "FL_REGISTER_DEVICES_FILE",
|
119
|
+
description: "File to save devices to",
|
120
|
+
type: String,
|
121
|
+
default_value: "devices.txt",
|
122
|
+
verify_block: proc do |value|
|
123
|
+
UI.important("Important: Devices file is #{value}. If you plan to upload this file to Apple Developer Center, the file must have the .txt extension") unless value && value.end_with?('.txt')
|
124
|
+
end),
|
125
|
+
FastlaneCore::ConfigItem.new(key: :destinations,
|
126
|
+
env_name: "APPCENTER_DISTRIBUTE_DESTINATIONS",
|
127
|
+
description: "Comma separated list of distribution group names. Default is 'Collaborators', use '*' for all distribution groups",
|
128
|
+
default_value: "Collaborators",
|
129
|
+
type: String)
|
130
|
+
]
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.is_supported?(platform)
|
134
|
+
[:ios].include?(platform)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
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
|
@@ -16,7 +28,7 @@ module Fastlane
|
|
16
28
|
api_token = params[:api_token]
|
17
29
|
owner_name = params[:owner_name]
|
18
30
|
app_name = params[:app_name]
|
19
|
-
file = params[:ipa]
|
31
|
+
file = params[:file] || params[:ipa]
|
20
32
|
dsym = params[:dsym]
|
21
33
|
build_number = params[:build_number]
|
22
34
|
version = params[:version]
|
@@ -64,7 +76,6 @@ module Fastlane
|
|
64
76
|
end
|
65
77
|
|
66
78
|
def self.run_mapping_upload(params)
|
67
|
-
values = params.values
|
68
79
|
api_token = params[:api_token]
|
69
80
|
owner_name = params[:owner_name]
|
70
81
|
app_name = params[:app_name]
|
@@ -94,6 +105,7 @@ module Fastlane
|
|
94
105
|
values = params.values
|
95
106
|
api_token = params[:api_token]
|
96
107
|
owner_name = params[:owner_name]
|
108
|
+
owner_type = params[:owner_type]
|
97
109
|
app_name = params[:app_name]
|
98
110
|
destinations = params[:destinations]
|
99
111
|
destination_type = params[:destination_type]
|
@@ -105,6 +117,7 @@ module Fastlane
|
|
105
117
|
timeout = params[:timeout]
|
106
118
|
build_number = params[:build_number]
|
107
119
|
version = params[:version]
|
120
|
+
dsa_signature = params[:dsa_signature]
|
108
121
|
|
109
122
|
if release_notes.length >= Constants::MAX_RELEASE_NOTES_LENGTH
|
110
123
|
unless should_clip
|
@@ -119,39 +132,46 @@ module Fastlane
|
|
119
132
|
end
|
120
133
|
|
121
134
|
file = [
|
135
|
+
params[:file],
|
122
136
|
params[:ipa],
|
123
137
|
params[:apk],
|
124
138
|
params[:aab],
|
125
|
-
params[:file]
|
126
139
|
].detect { |e| !e.to_s.empty? }
|
127
140
|
|
128
141
|
UI.user_error!("Couldn't find build file at path '#{file}'") unless file && File.exist?(file)
|
129
142
|
|
130
143
|
file_ext = Helper::AppcenterHelper.file_extname_full(file)
|
131
144
|
if destination_type == "group"
|
132
|
-
|
145
|
+
self.optional_error("Can't distribute #{file_ext} to groups, please use `destination_type: 'store'`") if Constants::STORE_ONLY_EXTENSIONS.include? file_ext
|
133
146
|
else
|
134
|
-
|
147
|
+
self.optional_error("Can't distribute #{file_ext} to stores, please use `destination_type: 'group'`") unless Constants::STORE_SUPPORTED_EXTENSIONS.include? file_ext
|
148
|
+
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 == "*"
|
135
149
|
end
|
136
150
|
|
151
|
+
release_upload_body = nil
|
137
152
|
unless params[:file].to_s.empty?
|
138
|
-
if
|
139
|
-
|
140
|
-
|
153
|
+
if Constants::FULL_VERSION_REQUIRED_EXTENSIONS.include? file_ext
|
154
|
+
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?
|
155
|
+
elsif Constants::VERSION_REQUIRED_EXTENSIONS.include? file_ext
|
156
|
+
self.optional_error("Field `version` must be specified to upload a #{file_ext} file") if version.to_s.empty?
|
141
157
|
else
|
142
|
-
|
158
|
+
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?
|
143
159
|
end
|
160
|
+
|
161
|
+
release_upload_body = { build_version: version } unless version.nil?
|
162
|
+
release_upload_body = { build_version: version, build_number: build_number } if !version.nil? && !build_number.nil?
|
144
163
|
end
|
145
164
|
|
146
165
|
if file_ext == ".app" && File.directory?(file)
|
147
166
|
UI.message("App path is a directory, zipping it before upload")
|
148
167
|
zip_file = file + ".zip"
|
149
|
-
if File.
|
168
|
+
if File.exist? zip_file
|
150
169
|
override = UI.interactive? ? UI.confirm("File '#{zip_file}' already exists, do you want to override it?") : true
|
151
170
|
UI.abort_with_message!("Not overriding, aborting publishing operation") unless override
|
152
|
-
UI.message("Deleting zip
|
171
|
+
UI.message("Deleting zip archive: #{zip_file}")
|
153
172
|
File.delete zip_file
|
154
173
|
end
|
174
|
+
UI.message("Creating zip archive: #{zip_file}")
|
155
175
|
file = Actions::ZipAction.run(path: file, output_path: zip_file)
|
156
176
|
end
|
157
177
|
|
@@ -166,29 +186,51 @@ module Fastlane
|
|
166
186
|
|
167
187
|
if uploaded
|
168
188
|
release_id = uploaded['release_id']
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
189
|
+
release_url = Helper::AppcenterHelper.get_release_url(owner_type, owner_name, app_name, release_id)
|
190
|
+
UI.message("Release '#{release_id}' committed: #{release_url}")
|
191
|
+
|
192
|
+
release = Helper::AppcenterHelper.update_release(api_token, owner_name, app_name, release_id, release_notes)
|
193
|
+
Helper::AppcenterHelper.update_release_metadata(api_token, owner_name, app_name, release_id, dsa_signature)
|
194
|
+
|
195
|
+
destinations_array = []
|
196
|
+
if destinations == '*'
|
197
|
+
UI.message("Looking up all distribution groups for #{owner_name}/#{app_name}")
|
198
|
+
distribution_groups = Helper::AppcenterHelper.fetch_distribution_groups(
|
199
|
+
api_token: api_token,
|
200
|
+
owner_name: owner_name,
|
201
|
+
app_name: app_name
|
202
|
+
)
|
203
|
+
|
204
|
+
UI.abort_with_message!("Failed to list distribution groups for #{owner_name}/#{app_name}") unless distribution_groups
|
205
|
+
|
206
|
+
destinations_array = distribution_groups.map {|h| h['name'] }
|
207
|
+
else
|
208
|
+
destinations_array = destinations.split(',').map(&:strip)
|
209
|
+
end
|
210
|
+
|
174
211
|
destinations_array.each do |destination_name|
|
175
212
|
destination = Helper::AppcenterHelper.get_destination(api_token, owner_name, app_name, destination_type, destination_name)
|
176
213
|
if destination
|
177
214
|
destination_id = destination['id']
|
178
215
|
distributed_release = Helper::AppcenterHelper.add_to_destination(api_token, owner_name, app_name, release_id, destination_type, destination_id, mandatory_update, notify_testers)
|
179
216
|
if distributed_release
|
180
|
-
UI.success("Release #{distributed_release['short_version']} was successfully distributed to #{destination_type} \"#{destination_name}\"")
|
217
|
+
UI.success("Release '#{release_id}' (#{distributed_release['short_version']}) was successfully distributed to #{destination_type} \"#{destination_name}\"")
|
181
218
|
else
|
182
|
-
UI.error("Release '#{release_id}' was not found")
|
219
|
+
UI.error("Release '#{release_id}' was not found for destination '#{destination_name}'")
|
183
220
|
end
|
184
221
|
else
|
185
222
|
UI.error("#{destination_type} '#{destination_name}' was not found")
|
186
223
|
end
|
187
224
|
end
|
225
|
+
|
226
|
+
safe_download_url = Helper::AppcenterHelper.get_install_url(owner_type, owner_name, app_name)
|
227
|
+
UI.message("Release '#{release_id}' is available for download at: #{safe_download_url}")
|
188
228
|
else
|
189
229
|
UI.user_error!("Failed to upload release")
|
190
230
|
end
|
191
231
|
end
|
232
|
+
|
233
|
+
release
|
192
234
|
end
|
193
235
|
|
194
236
|
# checks app existance, if ther is no such - creates it
|
@@ -204,11 +246,17 @@ module Fastlane
|
|
204
246
|
platforms = {
|
205
247
|
Android: %w[Java React-Native Xamarin],
|
206
248
|
iOS: %w[Objective-C-Swift React-Native Xamarin],
|
207
|
-
macOS: %w[Objective-C-Swift]
|
249
|
+
macOS: %w[Objective-C-Swift],
|
250
|
+
Windows: %w[UWP WPF WinForms Unity]
|
208
251
|
}
|
209
252
|
|
210
|
-
|
211
|
-
|
253
|
+
begin
|
254
|
+
if Helper::AppcenterHelper.get_app(api_token, owner_name, app_name)
|
255
|
+
return true
|
256
|
+
end
|
257
|
+
rescue URI::InvalidURIError
|
258
|
+
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.")
|
259
|
+
return false
|
212
260
|
end
|
213
261
|
|
214
262
|
should_create_app = !app_display_name.to_s.empty? || !app_os.to_s.empty? || !app_platform.to_s.empty?
|
@@ -216,9 +264,9 @@ module Fastlane
|
|
216
264
|
if Helper.test? || should_create_app || UI.confirm("App with name #{app_name} not found, create one?")
|
217
265
|
app_display_name = app_name if app_display_name.to_s.empty?
|
218
266
|
os = app_os.to_s.empty? && (Helper.test? ? "Android" : UI.select("Select OS", platforms.keys)) || app_os.to_s
|
219
|
-
platform = app_platform.to_s.empty? && (Helper.test? ?
|
267
|
+
platform = app_platform.to_s.empty? && (Helper.test? ? platforms[os.to_sym][0] : app_platform.to_s) || app_platform.to_s
|
220
268
|
if platform.to_s.empty?
|
221
|
-
platform = platforms[os].length == 1 ? platforms[os][0] : UI.select("Select Platform", platforms[os])
|
269
|
+
platform = platforms[os.to_sym].length == 1 ? platforms[os.to_sym][0] : UI.select("Select Platform", platforms[os.to_sym])
|
222
270
|
end
|
223
271
|
|
224
272
|
Helper::AppcenterHelper.create_app(api_token, owner_type, owner_name, app_name, app_display_name, os, platform)
|
@@ -230,14 +278,20 @@ module Fastlane
|
|
230
278
|
|
231
279
|
def self.run(params)
|
232
280
|
values = params.values
|
281
|
+
upload_build_only = params[:upload_build_only]
|
233
282
|
upload_dsym_only = params[:upload_dsym_only]
|
234
283
|
upload_mapping_only = params[:upload_mapping_only]
|
235
284
|
|
285
|
+
Options.strict_mode(params[:strict])
|
286
|
+
|
236
287
|
# if app found or successfully created
|
237
288
|
if self.get_or_create_app(params)
|
238
|
-
self.run_release_upload(params) unless upload_dsym_only || upload_mapping_only
|
239
|
-
|
240
|
-
|
289
|
+
release = self.run_release_upload(params) unless upload_dsym_only || upload_mapping_only
|
290
|
+
params[:version] = release['short_version'] if release
|
291
|
+
params[:build_number] = release['version'] if release
|
292
|
+
|
293
|
+
self.run_dsym_upload(params) unless upload_mapping_only || upload_build_only
|
294
|
+
self.run_mapping_upload(params) unless upload_dsym_only || upload_build_only
|
241
295
|
end
|
242
296
|
|
243
297
|
return values if Helper.test?
|
@@ -260,6 +314,7 @@ module Fastlane
|
|
260
314
|
FastlaneCore::ConfigItem.new(key: :api_token,
|
261
315
|
env_name: "APPCENTER_API_TOKEN",
|
262
316
|
description: "API Token for App Center",
|
317
|
+
default_value: Actions.lane_context[SharedValues::APPCENTER_API_TOKEN],
|
263
318
|
optional: false,
|
264
319
|
type: String,
|
265
320
|
verify_block: proc do |value|
|
@@ -268,18 +323,19 @@ module Fastlane
|
|
268
323
|
|
269
324
|
FastlaneCore::ConfigItem.new(key: :owner_type,
|
270
325
|
env_name: "APPCENTER_OWNER_TYPE",
|
271
|
-
description: "Owner type",
|
326
|
+
description: "Owner type, either 'user' or 'organization'",
|
272
327
|
optional: true,
|
273
328
|
default_value: "user",
|
274
329
|
type: String,
|
275
330
|
verify_block: proc do |value|
|
276
331
|
accepted_formats = ["user", "organization"]
|
277
|
-
UI.user_error!("Only \"user\" and \"organization\" types are allowed, you provided \"#{
|
332
|
+
UI.user_error!("Only \"user\" and \"organization\" types are allowed, you provided \"#{value}\"") unless accepted_formats.include? value
|
278
333
|
end),
|
279
334
|
|
280
335
|
FastlaneCore::ConfigItem.new(key: :owner_name,
|
281
336
|
env_name: "APPCENTER_OWNER_NAME",
|
282
|
-
description: "Owner name",
|
337
|
+
description: "Owner name as found in the App's URL in App Center",
|
338
|
+
default_value: Actions.lane_context[SharedValues::APPCENTER_OWNER_NAME],
|
283
339
|
optional: false,
|
284
340
|
type: String,
|
285
341
|
verify_block: proc do |value|
|
@@ -288,7 +344,8 @@ module Fastlane
|
|
288
344
|
|
289
345
|
FastlaneCore::ConfigItem.new(key: :app_name,
|
290
346
|
env_name: "APPCENTER_APP_NAME",
|
291
|
-
description: "App name. If there is no app with such name, you will be prompted to create one",
|
347
|
+
description: "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",
|
348
|
+
default_value: Actions.lane_context[SharedValues::APPCENTER_APP_NAME],
|
292
349
|
optional: false,
|
293
350
|
type: String,
|
294
351
|
verify_block: proc do |value|
|
@@ -303,13 +360,13 @@ module Fastlane
|
|
303
360
|
|
304
361
|
FastlaneCore::ConfigItem.new(key: :app_os,
|
305
362
|
env_name: "APPCENTER_APP_OS",
|
306
|
-
description: "App OS. Used for new app creation, if app
|
363
|
+
description: "App OS. Used for new app creation, if app 'app_name' was not found",
|
307
364
|
optional: true,
|
308
365
|
type: String),
|
309
366
|
|
310
367
|
FastlaneCore::ConfigItem.new(key: :app_platform,
|
311
368
|
env_name: "APPCENTER_APP_PLATFORM",
|
312
|
-
description: "App Platform. Used for new app creation, if app
|
369
|
+
description: "App Platform. Used for new app creation, if app 'app_name' was not found",
|
313
370
|
optional: true,
|
314
371
|
type: String),
|
315
372
|
|
@@ -318,6 +375,7 @@ module Fastlane
|
|
318
375
|
description: "Build release path for android build",
|
319
376
|
default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH],
|
320
377
|
optional: true,
|
378
|
+
deprecated: true,
|
321
379
|
type: String,
|
322
380
|
conflicting_options: [:ipa, :aab, :file],
|
323
381
|
conflict_block: proc do |value|
|
@@ -326,7 +384,7 @@ module Fastlane
|
|
326
384
|
verify_block: proc do |value|
|
327
385
|
accepted_formats = [".apk"]
|
328
386
|
file_extname_full = Helper::AppcenterHelper.file_extname_full(value)
|
329
|
-
|
387
|
+
self.optional_error("Only \".apk\" formats are allowed, you provided \"#{file_extname_full}\"") unless accepted_formats.include? file_extname_full
|
330
388
|
end),
|
331
389
|
|
332
390
|
FastlaneCore::ConfigItem.new(key: :aab,
|
@@ -334,6 +392,7 @@ module Fastlane
|
|
334
392
|
description: "Build release path for android app bundle build",
|
335
393
|
default_value: Actions.lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH],
|
336
394
|
optional: true,
|
395
|
+
deprecated: true,
|
337
396
|
type: String,
|
338
397
|
conflicting_options: [:ipa, :apk, :file],
|
339
398
|
conflict_block: proc do |value|
|
@@ -341,7 +400,7 @@ module Fastlane
|
|
341
400
|
end,
|
342
401
|
verify_block: proc do |value|
|
343
402
|
accepted_formats = [".aab"]
|
344
|
-
|
403
|
+
self.optional_error("Only \".aab\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
|
345
404
|
end),
|
346
405
|
|
347
406
|
FastlaneCore::ConfigItem.new(key: :ipa,
|
@@ -349,6 +408,7 @@ module Fastlane
|
|
349
408
|
description: "Build release path for iOS builds",
|
350
409
|
default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH],
|
351
410
|
optional: true,
|
411
|
+
deprecated: true,
|
352
412
|
type: String,
|
353
413
|
conflicting_options: [:apk, :aab, :file],
|
354
414
|
conflict_block: proc do |value|
|
@@ -356,12 +416,12 @@ module Fastlane
|
|
356
416
|
end,
|
357
417
|
verify_block: proc do |value|
|
358
418
|
accepted_formats = [".ipa"]
|
359
|
-
|
419
|
+
self.optional_error("Only \".ipa\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
|
360
420
|
end),
|
361
421
|
|
362
422
|
FastlaneCore::ConfigItem.new(key: :file,
|
363
423
|
env_name: "APPCENTER_DISTRIBUTE_FILE",
|
364
|
-
description: "
|
424
|
+
description: "File path to the release build to publish",
|
365
425
|
optional: true,
|
366
426
|
type: String,
|
367
427
|
conflicting_options: [:apk, :aab, :ipa],
|
@@ -369,11 +429,29 @@ module Fastlane
|
|
369
429
|
UI.user_error!("You can't use 'file' and '#{value.key}' options in one run")
|
370
430
|
end,
|
371
431
|
verify_block: proc do |value|
|
372
|
-
|
373
|
-
|
374
|
-
|
432
|
+
platform = Actions.lane_context[SharedValues::PLATFORM_NAME]
|
433
|
+
if platform
|
434
|
+
accepted_formats = Constants::SUPPORTED_EXTENSIONS[platform.to_sym]
|
435
|
+
unless accepted_formats
|
436
|
+
UI.important("Unknown platform '#{platform}', Supported are #{Constants::SUPPORTED_EXTENSIONS.keys}")
|
437
|
+
accepted_formats = Constants::ALL_SUPPORTED_EXTENSIONS
|
438
|
+
end
|
439
|
+
file_ext = Helper::AppcenterHelper.file_extname_full(value)
|
440
|
+
self.optional_error("Extension not supported: '#{file_ext}'. Supported formats for platform '#{platform}': #{accepted_formats.join ' '}") unless accepted_formats.include? file_ext
|
441
|
+
end
|
375
442
|
end),
|
376
443
|
|
444
|
+
FastlaneCore::ConfigItem.new(key: :upload_build_only,
|
445
|
+
env_name: "APPCENTER_DISTRIBUTE_UPLOAD_BUILD_ONLY",
|
446
|
+
description: "Flag to upload only the build to App Center. Skips uploading symbols or mapping",
|
447
|
+
optional: true,
|
448
|
+
is_string: false,
|
449
|
+
default_value: false,
|
450
|
+
conflicting_options: [:upload_dsym_only, :upload_mapping_only],
|
451
|
+
conflict_block: proc do |value|
|
452
|
+
UI.user_error!("You can't use 'upload_build_only' and '#{value.key}' options in one run")
|
453
|
+
end),
|
454
|
+
|
377
455
|
FastlaneCore::ConfigItem.new(key: :dsym,
|
378
456
|
env_name: "APPCENTER_DISTRIBUTE_DSYM",
|
379
457
|
description: "Path to your symbols file. For iOS provide path to app.dSYM.zip",
|
@@ -398,6 +476,7 @@ module Fastlane
|
|
398
476
|
FastlaneCore::ConfigItem.new(key: :mapping,
|
399
477
|
env_name: "APPCENTER_DISTRIBUTE_ANDROID_MAPPING",
|
400
478
|
description: "Path to your Android mapping.txt",
|
479
|
+
default_value: (defined? SharedValues::GRADLE_MAPPING_TXT_OUTPUT_PATH) && Actions.lane_context[SharedValues::GRADLE_MAPPING_TXT_OUTPUT_PATH] || nil,
|
401
480
|
optional: true,
|
402
481
|
type: String,
|
403
482
|
verify_block: proc do |value|
|
@@ -427,12 +506,11 @@ module Fastlane
|
|
427
506
|
|
428
507
|
FastlaneCore::ConfigItem.new(key: :destinations,
|
429
508
|
env_name: "APPCENTER_DISTRIBUTE_DESTINATIONS",
|
430
|
-
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",
|
431
|
-
default_value: "Collaborators",
|
509
|
+
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",
|
510
|
+
default_value: Actions.lane_context[SharedValues::APPCENTER_DISTRIBUTE_DESTINATIONS] || "Collaborators",
|
432
511
|
optional: true,
|
433
512
|
type: String),
|
434
513
|
|
435
|
-
|
436
514
|
FastlaneCore::ConfigItem.new(key: :destination_type,
|
437
515
|
env_name: "APPCENTER_DISTRIBUTE_DESTINATION_TYPE",
|
438
516
|
description: "Destination type of distribution destination. 'group' and 'store' are supported",
|
@@ -479,13 +557,13 @@ module Fastlane
|
|
479
557
|
|
480
558
|
FastlaneCore::ConfigItem.new(key: :build_number,
|
481
559
|
env_name: "APPCENTER_DISTRIBUTE_BUILD_NUMBER",
|
482
|
-
description: "The build number, required for
|
560
|
+
description: "The build number, required for macOS .pkg and .dmg builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only`",
|
483
561
|
optional: true,
|
484
562
|
type: String),
|
485
563
|
|
486
564
|
FastlaneCore::ConfigItem.new(key: :version,
|
487
565
|
env_name: "APPCENTER_DISTRIBUTE_VERSION",
|
488
|
-
description: "The build version, required for
|
566
|
+
description: "The build version, required for .pkg, .dmg, .zip and .msi builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only`",
|
489
567
|
optional: true,
|
490
568
|
type: String),
|
491
569
|
|
@@ -494,6 +572,18 @@ module Fastlane
|
|
494
572
|
description: "Request timeout in seconds",
|
495
573
|
optional: true,
|
496
574
|
type: Integer),
|
575
|
+
|
576
|
+
FastlaneCore::ConfigItem.new(key: :dsa_signature,
|
577
|
+
env_name: "APPCENTER_DISTRIBUTE_DSA_SIGNATURE",
|
578
|
+
description: "DSA signature of the macOS or Windows release for Sparkle update feed",
|
579
|
+
optional: true,
|
580
|
+
type: String),
|
581
|
+
|
582
|
+
FastlaneCore::ConfigItem.new(key: :strict,
|
583
|
+
env_name: "APPCENTER_STRICT_MODE",
|
584
|
+
description: "Strict mode, set to 'true' to fail early in case a potential error was detected",
|
585
|
+
optional: true,
|
586
|
+
type: String)
|
497
587
|
]
|
498
588
|
end
|
499
589
|
|
@@ -505,6 +595,8 @@ module Fastlane
|
|
505
595
|
end
|
506
596
|
|
507
597
|
def self.is_supported?(platform)
|
598
|
+
return Constants::SUPPORTED_EXTENSIONS.keys.include?(platform) if Options.strict
|
599
|
+
|
508
600
|
true
|
509
601
|
end
|
510
602
|
|
@@ -513,12 +605,10 @@ module Fastlane
|
|
513
605
|
'appcenter_upload(
|
514
606
|
api_token: "...",
|
515
607
|
owner_name: "appcenter_owner",
|
516
|
-
app_name: "
|
517
|
-
|
608
|
+
app_name: "testing_android_app",
|
609
|
+
file: "./app-release.apk",
|
518
610
|
destinations: "Testers",
|
519
611
|
destination_type: "group",
|
520
|
-
build_number: "3",
|
521
|
-
version: "1.0.0",
|
522
612
|
mapping: "./mapping.txt",
|
523
613
|
release_notes: "release notes",
|
524
614
|
notify_testers: false
|
@@ -526,9 +616,9 @@ module Fastlane
|
|
526
616
|
'appcenter_upload(
|
527
617
|
api_token: "...",
|
528
618
|
owner_name: "appcenter_owner",
|
529
|
-
app_name: "
|
530
|
-
|
531
|
-
destinations: "Testers,
|
619
|
+
app_name: "testing_ios_app",
|
620
|
+
file: "./app-release.ipa",
|
621
|
+
destinations: "Testers,Public",
|
532
622
|
destination_type: "group",
|
533
623
|
dsym: "./app.dSYM.zip",
|
534
624
|
release_notes: "release notes",
|
@@ -537,18 +627,46 @@ module Fastlane
|
|
537
627
|
'appcenter_upload(
|
538
628
|
api_token: "...",
|
539
629
|
owner_name: "appcenter_owner",
|
540
|
-
app_name: "
|
541
|
-
|
542
|
-
destinations: "
|
543
|
-
destination_type: "
|
544
|
-
build_number: "3",
|
545
|
-
version: "1.0.0",
|
546
|
-
mapping: "./mapping.txt",
|
630
|
+
app_name: "testing_ios_app",
|
631
|
+
file: "./app-release.ipa",
|
632
|
+
destinations: "*",
|
633
|
+
destination_type: "group",
|
547
634
|
release_notes: "release notes",
|
548
635
|
notify_testers: false
|
636
|
+
)',
|
637
|
+
'appcenter_upload(
|
638
|
+
api_token: "...",
|
639
|
+
owner_name: "appcenter_owner",
|
640
|
+
app_name: "testing_google_play_app",
|
641
|
+
file: "./app.aab",
|
642
|
+
destinations: "Alpha",
|
643
|
+
destination_type: "store",
|
644
|
+
release_notes: "this is a store release"
|
549
645
|
)'
|
550
646
|
]
|
551
647
|
end
|
648
|
+
|
649
|
+
class Options
|
650
|
+
include Singleton
|
651
|
+
|
652
|
+
def self.strict_mode(mode)
|
653
|
+
@strict = mode.to_s == "true"
|
654
|
+
UI.message("Enabled strict mode") if @strict
|
655
|
+
end
|
656
|
+
|
657
|
+
def self.strict
|
658
|
+
@strict
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
def self.optional_error(message)
|
663
|
+
if Options.strict
|
664
|
+
UI.user_error!(message)
|
665
|
+
else
|
666
|
+
UI.important(message)
|
667
|
+
UI.important("The current operation might fail, trying anyway...")
|
668
|
+
end
|
669
|
+
end
|
552
670
|
end
|
553
671
|
end
|
554
672
|
end
|
@@ -6,22 +6,20 @@ module Fastlane
|
|
6
6
|
# accounting for file types that can and should be zip-compressed
|
7
7
|
# before they are uploaded
|
8
8
|
def self.file_extname_full(path)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
unzip_basename = File.basename(path, ".zip")
|
13
|
-
unzip_extname = File.extname(unzip_basename)
|
9
|
+
%w(.app.zip .dSYM.zip).each do |suffix|
|
10
|
+
return suffix if path.to_s.downcase.end_with? suffix.downcase
|
11
|
+
end
|
14
12
|
|
15
|
-
|
13
|
+
File.extname path
|
16
14
|
end
|
17
15
|
|
18
16
|
# create request
|
19
|
-
def self.connection(upload_url =
|
17
|
+
def self.connection(upload_url = nil, dsym = false, csv = false)
|
20
18
|
require 'faraday'
|
21
19
|
require 'faraday_middleware'
|
22
20
|
|
23
21
|
options = {
|
24
|
-
url: upload_url
|
22
|
+
url: upload_url || ENV.fetch('APPCENTER_UPLOAD_URL', "https://api.appcenter.ms")
|
25
23
|
}
|
26
24
|
|
27
25
|
Faraday.new(options) do |builder|
|
@@ -31,7 +29,7 @@ module Fastlane
|
|
31
29
|
else
|
32
30
|
builder.request :json
|
33
31
|
end
|
34
|
-
builder.response :json, content_type: /\bjson$/
|
32
|
+
builder.response :json, content_type: /\bjson$/ unless csv
|
35
33
|
builder.use FaradayMiddleware::FollowRedirects
|
36
34
|
builder.adapter :net_http
|
37
35
|
end
|
@@ -151,6 +149,9 @@ module Fastlane
|
|
151
149
|
when 200...300
|
152
150
|
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
153
151
|
response.body
|
152
|
+
when 401
|
153
|
+
UI.user_error!("Auth Error, provided invalid token")
|
154
|
+
false
|
154
155
|
else
|
155
156
|
UI.error("Error #{response.status}: #{response.body}")
|
156
157
|
false
|
@@ -177,6 +178,9 @@ module Fastlane
|
|
177
178
|
when 200...300
|
178
179
|
self.update_symbol_upload(api_token, owner_name, app_name, symbol_upload_id, 'committed')
|
179
180
|
UI.success("#{logType} uploaded")
|
181
|
+
when 401
|
182
|
+
UI.user_error!("Auth Error, provided invalid token")
|
183
|
+
false
|
180
184
|
else
|
181
185
|
UI.error("Error uploading #{logType} #{response.status}: #{response.body}")
|
182
186
|
self.update_symbol_upload(api_token, owner_name, app_name, symbol_upload_id, 'aborted')
|
@@ -206,6 +210,9 @@ module Fastlane
|
|
206
210
|
when 200...300
|
207
211
|
UI.message("Binary uploaded")
|
208
212
|
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'committed')
|
213
|
+
when 401
|
214
|
+
UI.user_error!("Auth Error, provided invalid token")
|
215
|
+
false
|
209
216
|
else
|
210
217
|
UI.error("Error uploading binary #{response.status}: #{response.body}")
|
211
218
|
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'aborted')
|
@@ -230,6 +237,9 @@ module Fastlane
|
|
230
237
|
when 200...300
|
231
238
|
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
232
239
|
response.body
|
240
|
+
when 401
|
241
|
+
UI.user_error!("Auth Error, provided invalid token")
|
242
|
+
false
|
233
243
|
when 500...600
|
234
244
|
UI.crash!("Internal Service Error, please try again later")
|
235
245
|
else
|
@@ -254,6 +264,9 @@ module Fastlane
|
|
254
264
|
when 404
|
255
265
|
UI.error("Not found, invalid release url")
|
256
266
|
false
|
267
|
+
when 401
|
268
|
+
UI.user_error!("Auth Error, provided invalid token")
|
269
|
+
false
|
257
270
|
else
|
258
271
|
UI.error("Error fetching information about release #{response.status}: #{response.body}")
|
259
272
|
false
|
@@ -277,6 +290,9 @@ module Fastlane
|
|
277
290
|
when 404
|
278
291
|
UI.error("Not found, invalid distribution #{destination_type} name")
|
279
292
|
false
|
293
|
+
when 401
|
294
|
+
UI.user_error!("Auth Error, provided invalid token")
|
295
|
+
false
|
280
296
|
else
|
281
297
|
UI.error("Error getting #{destination_type} #{response.status}: #{response.body}")
|
282
298
|
false
|
@@ -291,7 +307,7 @@ module Fastlane
|
|
291
307
|
req.headers['X-API-Token'] = api_token
|
292
308
|
req.headers['internal-request-source'] = "fastlane"
|
293
309
|
req.body = {
|
294
|
-
|
310
|
+
release_notes: release_notes
|
295
311
|
}
|
296
312
|
end
|
297
313
|
|
@@ -300,6 +316,7 @@ module Fastlane
|
|
300
316
|
# get full release info
|
301
317
|
release = self.get_release(api_token, owner_name, app_name, release_id)
|
302
318
|
return false unless release
|
319
|
+
|
303
320
|
download_url = release['download_url']
|
304
321
|
|
305
322
|
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
@@ -307,18 +324,53 @@ module Fastlane
|
|
307
324
|
Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
308
325
|
Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
309
326
|
|
310
|
-
UI.message("Release #{release['short_version']} was successfully updated")
|
327
|
+
UI.message("Release '#{release_id}' (#{release['short_version']}) was successfully updated")
|
311
328
|
|
312
329
|
release
|
313
330
|
when 404
|
314
331
|
UI.error("Not found, invalid release id")
|
315
332
|
false
|
333
|
+
when 401
|
334
|
+
UI.user_error!("Auth Error, provided invalid token")
|
335
|
+
false
|
316
336
|
else
|
317
337
|
UI.error("Error adding updating release #{response.status}: #{response.body}")
|
318
338
|
false
|
319
339
|
end
|
320
340
|
end
|
321
341
|
|
342
|
+
# updates release metadata
|
343
|
+
def self.update_release_metadata(api_token, owner_name, app_name, release_id, dsa_signature)
|
344
|
+
return if dsa_signature.to_s == ''
|
345
|
+
|
346
|
+
release_metadata = {
|
347
|
+
dsa_signature: dsa_signature
|
348
|
+
}
|
349
|
+
|
350
|
+
connection = self.connection
|
351
|
+
response = connection.patch("v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}") do |req|
|
352
|
+
req.headers['X-API-Token'] = api_token
|
353
|
+
req.headers['internal-request-source'] = "fastlane"
|
354
|
+
req.body = {
|
355
|
+
metadata: release_metadata
|
356
|
+
}
|
357
|
+
end
|
358
|
+
|
359
|
+
case response.status
|
360
|
+
when 200...300
|
361
|
+
UI.message("Release Metadata was successfully updated for release '#{release_id}'")
|
362
|
+
when 404
|
363
|
+
UI.error("Not found, invalid release id")
|
364
|
+
false
|
365
|
+
when 401
|
366
|
+
UI.user_error!("Auth Error, provided invalid token")
|
367
|
+
false
|
368
|
+
else
|
369
|
+
UI.error("Error adding updating release metadata #{response.status}: #{response.body}")
|
370
|
+
false
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
322
374
|
# add release to distribution group or store
|
323
375
|
def self.add_to_destination(api_token, owner_name, app_name, release_id, destination_type, destination_id, mandatory_update = false, notify_testers = false)
|
324
376
|
connection = self.connection
|
@@ -342,6 +394,7 @@ module Fastlane
|
|
342
394
|
# get full release info
|
343
395
|
release = self.get_release(api_token, owner_name, app_name, release_id)
|
344
396
|
return false unless release
|
397
|
+
|
345
398
|
download_url = release['download_url']
|
346
399
|
|
347
400
|
UI.message("DEBUG: received release #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
@@ -349,12 +402,15 @@ module Fastlane
|
|
349
402
|
Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
350
403
|
Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
351
404
|
|
352
|
-
UI.message("
|
405
|
+
UI.message("Release '#{release_id}' (#{release['short_version']}) was successfully distributed'")
|
353
406
|
|
354
407
|
release
|
355
408
|
when 404
|
356
409
|
UI.error("Not found, invalid distribution #{destination_type} name")
|
357
410
|
false
|
411
|
+
when 401
|
412
|
+
UI.user_error!("Auth Error, provided invalid token")
|
413
|
+
false
|
358
414
|
else
|
359
415
|
UI.error("Error adding to #{destination_type} #{response.status}: #{response.body}")
|
360
416
|
false
|
@@ -377,6 +433,9 @@ module Fastlane
|
|
377
433
|
when 404
|
378
434
|
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
379
435
|
false
|
436
|
+
when 401
|
437
|
+
UI.user_error!("Auth Error, provided invalid token")
|
438
|
+
false
|
380
439
|
else
|
381
440
|
UI.error("Error getting app #{owner_name}/#{app_name}, #{response.status}: #{response.body}")
|
382
441
|
false
|
@@ -406,11 +465,78 @@ module Fastlane
|
|
406
465
|
UI.message("DEBUG: #{JSON.pretty_generate(created)}") if ENV['DEBUG']
|
407
466
|
UI.success("Created #{os}/#{platform} app with name \"#{created['name']}\" and display name \"#{created['display_name']}\" for #{owner_type} \"#{owner_name}\"")
|
408
467
|
true
|
468
|
+
when 401
|
469
|
+
UI.user_error!("Auth Error, provided invalid token")
|
470
|
+
false
|
409
471
|
else
|
410
472
|
UI.error("Error creating app #{response.status}: #{response.body}")
|
411
473
|
false
|
412
474
|
end
|
413
475
|
end
|
476
|
+
|
477
|
+
def self.fetch_distribution_groups(api_token:, owner_name:, app_name:)
|
478
|
+
connection = self.connection
|
479
|
+
|
480
|
+
endpoint = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups"
|
481
|
+
|
482
|
+
response = connection.get(endpoint) do |req|
|
483
|
+
req.headers['X-API-Token'] = api_token
|
484
|
+
req.headers['internal-request-source'] = "fastlane"
|
485
|
+
end
|
486
|
+
|
487
|
+
case response.status
|
488
|
+
when 200...300
|
489
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
490
|
+
response.body
|
491
|
+
when 401
|
492
|
+
UI.user_error!("Auth Error, provided invalid token")
|
493
|
+
false
|
494
|
+
when 404
|
495
|
+
UI.error("Not found, invalid owner or application name")
|
496
|
+
false
|
497
|
+
else
|
498
|
+
UI.error("Error #{response.status}: #{response.body}")
|
499
|
+
false
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def self.fetch_devices(api_token:, owner_name:, app_name:, distribution_group:)
|
504
|
+
connection = self.connection(nil, false, true)
|
505
|
+
|
506
|
+
endpoint = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups/#{ERB::Util.url_encode(distribution_group)}/devices/download_devices_list"
|
507
|
+
|
508
|
+
response = connection.get(endpoint) do |req|
|
509
|
+
req.headers['X-API-Token'] = api_token
|
510
|
+
req.headers['internal-request-source'] = "fastlane"
|
511
|
+
end
|
512
|
+
|
513
|
+
case response.status
|
514
|
+
when 200...300
|
515
|
+
UI.message("DEBUG: #{response.body.inspect}") if ENV['DEBUG']
|
516
|
+
response.body
|
517
|
+
when 401
|
518
|
+
UI.user_error!("Auth Error, provided invalid token")
|
519
|
+
false
|
520
|
+
when 404
|
521
|
+
UI.error("Not found, invalid owner, application or distribution group name")
|
522
|
+
false
|
523
|
+
else
|
524
|
+
UI.error("Error #{response.status}: #{response.body}")
|
525
|
+
false
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Note: This does not support testing environment (INT)
|
530
|
+
def self.get_release_url(owner_type, owner_name, app_name, release_id)
|
531
|
+
owner_path = owner_type == "user" ? "users/#{owner_name}" : "orgs/#{owner_name}"
|
532
|
+
return "https://appcenter.ms/#{owner_path}/apps/#{app_name}/distribute/releases/#{release_id}"
|
533
|
+
end
|
534
|
+
|
535
|
+
# Note: This does not support testing environment (INT)
|
536
|
+
def self.get_install_url(owner_type, owner_name, app_name)
|
537
|
+
owner_path = owner_type == "user" ? "users/#{owner_name}" : "orgs/#{owner_name}"
|
538
|
+
return "https://install.appcenter.ms/#{owner_path}/apps/#{app_name}"
|
539
|
+
end
|
414
540
|
end
|
415
541
|
end
|
416
542
|
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: 1.
|
4
|
+
version: 1.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Microsoft Corporation
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.
|
33
|
+
version: 2.96.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 2.
|
40
|
+
version: 2.96.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rspec_junit_formatter
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.77.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.77.0
|
97
111
|
description:
|
98
112
|
email:
|
99
113
|
executables: []
|
@@ -103,6 +117,7 @@ files:
|
|
103
117
|
- LICENSE
|
104
118
|
- README.md
|
105
119
|
- lib/fastlane/plugin/appcenter.rb
|
120
|
+
- lib/fastlane/plugin/appcenter/actions/appcenter_fetch_devices_action.rb
|
106
121
|
- lib/fastlane/plugin/appcenter/actions/appcenter_upload_action.rb
|
107
122
|
- lib/fastlane/plugin/appcenter/helper/appcenter_helper.rb
|
108
123
|
- lib/fastlane/plugin/appcenter/version.rb
|