fastlane-plugin-sapfire 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +135 -0
  4. data/lib/fastlane/plugin/sapfire/actions/app_certification_action.rb +79 -0
  5. data/lib/fastlane/plugin/sapfire/actions/associate_ms_store_action.rb +263 -0
  6. data/lib/fastlane/plugin/sapfire/actions/build_uwp_app_action.rb +55 -0
  7. data/lib/fastlane/plugin/sapfire/actions/dotnet_select_action.rb +46 -0
  8. data/lib/fastlane/plugin/sapfire/actions/ensure_dotnet_version_action.rb +123 -0
  9. data/lib/fastlane/plugin/sapfire/actions/ms_credentials_action.rb +91 -0
  10. data/lib/fastlane/plugin/sapfire/actions/msbuild_action.rb +45 -0
  11. data/lib/fastlane/plugin/sapfire/actions/msbuild_select_action.rb +53 -0
  12. data/lib/fastlane/plugin/sapfire/actions/nuget_pack_action.rb +46 -0
  13. data/lib/fastlane/plugin/sapfire/actions/update_uwp_signing_settings_action.rb +73 -0
  14. data/lib/fastlane/plugin/sapfire/actions/upload_ms_store_action.rb +218 -0
  15. data/lib/fastlane/plugin/sapfire/actions/upload_nuget_action.rb +95 -0
  16. data/lib/fastlane/plugin/sapfire/actions_base/msbuild_action_base.rb +42 -0
  17. data/lib/fastlane/plugin/sapfire/helper/ms_devcenter_helper.rb +292 -0
  18. data/lib/fastlane/plugin/sapfire/helper/sapfire_helper.rb +55 -0
  19. data/lib/fastlane/plugin/sapfire/msbuild/module.rb +34 -0
  20. data/lib/fastlane/plugin/sapfire/msbuild/options.rb +123 -0
  21. data/lib/fastlane/plugin/sapfire/msbuild/runner.rb +171 -0
  22. data/lib/fastlane/plugin/sapfire/sln_project/block.rb +30 -0
  23. data/lib/fastlane/plugin/sapfire/sln_project/global_block.rb +147 -0
  24. data/lib/fastlane/plugin/sapfire/sln_project/module.rb +14 -0
  25. data/lib/fastlane/plugin/sapfire/sln_project/project_block.rb +82 -0
  26. data/lib/fastlane/plugin/sapfire/sln_project/root_block.rb +72 -0
  27. data/lib/fastlane/plugin/sapfire/version.rb +5 -0
  28. data/lib/fastlane/plugin/sapfire.rb +16 -0
  29. metadata +182 -0
