fastlane-plugin-fastci 0.0.19 → 0.0.30

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76aa3571beeba986621e0cbff3eb16daf5235d7b080708624acd53d3e6afa560
4
- data.tar.gz: 74743848cbf6bfc3919199ec90bc92b1f5b181c8203b51c734f1149e474151f3
3
+ metadata.gz: 9eadcd995669c5cfd0d4d8a28b68667d0d18cdd710864eed98ed142e285c9b93
4
+ data.tar.gz: 2fa4908e62aedb5faa2ad21a5f6d4d5e210bf609848721c621856c84f80e9741
5
5
  SHA512:
6
- metadata.gz: 77ba058b10ad7811ba9c78f4d48eaee44bebab88f7709bd8f79186d2fa180a68df438b0bbe2238a8910875a758c2d26af43af41e071f3c8655e629b6cc64e418
7
- data.tar.gz: d834627d814264375a73285bd9e9c23f92e77bded00f1158a9843f5a3216ce1c7f643b55f84460c004c43a3915d8a67a09121f979402f79995a95470adfc081e
6
+ metadata.gz: 1606d02bc136b88b61652ad9218f7a3428ba36a516eb578d738328b12750abeb04b9caa7f30eebe790efae4e4019961c6429089f8d0d247cdc8ebd76e5ad2252
7
+ data.tar.gz: 3cf569702228448922d1d20e296900fc2c7594d3c51d3f82ef6ea0e88addc85227f27c3ea050a81ad45ce0630e5a5647aeab82a800b3433e8430eb31fc555d7a
data/README.md CHANGED
@@ -53,6 +53,7 @@ fastlane_cache/ # 插件缓存文件夹
53
53
  功能:自动编译并导出 ipa 包,支持多种打包方式和集成多项检查。
54
54
  生成完的 ipa 会放在桌面上,非 ` app-store ` 配置了蒲公英或 fir 参数会自动上传蒲公英或 fir。` app-store ` 和 ` testFlight ` 配置了商店参数会自动上传商店。
55
55
  配置了 Sentry 参数会自动上传符号表。
56
+ 配置了 Zealot 参数的非商店包也可自动上传,详见下方 Zealot 配置。
56
57
 
57
58
  ` build `: 不指定的话内部有递增逻辑,格式为 ` 20250905.15(日期+当天包的次数) `
58
59
 
@@ -71,7 +72,8 @@ package(
71
72
  is_detect_unused_code: false, # 是否检测未使用代码
72
73
  is_detect_unused_image: false, # 是否检测未使用图片
73
74
  changelog: options[:changelog], # fir 更新日志
74
- release_notes: options[:release_notes] # 配合 jenkins 传参上传 appstore 格式为 { \"zh-Hans\": \"修复问题\", \"en-US\": \"bugfix\"} JSON 字符串
75
+ release_notes: options[:release_notes], # 配合 jenkins 传参上传 appstore 格式为 { \"zh-Hans\": \"修复问题\", \"en-US\": \"bugfix\"} JSON 字符串
76
+ is_notice_dingding: options[:is_notice_dingding] # 配置钉钉token了,通过该参数控制是否发送通知
75
77
  )
