fastlane-plugin-appcenter 1.5.0 → 1.8.1

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