@@ -0,0 +1,95 @@
1
+ require "fastlane/action"
2
+ require_relative "../helper/sapfire_helper"
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class UploadNugetAction < Action
7
+ def self.run(params)
8
+ UI.user_error!("Can't find dotnet") unless Helper::SapfireHelper.dotnet_specified?
9
+
10
+ dotnet_path = Actions.lane_context[SharedValues::SF_DOTNET_PATH]
11
+ dotnet_args = get_dotnet_args(params)
12
+ cmd = "\"#{dotnet_path}\" nuget push #{dotnet_args.join(" ")}"
13
+
14
+ UI.command(cmd)
15
+
16
+ Open3.popen2(cmd) do |_, stdout, wait_thr|
17
+ until stdout.eof?
18
+ stdout.each do |l|
19
+ line = l.force_encoding("utf-8").chomp
20
+ puts line
21
+ end
22
+ end
23
+
24
+ UI.user_error!("NuGet package pushing failed. See the log above.") unless wait_thr.value.success?
25
+ UI.success("Package has successfully uploaded") if wait_thr.value.success?
26
+ end
27
+ end
28
+
29
+ def self.description
30
+ "Pushes a package to the server and publishes it"
31
+ end
32
+
33
+ def self.authors
34
+ ["CheeryLee"]
35
+ end
36
+
37
+ def self.is_supported?(platform)
38
+ true
39
+ end
40
+
41
+ def self.available_options
42
+ [
43
+ FastlaneCore::ConfigItem.new(
44
+ key: :api_key,
45
+ description: "The API key for the server",
46
+ optional: false,
47
+ type: String,
48
+ verify_block: proc do |value|
49
+ UI.user_error!("The API key for the server must be specified and must not be empty") unless value && !value.empty?
50
+ end
51
+ ),
52
+ FastlaneCore::ConfigItem.new(
53
+ key: :source,
54
+ description: "The server URL. NuGet identifies a UNC or local folder source and simply copies the file there instead of pushing it using HTTP",
55
+ optional: false,
56
+ type: String
57
+ ),
58
+ FastlaneCore::ConfigItem.new(
59
+ key: :timeout,
60
+ description: "The timeout for pushing to a server in seconds",
61
+ optional: true,
62
+ default_value: 0,
63
+ type: Integer
64
+ ),
65
+ FastlaneCore::ConfigItem.new(
66
+ key: :path,
67
+ description: "The file path to the package to be uploaded",
68
+ optional: false,
69
+ type: String,
70
+ verify_block: proc do |value|
71
+ UI.user_error!("Path to NuGet package is invalid") unless value && !value.empty?
72
+ UI.user_error!("The provided path doesn't point to NUPKG-file") unless
73
+ File.exist?(File.expand_path(value)) && value.end_with?(".nupkg")
74
+ end
75
+ )
76
+ ]
77
+ end
78
+
79
+ def self.category
80
+ :production
81
+ end
82
+
83
+ def self.get_dotnet_args(params)
84
+ args = []
85
+
86
+ args.append(params[:path])
87
+ args.append("--api-key #{params[:api_key]}")
88
+ args.append("--source #{params[:source]}")
89
+ args.append("--timeout #{params[:timeout]}")
90
+
91
+ args
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,42 @@
1
+ require "fastlane/action"
2
+ require_relative "../msbuild/runner"
3
+ require_relative "../msbuild/options"
4
+ require_relative "../msbuild/module"
5
+
6
+ module Fastlane
7
+ module Actions
8
+ class MsbuildActionBase < Action
9
+ def self.run(params)
10
+ Msbuild.config.params = params
11
+ Msbuild.config.msbuild_type = Fastlane::Actions.lane_context[SharedValues::SF_MSBUILD_TYPE]
12
+ Msbuild.config.overwritten_props = overwritten_msbuild_properties
13
+ msbuild_path = Fastlane::Actions.lane_context[SharedValues::SF_MSBUILD_PATH]
14
+
15
+ Msbuild.config.msbuild_path = if Msbuild.config.msbuild_type == Msbuild::MsbuildType::LIBRARY
16
+ "\"#{Fastlane::Actions.lane_context[SharedValues::SF_DOTNET_PATH]}\" \"#{msbuild_path}\""
17
+ else
18
+ "\"#{msbuild_path}\""
19
+ end
20
+
21
+ runner = Msbuild::Runner.new
22
+ runner.run
23
+ end
24
+
25
+ def self.overwritten_msbuild_properties
26
+ {
27
+ Configuration: "configuration",
28
+ Platform: "platform"
29
+ }
30
+ end
31
+
32
+ def self.available_options
33
+ rejected_options_array = rejected_options
34
+ return Msbuild::Options.available_options if rejected_options_array.nil?
35
+
36
+ Msbuild::Options.available_options.reject do |option|
37
+ rejected_options_array.include?(option.key)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,292 @@
1
+ module Fastlane
2
+ module Helper
3
+ class MsDevCenterHelper
4
+ HOST = "https://manage.devcenter.microsoft.com".freeze
5
+ API_VERSION = "v1.0".freeze
6
+ API_ROOT = "my/applications".freeze
7
+ REQUEST_HEADERS = {
8
+ "Accept": "application/json"
9
+ }.freeze
10
+ FILE_CHUNK_SIZE = 26_214_400
11
+ UPLOAD_RETRIES = 3
12
+
13
+ def self.upload_blob(url, zip_path, timeout = 0)
14
+ UI.user_error!("File upload URL need to be provided") if !url.is_a?(String) || url.nil? || url.empty?
15
+ UI.user_error!("File path is invalid") if !zip_path.is_a?(String) || zip_path.nil? || zip_path.empty?
16
+
17
+ expand_path = File.expand_path(zip_path)
18
+ UI.user_error!("The provided path doesn't point to ZIP file") unless File.exist?(expand_path) && zip_path.end_with?(".zip")
19
+
20
+ File.open(expand_path) do |file|
21
+ block_list = []
22
+ chunks_count = (file.size.to_f / FILE_CHUNK_SIZE).ceil
23
+ current_chunk = 1
24
+
25
+ until file.eof?
26
+ bytes = file.read(FILE_CHUNK_SIZE)
27
+ id = SecureRandom.uuid.delete("-")
28
+ block_list.append(id)
29
+ retry_count = 0
30
+ result = false
31
+
32
+ UI.message("Upload chunk [#{current_chunk} / #{chunks_count}]")
33
+
34
+ while !result && retry_count < UPLOAD_RETRIES
35
+ result = upload_block(url, bytes, id, timeout)
36
+ retry_count += 1
37
+ end
38
+
39
+ UI.user_error!("Uploading failed: some chunks have not been uploaded") unless result
40
+ current_chunk += 1
41
+ end
42
+
43
+ result = upload_block_list(url, block_list, timeout)
44
+ UI.user_error!("Uploading failed: block list hasn't been uploaded") unless result
45
+ end
46
+ end
47
+
48
+ def self.upload_block(url, bytes, id, timeout = 0)
49
+ headers = {
50
+ "Content-Length": bytes.length.to_s
51
+ }
52
+
53
+ url_data = parse_upload_url(url)
54
+ url_data[:query]["comp"] = "block"
55
+ url_data[:query]["blockid"] = id
56
+
57
+ connection = Faraday.new(url_data[:host])
58
+ response = connection.put(url_data[:path]) do |req|
59
+ req.headers = headers
60
+ req.params = url_data[:query]
61
+ req.body = bytes
62
+ req.options.timeout = timeout if timeout.positive?
63
+ end
64
+
65
+ return true if response.status == 201
66
+
67
+ error = response.body.to_s
68
+ UI.error("Upload request failed.\nCode: #{response.status}\nError: #{error}")
69
+ false
70
+ end
71
+
72
+ def self.upload_block_list(url, list, timeout = 0)
73
+ document = REXML::Document.new
74
+ document.xml_decl.version = "1.0"
75
+ document.xml_decl.encoding = "utf-8"
76
+ block_list = document.add_element("BlockList")
77
+
78
+ list.each do |block|
79
+ block_list.add_element("Latest").text = block
80
+ end
81
+
82
+ url_data = parse_upload_url(url)
83
+ url_data[:query]["comp"] = "blocklist"
84
+
85
+ connection = Faraday.new(url_data[:host])
86
+ response = connection.put(url_data[:path]) do |req|
87
+ req.params = url_data[:query]
88
+ req.body = document.to_s
89
+ req.options.timeout = timeout if timeout.positive?
90
+ end
91
+
92
+ return true if response.status == 201
93
+
94
+ error = response.body.to_s
95
+ UI.error("Upload block list request failed.\nCode: #{response.status}\nError: #{error}")
96
+ false
97
+ end
98
+
99
+ def self.get_app_info(app_id, auth_token, timeout = 0)
100
+ check_app_id(app_id)
101
+
102
+ connection = Faraday.new(HOST)
103
+ response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}") do |req|
104
+ req.headers = build_headers(auth_token)
105
+ req.options.timeout = timeout if timeout.positive?
106
+ end
107
+
108
+ begin
109
+ data = JSON.parse(response.body)
110
+ return data if response.status == 200
111
+
112
+ UI.user_error!("Request returned the error.\nCode: #{response.status}")
113
+ rescue StandardError => ex
114
+ UI.user_error!("Getting app info process failed: #{ex}")
115
+ end
116
+ end
117
+
118
+ def self.create_submission(app_id, auth_token, timeout = 0)
119
+ check_app_id(app_id)
120
+ if non_published_submission?(app_id, auth_token, timeout)
121
+ UI.user_error!([
122
+ "There is a pending submission has already been created.",
123
+ "You need to either proceed it or remove before creating a new one."
124
+ ].join(" "))
125
+ end
126
+
127
+ connection = Faraday.new(HOST)
128
+ response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions") do |req|
129
+ req.headers = build_headers(auth_token)
130
+ req.options.timeout = timeout if timeout.positive?
131
+ end
132
+
133
+ begin
134
+ data = JSON.parse(response.body)
135
+ return data if response.status == 201
136
+
137
+ code = data["code"]
138
+ message = data["message"]
139
+
140
+ UI.user_error!("Request returned the error.\nCode: #{response.status} #{code}.\nDescription: #{message}")
141
+ rescue StandardError => ex
142
+ UI.user_error!("Creating submission process failed: #{ex}")
143
+ end
144
+ end
145
+
146
+ def self.update_submission(app_id, submission_obj, auth_token, timeout = 0)
147
+ check_app_id(app_id)
148
+ UI.user_error!("Submission data object need to be provided") if submission_obj.nil?
149
+
150
+ submission_id = submission_obj["id"]
151
+ connection = Faraday.new(HOST)
152
+ response = connection.put("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}") do |req|
153
+ req.headers = build_headers(auth_token)
154
+ req.body = submission_obj.to_json
155
+ req.options.timeout = timeout if timeout.positive?
156
+ end
157
+
158
+ begin
159
+ data = JSON.parse(response.body)
160
+ return data if response.status == 200
161
+
162
+ UI.user_error!("Request returned the error.\nCode: #{response.status}")
163
+ rescue StandardError => ex
164
+ UI.user_error!("Updating submission process failed: #{ex}")
165
+ end
166
+ end
167
+
168
+ def self.commit_submission(app_id, submission_id, auth_token, timeout = 0)
169
+ check_app_id(app_id)
170
+ check_submission_id(submission_id)
171
+
172
+ connection = Faraday.new(HOST)
173
+ response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/commit") do |req|
174
+ req.headers = build_headers(auth_token)
175
+ req.options.timeout = timeout if timeout.positive?
176
+ end
177
+
178
+ UI.user_error!("Committing submission request returned the error.\nCode: #{response.status}") unless response.status == 202
179
+ end
180
+
181
+ def self.get_submission_status(app_id, submission_id, auth_token, timeout = 0)
182
+ check_app_id(app_id)
183
+ check_submission_id(submission_id)
184
+
185
+ connection = Faraday.new(HOST)
186
+ response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/status") do |req|
187
+ req.headers = build_headers(auth_token)
188
+ req.options.timeout = timeout if timeout.positive?
189
+ end
190
+
191
+ begin
192
+ data = JSON.parse(response.body)
193
+ return data if response.status == 200
194
+
195
+ UI.user_error!("Request returned the error.\nCode: #{response.status}")
196
+ rescue StandardError => ex
197
+ UI.user_error!("Submission status obtaining process failed: #{ex}")
198
+ end
199
+ end
200
+
201
+ def self.acquire_authorization_token(tenant_id, client_id, client_secret, timeout = 0)
202
+ body = {
203
+ client_id: client_id,
204
+ client_secret: client_secret,
205
+ grant_type: "client_credentials",
206
+ resource: HOST
207
+ }
208
+ headers = {
209
+ "Content-Type": "application/x-www-form-urlencoded"
210
+ }.merge(REQUEST_HEADERS)
211
+
212
+ connection = Faraday.new("https://login.microsoftonline.com")
213
+ response = connection.post("/#{tenant_id}/oauth2/token") do |req|
214
+ req.headers = headers
215
+ req.body = body
216
+ req.options.timeout = timeout if timeout.positive?
217
+ end
218
+
219
+ begin
220
+ data = JSON.parse(response.body)
221
+ return data["access_token"] if response.status == 200
222
+
223
+ error = data["error"]
224
+ error_description = data["error_description"]
225
+
226
+ UI.user_error!("Request returned the error.\nCode: #{error}.\nDescription: #{error_description}")
227
+ rescue StandardError => ex
228
+ UI.user_error!("Authorization failed: #{ex}")
229
+ end
230
+ end
231
+
232
+ def self.non_published_submission?(app_id, auth_token, timeout = 0)
233
+ check_app_id(app_id)
234
+ app_info = get_app_info(app_id, auth_token, timeout)
235
+ app_info.key?("pendingApplicationSubmission")
236
+ end
237
+
238
+ def self.build_headers(auth_token)
239
+ {
240
+ "Authorization": "Bearer #{auth_token}",
241
+ "Content-Type": "application/json"
242
+ }.merge(REQUEST_HEADERS)
243
+ end
244
+
245
+ def self.parse_upload_url(url)
246
+ url = URI.parse(url)
247
+ query_parts = {}
248
+ url.query.split("&").each do |x|
249
+ parts = x.split("=")
250
+ query_parts[parts[0]] = CGI.unescape(parts[1])
251
+ end
252
+
253
+ {
254
+ host: "https://#{url.host}",
255
+ path: url.path,
256
+ query: query_parts
257
+ }
258
+ end
259
+
260
+ def self.check_app_id(id)
261
+ UI.user_error!("App ID need to be provided") if !id.is_a?(String) || id.nil? || id.empty?
262
+ end
263
+
264
+ def self.check_submission_id(id)
265
+ UI.user_error!("Submission ID need to be provided") if !id.is_a?(String) || id.nil? || id.empty?
266
+ end
267
+
268
+ public_class_method(:upload_blob)
269
+ public_class_method(:get_app_info)
270
+ public_class_method(:create_submission)
271
+ public_class_method(:update_submission)
272
+ public_class_method(:commit_submission)
273
+ public_class_method(:get_submission_status)
274
+ public_class_method(:acquire_authorization_token)
275
+
276
+ private_class_method(:upload_block)
277
+ private_class_method(:upload_block_list)
278
+ private_class_method(:non_published_submission?)
279
+ private_class_method(:build_headers)
280
+ private_class_method(:parse_upload_url)
281
+ private_class_method(:check_app_id)
282
+ private_class_method(:check_submission_id)
283
+
284
+ private_constant(:HOST)
285
+ private_constant(:API_VERSION)
286
+ private_constant(:API_ROOT)
287
+ private_constant(:REQUEST_HEADERS)
288
+ private_constant(:FILE_CHUNK_SIZE)
289
+ private_constant(:UPLOAD_RETRIES)
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,55 @@
1
+ module Fastlane
2
+ module Helper
3
+ class SapfireHelper
4
+ def self.dotnet_specified?
5
+ path = Actions.lane_context[Fastlane::Actions::SharedValues::SF_DOTNET_PATH] || ""
6
+ path = File.expand_path(path) unless path.empty?
7
+
8
+ if path.empty?
9
+ UI.error("The path to dotnet executable is not specified")
10
+ return false
11
+ elsif !File.exist?(path)
12
+ UI.error("File '#{path}' doesn't exist")
13
+ return false
14
+ elsif !path.end_with?("dotnet") && !path.end_with?("dotnet.exe")
15
+ UI.error("The path to dotnet doesn't point to executable file: #{path}")
16
+ return false
17
+ end
18
+
19
+ true
20
+ end
21
+
22
+ def self.msbuild_specified?
23
+ path = Actions.lane_context[Fastlane::Actions::SharedValues::SF_MSBUILD_PATH] || ""
24
+ path = File.expand_path(path) unless path.empty?
25
+
26
+ if path.empty?
27
+ UI.error("The path to MSBuild executable is not specified")
28
+ return false
29
+ elsif !File.exist?(path)
30
+ UI.error("File '#{path}' doesn't exist")
31
+ return false
32
+ elsif !path.end_with?("MSBuild.dll") && !path.end_with?("MSBuild.exe")
33
+ UI.error("The path to MSBuild doesn't point to executable or DLL file: #{path}")
34
+ return false
35
+ end
36
+
37
+ true
38
+ end
39
+
40
+ def self.kits_10_location
41
+ require "win32/registry"
42
+
43
+ Win32::Registry::HKEY_LOCAL_MACHINE.open("SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots") do |reg|
44
+ value = reg["KitsRoot10"]
45
+ return value.strip
46
+ end
47
+ end
48
+
49
+ def self.root_plugin_location
50
+ path = File.join(File.dirname(__dir__), "../../../../")
51
+ File.expand_path(path)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ require "fastlane/boolean"
2
+
3
+ module Msbuild
4
+ class BuildType
5
+ NONE = 0
6
+ GENERIC = 1
7
+ UWP = 2
8
+ NUGET = 3
9
+ end
10
+
11
+ class MsbuildType
12
+ EXE = 0
13
+ LIBRARY = 1
14
+ end
15
+
16
+ class Config
17
+ attr_accessor :params
18
+ attr_accessor :msbuild_path
19
+ attr_accessor :overwritten_props
20
+ attr_accessor :certificate
21
+ attr_accessor :certificate_password
22
+ attr_accessor :certificate_thumbprint
23
+ attr_accessor :build_type
24
+ attr_accessor :msbuild_type
25
+ end
26
+
27
+ class << self
28
+ attr_accessor :config
29
+ end
30
+
31
+ UI = FastlaneCore::UI
32
+ self.config = Config.new
33
+ self.config.build_type = BuildType::NONE
34
+ end
@@ -0,0 +1,123 @@
1
+ require "fastlane_core/configuration/config_item"
2
+ require_relative "module"
3
+
4
+ module Msbuild
5
+ class Options
6
+ PACKAGE_FORMATS = %w[.appx .appxbundle .appxupload .msix .msixbundle .msixupload].freeze
7
+
8
+ def self.available_options
9
+ return @options if @options
10
+
11
+ @options = plain_options
12
+ end
13
+
14
+ def self.plain_options
15
+ [
16
+ FastlaneCore::ConfigItem.new(
17
+ key: :project,
18
+ description: "Path to the SLN-solution file",
19
+ optional: false,
20
+ type: String,
21
+ verify_block: proc do |value|
22
+ UI.user_error!("Path to SLN-file is invalid") unless value && !value.empty?
23
+ UI.user_error!("The provided path doesn't point to SLN-file") unless
24
+ File.exist?(File.expand_path(value)) && value.end_with?(".sln")
25
+ end
26
+ ),
27
+ FastlaneCore::ConfigItem.new(
28
+ key: :configuration,
29
+ description: "Build configuration",
30
+ optional: false,
31
+ type: String
32
+ ),
33
+ FastlaneCore::ConfigItem.new(
34
+ key: :platform,
35
+ description: "Target platform",
36
+ optional: false,
37
+ type: String
38
+ ),
39
+ FastlaneCore::ConfigItem.new(
40
+ key: :restore,
41
+ description: "Restore project prior to build the actual targets",
42
+ optional: true,
43
+ default_value: false,
44
+ type: Fastlane::Boolean
45
+ ),
46
+ FastlaneCore::ConfigItem.new(
47
+ key: :clean,
48
+ description: "Should the project be cleaned before building it?",
49
+ optional: true,
50
+ default_value: false,
51
+ type: Fastlane::Boolean
52
+ ),
53
+ FastlaneCore::ConfigItem.new(
54
+ key: :appx_output_path,
55
+ description: "Defines the folder to store the generated package artifacts. Relative path is a root folder where project is located",
56
+ optional: true,
57
+ default_value: "",
58
+ type: String
59
+ ),
60
+ FastlaneCore::ConfigItem.new(
61
+ key: :appx_output_name,
62
+ description: "Defines the name of the resulting package",
63
+ optional: true,
64
+ default_value: "",
65
+ type: String
66
+ ),
67
+ FastlaneCore::ConfigItem.new(
68
+ key: :appx_bundle_platforms,
69
+ description: [
70
+ "Enables you to define the platforms to include in the bundle.",
71
+ "It's possible to define multiple platforms divided by vertical line, e.g. 'x86|ARM'"
72
+ ].join("\n"),
73
+ optional: false,
74
+ type: String
75
+ ),
76
+ FastlaneCore::ConfigItem.new(
77
+ key: :build_mode,
78
+ description: "Package build mode. Use `SideloadOnly` for sideloading only or `StoreUpload` for generating the .msixupload/.appxupload file",
79
+ optional: false,
80
+ type: String
81
+ ),
82
+ FastlaneCore::ConfigItem.new(
83
+ key: :skip_codesigning,
84
+ description: "Build without package signing",
85
+ optional: true,
86
+ default_value: false,
87
+ type: Fastlane::Boolean
88
+ ),
89
+ FastlaneCore::ConfigItem.new(
90
+ key: :jobs,
91
+ description: [
92
+ "A number of concurrent processes to use when building.",
93
+ "Set it to -1 if you want to use up to the number of processors in the computer"
94
+ ].join("\n"),
95
+ optional: true,
96
+ default_value: 1,
97
+ type: Integer,
98
+ verify_block: proc do |value|
99
+ UI.important("A number of parallel jobs can't equals to zero. Using default value.") if value.zero?
100
+ end
101
+ ),
102
+ FastlaneCore::ConfigItem.new(
103
+ key: :properties,
104
+ description: "A hash of project properties to be set up, where the key is a property name and the value is it's value",
105
+ optional: true,
106
+ default_value: {},
107
+ type: Hash,
108
+ verify_block: proc do |value|
109
+ counter = 0
110
+
111
+ value.each do |key, _|
112
+ UI.user_error!("Item #{counter}: key type must be Symbol") unless key.is_a?(Symbol)
113
+ counter += 1
114
+ end
115
+ end
116
+ )
117
+ ]
118
+ end
119
+
120
+ public_class_method(:available_options)
121
+ private_class_method(:plain_options)
122
+ end
123
+ end