fastlane-plugin-fastci 0.0.20 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +310 -6
- data/lib/fastlane/plugin/fastci/actions/app_store_resources_action.rb +300 -0
- data/lib/fastlane/plugin/fastci/actions/check_zealot_version_action.rb +52 -0
- data/lib/fastlane/plugin/fastci/actions/package_action.rb +78 -9
- data/lib/fastlane/plugin/fastci/actions/sync_zealot_devices_action.rb +65 -0
- data/lib/fastlane/plugin/fastci/actions/upload_store_action.rb +116 -35
- data/lib/fastlane/plugin/fastci/actions/upload_zealot_action.rb +58 -0
- data/lib/fastlane/plugin/fastci/actions/upload_zealot_dsym_action.rb +63 -0
- data/lib/fastlane/plugin/fastci/helper/app_store_resources_helper.rb +436 -0
- data/lib/fastlane/plugin/fastci/helper/environment.rb +25 -1
- data/lib/fastlane/plugin/fastci/helper/fastci_zealot_helper.rb +51 -0
- data/lib/fastlane/plugin/fastci/version.rb +1 -1
- metadata +24 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'fastlane/plugin/fastci/helper/fastci_zealot_helper'
|
|
2
|
+
|
|
3
|
+
module Fastlane
|
|
4
|
+
module Actions
|
|
5
|
+
# 查询 iOS 应用已上传版本,是否跳过后续打包由调用方决定。
|
|
6
|
+
class CheckZealotVersionAction < ZealotVersionCheckAction
|
|
7
|
+
extend Fastlane::Helper::FastciZealotHelper
|
|
8
|
+
|
|
9
|
+
def self.run(params)
|
|
10
|
+
validate_zealot_connection!(zealot_parameters(params))
|
|
11
|
+
[
|
|
12
|
+
SharedValues::ZEALOT_VERSION_EXISTED,
|
|
13
|
+
SharedValues::ZEAALOT_ERROR_MESSAGE,
|
|
14
|
+
SharedValues::ZEALOT_LATEST_RELEASE_ID,
|
|
15
|
+
SharedValues::ZEALOT_LATEST_RELEASE_VERSION,
|
|
16
|
+
SharedValues::ZEALOT_LATEST_VERSION,
|
|
17
|
+
SharedValues::ZEALOT_LATEST_RELEASE_URL,
|
|
18
|
+
SharedValues::ZEALOT_LATEST_INSTALL_URL,
|
|
19
|
+
SharedValues::ZEALOT_LATEST_QRCODE_URL,
|
|
20
|
+
SharedValues::ZEALOT_LATEST_CONTEXT
|
|
21
|
+
].each { |key| Actions.lane_context.delete(key) }
|
|
22
|
+
|
|
23
|
+
response = check_app_version(params)
|
|
24
|
+
# 上游非终止模式在网络错误时返回 false,不能继续读取 response.status。
|
|
25
|
+
return nil unless response
|
|
26
|
+
|
|
27
|
+
unless [200, 404].include?(response.status) && response.body.is_a?(Hash)
|
|
28
|
+
show_error("Zealot 版本查询失败(HTTP #{response.status})", params[:fail_on_error])
|
|
29
|
+
return nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
parse_response(response, params[:fail_on_error])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.available_options
|
|
36
|
+
zealot_options(ZealotVersionCheckAction)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.description
|
|
40
|
+
'按 Bundle ID 与版本号或 Git commit 查询 Zealot,返回是否已存在'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.example_code
|
|
44
|
+
['check_zealot_version(bundle_id: "com.example.app", release_version: "1.0", build_version: "1")']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.is_supported?(platform)
|
|
48
|
+
platform == :ios
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'gym'
|
|
2
2
|
require 'fastlane_core'
|
|
3
3
|
require 'fastlane/action'
|
|
4
|
+
require 'fastlane/plugin/fastci/helper/app_store_resources_helper'
|
|
4
5
|
include Fastlane::Helper
|
|
5
6
|
|
|
6
7
|
module Fastlane
|
|
@@ -17,7 +18,7 @@ module Fastlane
|
|
|
17
18
|
is_detect_duplicity_code = params[:is_detect_duplicity_code] || false
|
|
18
19
|
is_detect_unused_code = params[:is_detect_unused_code] || false
|
|
19
20
|
is_detect_unused_image = params[:is_detect_unused_image] || false
|
|
20
|
-
|
|
21
|
+
store_options = store_upload_options(params, export_method)
|
|
21
22
|
configuration = 'Release' if %w[app-store testFlight].include?(export_method)
|
|
22
23
|
is_notice_dingding = params[:is_notice_dingding].nil? ? true : params[:is_notice_dingding]
|
|
23
24
|
# 统一发送钉钉消息匿名函数
|
|
@@ -145,14 +146,7 @@ module Fastlane
|
|
|
145
146
|
notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-打包完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
|
|
146
147
|
send_dingding_notice.call(notiText)
|
|
147
148
|
|
|
148
|
-
if
|
|
149
|
-
# 根据 export_method 决定是否为 TestFlight
|
|
150
|
-
is_test_flight = export_method == 'testFlight'
|
|
151
|
-
|
|
152
|
-
other_action.upload_store(
|
|
153
|
-
release_notes: release_notes,
|
|
154
|
-
isTestFlight: is_test_flight
|
|
155
|
-
)
|
|
149
|
+
if upload_store_if_configured(store_options, version: version, build: build)
|
|
156
150
|
notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-上传完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
|
|
157
151
|
send_dingding_notice.call(notiText)
|
|
158
152
|
end
|
|
@@ -182,6 +176,18 @@ module Fastlane
|
|
|
182
176
|
end
|
|
183
177
|
end
|
|
184
178
|
|
|
179
|
+
# 上传 Zealot
|
|
180
|
+
if CommonHelper.is_validate_string(Environment.zealot_token)
|
|
181
|
+
zealot_upload_info = other_action.upload_zealot(
|
|
182
|
+
file: ipa_path,
|
|
183
|
+
changelog: params[:changelog]
|
|
184
|
+
)
|
|
185
|
+
# dSYM 仅在 IPA 上传成功且显式开启时上传。
|
|
186
|
+
if zealot_upload_info && params[:is_upload_zealot_dsym]
|
|
187
|
+
other_action.upload_zealot_dsym
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
185
191
|
send_dingding_notice.call(notiText)
|
|
186
192
|
end
|
|
187
193
|
|
|
@@ -255,12 +261,75 @@ module Fastlane
|
|
|
255
261
|
UI.message('*************| 脚本完成 |*************')
|
|
256
262
|
end
|
|
257
263
|
|
|
264
|
+
# 构建前准备商店上传参数,开启资源功能却漏配路径时立即报错。
|
|
265
|
+
def self.store_upload_options(params, export_method)
|
|
266
|
+
config_path = AppStoreResourcesHelper.upload_config_path(params, app_store: export_method == 'app-store')
|
|
267
|
+
options = {
|
|
268
|
+
release_notes: params[:release_notes] || '',
|
|
269
|
+
isTestFlight: export_method == 'testFlight',
|
|
270
|
+
# 显式透传 false,避免下层 Action 从环境变量重新开启本次已关闭的功能。
|
|
271
|
+
app_store_resources_enabled: !config_path.nil?
|
|
272
|
+
}
|
|
273
|
+
options[:app_store_resources_config_path] = config_path if config_path
|
|
274
|
+
options
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# 根据凭据或已开启的资源配置判断是否上传,单独存在资源路径不能触发上传。
|
|
278
|
+
def self.store_upload_configured?(options)
|
|
279
|
+
has_p8_key = CommonHelper.is_validate_string(Environment.connect_key_id) &&
|
|
280
|
+
CommonHelper.is_validate_string(Environment.connect_issuer_id)
|
|
281
|
+
has_api_key_json = CommonHelper.is_validate_string(Environment.app_store_connect_api_key_path)
|
|
282
|
+
has_p8_key || has_api_key_json || options[:app_store_resources_enabled]
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# 上传本次产物,并返回是否执行了上传,供主流程决定是否发送完成通知。
|
|
286
|
+
def self.upload_store_if_configured(options, version:, build:)
|
|
287
|
+
return false unless store_upload_configured?(options)
|
|
288
|
+
|
|
289
|
+
upload_options = options.dup
|
|
290
|
+
if upload_options[:app_store_resources_enabled]
|
|
291
|
+
# 使用真实产物信息覆盖资源配置,避免选中 JSON 中残留的旧版本或 Build。
|
|
292
|
+
upload_options.merge!(
|
|
293
|
+
app_identifier: Environment.bundleID,
|
|
294
|
+
app_version: version.to_s,
|
|
295
|
+
build_number: build.to_s
|
|
296
|
+
)
|
|
297
|
+
end
|
|
298
|
+
other_action.upload_store(upload_options)
|
|
299
|
+
true
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
private_class_method :store_upload_options, :store_upload_configured?, :upload_store_if_configured
|
|
303
|
+
|
|
258
304
|
def self.description
|
|
259
305
|
'打包'
|
|
260
306
|
end
|
|
261
307
|
|
|
262
308
|
def self.available_options
|
|
263
309
|
[
|
|
310
|
+
FastlaneCore::ConfigItem.new(
|
|
311
|
+
key: :app_store_resources_enabled,
|
|
312
|
+
env_name: 'APP_STORE_RESOURCES_ENABLED',
|
|
313
|
+
description: '商店资源总开关,默认关闭,仅 app-store 打包时生效',
|
|
314
|
+
optional: true,
|
|
315
|
+
default_value: false,
|
|
316
|
+
type: Boolean
|
|
317
|
+
),
|
|
318
|
+
FastlaneCore::ConfigItem.new(
|
|
319
|
+
key: :app_store_resources_config_path,
|
|
320
|
+
env_name: 'APP_STORE_RESOURCES_CONFIG_PATH',
|
|
321
|
+
description: 'App Store 资源发布 JSON 路径,开启商店资源总开关后必填',
|
|
322
|
+
optional: true,
|
|
323
|
+
type: String
|
|
324
|
+
),
|
|
325
|
+
FastlaneCore::ConfigItem.new(
|
|
326
|
+
key: :is_upload_zealot_dsym,
|
|
327
|
+
env_name: 'ZEALOT_UPLOAD_DSYM',
|
|
328
|
+
description: 'IPA 上传 Zealot 成功后继续上传 iOS dSYM',
|
|
329
|
+
optional: true,
|
|
330
|
+
default_value: false,
|
|
331
|
+
type: Boolean
|
|
332
|
+
),
|
|
264
333
|
FastlaneCore::ConfigItem.new(
|
|
265
334
|
key: :scheme,
|
|
266
335
|
description: '不采取默认配置,自定义 `scheme` 名称',
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'fastlane/plugin/fastci/helper/fastci_zealot_helper'
|
|
2
|
+
|
|
3
|
+
module Fastlane
|
|
4
|
+
module Actions
|
|
5
|
+
# 手动同步 Apple 开发者账号中的 iOS 设备,不挂入自动打包流程。
|
|
6
|
+
class SyncZealotDevicesAction < ZealotSyncDevicesAction
|
|
7
|
+
extend Fastlane::Helper::FastciZealotHelper
|
|
8
|
+
|
|
9
|
+
def self.run(params)
|
|
10
|
+
validate_zealot_connection!(zealot_parameters(params))
|
|
11
|
+
Actions.lane_context.delete(SharedValues::ZEAALOT_ERROR_MESSAGE)
|
|
12
|
+
devices = fetch_ios_devices(params)
|
|
13
|
+
print_table(build_table_data(params, devices), title: 'sync_zealot_devices')
|
|
14
|
+
|
|
15
|
+
# 沿用官方的设备上传协议,按实际 HTTP 结果统计,避免失败被计为成功。
|
|
16
|
+
failed = devices.count { |device| !sync_ios_device(params, device) }
|
|
17
|
+
result = { total: devices.size, succeeded: devices.size - failed, failed: failed }
|
|
18
|
+
UI.message("Zealot iOS 设备同步:成功 #{result[:succeeded]},失败 #{failed}")
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.fetch_ios_devices(params)
|
|
23
|
+
require 'spaceship'
|
|
24
|
+
|
|
25
|
+
token = Spaceship::ConnectAPI::Token.from(hash: params[:api_key], filepath: params[:api_key_path])
|
|
26
|
+
if token
|
|
27
|
+
Spaceship::ConnectAPI.token = token
|
|
28
|
+
return Spaceship::ConnectAPI::Device.all(filter: { platform: 'IOS' })
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
credentials = CredentialsManager::AccountManager.new(user: params[:username])
|
|
32
|
+
Spaceship.login(credentials.user, credentials.password)
|
|
33
|
+
Spaceship.select_team
|
|
34
|
+
Spaceship::Device.all(mac: false)
|
|
35
|
+
end
|
|
36
|
+
private_class_method :fetch_ios_devices
|
|
37
|
+
|
|
38
|
+
def self.sync_ios_device(params, device)
|
|
39
|
+
response = sync_deivce(params, device)
|
|
40
|
+
return true if response.success?
|
|
41
|
+
|
|
42
|
+
show_error("Zealot 设备同步失败(HTTP #{response.status})", params[:fail_on_error])
|
|
43
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => error
|
|
44
|
+
show_error("Zealot 设备同步请求失败:#{error.class}", params[:fail_on_error])
|
|
45
|
+
end
|
|
46
|
+
private_class_method :sync_ios_device
|
|
47
|
+
|
|
48
|
+
def self.available_options
|
|
49
|
+
zealot_options(ZealotSyncDevicesAction, excluding: [:platform])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.description
|
|
53
|
+
'同步 Apple 开发者账号中的 iOS 设备到 Zealot'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.example_code
|
|
57
|
+
['sync_zealot_devices(api_key_path: "./app_store_connect_api_key.json")']
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.is_supported?(platform)
|
|
61
|
+
platform == :ios
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require 'fastlane/action'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'fastlane/plugin/fastci/helper/app_store_resources_helper'
|
|
2
4
|
include Fastlane::Helper
|
|
3
5
|
|
|
4
6
|
module Fastlane
|
|
@@ -6,62 +8,141 @@ module Fastlane
|
|
|
6
8
|
# 上传 AppStore
|
|
7
9
|
class UploadStoreAction < Action
|
|
8
10
|
def self.run(params)
|
|
11
|
+
resource_params, api_key_options = prepare_upload(params)
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
if params[:isTestFlight]
|
|
14
|
+
upload_testflight(api_key_options)
|
|
15
|
+
else
|
|
16
|
+
upload_app_store(resource_params, api_key_options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
# 上传前统一准备认证和资源,确保配置错误或资源缺失在 IPA 上传前被发现。
|
|
21
|
+
def self.prepare_upload(params)
|
|
22
|
+
resource_params = load_store_resources(params)
|
|
23
|
+
api_key_options = AppStoreResourcesHelper.authentication_options(resource_params || {}, other_action)
|
|
24
|
+
if resource_params
|
|
25
|
+
resource_params.merge!(api_key_options)
|
|
26
|
+
resource_params = AppStoreResourcesHelper.prepare(resource_params)
|
|
27
|
+
end
|
|
28
|
+
[resource_params, api_key_options]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 只有显式开启的 App Store 流程读取资源配置,关闭时不解析路径或更新说明。
|
|
32
|
+
def self.load_store_resources(params)
|
|
33
|
+
config_path = AppStoreResourcesHelper.upload_config_path(params, app_store: !params[:isTestFlight])
|
|
34
|
+
return nil unless config_path
|
|
35
|
+
|
|
36
|
+
AppStoreResourcesHelper.load_params(
|
|
37
|
+
app_store_resources_enabled: true,
|
|
38
|
+
config_path: config_path,
|
|
39
|
+
app_identifier: params[:app_identifier],
|
|
40
|
+
app_version: params[:app_version],
|
|
41
|
+
build_number: params[:build_number],
|
|
42
|
+
release_notes: parse_release_notes(params[:release_notes])
|
|
18
43
|
)
|
|
44
|
+
end
|
|
19
45
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
upload_options[:release_notes] = release_notes
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
other_action.upload_to_app_store(upload_options)
|
|
46
|
+
# 保留原 JSON 字符串接口;空值或无效 JSON 不覆盖资源文件中的更新说明。
|
|
47
|
+
def self.parse_release_notes(value)
|
|
48
|
+
JSON.parse(value || '')
|
|
49
|
+
rescue JSON::ParserError, TypeError
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# TestFlight 沿用现有上传方式,不执行 App Store 资源流程。
|
|
54
|
+
def self.upload_testflight(api_key_options)
|
|
55
|
+
UI.message('*************| 开始上传 TestFlight |*************')
|
|
56
|
+
other_action.upload_to_testflight({ skip_waiting_for_build_processing: true }.merge(api_key_options))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 先上传 IPA,再处理已校验的资源;返回 IPA 上传结果以兼容已有 lane。
|
|
60
|
+
def self.upload_app_store(resource_params, api_key_options)
|
|
61
|
+
UI.message('*************| 开始上传 AppStore |*************')
|
|
62
|
+
upload_options = app_store_upload_options(resource_params).merge(api_key_options)
|
|
63
|
+
upload_result = other_action.upload_to_app_store(upload_options)
|
|
64
|
+
if resource_params
|
|
65
|
+
# 新版本判断已在 IPA 上传前完成,之后直接使用确认过的资源上传开关。
|
|
66
|
+
other_action.app_store_resources(resource_params.merge(upload_metadata_on_new_version: false))
|
|
45
67
|
end
|
|
68
|
+
upload_result
|
|
46
69
|
end
|
|
47
70
|
|
|
71
|
+
# IPA 阶段始终关闭资源上传,避免默认目录或 Deliverfile 绕过资源总开关。
|
|
72
|
+
def self.app_store_upload_options(resource_params)
|
|
73
|
+
options = {
|
|
74
|
+
skip_metadata: true,
|
|
75
|
+
skip_screenshots: true,
|
|
76
|
+
# deliver 的截图同步分支不受 skip_screenshots 控制,必须一并关闭。
|
|
77
|
+
sync_screenshots: false,
|
|
78
|
+
overwrite_screenshots: false,
|
|
79
|
+
run_precheck_before_submit: false,
|
|
80
|
+
precheck_include_in_app_purchases: false,
|
|
81
|
+
force: true,
|
|
82
|
+
submit_for_review: false,
|
|
83
|
+
automatic_release: false
|
|
84
|
+
}
|
|
85
|
+
if resource_params
|
|
86
|
+
options[:app_identifier] = resource_params[:app_identifier]
|
|
87
|
+
options[:app_version] = resource_params[:app_version] if resource_params[:app_version]
|
|
88
|
+
end
|
|
89
|
+
options
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private_class_method :prepare_upload, :load_store_resources, :parse_release_notes,
|
|
93
|
+
:upload_testflight, :upload_app_store, :app_store_upload_options
|
|
94
|
+
|
|
48
95
|
def self.description
|
|
49
96
|
"上传 AppStore"
|
|
50
97
|
end
|
|
51
98
|
|
|
52
99
|
def self.available_options
|
|
53
100
|
[
|
|
101
|
+
FastlaneCore::ConfigItem.new(
|
|
102
|
+
key: :app_store_resources_enabled,
|
|
103
|
+
env_name: 'APP_STORE_RESOURCES_ENABLED',
|
|
104
|
+
description: '商店资源总开关,默认关闭;关闭时仅上传 IPA,不更新元数据或截图',
|
|
105
|
+
optional: true,
|
|
106
|
+
default_value: false,
|
|
107
|
+
type: Boolean
|
|
108
|
+
),
|
|
54
109
|
FastlaneCore::ConfigItem.new(
|
|
55
110
|
key: :release_notes,
|
|
56
111
|
description: "更新文案, 格式为 { \"zh-Hans\": \"修复问题\", \"en-US\": \"bugfix\"} JSON 字符串",
|
|
57
|
-
optional:
|
|
112
|
+
optional: true,
|
|
58
113
|
type: String
|
|
59
114
|
),
|
|
60
115
|
FastlaneCore::ConfigItem.new(
|
|
61
116
|
key: :isTestFlight,
|
|
62
117
|
description: "是否为 TestFlight 打包",
|
|
63
|
-
optional:
|
|
118
|
+
optional: true,
|
|
119
|
+
default_value: false,
|
|
64
120
|
type: Boolean
|
|
121
|
+
),
|
|
122
|
+
FastlaneCore::ConfigItem.new(
|
|
123
|
+
key: :app_store_resources_config_path,
|
|
124
|
+
env_name: 'APP_STORE_RESOURCES_CONFIG_PATH',
|
|
125
|
+
description: 'App Store 资源发布 JSON 路径,开启商店资源总开关后必填',
|
|
126
|
+
optional: true,
|
|
127
|
+
type: String
|
|
128
|
+
),
|
|
129
|
+
FastlaneCore::ConfigItem.new(
|
|
130
|
+
key: :app_identifier,
|
|
131
|
+
description: '本次构建的 Bundle ID,覆盖资源配置',
|
|
132
|
+
optional: true,
|
|
133
|
+
type: String
|
|
134
|
+
),
|
|
135
|
+
FastlaneCore::ConfigItem.new(
|
|
136
|
+
key: :app_version,
|
|
137
|
+
description: '本次构建的版本号,覆盖资源配置',
|
|
138
|
+
optional: true,
|
|
139
|
+
type: String
|
|
140
|
+
),
|
|
141
|
+
FastlaneCore::ConfigItem.new(
|
|
142
|
+
key: :build_number,
|
|
143
|
+
description: '本次构建的 Build 号,覆盖资源配置',
|
|
144
|
+
optional: true,
|
|
145
|
+
type: String
|
|
65
146
|
)
|
|
66
147
|
]
|
|
67
148
|
end
|
|
@@ -72,4 +153,4 @@ module Fastlane
|
|
|
72
153
|
|
|
73
154
|
end
|
|
74
155
|
end
|
|
75
|
-
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'fastlane/plugin/fastci/helper/fastci_zealot_helper'
|
|
2
|
+
|
|
3
|
+
module Fastlane
|
|
4
|
+
module Actions
|
|
5
|
+
# iOS IPA 上传:沿用官方传输与结果解析,避免复制 Zealot HTTP 协议。
|
|
6
|
+
class UploadZealotAction < ZealotAction
|
|
7
|
+
extend Fastlane::Helper::FastciZealotHelper
|
|
8
|
+
|
|
9
|
+
def self.run(params)
|
|
10
|
+
validate_zealot_connection!(zealot_parameters(params))
|
|
11
|
+
UI.user_error!('请指定 IPA 文件或先执行 gym/package') if params[:file].to_s.empty?
|
|
12
|
+
|
|
13
|
+
# 同一 lane 可多次上传,失败时不能返回上一次上传的地址。
|
|
14
|
+
output.each { |key, _description| Actions.lane_context.delete(key.to_sym) }
|
|
15
|
+
super(params)
|
|
16
|
+
return nil unless Actions.lane_context[SharedValues::ZEALOT_RELEASE_ID]
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
app_id: Actions.lane_context[SharedValues::ZEALOT_APP_ID],
|
|
20
|
+
release_id: Actions.lane_context[SharedValues::ZEALOT_RELEASE_ID],
|
|
21
|
+
release_url: Actions.lane_context[SharedValues::ZEALOT_RELEASE_URL],
|
|
22
|
+
install_url: Actions.lane_context[SharedValues::ZEALOT_INSTALL_URL],
|
|
23
|
+
qrcode_url: Actions.lane_context[SharedValues::ZEALOT_QRCODE_URL]
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.available_options
|
|
28
|
+
options = zealot_options(ZealotAction, excluding: [:file])
|
|
29
|
+
options << FastlaneCore::ConfigItem.new(
|
|
30
|
+
key: :file,
|
|
31
|
+
env_name: 'ZEALOT_FILE',
|
|
32
|
+
description: 'iOS IPA 路径,默认使用本次 gym 的产物',
|
|
33
|
+
optional: true,
|
|
34
|
+
default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH],
|
|
35
|
+
type: String,
|
|
36
|
+
verify_block: proc do |value|
|
|
37
|
+
unless File.file?(value) && File.extname(value).casecmp('.ipa').zero?
|
|
38
|
+
UI.user_error!("不是有效的 IPA 文件:#{value}")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
)
|
|
42
|
+
options
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.description
|
|
46
|
+
'上传 iOS IPA 到 Zealot,返回应用、版本和下载地址'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.example_code
|
|
50
|
+
['upload_zealot(file: "./App.ipa", changelog: "iOS 更新说明")']
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.is_supported?(platform)
|
|
54
|
+
platform == :ios
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'fastlane/plugin/fastci/helper/fastci_zealot_helper'
|
|
2
|
+
|
|
3
|
+
module Fastlane
|
|
4
|
+
module Actions
|
|
5
|
+
# 固定走官方 iOS dSYM 流程,不暴露 Android 或 macOS 参数。
|
|
6
|
+
class UploadZealotDsymAction < ZealotDebugFileAction
|
|
7
|
+
extend Fastlane::Helper::FastciZealotHelper
|
|
8
|
+
|
|
9
|
+
def self.run(params)
|
|
10
|
+
values = zealot_parameters(params)
|
|
11
|
+
validate_zealot_connection!(values)
|
|
12
|
+
values[:platform] = 'ios'
|
|
13
|
+
values[:zip_file] ||= Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH] unless values[:path]
|
|
14
|
+
values[:path] ||= Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE] unless values[:zip_file]
|
|
15
|
+
|
|
16
|
+
unless values[:zip_file] || values[:path]
|
|
17
|
+
UI.user_error!('请指定 dSYM zip_file、xcarchive path,或先执行 gym/package')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if values[:zip_file] && !File.file?(values[:zip_file])
|
|
21
|
+
UI.user_error!("dSYM 压缩文件不存在:#{values[:zip_file]}")
|
|
22
|
+
end
|
|
23
|
+
if values[:path] && !File.directory?(values[:path])
|
|
24
|
+
UI.user_error!("Xcode archive 不存在:#{values[:path]}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Actions.lane_context.delete(SharedValues::ZEAALOT_ERROR_MESSAGE)
|
|
28
|
+
super(FastlaneCore::Configuration.create(ZealotDebugFileAction.available_options, values))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 上游只检查响应中的 error 字段;非成功 HTTP 状态不能计为上传成功。
|
|
32
|
+
def self.parse_response(response, upload_url, fail_on_error)
|
|
33
|
+
return unless response
|
|
34
|
+
|
|
35
|
+
unless response.success? && response.body.is_a?(Hash)
|
|
36
|
+
return show_error("Zealot dSYM 上传失败(HTTP #{response.status})", fail_on_error)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
private_class_method :parse_response
|
|
42
|
+
|
|
43
|
+
def self.available_options
|
|
44
|
+
zealot_options(
|
|
45
|
+
ZealotDebugFileAction,
|
|
46
|
+
excluding: [:platform, :android_build_type, :android_flavor]
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.description
|
|
51
|
+
'上传 iOS dSYM 到 Zealot,支持 zip_file、xcarchive 或本次 gym 产物'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.example_code
|
|
55
|
+
['upload_zealot_dsym(zip_file: "./App.app.dSYM.zip")']
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.is_supported?(platform)
|
|
59
|
+
platform == :ios
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|