fastlane-plugin-fastci 0.0.30 → 0.0.41
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 +380 -11
- data/lib/fastlane/plugin/fastci/actions/app_store_resources_action.rb +300 -0
- data/lib/fastlane/plugin/fastci/actions/package_action.rb +129 -124
- data/lib/fastlane/plugin/fastci/actions/upload_store_action.rb +141 -35
- data/lib/fastlane/plugin/fastci/helper/app_store_resources_helper.rb +436 -0
- data/lib/fastlane/plugin/fastci/helper/dingding_helper.rb +57 -22
- data/lib/fastlane/plugin/fastci/helper/dingding_image_helper.rb +70 -0
- data/lib/fastlane/plugin/fastci/helper/environment.rb +26 -0
- data/lib/fastlane/plugin/fastci/helper/package_notification_helper.rb +184 -0
- data/lib/fastlane/plugin/fastci/version.rb +1 -1
- metadata +6 -2
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
require 'fastlane/action'
|
|
2
|
+
require 'fastlane/plugin/fastci/helper/app_store_resources_helper'
|
|
3
|
+
include Fastlane::Helper
|
|
4
|
+
|
|
5
|
+
module Fastlane
|
|
6
|
+
module Actions
|
|
7
|
+
# 校验并上传 App Store Connect metadata 和截图
|
|
8
|
+
class AppStoreResourcesAction < Action
|
|
9
|
+
# 独立资源入口:准备资源 -> 上传 -> 可选构建选择 -> 可选提交审核,全程不上传 IPA。
|
|
10
|
+
def self.run(params)
|
|
11
|
+
return nil unless resources_enabled?(params)
|
|
12
|
+
|
|
13
|
+
params = prepare_resources(params)
|
|
14
|
+
upload_resources(params)
|
|
15
|
+
select_build_and_submit_review(params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# 总开关先于配置读取和认证检查,单独指定路径或资源子开关不能启用此能力。
|
|
19
|
+
def self.resources_enabled?(params)
|
|
20
|
+
return true if params[:app_store_resources_enabled] == true
|
|
21
|
+
|
|
22
|
+
UI.message('*************| 商店资源功能未开启,跳过(APP_STORE_RESOURCES_ENABLED=false)|*************')
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 统一读取配置、建立认证并准备资源目录,下载与校验规则由 Helper 负责。
|
|
27
|
+
def self.prepare_resources(params)
|
|
28
|
+
params = AppStoreResourcesHelper.load_params(params)
|
|
29
|
+
params.merge!(AppStoreResourcesHelper.authentication_options(params, other_action))
|
|
30
|
+
AppStoreResourcesHelper.prepare(params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 只上传配置指定的资源类别,无资源变化时跳过上传。
|
|
34
|
+
def self.upload_resources(params)
|
|
35
|
+
if params[:metadata_changed] || params[:screenshots_changed]
|
|
36
|
+
UI.message('*************| 开始上传 App Store Connect 资源 |*************')
|
|
37
|
+
other_action.upload_to_app_store(
|
|
38
|
+
AppStoreResourcesHelper.resource_options(params, params[:metadata_path], params[:screenshots_path])
|
|
39
|
+
)
|
|
40
|
+
UI.success('App Store Connect 资源上传完成')
|
|
41
|
+
else
|
|
42
|
+
UI.message('*************| 没有 metadata 或截图变更,跳过资源上传 |*************')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 资源上传完成后,按配置选择精确构建并提交审核;两个步骤默认均不执行。
|
|
47
|
+
def self.select_build_and_submit_review(params)
|
|
48
|
+
# 提交审核也必须先确认精确构建,不能依赖 deliver 自动选择最新 Build。
|
|
49
|
+
if params[:select_build] || params[:submit_for_review]
|
|
50
|
+
AppStoreResourcesHelper.select_build(params)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if params[:submit_for_review]
|
|
54
|
+
UI.message('*************| 开始提交 App Store 审核 |*************')
|
|
55
|
+
other_action.upload_to_app_store(AppStoreResourcesHelper.review_options(params))
|
|
56
|
+
UI.success('App Store 审核提交完成')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private_class_method :resources_enabled?, :prepare_resources, :upload_resources, :select_build_and_submit_review
|
|
61
|
+
|
|
62
|
+
def self.description
|
|
63
|
+
'校验并上传 App Store Connect metadata 和截图'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# JSON 合并后也通过这组参数校验,使文件配置与直接调用拥有相同约束。
|
|
67
|
+
def self.available_options
|
|
68
|
+
[
|
|
69
|
+
FastlaneCore::ConfigItem.new(
|
|
70
|
+
key: :app_store_resources_enabled,
|
|
71
|
+
env_name: 'APP_STORE_RESOURCES_ENABLED',
|
|
72
|
+
description: '商店资源总开关,必须显式开启后才能使用资源上传、下载及发布流程',
|
|
73
|
+
optional: true,
|
|
74
|
+
default_value: false,
|
|
75
|
+
type: Boolean
|
|
76
|
+
),
|
|
77
|
+
FastlaneCore::ConfigItem.new(
|
|
78
|
+
key: :config_path,
|
|
79
|
+
env_name: 'APP_STORE_RESOURCES_CONFIG_PATH',
|
|
80
|
+
description: '资源发布 JSON 配置文件路径,显式参数优先于配置文件',
|
|
81
|
+
optional: true,
|
|
82
|
+
default_value: nil,
|
|
83
|
+
# 路径校验由开启后的 load_params 执行,关闭功能时不读取或校验资源文件。
|
|
84
|
+
type: String
|
|
85
|
+
),
|
|
86
|
+
FastlaneCore::ConfigItem.new(
|
|
87
|
+
key: :app_identifier,
|
|
88
|
+
description: 'App Store Connect App 的 Bundle ID',
|
|
89
|
+
optional: true,
|
|
90
|
+
type: String
|
|
91
|
+
),
|
|
92
|
+
FastlaneCore::ConfigItem.new(
|
|
93
|
+
key: :app_version,
|
|
94
|
+
description: '要更新的 App Store 版本号',
|
|
95
|
+
optional: true,
|
|
96
|
+
default_value: nil,
|
|
97
|
+
type: String
|
|
98
|
+
),
|
|
99
|
+
FastlaneCore::ConfigItem.new(
|
|
100
|
+
key: :metadata_path,
|
|
101
|
+
description: 'metadata 目录路径',
|
|
102
|
+
optional: true,
|
|
103
|
+
default_value: 'fastlane/metadata',
|
|
104
|
+
type: String
|
|
105
|
+
),
|
|
106
|
+
FastlaneCore::ConfigItem.new(
|
|
107
|
+
key: :screenshots_path,
|
|
108
|
+
description: '截图目录路径',
|
|
109
|
+
optional: true,
|
|
110
|
+
default_value: 'fastlane/screenshots',
|
|
111
|
+
type: String
|
|
112
|
+
),
|
|
113
|
+
FastlaneCore::ConfigItem.new(
|
|
114
|
+
key: :metadata_changed,
|
|
115
|
+
description: '是否上传 metadata',
|
|
116
|
+
optional: true,
|
|
117
|
+
default_value: true,
|
|
118
|
+
type: Boolean
|
|
119
|
+
),
|
|
120
|
+
FastlaneCore::ConfigItem.new(
|
|
121
|
+
key: :screenshots_changed,
|
|
122
|
+
description: '是否上传截图',
|
|
123
|
+
optional: true,
|
|
124
|
+
default_value: false,
|
|
125
|
+
type: Boolean
|
|
126
|
+
),
|
|
127
|
+
FastlaneCore::ConfigItem.new(
|
|
128
|
+
key: :upload_metadata_on_new_version,
|
|
129
|
+
description: '目标 App Store 版本不存在时是否强制上传 metadata',
|
|
130
|
+
optional: true,
|
|
131
|
+
default_value: true,
|
|
132
|
+
type: Boolean
|
|
133
|
+
),
|
|
134
|
+
FastlaneCore::ConfigItem.new(
|
|
135
|
+
key: :download_missing_metadata,
|
|
136
|
+
description: 'metadata 缺失时是否从 App Store Connect 下载',
|
|
137
|
+
optional: true,
|
|
138
|
+
default_value: false,
|
|
139
|
+
type: Boolean
|
|
140
|
+
),
|
|
141
|
+
FastlaneCore::ConfigItem.new(
|
|
142
|
+
key: :download_missing_screenshots,
|
|
143
|
+
description: '截图缺失时是否从 App Store Connect 下载',
|
|
144
|
+
optional: true,
|
|
145
|
+
default_value: false,
|
|
146
|
+
type: Boolean
|
|
147
|
+
),
|
|
148
|
+
FastlaneCore::ConfigItem.new(
|
|
149
|
+
key: :use_live_version,
|
|
150
|
+
description: '下载截图时是否使用线上版本',
|
|
151
|
+
optional: true,
|
|
152
|
+
default_value: false,
|
|
153
|
+
type: Boolean
|
|
154
|
+
),
|
|
155
|
+
FastlaneCore::ConfigItem.new(
|
|
156
|
+
key: :api_key,
|
|
157
|
+
description: 'App Store Connect API Key Hash',
|
|
158
|
+
optional: true,
|
|
159
|
+
sensitive: true,
|
|
160
|
+
type: Hash,
|
|
161
|
+
conflicting_options: [:api_key_path]
|
|
162
|
+
),
|
|
163
|
+
FastlaneCore::ConfigItem.new(
|
|
164
|
+
key: :api_key_path,
|
|
165
|
+
description: 'App Store Connect API Key JSON 文件路径',
|
|
166
|
+
optional: true,
|
|
167
|
+
type: String,
|
|
168
|
+
conflicting_options: [:api_key],
|
|
169
|
+
verify_block: proc do |value|
|
|
170
|
+
UI.user_error!("API Key JSON 不存在: #{value}") unless File.file?(value)
|
|
171
|
+
end
|
|
172
|
+
),
|
|
173
|
+
FastlaneCore::ConfigItem.new(
|
|
174
|
+
key: :username,
|
|
175
|
+
description: 'Apple ID 用户名,仅在未提供 API Key 时使用',
|
|
176
|
+
optional: true,
|
|
177
|
+
type: String
|
|
178
|
+
),
|
|
179
|
+
FastlaneCore::ConfigItem.new(
|
|
180
|
+
key: :platform,
|
|
181
|
+
description: 'App Store Connect 平台',
|
|
182
|
+
optional: true,
|
|
183
|
+
default_value: 'ios',
|
|
184
|
+
type: String,
|
|
185
|
+
verify_block: proc { |value| UI.user_error!('资源上传仅支持 ios 平台') unless value == 'ios' }
|
|
186
|
+
),
|
|
187
|
+
FastlaneCore::ConfigItem.new(
|
|
188
|
+
key: :sync_screenshots,
|
|
189
|
+
description: '是否让远端截图与本地截图同步',
|
|
190
|
+
optional: true,
|
|
191
|
+
default_value: false,
|
|
192
|
+
type: Boolean
|
|
193
|
+
),
|
|
194
|
+
FastlaneCore::ConfigItem.new(
|
|
195
|
+
key: :overwrite_screenshots,
|
|
196
|
+
description: '上传前是否清空远端截图',
|
|
197
|
+
optional: true,
|
|
198
|
+
default_value: false,
|
|
199
|
+
type: Boolean
|
|
200
|
+
),
|
|
201
|
+
FastlaneCore::ConfigItem.new(
|
|
202
|
+
key: :submit_for_review,
|
|
203
|
+
description: '上传资源后是否提交审核',
|
|
204
|
+
optional: true,
|
|
205
|
+
default_value: false,
|
|
206
|
+
type: Boolean
|
|
207
|
+
),
|
|
208
|
+
FastlaneCore::ConfigItem.new(
|
|
209
|
+
key: :automatic_release,
|
|
210
|
+
description: '审核通过后是否自动发布',
|
|
211
|
+
optional: true,
|
|
212
|
+
default_value: false,
|
|
213
|
+
type: Boolean
|
|
214
|
+
),
|
|
215
|
+
FastlaneCore::ConfigItem.new(
|
|
216
|
+
key: :select_build,
|
|
217
|
+
description: '是否按 app_version 和 build_number 自动选择构建',
|
|
218
|
+
optional: true,
|
|
219
|
+
default_value: false,
|
|
220
|
+
type: Boolean
|
|
221
|
+
),
|
|
222
|
+
FastlaneCore::ConfigItem.new(
|
|
223
|
+
key: :build_number,
|
|
224
|
+
description: '要选择的精确 Build Number',
|
|
225
|
+
optional: true,
|
|
226
|
+
default_value: nil,
|
|
227
|
+
type: String
|
|
228
|
+
),
|
|
229
|
+
FastlaneCore::ConfigItem.new(
|
|
230
|
+
key: :wait_for_build_processing,
|
|
231
|
+
description: '选择构建前是否等待 App Store Connect 处理完成',
|
|
232
|
+
optional: true,
|
|
233
|
+
default_value: true,
|
|
234
|
+
type: Boolean
|
|
235
|
+
),
|
|
236
|
+
FastlaneCore::ConfigItem.new(
|
|
237
|
+
key: :build_processing_timeout,
|
|
238
|
+
description: '等待构建处理完成的超时时间,单位秒',
|
|
239
|
+
optional: true,
|
|
240
|
+
default_value: 3600,
|
|
241
|
+
type: Integer,
|
|
242
|
+
verify_block: proc { |value| UI.user_error!('构建处理超时必须大于 0') unless value.positive? }
|
|
243
|
+
),
|
|
244
|
+
FastlaneCore::ConfigItem.new(
|
|
245
|
+
key: :build_processing_poll_interval,
|
|
246
|
+
description: '查询构建处理状态的时间间隔,单位秒',
|
|
247
|
+
optional: true,
|
|
248
|
+
default_value: 30,
|
|
249
|
+
type: Integer,
|
|
250
|
+
verify_block: proc { |value| UI.user_error!('构建轮询间隔必须大于 0') unless value.positive? }
|
|
251
|
+
),
|
|
252
|
+
FastlaneCore::ConfigItem.new(
|
|
253
|
+
key: :build_processing_retry_limit,
|
|
254
|
+
description: '查询构建处理状态遇到网络错误时的最大重试次数',
|
|
255
|
+
optional: true,
|
|
256
|
+
default_value: 3,
|
|
257
|
+
type: Integer,
|
|
258
|
+
verify_block: proc { |value| UI.user_error!('构建查询重试次数不能小于 0') if value.negative? }
|
|
259
|
+
),
|
|
260
|
+
FastlaneCore::ConfigItem.new(
|
|
261
|
+
key: :build_processing_retry_interval,
|
|
262
|
+
description: '查询构建处理状态网络错误后的重试等待时间,单位秒',
|
|
263
|
+
optional: true,
|
|
264
|
+
default_value: 15,
|
|
265
|
+
type: Integer,
|
|
266
|
+
verify_block: proc { |value| UI.user_error!('构建查询重试间隔不能小于 0') if value.negative? }
|
|
267
|
+
),
|
|
268
|
+
FastlaneCore::ConfigItem.new(
|
|
269
|
+
key: :submission_information,
|
|
270
|
+
description: 'deliver 提交审核信息,例如出口合规声明',
|
|
271
|
+
optional: true,
|
|
272
|
+
default_value: nil,
|
|
273
|
+
type: Hash
|
|
274
|
+
),
|
|
275
|
+
FastlaneCore::ConfigItem.new(
|
|
276
|
+
key: :release_notes,
|
|
277
|
+
description: '按语言配置的更新说明;优先于 metadata 中的 release_notes.txt',
|
|
278
|
+
optional: true,
|
|
279
|
+
type: Hash
|
|
280
|
+
),
|
|
281
|
+
FastlaneCore::ConfigItem.new(
|
|
282
|
+
key: :force,
|
|
283
|
+
description: '是否跳过 HTML 预览确认',
|
|
284
|
+
optional: true,
|
|
285
|
+
default_value: true,
|
|
286
|
+
type: Boolean
|
|
287
|
+
)
|
|
288
|
+
]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def self.is_supported?(platform)
|
|
292
|
+
platform == :ios
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def self.category
|
|
296
|
+
:production
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require 'gym'
|
|
2
2
|
require 'fastlane_core'
|
|
3
3
|
require 'fastlane/action'
|
|
4
|
+
require 'fastlane/plugin/fastci/helper/app_store_resources_helper'
|
|
5
|
+
require 'fastlane/plugin/fastci/helper/package_notification_helper'
|
|
4
6
|
include Fastlane::Helper
|
|
5
7
|
|
|
6
8
|
module Fastlane
|
|
@@ -8,27 +10,14 @@ module Fastlane
|
|
|
8
10
|
# 打包
|
|
9
11
|
class PackageAction < Action
|
|
10
12
|
def self.run(params)
|
|
13
|
+
notice = PackageNotificationHelper.new(params)
|
|
11
14
|
# 入参配置
|
|
12
15
|
configuration = params[:configuration] || 'Debug'
|
|
13
16
|
export_method = params[:export_method] || 'development'
|
|
14
17
|
build = params[:build] || nil
|
|
15
18
|
version = params[:version] || nil
|
|
16
|
-
|
|
17
|
-
is_detect_duplicity_code = params[:is_detect_duplicity_code] || false
|
|
18
|
-
is_detect_unused_code = params[:is_detect_unused_code] || false
|
|
19
|
-
is_detect_unused_image = params[:is_detect_unused_image] || false
|
|
20
|
-
release_notes = params[:release_notes] || ''
|
|
19
|
+
store_options = store_upload_options(params, export_method)
|
|
21
20
|
configuration = 'Release' if %w[app-store testFlight].include?(export_method)
|
|
22
|
-
is_notice_dingding = params[:is_notice_dingding].nil? ? true : params[:is_notice_dingding]
|
|
23
|
-
# 统一发送钉钉消息匿名函数
|
|
24
|
-
send_dingding_notice = lambda do |text|
|
|
25
|
-
if is_notice_dingding
|
|
26
|
-
DingdingHelper.sendMarkdown(text)
|
|
27
|
-
else
|
|
28
|
-
UI.message('*************| 跳过钉钉消息通知(is_notice_dingding=false)|*************')
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
send_dingding_notice.call('测试一下')
|
|
32
21
|
# 清理上一次的打包缓存
|
|
33
22
|
FileUtils.rm_rf(Dir.glob("#{Constants.BUILD_LOG_DIR}/*"))
|
|
34
23
|
FileUtils.rm_rf(Dir.glob("#{Constants.IPA_OUTPUT_DIR}/*"))
|
|
@@ -52,7 +41,6 @@ module Fastlane
|
|
|
52
41
|
other_action.update_build_number(
|
|
53
42
|
build: build
|
|
54
43
|
)
|
|
55
|
-
time = Time.new.strftime('%Y%m%d%H%M')
|
|
56
44
|
|
|
57
45
|
# 获取版本号
|
|
58
46
|
version_options = { target: scheme }
|
|
@@ -65,6 +53,7 @@ module Fastlane
|
|
|
65
53
|
build = other_action.get_build_number(build_options)
|
|
66
54
|
# 生成ipa包的名字格式
|
|
67
55
|
ipaName = "#{scheme}_#{export_method}_#{version}_#{build}.ipa"
|
|
56
|
+
notice.build_info(version: version, build: build)
|
|
68
57
|
|
|
69
58
|
# 获取 Extension 的 Bundle ID(可能有多个,用逗号分隔)
|
|
70
59
|
extension_bundle_ids = Environment.extension_bundle_ids
|
|
@@ -119,11 +108,13 @@ module Fastlane
|
|
|
119
108
|
gym_options[:export_options][:provisioningProfiles] = provisioningProfiles_map
|
|
120
109
|
end
|
|
121
110
|
|
|
111
|
+
notice.stage(:local, '构建 IPA')
|
|
122
112
|
other_action.gym(gym_options)
|
|
123
113
|
|
|
124
114
|
UI.message('*************| 打包完成 |*************')
|
|
125
115
|
|
|
126
116
|
UI.message('*************| 复制打包产物 |*************')
|
|
117
|
+
notice.stage(:local, '复制构建产物')
|
|
127
118
|
# 定义桌面路径
|
|
128
119
|
desktop_path = File.expand_path('~/Desktop')
|
|
129
120
|
output_path = File.join(desktop_path, "BuildOutput_#{scheme}")
|
|
@@ -140,139 +131,153 @@ module Fastlane
|
|
|
140
131
|
# system("git reset --hard")
|
|
141
132
|
|
|
142
133
|
ipa_path = "#{Constants.IPA_OUTPUT_DIR}/#{ipaName}"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-打包完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
|
|
146
|
-
send_dingding_notice.call(notiText)
|
|
147
|
-
|
|
148
|
-
if CommonHelper.is_validate_string(Environment.connect_key_id) && CommonHelper.is_validate_string(Environment.connect_issuer_id)
|
|
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
|
-
)
|
|
156
|
-
notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-上传完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
|
|
157
|
-
send_dingding_notice.call(notiText)
|
|
158
|
-
end
|
|
159
|
-
else
|
|
160
|
-
# 钉钉通知
|
|
161
|
-
notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-打包完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
|
|
162
|
-
|
|
163
|
-
# 上传蒲公英
|
|
164
|
-
if CommonHelper.is_validate_string(Environment.pgy_api_key)
|
|
165
|
-
pgy_upload_info = other_action.upload_pgy
|
|
166
|
-
qrCode = pgy_upload_info['buildQRCodeURL']
|
|
167
|
-
|
|
168
|
-
if CommonHelper.is_validate_string(qrCode)
|
|
169
|
-
notiText << "\n\n⬇️⬇️⬇️ 扫码安装 ⬇️⬇️⬇️\n\n\n密码: #{Environment.pgy_password}\n"
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# 上传 fir
|
|
174
|
-
if CommonHelper.is_validate_string(Environment.fir_api_token)
|
|
175
|
-
fir_upload_info = other_action.upload_fir(
|
|
176
|
-
changelog: params[:changelog]
|
|
177
|
-
)
|
|
178
|
-
download_url = fir_upload_info[:download_url]
|
|
179
|
-
|
|
180
|
-
if CommonHelper.is_validate_string(download_url)
|
|
181
|
-
notiText << "\n\n⬇️⬇️⬇️ 点击链接安装 ⬇️⬇️⬇️\n\n\n密码: #{Environment.fir_password}\n[_点击下载_](#{download_url})"
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# 上传 Zealot
|
|
186
|
-
if CommonHelper.is_validate_string(Environment.zealot_token)
|
|
187
|
-
zealot_upload_info = other_action.upload_zealot(
|
|
188
|
-
file: ipa_path,
|
|
189
|
-
changelog: params[:changelog]
|
|
190
|
-
)
|
|
191
|
-
# dSYM 仅在 IPA 上传成功且显式开启时上传。
|
|
192
|
-
if zealot_upload_info && params[:is_upload_zealot_dsym]
|
|
193
|
-
other_action.upload_zealot_dsym
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
send_dingding_notice.call(notiText)
|
|
198
|
-
end
|
|
134
|
+
# 构建完成仅记录终端日志,钉钉成功通知由各分发平台上传结果触发。
|
|
135
|
+
distribute_package(params, notice, store_options, ipa_path: ipa_path, version: version, build: build)
|
|
199
136
|
|
|
200
137
|
# Sentry 上传 dSYM
|
|
201
138
|
if CommonHelper.is_validate_string(Environment.sentry_auth_token)
|
|
139
|
+
notice.stage(:sentry, '上传 dSYM')
|
|
202
140
|
other_action.sentry_upload_dsym
|
|
203
141
|
else
|
|
204
142
|
UI.message('*************| 未配置 Sentry 跳过 dSYM 上传 |*************')
|
|
205
143
|
end
|
|
206
144
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
other_action.analyze_swiftlint(
|
|
210
|
-
is_from_package: true,
|
|
211
|
-
configuration: configuration
|
|
212
|
-
)
|
|
213
|
-
# 结果复制到桌面
|
|
214
|
-
FileUtils.cp(SWIFTLINT_ANALYZE_HTML_FILE, target_path)
|
|
215
|
-
UI.message('*************| 代码分析完成 |*************')
|
|
216
|
-
else
|
|
217
|
-
UI.message('*************| 跳过代码分析 |*************')
|
|
218
|
-
end
|
|
145
|
+
run_package_checks(params, notice, gym_method: gym_method, scheme: scheme,
|
|
146
|
+
configuration: configuration, target_path: target_path)
|
|
219
147
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# 结果复制到桌面
|
|
226
|
-
FileUtils.cp(DUPLICITY_CODE_HTML_FILE, target_path)
|
|
227
|
-
UI.message('*************| 重复代码检查完成 |*************')
|
|
228
|
-
else
|
|
229
|
-
UI.message('*************| 跳过重复代码检查 |*************')
|
|
230
|
-
end
|
|
148
|
+
UI.message('*************| 脚本完成 |*************')
|
|
149
|
+
rescue StandardError
|
|
150
|
+
notice&.failure(store_result: Actions.lane_context[SharedValues::FASTCI_STORE_UPLOAD_RESULT] || {})
|
|
151
|
+
raise
|
|
152
|
+
end
|
|
231
153
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
154
|
+
# 各平台分别发送结果;后一个平台失败时,已经成功的平台仍有明确通知。
|
|
155
|
+
def self.distribute_package(params, notice, store_options, ipa_path:, version:, build:)
|
|
156
|
+
if %w[app-store testFlight].include?(params[:export_method])
|
|
157
|
+
platform = params[:export_method] == 'testFlight' ? :testflight : :app_store
|
|
158
|
+
notice.stage(platform, '准备商店上传')
|
|
159
|
+
Actions.lane_context.delete(SharedValues::FASTCI_STORE_UPLOAD_RESULT)
|
|
160
|
+
if upload_store_if_configured(store_options, version: version, build: build)
|
|
161
|
+
result = Actions.lane_context[SharedValues::FASTCI_STORE_UPLOAD_RESULT] || {}
|
|
162
|
+
notice.distribution_completed(platform, { uploaded: true }, store_result: result)
|
|
163
|
+
else
|
|
164
|
+
notice.store_skipped(platform)
|
|
165
|
+
end
|
|
242
166
|
else
|
|
243
|
-
|
|
167
|
+
distribute_test_package(params, notice, ipa_path)
|
|
244
168
|
end
|
|
169
|
+
end
|
|
245
170
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
UI.message('*************| 无用图片检查完成 |*************')
|
|
252
|
-
else
|
|
253
|
-
UI.message('*************| 跳过未使用图片检查 |*************')
|
|
171
|
+
# 保持原有上传顺序与开关;安装链接的整理交给通知 Helper。
|
|
172
|
+
def self.distribute_test_package(params, notice, ipa_path)
|
|
173
|
+
if CommonHelper.is_validate_string(Environment.pgy_api_key)
|
|
174
|
+
notice.stage(:pgy, '上传 IPA')
|
|
175
|
+
notice.distribution_completed(:pgy, other_action.upload_pgy)
|
|
254
176
|
end
|
|
177
|
+
if CommonHelper.is_validate_string(Environment.fir_api_token)
|
|
178
|
+
notice.stage(:fir, '上传 IPA')
|
|
179
|
+
notice.distribution_completed(:fir, other_action.upload_fir(changelog: params[:changelog]))
|
|
180
|
+
end
|
|
181
|
+
return unless CommonHelper.is_validate_string(Environment.zealot_token)
|
|
182
|
+
|
|
183
|
+
notice.stage(:zealot, '上传 IPA')
|
|
184
|
+
result = other_action.upload_zealot(file: ipa_path, changelog: params[:changelog])
|
|
185
|
+
if result && params[:is_upload_zealot_dsym]
|
|
186
|
+
notice.stage(:zealot, '上传 dSYM(IPA 已上传)')
|
|
187
|
+
other_action.upload_zealot_dsym
|
|
188
|
+
result = result.merge(dsym_status: Actions.lane_context[SharedValues::ZEAALOT_ERROR_MESSAGE] ? '上传未完成' : '已上传')
|
|
189
|
+
end
|
|
190
|
+
notice.distribution_completed(:zealot, result)
|
|
191
|
+
end
|
|
255
192
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
193
|
+
# 仅统计实际调用的检查,商店包跳过检查时不发送“检查完成”。
|
|
194
|
+
def self.run_package_checks(params, notice, gym_method:, scheme:, configuration:, target_path:)
|
|
195
|
+
return if gym_method == 'app-store'
|
|
196
|
+
|
|
197
|
+
checks = [
|
|
198
|
+
[:is_analyze_swiftlint, 'SwiftLint', :analyze_swiftlint,
|
|
199
|
+
{ is_from_package: true, configuration: configuration }, Constants.SWIFTLINT_ANALYZE_HTML_FILE],
|
|
200
|
+
[:is_detect_duplicity_code, '重复代码', :detect_duplicity_code,
|
|
201
|
+
{ is_all: true }, Constants.DUPLICITY_CODE_HTML_FILE],
|
|
202
|
+
[:is_detect_unused_code, '无用代码', :detect_unused_code,
|
|
203
|
+
{ scheme: scheme, is_from_package: true, configuration: configuration }, Constants.UNUSED_CODE_HTML_FILE],
|
|
204
|
+
[:is_detect_unused_image, '无用图片', :detect_unused_image, {}, Constants.UNUSED_IMAGE_HTML_FILE]
|
|
205
|
+
]
|
|
206
|
+
completed = []
|
|
207
|
+
checks.each do |key, name, action, options, report|
|
|
208
|
+
next unless params[key]
|
|
209
|
+
|
|
210
|
+
notice.stage(:checks, name)
|
|
211
|
+
options.empty? ? other_action.public_send(action) : other_action.public_send(action, **options)
|
|
212
|
+
FileUtils.cp(report, target_path)
|
|
213
|
+
completed << name
|
|
265
214
|
end
|
|
215
|
+
notice.checks_completed(completed)
|
|
216
|
+
end
|
|
266
217
|
|
|
267
|
-
|
|
218
|
+
private_class_method :distribute_package, :distribute_test_package, :run_package_checks
|
|
219
|
+
|
|
220
|
+
# 构建前准备商店上传参数,开启资源功能却漏配路径时立即报错。
|
|
221
|
+
def self.store_upload_options(params, export_method)
|
|
222
|
+
config_path = AppStoreResourcesHelper.upload_config_path(params, app_store: export_method == 'app-store')
|
|
223
|
+
options = {
|
|
224
|
+
release_notes: params[:release_notes] || '',
|
|
225
|
+
isTestFlight: export_method == 'testFlight',
|
|
226
|
+
# 显式透传 false,避免下层 Action 从环境变量重新开启本次已关闭的功能。
|
|
227
|
+
app_store_resources_enabled: !config_path.nil?
|
|
228
|
+
}
|
|
229
|
+
options[:app_store_resources_config_path] = config_path if config_path
|
|
230
|
+
options
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# 根据凭据或已开启的资源配置判断是否上传,单独存在资源路径不能触发上传。
|
|
234
|
+
def self.store_upload_configured?(options)
|
|
235
|
+
has_p8_key = CommonHelper.is_validate_string(Environment.connect_key_id) &&
|
|
236
|
+
CommonHelper.is_validate_string(Environment.connect_issuer_id)
|
|
237
|
+
has_api_key_json = CommonHelper.is_validate_string(Environment.app_store_connect_api_key_path)
|
|
238
|
+
has_p8_key || has_api_key_json || options[:app_store_resources_enabled]
|
|
268
239
|
end
|
|
269
240
|
|
|
241
|
+
# 上传本次产物,并返回是否执行了上传,供主流程决定是否发送完成通知。
|
|
242
|
+
def self.upload_store_if_configured(options, version:, build:)
|
|
243
|
+
return false unless store_upload_configured?(options)
|
|
244
|
+
|
|
245
|
+
upload_options = options.dup
|
|
246
|
+
if upload_options[:app_store_resources_enabled]
|
|
247
|
+
# 使用真实产物信息覆盖资源配置,避免选中 JSON 中残留的旧版本或 Build。
|
|
248
|
+
upload_options.merge!(
|
|
249
|
+
app_identifier: Environment.bundleID,
|
|
250
|
+
app_version: version.to_s,
|
|
251
|
+
build_number: build.to_s
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
other_action.upload_store(upload_options)
|
|
255
|
+
true
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
private_class_method :store_upload_options, :store_upload_configured?, :upload_store_if_configured
|
|
259
|
+
|
|
270
260
|
def self.description
|
|
271
261
|
'打包'
|
|
272
262
|
end
|
|
273
263
|
|
|
274
264
|
def self.available_options
|
|
275
265
|
[
|
|
266
|
+
FastlaneCore::ConfigItem.new(
|
|
267
|
+
key: :app_store_resources_enabled,
|
|
268
|
+
env_name: 'APP_STORE_RESOURCES_ENABLED',
|
|
269
|
+
description: '商店资源总开关,默认关闭,仅 app-store 打包时生效',
|
|
270
|
+
optional: true,
|
|
271
|
+
default_value: false,
|
|
272
|
+
type: Boolean
|
|
273
|
+
),
|
|
274
|
+
FastlaneCore::ConfigItem.new(
|
|
275
|
+
key: :app_store_resources_config_path,
|
|
276
|
+
env_name: 'APP_STORE_RESOURCES_CONFIG_PATH',
|
|
277
|
+
description: 'App Store 资源发布 JSON 路径,开启商店资源总开关后必填',
|
|
278
|
+
optional: true,
|
|
279
|
+
type: String
|
|
280
|
+
),
|
|
276
281
|
FastlaneCore::ConfigItem.new(
|
|
277
282
|
key: :is_upload_zealot_dsym,
|
|
278
283
|
env_name: 'ZEALOT_UPLOAD_DSYM',
|