fastlane-plugin-appcenter 1.4.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f420746739b204f6bfd6e9e5dd23f841028009e63ed49adf45107b053eeb24ac
4
- data.tar.gz: '07896d07c221682028e2c59166c8d248a2ede8d953a3da408f734509eb10ae3a'
3
+ metadata.gz: ba75670e59a7a2ba066a788088aeb05d96991bcc1ab693633bb0fca27fc821c5
4
+ data.tar.gz: b11523450e225a2ec2b596d0a4db135cf1dd1a5acefd4a0a0bc0136cf42c8917
5
5
  SHA512:
6
- metadata.gz: 71b84cb05df74504d82d8aaf83626c999f69fdc06481fc072baada8aa29d24fd9e13652359f84a8cca5417b64f3457b5f33dd12d1a769e388e9aa430b0befb7e
7
- data.tar.gz: 6e85fa4da98dfbba7a4147aad34aed090dc1e69224495be770587149dd0efb27747ac5b4ed4b7d1570b24656a610477dab359edbaaedcd6a05fde8d5ef50a452
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 an `appcenter_upload` action which allows you to upload and [release 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.
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
- appcenter_upload(
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
- apk: "<path to android build binary>",
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
- The action parameters `api_token` and `owner_name` can also be omitted when their values are [set as environment variables](https://docs.fastlane.tools/advanced/#environment-variables). Below a list of all available environment variables:
33
-
34
- - `APPCENTER_API_TOKEN` - API Token for App Center
35
- - `APPCENTER_OWNER_TYPE` - Owner type - `user` or `organization` (default value is `user`)
36
- - `APPCENTER_OWNER_NAME` - Owner name
37
- - `APPCENTER_APP_NAME` - App name. If there is no app with such name, you will be prompted to create one
38
- - `APPCENTER_DISTRIBUTE_APK` - Build release path for android build
39
- - `APPCENTER_DISTRIBUTE_AAB` - Build release path for android app bundle build
40
- - `APPCENTER_DISTRIBUTE_IPA` - Build release path for ios build
41
- - `APPCENTER_DISTRIBUTE_DSYM` - Path to your symbols (app.dSYM.zip) file
42
- - `APPCENTER_DISTRIBUTE_UPLOAD_DSYM_ONLY` - Flag to upload only the dSYM file to App Center
43
- - `APPCENTER_DISTRIBUTE_ANDROID_MAPPING` - Path to your Android mapping.txt file
44
- - `APPCENTER_DISTRIBUTE_UPLOAD_ANDROID_MAPPING_ONLY` - Flag to upload only the mapping file to App Center
45
- - `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 is `Collaborators`.
46
- - `APPCENTER_DISTRIBUTE_DESTINATION_TYPE` - Destination type of distribution destination. `group` and `store` are supported. Default is `group`
47
- - `APPCENTER_DISTRIBUTE_MANDATORY_UPDATE` - Require users to update to this release
48
- - `APPCENTER_DISTRIBUTE_NOTIFY_TESTERS` - Send email notification about release (default: `false`)
49
- - `APPCENTER_DISTRIBUTE_RELEASE_NOTES` - Release notes
50
- - `APPCENTER_DISTRIBUTE_RELEASE_NOTES_CLIPPING` - Clip release notes if its length is more then 5000, `true` by default
51
- - `APPCENTER_DISTRIBUTE_RELEASE_NOTES_LINK` - Additional release notes link
52
- - `APPCENTER_DISTRIBUTE_TIMEOUT` - Sets the request timeout in seconds. Used when uploading builds to App Center.
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 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`.
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
- UI.user_error!("Can't distribute #{file_ext} to groups, please use `destination_type: 'store'`") if %w(.aab).include? file_ext
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
- UI.user_error!("Can't distribute #{file_ext} to stores, please use `destination_type: 'group'`") if %w(.app .app.zip .dmg .pkg).include? file_ext
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 %w[.dmg .pkg].include? file_ext
139
- UI.user_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?
140
- release_upload_body = { build_version: version, build_number: build_number }
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
- UI.message("Fields `version` and `build_number` are not required for files of type #{file_ext}, ignored") unless build_number.to_s.empty? && version.to_s.empty?
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.exists? zip_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 file: #{zip_file}")
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
- UI.message("Release '#{release_id}' committed")
170
-
171
- Helper::AppcenterHelper.update_release(api_token, owner_name, app_name, release_id, release_notes)
172
-
173
- destinations_array = destinations.split(',')
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
- if Helper::AppcenterHelper.get_app(api_token, owner_name, app_name)
211
- return true
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? ? "Java" : app_platform.to_s) || app_platform.to_s
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
- self.run_dsym_upload(params) unless upload_mapping_only
240
- self.run_mapping_upload(params) unless upload_dsym_only
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 \"#{File.extname(value)}\"") unless accepted_formats.include? value
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 with 'app_name' name was not found",
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 with 'app_name' name was not found",
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
- UI.user_error!("Only \".apk\" formats are allowed, you provided \"#{file_extname_full}\"") unless accepted_formats.include? file_extname_full
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
- UI.user_error!("Only \".aab\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
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
- UI.user_error!("Only \".ipa\" formats are allowed, you provided \"#{File.extname(value)}\"") unless accepted_formats.include? File.extname(value)
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: "Build release path for generic builds (.aab, .app, .app.zip, .apk, .dmg, .ipa, .pkg)",
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
- accepted_formats = %w(.aab .app .app.zip .apk .dmg .ipa .pkg)
373
- file_ext = Helper::AppcenterHelper.file_extname_full(value)
374
- UI.user_error!("Only #{accepted_formats.to_s} formats are allowed, you provided \"#{file_ext}\"") unless accepted_formats.include? file_ext
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 Android ProGuard mapping files, as well as macOS .pkg and .dmg builds",
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 Android ProGuard mapping files, as well as macOS .pkg and .dmg builds",
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: "testing_app",
517
- apk: "./app-release.apk",
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: "testing_app",
530
- apk: "./app-release.ipa",
531
- destinations: "Testers,Alpha",
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: "testing_app",
541
- aab: "./app.aab",
542
- destinations: "Alpha",
543
- destination_type: "store",
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
- is_zip = File.extname(path) == ".zip"
10
-
11
- # if file is not .zip'ed, these do not change basename and extname
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
- is_zip ? unzip_extname + ".zip" : unzip_extname
13
+ File.extname path
16
14
  end
17
15
 
18
16
  # create request
19
- def self.connection(upload_url = false, dsym = false)
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 ? upload_url : ENV.fetch('APPCENTER_UPLOAD_URL', "https://api.appcenter.ms")
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
- "release_notes" => release_notes
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("Public Download URL: #{download_url}") if download_url
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
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Appcenter
3
- VERSION = "1.4.0"
3
+ VERSION = "1.7.1"
4
4
  end
5
5
  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.0
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-08-30 00:00:00.000000000 Z
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.29.0
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.29.0
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: rubocop
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