76
78
  ```
77
79
 
@@ -123,6 +125,89 @@ detect_unused_image(
123
125
 
124
126
  ---
125
127
 
128
+ ### 6. Zealot(仅 iOS)
129
+
130
+ 通过官方 [fastlane-plugin-zealot](https://github.com/tryzealot/fastlane-plugin-zealot) 封装以下独立 Action:
131
+
132
+ | Action | 用途 |
133
+ | --- | --- |
134
+ | `upload_zealot` | 上传 IPA,返回版本与下载地址 |
135
+ | `upload_zealot_dsym` | 上传 iOS dSYM,支持 zip 或 xcarchive |
136
+ | `check_zealot_version` | 按 Bundle ID、版本号或 commit 查询版本 |
137
+ | `sync_zealot_devices` | 将 Apple 开发者账号中的 iOS 设备同步到 Zealot |
138
+
139
+ 在本机环境文件或 CI 密钥中配置,真实 token 不要提交到仓库:
140
+
141
+ ```dotenv
142
+ ZEALOT_ENDPOINT=https://zealot.example.com
143
+ ZEALOT_TOKEN=your-user-token
144
+ ZEALOT_CHANNEL_KEY=your-ios-channel-key
145
+ ZEALOT_UPLOAD_DSYM=false
146
+ ```
147
+
148
+ `package` 与 Fir 一样按 token 判断是否上传:配置 `ZEALOT_TOKEN` 后,自动上传 `development`、`ad-hoc`、`enterprise` 包;token 留空时跳过。上传时必须同时提供 `ZEALOT_ENDPOINT` 和 `ZEALOT_CHANNEL_KEY`。`app-store` 和 `testFlight` 保持原上传流程。
149
+
150
+ ```ruby
151
+ package(
152
+ export_method: "development",
153
+ is_upload_zealot_dsym: true, # 默认 false;仅 IPA 上传成功后执行
154
+ changelog: "iOS 更新说明"
155
+ )
156
+ ```
157
+
158
+ 也可以单独调用,不需要重新构建:
159
+
160
+ ```ruby
161
+ result = upload_zealot(file: "./App.ipa", changelog: "iOS 更新说明")
162
+ # result:app_id、release_id、release_url、install_url、qrcode_url
163
+
164
+ upload_zealot_dsym(zip_file: "./App.app.dSYM.zip")
165
+ # 或指定 path: "./App.xcarchive";均未指定时使用本次 gym 产物。
166
+
167
+ exists = check_zealot_version(
168
+ bundle_id: "com.example.app",
169
+ release_version: "1.0",
170
+ build_version: "1"
171
+ )
172
+ # 也可用 git_commit 替代 release_version / build_version。
173
+
174
+ sync_zealot_devices(api_key_path: "./app_store_connect_api_key.json")
175
+ # 也可使用 username / team_id;设备同步不会在 package 中自动执行。
176
+ ```
177
+
178
+ 请求失败默认终止 lane,可传 `fail_on_error: false` 或设置 `ZEALOT_FAIL_ON_ERROR=false` 继续执行。版本查询返回 `true`(存在)、`false`(不存在)、`nil`(请求失败且允许继续);设备同步返回 `total`、`succeeded`、`failed`。token 在请求摘要中始终隐藏。
179
+
180
+ 上传参数支持官方的 `password`、`timeout`、`verify_ssl`、`branch`、`git_commit` 等选项,使用 `bundle exec fastlane action upload_zealot` 查看。示例 Fastfile 也提供 `uploadZealot`、`uploadZealotDsym`、`checkZealotVersion`、`syncZealotDevices` 四个 lane。
181
+
182
+ 依赖兼容:Zealot 的 `debug_file` 依赖 `ruby-macho < 3`,不能与 CocoaPods 1.17(依赖 4.x)放在同一 bundle。此类工程应将 CocoaPods 放入独立 Gemfile;使用 Faraday 1 的 Fastlane 工程还应将 `faraday-net_http` 限制为 `~> 1.0`。
183
+
184
+ ## 发布到 RubyGems
185
+
186
+ 工作流位于 [`.github/workflows/release.yml`](.github/workflows/release.yml)。使用 `1.0.0` 这样的纯版本标签,无需 `v` 前缀。推送数字开头的标签后,自动校验标签与 `lib/fastlane/plugin/fastci/version.rb` 中的 `VERSION` 完全一致,检查 Ruby 语法、构建 gem 并发布到 RubyGems。
187
+
188
+ 首次使用需完成一次 [RubyGems Trusted Publishing 配置](https://guides.rubygems.org/trusted-publishing/):
189
+
190
+ 用 gem 所有者账号打开 [fastlane-plugin-fastci](https://rubygems.org/gems/fastlane-plugin-fastci),进入 **Trusted publishers → Create**,填写:
191
+
192
+ | 字段 | 值 |
193
+ | --- | --- |
194
+ | Repository owner | `watermelon520` |
195
+ | Repository name | `fastlane-plugin-fastci` |
196
+ | Workflow filename | `release.yml` |
197
+ | Environment | 留空(可选) |
198
+
199
+ 工作流使用官方 OIDC 短期凭据,不需要配置 `RUBYGEMS_API_KEY` 或创建 GitHub Environment。工作流文件在当前仓库内,Workflow Repository 两项留空。若以后设置 Environment,RubyGems 配置与工作流的 `environment` 必须保持一致。
200
+
201
+ 每次发布先修改 `VERSION`,执行 `bundle install` 同步 `Gemfile.lock`,并提交本次拟发布的代码和工作流。然后推送对应标签,例如版本更新为 `0.0.30` 后:
202
+
203
+ ```bash
204
+ git push origin main
205
+ git tag -a 0.0.30 -m "Release 0.0.30"
206
+ git push origin 0.0.30
207
+ ```
208
+
209
+ 到 GitHub **Actions → Release to RubyGems** 查看结果。已发布版本不能覆盖;失败重跑前先确认该版本是否已经发布。只有打入标签提交的文件会被发布,本地未提交改动不会包含在内。
210
+
126
211
  ## 贡献与支持
127
212
 
128
213
  如需更多帮助或贡献,请提交 Issue 或 PR。
@@ -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
@@ -8,38 +8,43 @@ module Fastlane
8
8
  # 打包
9
9
  class PackageAction < Action
10
10
  def self.run(params)
11
-
12
11
  # 入参配置
13
- configuration = params[:configuration] || "Debug"
14
- export_method = params[:export_method] || "development"
12
+ configuration = params[:configuration] || 'Debug'
13
+ export_method = params[:export_method] || 'development'
15
14
  build = params[:build] || nil
16
15
  version = params[:version] || nil
17
16
  is_analyze_swiftlint = params[:is_analyze_swiftlint] || false
18
17
  is_detect_duplicity_code = params[:is_detect_duplicity_code] || false
19
18
  is_detect_unused_code = params[:is_detect_unused_code] || false
20
19
  is_detect_unused_image = params[:is_detect_unused_image] || false
21
- release_notes = params[:release_notes] || ""
22
- if export_method == "app-store" || export_method == "testFlight"
23
- configuration = "Release"
20
+ release_notes = params[:release_notes] || ''
21
+ 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
24
30
  end
25
-
31
+ send_dingding_notice.call('测试一下')
26
32
  # 清理上一次的打包缓存
27
33
  FileUtils.rm_rf(Dir.glob("#{Constants.BUILD_LOG_DIR}/*"))
28
34
  FileUtils.rm_rf(Dir.glob("#{Constants.IPA_OUTPUT_DIR}/*"))
29
-
30
- # 安装证书
31
- other_action.install_certificate()
32
- # 安装 provisioningProfile
33
- other_action.install_profile()
35
+
36
+ # 非自动更新模式下,安装证书和 provisioningProfile
37
+ unless Environment.is_auto_update_provisioning
38
+ other_action.install_certificate
39
+ other_action.install_profile
40
+ end
34
41
 
35
42
  scheme = params[:scheme] || Environment.scheme
36
43
 
37
44
  # 更改项目version
38
45
  if CommonHelper.is_validate_string(version)
39
46
  increment_options = { version_number: version }
40
- if CommonHelper.is_validate_string(Environment.project)
41
- increment_options[:xcodeproj] = Environment.project
42
- end
47
+ increment_options[:xcodeproj] = Environment.project if CommonHelper.is_validate_string(Environment.project)
43
48
  other_action.increment_version_number(increment_options)
44
49
  end
45
50
 
@@ -47,45 +52,43 @@ module Fastlane
47
52
  other_action.update_build_number(
48
53
  build: build
49
54
  )
50
- time = Time.new.strftime("%Y%m%d%H%M")
51
-
55
+ time = Time.new.strftime('%Y%m%d%H%M')
56
+
52
57
  # 获取版本号
53
58
  version_options = { target: scheme }
54
- if CommonHelper.is_validate_string(Environment.project)
55
- version_options[:xcodeproj] = Environment.project
56
- end
59
+ version_options[:xcodeproj] = Environment.project if CommonHelper.is_validate_string(Environment.project)
57
60
  version = other_action.get_version_number(version_options)
58
-
61
+
59
62
  # 获取 build 号
60
63
  build_options = {}
61
- if CommonHelper.is_validate_string(Environment.project)
62
- build_options[:xcodeproj] = Environment.project
63
- end
64
+ build_options[:xcodeproj] = Environment.project if CommonHelper.is_validate_string(Environment.project)
64
65
  build = other_action.get_build_number(build_options)
65
66
  # 生成ipa包的名字格式
66
67
  ipaName = "#{scheme}_#{export_method}_#{version}_#{build}.ipa"
67
-
68
+
68
69
  # 获取 Extension 的 Bundle ID(可能有多个,用逗号分隔)
69
70
  extension_bundle_ids = Environment.extension_bundle_ids
70
71
  extension_profile_names = []
71
72
  # profile 名字
72
- profile_name = ""
73
+ profile_name = ''
73
74
 
74
75
  case export_method
75
- when "development"
76
+ when 'development'
76
77
  profile_name = Environment.provisioningProfiles_development
77
78
  extension_profile_names = Environment.extension_profiles_development
78
- when "ad-hoc"
79
+ when 'ad-hoc'
79
80
  profile_name = Environment.provisioningProfiles_adhoc
80
81
  extension_profile_names = Environment.extension_profiles_adhoc
81
- when "app-store", "testFlight"
82
+ when 'app-store', 'testFlight'
82
83
  profile_name = Environment.provisioningProfiles_appstore
83
84
  extension_profile_names = Environment.extension_profiles_appstore
84
85
  else
85
86
  raise "Unsupported export method: #{export_method}"
86
87
  end
87
88
 
88
- # 组装 provisioningProfiles
89
+ UI.message('*************| 开始打包 |*************')
90
+
91
+ # 组装 provisioningProfiles
89
92
  provisioningProfiles_map = {
90
93
  "#{Environment.bundleID}" => "#{profile_name}"
91
94
  }
@@ -93,11 +96,9 @@ module Fastlane
93
96
  provisioningProfiles_map[ext_bundle_id.strip] = extension_profile_names[idx]&.strip
94
97
  end
95
98
 
96
- UI.message("*************| 开始打包 |*************")
97
-
98
99
  # 对于 testFlight,使用 app-store 方法
99
- gym_method = export_method == "testFlight" ? "app-store" : export_method
100
-
100
+ gym_method = export_method == 'testFlight' ? 'app-store' : export_method
101
+
101
102
  gym_options = {
102
103
  clean: true,
103
104
  silent: true,
@@ -120,11 +121,11 @@ module Fastlane
120
121
 
121
122
  other_action.gym(gym_options)
122
123
 
123
- UI.message("*************| 打包完成 |*************")
124
+ UI.message('*************| 打包完成 |*************')
124
125
 
125
- UI.message("*************| 复制打包产物 |*************")
126
+ UI.message('*************| 复制打包产物 |*************')
126
127
  # 定义桌面路径
127
- desktop_path = File.expand_path("~/Desktop")
128
+ desktop_path = File.expand_path('~/Desktop')
128
129
  output_path = File.join(desktop_path, "BuildOutput_#{scheme}")
129
130
  target_path = File.join(output_path, "#{build}")
130
131
  FileUtils.mkdir_p(target_path)
@@ -140,20 +141,20 @@ module Fastlane
140
141
 
141
142
  ipa_path = "#{Constants.IPA_OUTPUT_DIR}/#{ipaName}"
142
143
 
143
- if gym_method == "app-store"
144
+ if gym_method == 'app-store'
144
145
  notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-打包完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
145
- DingdingHelper.sendMarkdown(notiText)
146
+ send_dingding_notice.call(notiText)
146
147
 
147
148
  if CommonHelper.is_validate_string(Environment.connect_key_id) && CommonHelper.is_validate_string(Environment.connect_issuer_id)
148
149
  # 根据 export_method 决定是否为 TestFlight
149
- is_test_flight = export_method == "testFlight"
150
-
150
+ is_test_flight = export_method == 'testFlight'
151
+
151
152
  other_action.upload_store(
152
153
  release_notes: release_notes,
153
154
  isTestFlight: is_test_flight
154
155
  )
155
156
  notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-上传完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
156
- DingdingHelper.sendMarkdown(notiText)
157
+ send_dingding_notice.call(notiText)
157
158
  end
158
159
  else
159
160
  # 钉钉通知
@@ -161,8 +162,8 @@ module Fastlane
161
162
 
162
163
  # 上传蒲公英
163
164
  if CommonHelper.is_validate_string(Environment.pgy_api_key)
164
- pgy_upload_info = other_action.upload_pgy()
165
- qrCode = pgy_upload_info["buildQRCodeURL"]
165
+ pgy_upload_info = other_action.upload_pgy
166
+ qrCode = pgy_upload_info['buildQRCodeURL']
166
167
 
167
168
  if CommonHelper.is_validate_string(qrCode)
168
169
  notiText << "\n\n⬇️⬇️⬇️ 扫码安装 ⬇️⬇️⬇️\n\n\n密码: #{Environment.pgy_password}\n![screenshot](#{qrCode})"
@@ -181,43 +182,55 @@ module Fastlane
181
182
  end
182
183
  end
183
184
 
184
- DingdingHelper.sendMarkdown(notiText)
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)
185
198
  end
186
199
 
187
200
  # Sentry 上传 dSYM
188
- if CommonHelper.is_validate_string(Environment.sentry_auth_token)
189
- other_action.sentry_upload_dsym()
201
+ if CommonHelper.is_validate_string(Environment.sentry_auth_token)
202
+ other_action.sentry_upload_dsym
190
203
  else
191
- UI.message("*************| 未配置 Sentry 跳过 dSYM 上传 |*************")
204
+ UI.message('*************| 未配置 Sentry 跳过 dSYM 上传 |*************')
192
205
  end
193
206
 
194
207
  # 代码分析
195
- if is_analyze_swiftlint && gym_method != "app-store"
208
+ if is_analyze_swiftlint && gym_method != 'app-store'
196
209
  other_action.analyze_swiftlint(
197
210
  is_from_package: true,
198
211
  configuration: configuration
199
212
  )
200
213
  # 结果复制到桌面
201
214
  FileUtils.cp(SWIFTLINT_ANALYZE_HTML_FILE, target_path)
202
- UI.message("*************| 代码分析完成 |*************")
215
+ UI.message('*************| 代码分析完成 |*************')
203
216
  else
204
- UI.message("*************| 跳过代码分析 |*************")
217
+ UI.message('*************| 跳过代码分析 |*************')
205
218
  end
206
219
 
207
220
  # 重复代码检查
208
- if is_detect_duplicity_code && gym_method != "app-store"
221
+ if is_detect_duplicity_code && gym_method != 'app-store'
209
222
  other_action.detect_duplicity_code(
210
223
  is_all: true
211
224
  )
212
225
  # 结果复制到桌面
213
226
  FileUtils.cp(DUPLICITY_CODE_HTML_FILE, target_path)
214
- UI.message("*************| 重复代码检查完成 |*************")
227
+ UI.message('*************| 重复代码检查完成 |*************')
215
228
  else
216
- UI.message("*************| 跳过重复代码检查 |*************")
229
+ UI.message('*************| 跳过重复代码检查 |*************')
217
230
  end
218
231
 
219
232
  # 无用代码检查
220
- if is_detect_unused_code && gym_method != "app-store"
233
+ if is_detect_unused_code && gym_method != 'app-store'
221
234
  other_action.detect_unused_code(
222
235
  scheme: scheme,
223
236
  is_from_package: true,
@@ -225,127 +238,138 @@ module Fastlane
225
238
  )
226
239
  # 结果复制到桌面
227
240
  FileUtils.cp(Constants.UNUSED_CODE_HTML_FILE, target_path)
228
- UI.message("*************| 无用代码检查完成 |*************")
241
+ UI.message('*************| 无用代码检查完成 |*************')
229
242
  else
230
- UI.message("*************| 跳过无用代码检查 |*************")
243
+ UI.message('*************| 跳过无用代码检查 |*************')
231
244
  end
232
245
 
233
246
  # 无用图片检查
234
- if is_detect_unused_image && gym_method != "app-store"
235
- other_action.detect_unused_image()
247
+ if is_detect_unused_image && gym_method != 'app-store'
248
+ other_action.detect_unused_image
236
249
  # 结果复制到桌面
237
250
  FileUtils.cp(Constants.UNUSED_IMAGE_HTML_FILE, target_path)
238
- UI.message("*************| 无用图片检查完成 |*************")
251
+ UI.message('*************| 无用图片检查完成 |*************')
239
252
  else
240
- UI.message("*************| 跳过未使用图片检查 |*************")
253
+ UI.message('*************| 跳过未使用图片检查 |*************')
241
254
  end
242
255
 
243
256
  if is_analyze_swiftlint ||
244
- is_detect_duplicity_code ||
245
- is_detect_unused_code ||
246
- is_detect_unused_image
257
+ is_detect_duplicity_code ||
258
+ is_detect_unused_code ||
259
+ is_detect_unused_image
247
260
  # 钉钉通知
248
261
  notiText = "🚀🚀🚀🚀🚀🚀\n\n#{scheme}-iOS-代码检查完成\n\n#{version}_#{build}_#{export_method}\n\n🚀🚀🚀🚀🚀🚀"
249
- DingdingHelper.sendMarkdown(notiText)
262
+ send_dingding_notice.call(notiText)
250
263
  else
251
- UI.message("*************| 跳过代码检查 |*************")
264
+ UI.message('*************| 跳过代码检查 |*************')
252
265
  end
253
266
 
254
- UI.message("*************| 脚本完成 |*************")
267
+ UI.message('*************| 脚本完成 |*************')
255
268
  end
256
269
 
257
270
  def self.description
258
- "打包"
271
+ '打包'
259
272
  end
260
273
 
261
274
  def self.available_options
262
275
  [
276
+ FastlaneCore::ConfigItem.new(
277
+ key: :is_upload_zealot_dsym,
278
+ env_name: 'ZEALOT_UPLOAD_DSYM',
279
+ description: 'IPA 上传 Zealot 成功后继续上传 iOS dSYM',
280
+ optional: true,
281
+ default_value: false,
282
+ type: Boolean
283
+ ),
263
284
  FastlaneCore::ConfigItem.new(
264
285
  key: :scheme,
265
- description: "不采取默认配置,自定义 `scheme` 名称",
286
+ description: '不采取默认配置,自定义 `scheme` 名称',
266
287
  optional: true,
267
288
  default_value: nil,
268
289
  type: String
269
290
  ),
270
291
  FastlaneCore::ConfigItem.new(
271
292
  key: :configuration,
272
- description: "编译环境 Release or Debug",
293
+ description: '编译环境 Release or Debug',
273
294
  optional: true,
274
- default_value: "Debug",
295
+ default_value: 'Debug',
275
296
  type: String,
276
297
  verify_block: proc do |value|
277
- valid_params = ["Release", "Debug"]
278
- unless valid_params.include?(value)
279
- UI.user_error!("无效的编译环境: #{value}。支持的环境: #{valid_params.join(', ')}")
280
- end
298
+ valid_params = %w[Release Debug]
299
+ UI.user_error!("无效的编译环境: #{value}。支持的环境: #{valid_params.join(', ')}") unless valid_params.include?(value)
281
300
  end
282
301
  ),
283
302
  FastlaneCore::ConfigItem.new(
284
303
  key: :export_method,
285
- description: "打包方式 ad-hoc, enterprise, app-store, development, testFlight",
304
+ description: '打包方式 ad-hoc, enterprise, app-store, development, testFlight',
286
305
  optional: true,
287
- default_value: "development",
306
+ default_value: 'development',
288
307
  type: String,
289
308
  verify_block: proc do |value|
290
- valid_params = ["ad-hoc", "enterprise", "app-store", "development", "testFlight"]
291
- unless valid_params.include?(value)
292
- UI.user_error!("无效的打包方式: #{value}。支持的方式: #{valid_params.join(', ')}")
293
- end
309
+ valid_params = %w[ad-hoc enterprise app-store development testFlight]
310
+ UI.user_error!("无效的打包方式: #{value}。支持的方式: #{valid_params.join(', ')}") unless valid_params.include?(value)
294
311
  end
295
312
  ),
296
313
  FastlaneCore::ConfigItem.new(
297
314
  key: :version,
298
- description: "自定义 `version`。在 Xcode13 之后创建的项目,不再支持脚本修改。需要兼容请在 Build settings 中将 GENERATE_INFOPLIST_FILE 设置为 NO",
315
+ description: '自定义 `version`。在 Xcode13 之后创建的项目,不再支持脚本修改。需要兼容请在 Build settings 中将 GENERATE_INFOPLIST_FILE 设置为 NO',
299
316
  optional: true,
300
317
  default_value: nil,
301
318
  type: String
302
319
  ),
303
320
  FastlaneCore::ConfigItem.new(
304
321
  key: :build,
305
- description: "不采取自动更新,自定义 `build` 号",
322
+ description: '不采取自动更新,自定义 `build` 号',
306
323
  optional: true,
307
324
  default_value: nil,
308
325
  type: String
309
326
  ),
310
327
  FastlaneCore::ConfigItem.new(
311
328
  key: :is_analyze_swiftlint,
312
- description: "是否代码分析",
329
+ description: '是否代码分析',
313
330
  optional: true,
314
331
  default_value: false,
315
332
  type: Boolean
316
333
  ),
317
334
  FastlaneCore::ConfigItem.new(
318
335
  key: :is_detect_duplicity_code,
319
- description: "是否检查重复代码",
336
+ description: '是否检查重复代码',
320
337
  optional: true,
321
338
  default_value: false,
322
339
  type: Boolean
323
340
  ),
324
341
  FastlaneCore::ConfigItem.new(
325
342
  key: :is_detect_unused_code,
326
- description: "是否检查无用代码",
343
+ description: '是否检查无用代码',
327
344
  optional: true,
328
345
  default_value: false,
329
346
  type: Boolean
330
347
  ),
331
348
  FastlaneCore::ConfigItem.new(
332
349
  key: :is_detect_unused_image,
333
- description: "是否检查无用图片",
350
+ description: '是否检查无用图片',
334
351
  optional: true,
335
352
  default_value: false,
336
353
  type: Boolean
337
354
  ),
338
355
  FastlaneCore::ConfigItem.new(
339
356
  key: :changelog,
340
- description: "更新日志",
357
+ description: '更新日志',
341
358
  optional: true,
342
359
  type: String
343
360
  ),
344
361
  FastlaneCore::ConfigItem.new(
345
362
  key: :release_notes,
346
- description: "更新文案, 格式为 { \"zh-Hans\": \"修复问题\", \"en-US\": \"bugfix\"} JSON 字符串",
363
+ description: '更新文案, 格式为 { "zh-Hans": "修复问题", "en-US": "bugfix"} JSON 字符串',
347
364
  optional: true,
348
365
  type: String
366
+ ),
367
+ FastlaneCore::ConfigItem.new(
368
+ key: :is_notice_dingding,
369
+ description: '是否通知钉钉(默认true, 优先级高于DINGDING_TOKEN)',
370
+ optional: true,
371
+ default_value: true,
372
+ type: Boolean
349
373
  )
350
374
  ]
351
375
  end
@@ -357,7 +381,6 @@ module Fastlane
357
381
  def self.category
358
382
  :building
359
383
  end
360
-
361
384
  end
362
385
  end
363
- end
386
+ end
@@ -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
@@ -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
@@ -91,6 +91,17 @@ module Fastlane
91
91
  ENV['FIR_SWITCH_QINIU'] == "true"
92
92
  end
93
93
 
94
+ # Zealot 配置;可选上传渠道,未配置时不影响现有打包流程。
95
+ def self.zealot_endpoint
96
+ ENV['ZEALOT_ENDPOINT']
97
+ end
98
+ def self.zealot_token
99
+ ENV['ZEALOT_TOKEN']
100
+ end
101
+ def self.zealot_channel_key
102
+ ENV['ZEALOT_CHANNEL_KEY']
103
+ end
104
+
94
105
  # 钉钉配置
95
106
  def self.dingdingToken
96
107
  ENV['DINGDING_TOKEN']
@@ -112,4 +123,4 @@ module Fastlane
112
123
 
113
124
  end
114
125
  end
115
- end
126
+ end
@@ -0,0 +1,51 @@
1
+ require 'fastlane/plugin/zealot'
2
+
3
+ module Fastlane
4
+ module Helper
5
+ # 复用官方 Action 的协议与参数,仅收口 FastCI 的 iOS 边界和日志脱敏。
6
+ module FastciZealotHelper
7
+ def zealot_options(action, excluding: [])
8
+ options = action.available_options.reject do |option|
9
+ excluding.include?(option.key) || option.key == :fail_on_error
10
+ end
11
+
12
+ # 新增上传渠道默认明确报错,避免 CI 把失败当作上传完成。
13
+ options << FastlaneCore::ConfigItem.new(
14
+ key: :fail_on_error,
15
+ env_name: 'ZEALOT_FAIL_ON_ERROR',
16
+ description: 'Zealot 请求失败时终止当前任务',
17
+ optional: true,
18
+ default_value: true,
19
+ type: Boolean
20
+ )
21
+ options
22
+ end
23
+
24
+ def zealot_parameters(params)
25
+ params.all_keys.each_with_object({}) do |key, values|
26
+ value = params[key]
27
+ values[key] = value unless value.nil?
28
+ end
29
+ end
30
+
31
+ def validate_zealot_connection!(values)
32
+ keys = [:endpoint, :token]
33
+ keys << :channel_key if values.key?(:channel_key)
34
+ keys.each do |key|
35
+ UI.user_error!("缺少 Zealot #{key}") if values[key].to_s.strip.empty?
36
+ end
37
+ end
38
+
39
+ # 官方部分 Action 未隐藏 token;只改变当前封装的输出,不修改上游全局实现。
40
+ def print_table(form, title: 'zealot', hidden_keys: [], remove_empty_value: true)
41
+ super(
42
+ form,
43
+ title: title,
44
+ hidden_keys: hidden_keys + [:token, :Token],
45
+ remove_empty_value: remove_empty_value
46
+ )
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Fastci
3
- VERSION = "0.0.19"
3
+ VERSION = "0.0.30"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-fastci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.19
4
+ version: 0.0.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - watermelon
@@ -57,14 +57,28 @@ dependencies:
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: 1.36.0
60
+ version: 2.5.1
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 1.36.0
67
+ version: 2.5.1
68
+ - !ruby/object:Gem::Dependency
69
+ name: fastlane-plugin-zealot
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 1.0.1
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.0.1
68
82
  email: watermelon_lp@163.com
69
83
  executables: []
70
84
  extensions: []
@@ -74,6 +88,7 @@ files:
74
88
  - README.md
75
89
  - lib/fastlane/plugin/fastci.rb
76
90
  - lib/fastlane/plugin/fastci/actions/analyze_swiftlint_action.rb
91
+ - lib/fastlane/plugin/fastci/actions/check_zealot_version_action.rb
77
92
  - lib/fastlane/plugin/fastci/actions/detect_duplicity_code_action.rb
78
93
  - lib/fastlane/plugin/fastci/actions/detect_unused_code_action.rb
79
94
  - lib/fastlane/plugin/fastci/actions/detect_unused_image_action.rb
@@ -81,14 +96,18 @@ files:
81
96
  - lib/fastlane/plugin/fastci/actions/install_profile_action.rb
82
97
  - lib/fastlane/plugin/fastci/actions/package_action.rb
83
98
  - lib/fastlane/plugin/fastci/actions/sentry_upload_dsym.rb
99
+ - lib/fastlane/plugin/fastci/actions/sync_zealot_devices_action.rb
84
100
  - lib/fastlane/plugin/fastci/actions/update_build_number_action.rb
85
101
  - lib/fastlane/plugin/fastci/actions/upload_fir_action.rb
86
102
  - lib/fastlane/plugin/fastci/actions/upload_pgy_action.rb
87
103
  - lib/fastlane/plugin/fastci/actions/upload_store_action.rb
104
+ - lib/fastlane/plugin/fastci/actions/upload_zealot_action.rb
105
+ - lib/fastlane/plugin/fastci/actions/upload_zealot_dsym_action.rb
88
106
  - lib/fastlane/plugin/fastci/helper/common_helper.rb
89
107
  - lib/fastlane/plugin/fastci/helper/constants.rb
90
108
  - lib/fastlane/plugin/fastci/helper/dingding_helper.rb
91
109
  - lib/fastlane/plugin/fastci/helper/environment.rb
110
+ - lib/fastlane/plugin/fastci/helper/fastci_zealot_helper.rb
92
111
  - lib/fastlane/plugin/fastci/helper/fir_helper.rb
93
112
  - lib/fastlane/plugin/fastci/version.rb
94
113
  - lib/fastlane/plugin/python/generate_duplicity_code_html.py
@@ -114,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
133
  - !ruby/object:Gem::Version
115
134
  version: '0'
116
135
  requirements: []
117
- rubygems_version: 3.7.2
136
+ rubygems_version: 4.0.23
118
137
  specification_version: 4
119
138
  summary: supports multiple functions such as packaging, uploading, and code detection.
120
139
  test_files: []