pindo 5.21.4 → 5.22.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.
- checksums.yaml +4 -4
- data/lib/pindo/base/git_handler.rb +21 -2
- data/lib/pindo/base/xcodeproj_compat.rb +42 -0
- data/lib/pindo/command/jps/config.rb +21 -12
- data/lib/pindo/module/pgyer/pgyerhelper.rb +265 -199
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +34 -4
- data/lib/pindo/module/task/model/nuget/nuget_upload_task.rb +5 -2
- data/lib/pindo/module/xcode/res/xcode_res_handler.rb +87 -26
- data/lib/pindo/module/xcode/scene_lifecycle_guard.rb +16 -5
- data/lib/pindo/module/xcode/xcode_build_config.rb +47 -3
- data/lib/pindo/options/groups/jps_options.rb +3 -3
- data/lib/pindo/version.rb +1 -1
- data/lib/pindo.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 683bf46a8095e20c20a6fdc339b99a6ae83337dc58431b67fb6b1d00263e42a8
|
|
4
|
+
data.tar.gz: 291caf87a1647dc8af05b26fbbfa94cd588385177869e43ec5983db3e90233f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d0176e844e360885e6bf90ebd9024b1982ee7c519ce6d01f3fe994e085110b12107f34271d379283ca4ca27165e0cb1d2187a1daf4cfcbc2ce65b3445688786
|
|
7
|
+
data.tar.gz: 5a94ae4f340b1cc924a0e85e1c0a57272014d58c7498d248f91ab5d3d6bba399f2d3521c1d6d90dec2d5b5fdb578d4d05b1ce02b3bf55fd7ecc3eb90837a4e64
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
|
+
require 'open3'
|
|
2
3
|
require 'pindo/base/executable'
|
|
3
4
|
require 'pindo/base/informative'
|
|
4
5
|
require 'etc'
|
|
@@ -556,6 +557,16 @@ module Pindo
|
|
|
556
557
|
|
|
557
558
|
|
|
558
559
|
|
|
560
|
+
# 仓库是否已有提交(git init 后尚未首次提交的仓库没有 HEAD)
|
|
561
|
+
# @param git_root [String] Git 根目录
|
|
562
|
+
# @return [Boolean]
|
|
563
|
+
def head_commit_exists?(git_root:)
|
|
564
|
+
_stdout, _stderr, status = Open3.capture3('git', '-C', git_root, 'rev-parse', '--verify', '--quiet', 'HEAD')
|
|
565
|
+
status.success?
|
|
566
|
+
rescue SystemCallError
|
|
567
|
+
false
|
|
568
|
+
end
|
|
569
|
+
|
|
559
570
|
# 获取未提交的文件列表
|
|
560
571
|
# @param project_dir [String] 项目目录路径
|
|
561
572
|
# @return [Array<String>, nil] 返回未提交的文件列表,如果没有则返回 nil
|
|
@@ -970,6 +981,15 @@ module Pindo
|
|
|
970
981
|
# @param default_process_type [Symbol, nil] 非交互模式下的默认处理方式,默认为 nil
|
|
971
982
|
# @return [Symbol] UncommittedFilesProcessType 枚举常量
|
|
972
983
|
def get_uncommitted_files_process_type(project_dir:, interactive: true, default_process_type: nil)
|
|
984
|
+
# 0. 空仓库没有 HEAD,后续的暂存区重置、版本计算、分支同步和打 tag 都无法进行
|
|
985
|
+
git_root = git_root_directory(local_repo_dir: project_dir)
|
|
986
|
+
if git_root && !head_commit_exists?(git_root: git_root)
|
|
987
|
+
raise Informative, "当前 Git 仓库还没有任何提交,无法计算版本号和打 tag:#{git_root}\n" \
|
|
988
|
+
"请先完成首次提交后重新执行:\n" \
|
|
989
|
+
" git -C \"#{git_root}\" add -A\n" \
|
|
990
|
+
" git -C \"#{git_root}\" commit -m \"chore: 初始化工程\""
|
|
991
|
+
end
|
|
992
|
+
|
|
973
993
|
# 1. 检查是否有未提交的文件
|
|
974
994
|
uncommitted_files = get_uncommitted_files(project_dir: project_dir)
|
|
975
995
|
|
|
@@ -986,8 +1006,7 @@ module Pindo
|
|
|
986
1006
|
return default_process_type
|
|
987
1007
|
end
|
|
988
1008
|
|
|
989
|
-
# 4.
|
|
990
|
-
git_root = git_root_directory(local_repo_dir: project_dir)
|
|
1009
|
+
# 4. 获取当前分支名
|
|
991
1010
|
unless git_root
|
|
992
1011
|
Funlog.instance.fancyinfo_warning("当前目录不是Git仓库")
|
|
993
1012
|
return Pindo::UncommittedFilesProcessType::SKIP_WITH_TAG
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'xcodeproj'
|
|
2
|
+
|
|
3
|
+
module Pindo
|
|
4
|
+
# Xcode 26.4+/27 工程格式兼容补丁(xcodeproj 1.28.1 尚未支持,见 CocoaPods/Xcodeproj#1017)
|
|
5
|
+
#
|
|
6
|
+
# CocoaPods 与 fastlane gym 在 pindo 进程内运行,共用同一份补丁。
|
|
7
|
+
module XcodeprojCompat
|
|
8
|
+
# 新格式把 shellScript 存为按行拆分的数组,合并回字符串
|
|
9
|
+
module ShellScriptArray
|
|
10
|
+
def shell_script=(value)
|
|
11
|
+
super(value.is_a?(Array) ? value.join("\n") : value)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# 保存时保留 xcodeproj 不认识的字段(如 Copy Files 阶段的 dstSubfolder),
|
|
16
|
+
# 否则这些字段会被丢弃,导致扩展等产物的嵌入位置丢失
|
|
17
|
+
module UnknownAttributes
|
|
18
|
+
def configure_with_plist(objects_by_uuid_plist)
|
|
19
|
+
object_plist = objects_by_uuid_plist[uuid]
|
|
20
|
+
known_keys = self.class.attributes.map(&:plist_name) << 'isa'
|
|
21
|
+
@pindo_unknown_attributes = object_plist.reject { |key, _| known_keys.include?(key) }
|
|
22
|
+
|
|
23
|
+
# 暂时移除未知字段,避免 xcodeproj 对已保留的字段输出误导性警告
|
|
24
|
+
@pindo_unknown_attributes.each_key { |key| object_plist.delete(key) }
|
|
25
|
+
super
|
|
26
|
+
ensure
|
|
27
|
+
object_plist&.merge!(@pindo_unknown_attributes || {})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def to_hash_as(method = :to_hash)
|
|
33
|
+
plist = super
|
|
34
|
+
(@pindo_unknown_attributes || {}).each { |key, value| plist[key] = value unless plist.key?(key) }
|
|
35
|
+
plist
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Xcodeproj::Project::Object::PBXShellScriptBuildPhase.prepend(Pindo::XcodeprojCompat::ShellScriptArray)
|
|
42
|
+
Xcodeproj::Project::Object::AbstractObject.prepend(Pindo::XcodeprojCompat::UnknownAttributes)
|
|
@@ -10,31 +10,31 @@ module Pindo
|
|
|
10
10
|
class Jps < Command
|
|
11
11
|
class Config < Jps
|
|
12
12
|
|
|
13
|
-
self.summary = '配置当前工程与 JPS
|
|
13
|
+
self.summary = '配置当前工程与 JPS 项目及安装包工具 / Git 工作流的绑定'
|
|
14
14
|
|
|
15
15
|
self.description = <<-DESC
|
|
16
|
-
将当前工程绑定到 JPS
|
|
16
|
+
将当前工程绑定到 JPS 项目的安装包工具(ipa/apk/zip/mac/exe)和 Git 工作流,并写入 JPSBuildConfig.json。
|
|
17
17
|
|
|
18
18
|
只做配置绑定,不编译、不上传,产出与 pindo ios/and autobuild 一致的配置文件。
|
|
19
19
|
|
|
20
20
|
支持功能:
|
|
21
21
|
|
|
22
|
-
* 选一次 JPS
|
|
22
|
+
* 选一次 JPS 项目,一次配置多个类型
|
|
23
23
|
* 交互多选菜单,或用 --type 非交互指定类型
|
|
24
24
|
* --proj 直接指定项目名,跳过项目选择菜单(非交互场景)
|
|
25
|
-
* --workflow_name
|
|
25
|
+
* --workflow_name 按名称指定 Git 工作流或安装包工具,用于同类型多个时消歧(非交互场景)
|
|
26
26
|
* 支持类型: ipa、apk、zip(WebGL)、mac(macOS)、exe(Windows)、git
|
|
27
|
-
* --clean
|
|
27
|
+
* --clean 清除已绑定的工具 / 工作流或整个配置文件
|
|
28
28
|
|
|
29
29
|
使用示例:
|
|
30
30
|
|
|
31
|
-
$ pindo jps config #
|
|
31
|
+
$ pindo jps config # 交互选择项目和绑定类型
|
|
32
32
|
|
|
33
33
|
$ pindo jps config --type=ipa,apk,git # 非交互配置指定类型(项目仍需交互选)
|
|
34
34
|
|
|
35
35
|
$ pindo jps config --proj=MyApp --type=ipa,git # 全程非交互(agent/CI 适用)
|
|
36
36
|
|
|
37
|
-
$ pindo jps config --clean #
|
|
37
|
+
$ pindo jps config --clean # 交互清除已绑定的工具 / 工作流
|
|
38
38
|
|
|
39
39
|
$ pindo jps config --clean --type=ipa # 清除指定类型的绑定
|
|
40
40
|
|
|
@@ -96,7 +96,7 @@ module Pindo
|
|
|
96
96
|
|
|
97
97
|
selected_types = resolve_selected_types
|
|
98
98
|
if selected_types.empty?
|
|
99
|
-
puts "
|
|
99
|
+
puts "未选择任何绑定类型,已取消操作"
|
|
100
100
|
return
|
|
101
101
|
end
|
|
102
102
|
|
|
@@ -110,9 +110,9 @@ module Pindo
|
|
|
110
110
|
raise Informative, "未找到 JPS 项目,请检查项目名称或配置文件"
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
# Phase 1:
|
|
113
|
+
# Phase 1: 先选定所有类型的工具 / 工作流(任一歧义/缺失即报错,此时尚未写任何文件)
|
|
114
114
|
selections = selected_types.map do |type|
|
|
115
|
-
puts "\n
|
|
115
|
+
puts "\n选择#{binding_label(type)}: #{type[:name]}"
|
|
116
116
|
workflow_info = pgyer_helper.select_workflow_for_project(
|
|
117
117
|
project_id: app_info_obj["id"],
|
|
118
118
|
package_type: type[:package_type],
|
|
@@ -169,7 +169,7 @@ module Pindo
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
def select_types_interactive
|
|
172
|
-
puts "\n
|
|
172
|
+
puts "\n请选择要配置的绑定类型(安装包为工具,git 为工作流,可多选):"
|
|
173
173
|
puts DIVIDER
|
|
174
174
|
WORKFLOW_TYPES.each_with_index do |type, index|
|
|
175
175
|
puts " #{index + 1}. #{type[:name]}"
|
|
@@ -343,11 +343,20 @@ module Pindo
|
|
|
343
343
|
WORKFLOW_TYPES.each do |type|
|
|
344
344
|
workflow = config[type[:workflow_key]]
|
|
345
345
|
next if workflow.nil?
|
|
346
|
-
|
|
346
|
+
if type[:manage_type] == 'git'
|
|
347
|
+
puts " #{type[:name]}: 工作流: #{workflow['tab_name']} (ID: #{workflow['workflow_id']})"
|
|
348
|
+
else
|
|
349
|
+
puts " #{type[:name]}: 工具: #{workflow['tab_name']} (packageId: #{workflow['package_id']})"
|
|
350
|
+
end
|
|
347
351
|
end
|
|
348
352
|
puts DIVIDER
|
|
349
353
|
end
|
|
350
354
|
|
|
355
|
+
# 安装包类型绑定的是「工具」,Git 绑定的是「工作流」
|
|
356
|
+
def binding_label(type)
|
|
357
|
+
type[:manage_type] == 'git' ? '工作流' : '工具'
|
|
358
|
+
end
|
|
359
|
+
|
|
351
360
|
end
|
|
352
361
|
end
|
|
353
362
|
end
|
|
@@ -156,6 +156,7 @@ module Pindo
|
|
|
156
156
|
# 7. 所有验证通过
|
|
157
157
|
result[:valid] = true
|
|
158
158
|
result[:workflow_info] = {
|
|
159
|
+
package_id: workflow['package_id'].to_s,
|
|
159
160
|
workflow_id: workflow['workflow_id'],
|
|
160
161
|
tab_name: workflow['tab_name'],
|
|
161
162
|
package_name: workflow['package_name'],
|
|
@@ -166,6 +167,7 @@ module Pindo
|
|
|
166
167
|
if ENV['PINDO_DEBUG']
|
|
167
168
|
puts "[PINDO_DEBUG] ✓✓✓ 配置验证通过 (#{workflow_key})"
|
|
168
169
|
puts "[PINDO_DEBUG] 返回 valid=true,workflow_info:"
|
|
170
|
+
puts " package_id: #{result[:workflow_info][:package_id]}"
|
|
169
171
|
puts " workflow_id: #{result[:workflow_info][:workflow_id]}"
|
|
170
172
|
puts " tab_name: #{result[:workflow_info][:tab_name]}"
|
|
171
173
|
puts " manage_type: #{result[:workflow_info][:manage_type]}"
|
|
@@ -174,86 +176,44 @@ module Pindo
|
|
|
174
176
|
return result
|
|
175
177
|
end
|
|
176
178
|
|
|
177
|
-
#
|
|
179
|
+
# 为项目选择工作流(Git)或安装包工具(ipa/apk/zip/mac/exe)
|
|
180
|
+
#
|
|
181
|
+
# JPS V1.3.94 起,workflow/list 不再返回产物工作流;安装包入口改为项目的 packageTools,
|
|
182
|
+
# 因此非 git 类型一律从 project/detail 的 packageTools 中选择。
|
|
178
183
|
#
|
|
179
184
|
# @param project_id [String] 项目ID
|
|
180
185
|
# @param package_type [String] 包类型
|
|
181
186
|
# @param working_directory [String] 工作目录
|
|
182
|
-
# @param manage_type [String] 管理类型(默认为空,"git"
|
|
183
|
-
# @
|
|
187
|
+
# @param manage_type [String] 管理类型(默认为空,"git" 时选择 Git 工作流)
|
|
188
|
+
# @param workflow_name [String, nil] 按名称精确选择(Git 匹配 tabName;工具匹配 name、subtitle 或 name(subtitle))
|
|
189
|
+
# @return [Hash] 选择的工作流/工具信息(symbol key 结构)
|
|
184
190
|
def select_workflow_for_project(project_id:, package_type:, working_directory:, manage_type: "", workflow_name: nil)
|
|
185
|
-
|
|
191
|
+
if manage_type == "git"
|
|
192
|
+
return select_git_workflow_for_project(project_id: project_id, package_type: package_type, workflow_name: workflow_name)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
select_package_tool_for_project(project_id: project_id, package_type: package_type, workflow_name: workflow_name)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# 选择 Git 仓库工作流(仍通过 workflow/list 获取)
|
|
199
|
+
def select_git_workflow_for_project(project_id:, package_type: nil, workflow_name: nil)
|
|
186
200
|
Funlog.instance.fancyinfo_start("正在获取可用工作流...")
|
|
187
201
|
|
|
188
202
|
workflow_result = @pgyer_client.get_project_workflows(project_id: project_id)
|
|
189
|
-
|
|
190
203
|
if workflow_result.nil? || workflow_result['data'].nil?
|
|
191
204
|
raise Informative, "获取工作流列表失败"
|
|
192
205
|
end
|
|
193
206
|
|
|
194
207
|
workflows = workflow_result['data']
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
208
|
+
type_desc = 'Git 管理类型'
|
|
209
|
+
# 严格筛选 manage_type == "git",不降级到其它类型
|
|
210
|
+
display_workflows = workflows.select do |w|
|
|
211
|
+
(w['manageType'] == 'git') || (w['manage_type'] == 'git')
|
|
198
212
|
end
|
|
199
213
|
|
|
200
|
-
puts "package_type #{package_type} manage_type: #{manage_type}"
|
|
201
|
-
|
|
202
|
-
# 2. 根据 manage_type 或 package_type 过滤工作流
|
|
203
|
-
if manage_type == "git"
|
|
204
|
-
# manage_type 为 git 时,只筛选 manage_type == "git" 的工作流,不看 package_type
|
|
205
|
-
# 严格筛选,不包括 manage_type 为空或其他类型的工作流
|
|
206
|
-
filtered_workflows = workflows.select do |w|
|
|
207
|
-
(w['manageType'] == 'git') || (w['manage_type'] == 'git')
|
|
208
|
-
end
|
|
209
|
-
type_desc = 'Git 管理类型'
|
|
210
|
-
# manage_type == "git" 时严格使用筛选结果,不降级
|
|
211
|
-
display_workflows = filtered_workflows
|
|
212
|
-
else
|
|
213
|
-
# 按 package_type 过滤工作流
|
|
214
|
-
filtered_workflows = workflows.select do |w|
|
|
215
|
-
# 如果工作流有 packageType 字段,进行过滤
|
|
216
|
-
if w['packageType']
|
|
217
|
-
case package_type
|
|
218
|
-
when 'ipa'
|
|
219
|
-
w['packageType'] == 'ipa'
|
|
220
|
-
when 'apk'
|
|
221
|
-
w['packageType'] == 'apk'
|
|
222
|
-
when 'zip'
|
|
223
|
-
w['packageType'] == 'zip'
|
|
224
|
-
when 'mac'
|
|
225
|
-
w['packageType'] == 'mac'
|
|
226
|
-
when 'exe'
|
|
227
|
-
w['packageType'] == 'exe'
|
|
228
|
-
else
|
|
229
|
-
true
|
|
230
|
-
end
|
|
231
|
-
else
|
|
232
|
-
# 如果没有 packageType 字段,显示所有工作流
|
|
233
|
-
true
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
type_desc = case package_type
|
|
238
|
-
when 'ipa' then 'iOS IPA'
|
|
239
|
-
when 'apk' then 'Android APK'
|
|
240
|
-
when 'zip' then 'WebGL'
|
|
241
|
-
when 'mac' then 'macOS App'
|
|
242
|
-
when 'exe' then 'Windows EXE'
|
|
243
|
-
else package_type
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# 非 git 模式:过滤结果即为候选。若为空则交由下方“没有找到匹配工作流”报错,
|
|
247
|
-
# 不再回退到全部工作流——否则会把其它类型的工作流误当作请求类型(写错 workflow_key)
|
|
248
|
-
display_workflows = filtered_workflows
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# 过滤后再报数:只报总数会让人以为剩下的工作流被漏掉了,
|
|
252
|
-
# 实际是按 manage_type / package_type 筛掉的,两个数字一起给出才不歧义
|
|
253
214
|
Funlog.instance.fancyinfo_success("获取工作流列表成功,共 #{workflows.size} 个工作流,其中 #{type_desc} #{display_workflows.size} 个")
|
|
254
215
|
|
|
255
|
-
#
|
|
256
|
-
# 只接受完整精确匹配,不做子串模糊匹配,避免 "Production" 误命中 "Legacy Production Upload"。
|
|
216
|
+
# 指定 workflow_name 时只接受完整精确匹配(忽略大小写与首尾空格),不做子串模糊匹配
|
|
257
217
|
if workflow_name && !workflow_name.to_s.strip.empty? && !display_workflows.empty?
|
|
258
218
|
key_name = workflow_name.to_s.strip.downcase
|
|
259
219
|
name_matched = display_workflows.select do |w|
|
|
@@ -266,44 +226,23 @@ module Pindo
|
|
|
266
226
|
display_workflows = name_matched
|
|
267
227
|
end
|
|
268
228
|
|
|
269
|
-
# 3. 根据匹配数量决定选择方式
|
|
270
|
-
workflow = nil
|
|
271
|
-
|
|
272
229
|
if display_workflows.empty?
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
"没有找到匹配 #{type_desc} 的工作流,请在 JPS 后台创建 Git 类型工作流(项目ID: #{project_id})"
|
|
276
|
-
else
|
|
277
|
-
"没有找到匹配 #{type_desc} 的工作流,请在 JPS 后台配置(项目ID: #{project_id})"
|
|
278
|
-
end
|
|
279
|
-
raise Informative, error_msg
|
|
280
|
-
|
|
281
|
-
elsif display_workflows.size == 1
|
|
282
|
-
# 情况2: 只有1个匹配的工作流,自动选择
|
|
283
|
-
workflow = display_workflows.first
|
|
284
|
-
workflow_name = workflow['tabName'] || workflow['tab_name']
|
|
285
|
-
Funlog.instance.fancyinfo_success("自动选择唯一匹配的工作流: #{workflow_name}")
|
|
230
|
+
raise Informative, "没有找到匹配 #{type_desc} 的工作流,请在 JPS 后台创建 Git 类型工作流(项目ID: #{project_id})"
|
|
231
|
+
end
|
|
286
232
|
|
|
233
|
+
workflow = if display_workflows.size == 1
|
|
234
|
+
Funlog.instance.fancyinfo_success("自动选择唯一匹配的工作流: #{display_workflows.first['tabName'] || display_workflows.first['tab_name']}")
|
|
235
|
+
display_workflows.first
|
|
287
236
|
else
|
|
288
|
-
|
|
289
|
-
cli = HighLine.new
|
|
290
|
-
workflow_choices = display_workflows.map do |w|
|
|
237
|
+
choices = display_workflows.map do |w|
|
|
291
238
|
"#{w['tabName'] || w['tab_name']} (ID: #{w['id'] || w['workflow_id']})"
|
|
292
239
|
end
|
|
293
|
-
|
|
294
|
-
selected = cli.choose do |menu|
|
|
295
|
-
menu.prompt = "请选择 #{type_desc} 工作流:"
|
|
296
|
-
menu.choices(*workflow_choices)
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# 提取选择的工作流
|
|
300
|
-
selected_index = workflow_choices.index(selected)
|
|
301
|
-
workflow = display_workflows[selected_index]
|
|
240
|
+
choose_from_menu(prompt: "请选择 #{type_desc} 工作流:", choices: choices, items: display_workflows)
|
|
302
241
|
end
|
|
303
242
|
|
|
304
|
-
# 4. 返回标准化的工作流信息
|
|
305
243
|
{
|
|
306
244
|
workflow_id: workflow['id'] || workflow['workflow_id'],
|
|
245
|
+
package_id: "",
|
|
307
246
|
tab_name: workflow['tabName'] || workflow['tab_name'],
|
|
308
247
|
package_name: workflow['packageName'] || workflow['package_name'] || workflow['tabName'] || workflow['tab_name'],
|
|
309
248
|
package_type: package_type,
|
|
@@ -311,96 +250,73 @@ module Pindo
|
|
|
311
250
|
}
|
|
312
251
|
end
|
|
313
252
|
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
# 2. tab_name 必须包含 "提交包"
|
|
318
|
-
#
|
|
319
|
-
# @param project_id [String] 项目ID
|
|
320
|
-
# @param package_type [String] 包类型(ipa/apk/zip/app)
|
|
321
|
-
# @return [Hash] 选择的工作流信息
|
|
322
|
-
def select_workflow_for_adhoc(project_id:, package_type:)
|
|
323
|
-
# 1. 从 JPS API 获取可用工作流列表
|
|
324
|
-
workflow_result = @pgyer_client.get_project_workflows(project_id: project_id)
|
|
253
|
+
# 选择安装包工具(来自 project/detail 的 packageTools)
|
|
254
|
+
def select_package_tool_for_project(project_id:, package_type:, workflow_name: nil)
|
|
255
|
+
Funlog.instance.fancyinfo_start("正在获取项目工具列表...")
|
|
325
256
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
end
|
|
257
|
+
tools = get_project_package_tools(project_id: project_id, package_type: package_type)
|
|
258
|
+
type_desc = package_type_desc(package_type)
|
|
329
259
|
|
|
330
|
-
|
|
260
|
+
Funlog.instance.fancyinfo_success("获取工具列表成功,#{type_desc} 工具共 #{tools.size} 个")
|
|
331
261
|
|
|
332
|
-
if
|
|
333
|
-
|
|
262
|
+
if workflow_name && !workflow_name.to_s.strip.empty? && !tools.empty?
|
|
263
|
+
tools = match_package_tools_by_name(tools: tools, name: workflow_name, type_desc: type_desc)
|
|
334
264
|
end
|
|
335
265
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if w['packageType']
|
|
339
|
-
case package_type
|
|
340
|
-
when 'ipa'
|
|
341
|
-
w['packageType'] == 'ipa'
|
|
342
|
-
when 'apk'
|
|
343
|
-
w['packageType'] == 'apk'
|
|
344
|
-
when 'zip'
|
|
345
|
-
w['packageType'] == 'zip'
|
|
346
|
-
when 'mac'
|
|
347
|
-
w['packageType'] == 'mac'
|
|
348
|
-
else
|
|
349
|
-
true
|
|
350
|
-
end
|
|
351
|
-
else
|
|
352
|
-
# 如果没有 packageType 字段,保留(后续通过名称筛选)
|
|
353
|
-
true
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
# 3. 第二层过滤:tab_name 必须包含 "提交包"
|
|
358
|
-
adhoc_workflows = filtered_by_type.select do |w|
|
|
359
|
-
tab_name = w['tabName'] || w['tab_name'] || ''
|
|
360
|
-
tab_name.include?('提交包')
|
|
266
|
+
if tools.empty?
|
|
267
|
+
raise Informative, "项目中没有可用的 #{type_desc} 工具,请到 JPS 项目的「工具」页新建(项目ID: #{project_id})"
|
|
361
268
|
end
|
|
362
269
|
|
|
363
|
-
if
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
270
|
+
tool = if tools.size == 1
|
|
271
|
+
Funlog.instance.fancyinfo_success("自动选择唯一匹配的工具: #{package_tool_display_name(tools.first)}")
|
|
272
|
+
tools.first
|
|
273
|
+
else
|
|
274
|
+
choices = tools.map { |t| "#{package_tool_display_name(t)} (版本数: #{t['packageCount'] || 0})" }
|
|
275
|
+
choose_from_menu(prompt: "请选择 #{type_desc} 工具:", choices: choices, items: tools)
|
|
367
276
|
end
|
|
368
277
|
|
|
369
|
-
|
|
370
|
-
|
|
278
|
+
package_tool_to_workflow_info(tool, package_type)
|
|
279
|
+
end
|
|
371
280
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
281
|
+
# 按名称精确匹配工具(忽略大小写与首尾空格):显示名 name(subtitle)、subtitle(原 tab 名)、name 任一相等
|
|
282
|
+
def match_package_tools_by_name(tools:, name:, type_desc:)
|
|
283
|
+
key = name.to_s.strip.downcase
|
|
284
|
+
matched = tools.select do |t|
|
|
285
|
+
[package_tool_display_name(t), t["subtitle"], t["name"]].any? { |v| v.to_s.strip.downcase == key }
|
|
286
|
+
end
|
|
287
|
+
if matched.empty?
|
|
288
|
+
available = tools.map { |t| package_tool_display_name(t) }.join('、')
|
|
289
|
+
raise Informative, "未找到名称精确匹配 \"#{name}\" 的 #{type_desc} 工具,可选: #{available}"
|
|
290
|
+
end
|
|
291
|
+
matched
|
|
292
|
+
end
|
|
379
293
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
294
|
+
# AdHoc 专用工具选择
|
|
295
|
+
# 筛选条件:packageType 匹配,且副标题(原 tab 名)或名称包含 "提交包"
|
|
296
|
+
#
|
|
297
|
+
# @param project_id [String] 项目ID
|
|
298
|
+
# @param package_type [String] 包类型(ipa/apk/zip/mac)
|
|
299
|
+
# @return [Hash] 选择的工具信息
|
|
300
|
+
def select_workflow_for_adhoc(project_id:, package_type:)
|
|
301
|
+
tools = get_project_package_tools(project_id: project_id, package_type: package_type)
|
|
302
|
+
adhoc_tools = tools.select do |t|
|
|
303
|
+
t["subtitle"].to_s.include?('提交包') || t["name"].to_s.include?('提交包')
|
|
304
|
+
end
|
|
385
305
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
306
|
+
if adhoc_tools.empty?
|
|
307
|
+
raise Informative, "未找到符合条件的 AdHoc 工具\n" \
|
|
308
|
+
"要求:packageType=#{package_type} 且副标题包含 '提交包'\n" \
|
|
309
|
+
"请到 JPS 项目的「工具」页检查"
|
|
310
|
+
end
|
|
390
311
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
312
|
+
tool = if adhoc_tools.size == 1
|
|
313
|
+
adhoc_tools.first
|
|
314
|
+
else
|
|
315
|
+
choices = adhoc_tools.map { |t| package_tool_display_name(t) }
|
|
316
|
+
choose_from_menu(prompt: "请选择 AdHoc 工具:", choices: choices, items: adhoc_tools)
|
|
394
317
|
end
|
|
395
318
|
|
|
396
|
-
|
|
397
|
-
{
|
|
398
|
-
workflow_id: workflow['id'] || workflow['workflow_id'],
|
|
399
|
-
tab_name: workflow['tabName'] || workflow['tab_name'],
|
|
400
|
-
package_type: workflow['packageType'] || package_type,
|
|
401
|
-
package_name: workflow['packageName'] || workflow['package_name'] || '',
|
|
402
|
-
manage_type: workflow['manageType'] || workflow['manage_type'] || ''
|
|
403
|
-
}
|
|
319
|
+
package_tool_to_workflow_info(tool, package_type)
|
|
404
320
|
end
|
|
405
321
|
|
|
406
322
|
# 获取 AdHoc 上传信息
|
|
@@ -463,19 +379,19 @@ module Pindo
|
|
|
463
379
|
raise Informative, "未找到项目: #{upload_proj_name},请检查项目名称是否正确"
|
|
464
380
|
end
|
|
465
381
|
|
|
466
|
-
# 4. 获取 AdHoc
|
|
382
|
+
# 4. 获取 AdHoc 工具(调用专用函数)
|
|
467
383
|
workflow_info = select_workflow_for_adhoc(
|
|
468
384
|
project_id: app_info_obj["id"],
|
|
469
385
|
package_type: package_type
|
|
470
386
|
)
|
|
471
387
|
|
|
472
388
|
unless workflow_info
|
|
473
|
-
raise Informative, "未能获取 AdHoc
|
|
389
|
+
raise Informative, "未能获取 AdHoc 工具信息"
|
|
474
390
|
end
|
|
475
391
|
|
|
476
|
-
# 5.
|
|
392
|
+
# 5. 输出最终选择的项目和工具
|
|
477
393
|
puts "\n✓ 项目: #{app_info_obj['projectName']}"
|
|
478
|
-
puts "✓
|
|
394
|
+
puts "✓ 工具: #{workflow_info[:tab_name]}"
|
|
479
395
|
|
|
480
396
|
# 6. 保存项目名称到实例变量
|
|
481
397
|
@proj_name = upload_proj_name
|
|
@@ -532,9 +448,10 @@ module Pindo
|
|
|
532
448
|
puts "[PINDO_DEBUG] 配置加载结果: valid=#{config_result[:valid]}" if ENV['PINDO_DEBUG']
|
|
533
449
|
|
|
534
450
|
if config_result[:valid]
|
|
451
|
+
is_git_config = manage_type.to_s == "git"
|
|
535
452
|
puts "\n使用 JPSBuildConfig.json 配置:"
|
|
536
453
|
puts " 项目: #{config_result[:project_name]}"
|
|
537
|
-
puts "
|
|
454
|
+
puts " #{is_git_config ? '工作流' : '工具'}: #{config_result[:workflow_info][:tab_name]}"
|
|
538
455
|
|
|
539
456
|
if ENV['PINDO_DEBUG']
|
|
540
457
|
puts "[PINDO_DEBUG] 配置详细信息:"
|
|
@@ -547,6 +464,27 @@ module Pindo
|
|
|
547
464
|
raise Informative, "JPS 未登录或登录已失效,请执行 pindo jps login 重新登录"
|
|
548
465
|
end
|
|
549
466
|
|
|
467
|
+
# 安装包配置只有 workflow_id(旧格式)时,换算成 package_id 并写回;
|
|
468
|
+
# 找不到对应工具则落到下方交互重选流程
|
|
469
|
+
unless is_git_config
|
|
470
|
+
migrated = migrate_legacy_workflow_info!(
|
|
471
|
+
config_path: config_path,
|
|
472
|
+
project_id: config_result[:project_id],
|
|
473
|
+
package_type: package_type,
|
|
474
|
+
workflow_info: config_result[:workflow_info]
|
|
475
|
+
)
|
|
476
|
+
if migrated.nil?
|
|
477
|
+
puts "旧工作流(ID: #{config_result[:workflow_info][:workflow_id]})未找到对应工具,将重新选择"
|
|
478
|
+
config_result[:valid] = false
|
|
479
|
+
# 项目已知,只需重选工具:走下方 --proj 非交互项目解析分支
|
|
480
|
+
proj_name = config_result[:project_name]
|
|
481
|
+
else
|
|
482
|
+
config_result[:workflow_info] = migrated
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
if config_result[:valid]
|
|
550
488
|
puts "[PINDO_DEBUG] 正在查找项目: #{config_result[:project_name]}" if ENV['PINDO_DEBUG']
|
|
551
489
|
lookup_bundle_id = resolve_lookup_bundle_id(
|
|
552
490
|
bundle_name: bundle_name,
|
|
@@ -740,9 +678,11 @@ module Pindo
|
|
|
740
678
|
|
|
741
679
|
def start_upload(app_info_obj:nil, ipa_file_upload:nil, description:nil, workflow_info:nil)
|
|
742
680
|
|
|
743
|
-
#
|
|
744
|
-
|
|
745
|
-
|
|
681
|
+
# 验证工具信息必传:package_id 优先,旧配置换算不到时才用 workflow_id 兼容
|
|
682
|
+
package_id = workflow_info && workflow_info[:package_id].to_s.strip
|
|
683
|
+
workflow_id = workflow_info && workflow_info[:workflow_id]
|
|
684
|
+
if workflow_info.nil? || (package_id.to_s.empty? && (workflow_id.nil? || workflow_id.to_s.empty?))
|
|
685
|
+
raise Informative, "缺少工具信息(package_id),请确保调用 prepare_upload 时传入了 package_type"
|
|
746
686
|
end
|
|
747
687
|
|
|
748
688
|
if !ipa_file_upload.nil? &&
|
|
@@ -901,13 +841,13 @@ module Pindo
|
|
|
901
841
|
|
|
902
842
|
puts attachFileUrls
|
|
903
843
|
|
|
904
|
-
|
|
905
|
-
upload_params = {
|
|
844
|
+
upload_params = build_package_upload_params(
|
|
906
845
|
description: description,
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
846
|
+
package_url: upload_res,
|
|
847
|
+
attach_file_urls: attachFileUrls,
|
|
848
|
+
package_id: package_id,
|
|
849
|
+
workflow_id: workflow_id
|
|
850
|
+
)
|
|
911
851
|
|
|
912
852
|
# 提交包信息到服务器
|
|
913
853
|
Funlog.instance.fancyinfo_start("正在提交包信息到服务器...")
|
|
@@ -926,7 +866,9 @@ module Pindo
|
|
|
926
866
|
if result_data && result_data["data"] && result_data["data"]["id"]
|
|
927
867
|
Funlog.instance.fancyinfo_success("提交包信息完成!")
|
|
928
868
|
else
|
|
929
|
-
error_msg = result_data&.dig('msg') || '未知错误'
|
|
869
|
+
error_msg = result_data&.dig('msg') || result_data&.dig('meta', 'message') || '未知错误'
|
|
870
|
+
hint = package_upload_error_hint(result_data)
|
|
871
|
+
error_msg = "#{error_msg}(#{hint})" if hint
|
|
930
872
|
Funlog.instance.fancyinfo_error("提交包信息失败: #{error_msg}")
|
|
931
873
|
raise Informative, "上传失败:未获取到有效的返回数据"
|
|
932
874
|
end
|
|
@@ -945,6 +887,37 @@ module Pindo
|
|
|
945
887
|
|
|
946
888
|
end
|
|
947
889
|
|
|
890
|
+
# 项目包上传错误码 → 处理提示
|
|
891
|
+
PACKAGE_UPLOAD_ERROR_HINTS = {
|
|
892
|
+
97931 => "未传 packageId,请重新执行 pindo jps config 绑定工具",
|
|
893
|
+
97992 => "packageId 与 workflowId 冲突,只应传 packageId",
|
|
894
|
+
97953 => "该工具不是安装包类型,不能走项目包上传"
|
|
895
|
+
}.freeze
|
|
896
|
+
|
|
897
|
+
# 组装项目包上传参数:packageId 与 workflowId 只传一个(同时传且不匹配会报 97992)
|
|
898
|
+
def build_package_upload_params(description:, package_url:, attach_file_urls:, package_id:, workflow_id:)
|
|
899
|
+
params = {
|
|
900
|
+
description: description,
|
|
901
|
+
packageUrl: package_url,
|
|
902
|
+
attachFileUrls: attach_file_urls
|
|
903
|
+
}
|
|
904
|
+
if !package_id.to_s.strip.empty?
|
|
905
|
+
params.merge(packageId: package_id.to_s.strip)
|
|
906
|
+
else
|
|
907
|
+
# 换算不到工具的旧配置:按 workflowId 兼容上传(JPS 长期兼容)
|
|
908
|
+
Funlog.instance.fancyinfo_warning("未绑定工具,按旧工作流ID兼容上传: #{workflow_id}")
|
|
909
|
+
params.merge(workflowId: workflow_id)
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# 从上传返回体中取错误码提示(码可能在 code 或 meta.code)
|
|
914
|
+
def package_upload_error_hint(result_data)
|
|
915
|
+
return nil unless result_data.is_a?(Hash)
|
|
916
|
+
code = result_data['code'] || result_data.dig('meta', 'code')
|
|
917
|
+
return nil if code.nil?
|
|
918
|
+
PACKAGE_UPLOAD_ERROR_HINTS[code.to_i]
|
|
919
|
+
end
|
|
920
|
+
|
|
948
921
|
def package_windows_exe_for_upload(exe_file_upload)
|
|
949
922
|
windows_exe_path = File.expand_path(exe_file_upload)
|
|
950
923
|
windows_build_dir = File.dirname(windows_exe_path)
|
|
@@ -1435,6 +1408,67 @@ module Pindo
|
|
|
1435
1408
|
result&.dig("data")
|
|
1436
1409
|
end
|
|
1437
1410
|
|
|
1411
|
+
# 取项目下可上传的安装包工具(project/detail 的 packageTools)
|
|
1412
|
+
# 按 package_type 筛选,去掉已下架(lifecycle=deprecated)和缺 packageId 的,保持接口返回的排序
|
|
1413
|
+
#
|
|
1414
|
+
# @param project_id [String] 项目ID
|
|
1415
|
+
# @param package_type [String] 包类型(ipa/apk/zip/mac/exe)
|
|
1416
|
+
# @return [Array<Hash>] packageTools 原始项
|
|
1417
|
+
def get_project_package_tools(project_id:, package_type:)
|
|
1418
|
+
detail = get_project_detail(project_id: project_id)
|
|
1419
|
+
tools = detail.is_a?(Hash) ? detail["packageTools"] : nil
|
|
1420
|
+
return [] unless tools.is_a?(Array)
|
|
1421
|
+
|
|
1422
|
+
tools.select do |t|
|
|
1423
|
+
t.is_a?(Hash) &&
|
|
1424
|
+
t["packageType"].to_s.downcase == package_type.to_s.downcase &&
|
|
1425
|
+
t["lifecycle"].to_s != "deprecated" &&
|
|
1426
|
+
!t["packageId"].to_s.strip.empty?
|
|
1427
|
+
end
|
|
1428
|
+
end
|
|
1429
|
+
|
|
1430
|
+
# 工具显示名:name(subtitle),与 GoodUnityBuild 写入的 tab_name 保持一致
|
|
1431
|
+
def package_tool_display_name(tool)
|
|
1432
|
+
name = tool["name"].to_s
|
|
1433
|
+
subtitle = tool["subtitle"].to_s
|
|
1434
|
+
subtitle.strip.empty? ? name : "#{name}(#{subtitle})"
|
|
1435
|
+
end
|
|
1436
|
+
|
|
1437
|
+
# 工具 → workflow_info(沿用 symbol key 结构,调用方无需改动)
|
|
1438
|
+
def package_tool_to_workflow_info(tool, package_type)
|
|
1439
|
+
{
|
|
1440
|
+
package_id: tool["packageId"].to_s,
|
|
1441
|
+
workflow_id: tool["workflowId"], # 老工具才有;新工具为 nil,仅用于兼容
|
|
1442
|
+
tab_name: package_tool_display_name(tool),
|
|
1443
|
+
package_name: tool["name"],
|
|
1444
|
+
package_type: package_type,
|
|
1445
|
+
manage_type: "package",
|
|
1446
|
+
subtitle: tool["subtitle"],
|
|
1447
|
+
bundle_id: tool["bundleId"]
|
|
1448
|
+
}
|
|
1449
|
+
end
|
|
1450
|
+
|
|
1451
|
+
# 旧配置(只有 workflow_id、没有 package_id)换算成工具并写回配置文件
|
|
1452
|
+
# 已有 package_id → 原样返回、不发请求;只有 workflow_id → 在 packageTools 里按 workflowId 查找
|
|
1453
|
+
#
|
|
1454
|
+
# @return [Hash, nil] 换算后的 workflow_info;找不到对应工具返回 nil
|
|
1455
|
+
def migrate_legacy_workflow_info!(config_path:, project_id:, package_type:, workflow_info:)
|
|
1456
|
+
return workflow_info unless workflow_info[:package_id].to_s.strip.empty?
|
|
1457
|
+
return nil if workflow_info[:workflow_id].nil? || workflow_info[:workflow_id].to_s.empty?
|
|
1458
|
+
|
|
1459
|
+
tool = get_project_package_tools(project_id: project_id, package_type: package_type)
|
|
1460
|
+
.find { |t| t["workflowId"].to_s == workflow_info[:workflow_id].to_s }
|
|
1461
|
+
return nil if tool.nil?
|
|
1462
|
+
|
|
1463
|
+
new_info = package_tool_to_workflow_info(tool, package_type)
|
|
1464
|
+
config = JSON.parse(File.read(config_path))
|
|
1465
|
+
key = jps_workflow_key(package_type: package_type)
|
|
1466
|
+
config[key] = build_workflow_data(workflow_info: new_info, package_type: package_type)
|
|
1467
|
+
atomic_write(config_path, JSON.pretty_generate(config))
|
|
1468
|
+
puts "已将旧工作流(ID: #{workflow_info[:workflow_id]})换算为工具: #{new_info[:tab_name]}"
|
|
1469
|
+
new_info
|
|
1470
|
+
end
|
|
1471
|
+
|
|
1438
1472
|
PROJECT_PAGE_SIZE = 100
|
|
1439
1473
|
# 安全上限:最多翻 200 页(约 2 万项目),防止服务端异常(如忽略 pageNo 恒返满页)导致死循环
|
|
1440
1474
|
MAX_PROJECT_PAGES = 200
|
|
@@ -2210,7 +2244,7 @@ module Pindo
|
|
|
2210
2244
|
end
|
|
2211
2245
|
|
|
2212
2246
|
atomic_write(config_file, JSON.pretty_generate(config) + "\n")
|
|
2213
|
-
puts "已写入 JPSBuildConfig.json(#{entries.size}
|
|
2247
|
+
puts "已写入 JPSBuildConfig.json(#{entries.size} 项绑定)"
|
|
2214
2248
|
config
|
|
2215
2249
|
end
|
|
2216
2250
|
|
|
@@ -2252,10 +2286,34 @@ module Pindo
|
|
|
2252
2286
|
end
|
|
2253
2287
|
end
|
|
2254
2288
|
|
|
2255
|
-
#
|
|
2289
|
+
# 包类型 → 显示名
|
|
2290
|
+
def package_type_desc(package_type)
|
|
2291
|
+
case package_type
|
|
2292
|
+
when 'ipa' then 'iOS IPA'
|
|
2293
|
+
when 'apk' then 'Android APK'
|
|
2294
|
+
when 'zip' then 'WebGL'
|
|
2295
|
+
when 'mac' then 'macOS App'
|
|
2296
|
+
when 'exe' then 'Windows EXE'
|
|
2297
|
+
else package_type.to_s
|
|
2298
|
+
end
|
|
2299
|
+
end
|
|
2300
|
+
|
|
2301
|
+
# HighLine 菜单选择,返回 items 中与所选 choice 同下标的元素
|
|
2302
|
+
def choose_from_menu(prompt:, choices:, items:)
|
|
2303
|
+
cli = HighLine.new
|
|
2304
|
+
selected = cli.choose do |menu|
|
|
2305
|
+
menu.prompt = prompt
|
|
2306
|
+
menu.choices(*choices)
|
|
2307
|
+
end
|
|
2308
|
+
items[choices.index(selected)]
|
|
2309
|
+
end
|
|
2310
|
+
|
|
2311
|
+
# 构建单个工作流/工具的写入结构(与 GoodUnityBuild 共用的 JPSBuildConfig.json 格式)
|
|
2312
|
+
# 安装包工具:package_id 为上传依据,workflow_id 仅兼容(新工具为 nil);Git 工作流:package_id 为空串
|
|
2256
2313
|
def build_workflow_data(workflow_info:, package_type:)
|
|
2257
2314
|
{
|
|
2258
2315
|
"workflow_id" => workflow_info[:workflow_id],
|
|
2316
|
+
"package_id" => workflow_info[:package_id].to_s,
|
|
2259
2317
|
"tab_name" => workflow_info[:tab_name],
|
|
2260
2318
|
"package_type" => package_type,
|
|
2261
2319
|
"package_name" => workflow_info[:package_name],
|
|
@@ -2268,20 +2326,24 @@ module Pindo
|
|
|
2268
2326
|
# @param workflow [Hash] 工作流配置对象
|
|
2269
2327
|
# @return [Boolean] 是否有效
|
|
2270
2328
|
def workflow_valid?(workflow)
|
|
2271
|
-
return false
|
|
2329
|
+
return false unless workflow.is_a?(Hash)
|
|
2272
2330
|
|
|
2273
|
-
# 检查 workflow_id
|
|
2274
|
-
return false if workflow['workflow_id'].nil? || workflow['workflow_id'].to_s.empty?
|
|
2275
|
-
|
|
2276
|
-
# git 类型的 workflow 不需要 package_name(管理 git commit,不是包文件)
|
|
2277
|
-
# 其他类型的 workflow 需要 package_name
|
|
2278
2331
|
is_git_workflow = workflow['manage_type'] == 'git'
|
|
2279
|
-
|
|
2280
|
-
|
|
2332
|
+
has_workflow_id = !workflow['workflow_id'].nil? && !workflow['workflow_id'].to_s.empty?
|
|
2333
|
+
has_package_id = !workflow['package_id'].to_s.strip.empty?
|
|
2334
|
+
|
|
2335
|
+
if is_git_workflow
|
|
2336
|
+
# Git 工作流必须有 workflow_id,不需要 package_name
|
|
2337
|
+
return false unless has_workflow_id
|
|
2338
|
+
else
|
|
2339
|
+
# 安装包工具:package_id / workflow_id 有一个即可(只有 workflow_id 的旧配置在 prepare_upload 中换算)
|
|
2340
|
+
return false unless has_package_id || has_workflow_id
|
|
2341
|
+
return false if workflow['package_name'].nil? || workflow['package_name'].to_s.empty?
|
|
2281
2342
|
end
|
|
2282
2343
|
|
|
2283
2344
|
if ENV['PINDO_DEBUG']
|
|
2284
2345
|
puts "[PINDO_DEBUG] workflow_valid? 检查通过:"
|
|
2346
|
+
puts " package_id: #{workflow['package_id']}"
|
|
2285
2347
|
puts " workflow_id: #{workflow['workflow_id']}"
|
|
2286
2348
|
puts " manage_type: #{workflow['manage_type']}"
|
|
2287
2349
|
puts " is_git_workflow: #{is_git_workflow}"
|
|
@@ -2395,14 +2457,18 @@ module Pindo
|
|
|
2395
2457
|
end
|
|
2396
2458
|
|
|
2397
2459
|
# 8. Project ID 不变,检查工作流是否需要更新
|
|
2460
|
+
# 安装包按 package_id 比较(新工具 workflow_id 都是 nil);Git 按 workflow_id 比较
|
|
2398
2461
|
existing_workflow = existing_config[workflow_key]
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2462
|
+
changed = if manage_type.to_s == 'git'
|
|
2463
|
+
existing_workflow_id = existing_workflow.is_a?(Hash) ? existing_workflow['workflow_id'] : nil
|
|
2464
|
+
existing_workflow_id.to_s.empty? || existing_workflow_id.to_s != workflow_info[:workflow_id].to_s
|
|
2465
|
+
else
|
|
2466
|
+
existing_package_id = existing_workflow.is_a?(Hash) ? existing_workflow['package_id'] : nil
|
|
2467
|
+
existing_package_id.to_s.empty? || existing_package_id.to_s != workflow_info[:package_id].to_s
|
|
2468
|
+
end
|
|
2404
2469
|
|
|
2405
|
-
|
|
2470
|
+
if changed
|
|
2471
|
+
# 工作流/工具变更 → 更新对应的 workflow 和项目信息
|
|
2406
2472
|
puts "#{workflow_key} 变更,更新工作流配置"
|
|
2407
2473
|
existing_config[workflow_key] = new_workflow_data
|
|
2408
2474
|
existing_config["bundle_group_name"] = new_bundle_group_name
|
|
@@ -2412,7 +2478,7 @@ module Pindo
|
|
|
2412
2478
|
return
|
|
2413
2479
|
end
|
|
2414
2480
|
|
|
2415
|
-
# 9. Project ID
|
|
2481
|
+
# 9. Project ID 和工作流/工具都不变 → 不修改
|
|
2416
2482
|
puts "配置未变更,跳过保存" if ENV['PINDO_DEBUG']
|
|
2417
2483
|
end
|
|
2418
2484
|
end
|
|
@@ -142,7 +142,8 @@ module Pindo
|
|
|
142
142
|
project_dir: @project_path,
|
|
143
143
|
workflow_packname: workflow_packname,
|
|
144
144
|
project_id: project_id,
|
|
145
|
-
bundle_id: @bundle_id
|
|
145
|
+
bundle_id: @bundle_id,
|
|
146
|
+
bundle_id_prefix: Pindo::XcodeBuildConfig.bundle_id_prefix_from(config_bundle_id)
|
|
146
147
|
)
|
|
147
148
|
|
|
148
149
|
# 下载并替换 Icon,优先使用 packageProjectIcon,为空则回退到 projectIcon
|
|
@@ -159,7 +160,7 @@ module Pindo
|
|
|
159
160
|
)
|
|
160
161
|
|
|
161
162
|
unless icon_replace_success
|
|
162
|
-
raise Informative, "Icon
|
|
163
|
+
raise Informative, "Icon 下载或替换失败,具体原因见上方日志(Icon URL: #{icon_url})"
|
|
163
164
|
end
|
|
164
165
|
else
|
|
165
166
|
Funlog.warning("未配置 Icon URL,跳过 Icon 替换")
|
|
@@ -196,8 +197,8 @@ module Pindo
|
|
|
196
197
|
commit_short = commit_hash ? commit_hash[0..7] : "unknown"
|
|
197
198
|
Funlog.instance.fancyinfo_success("自己计算版本号: #{build_version}, Build: #{build_number}, Commit: #{commit_short}")
|
|
198
199
|
else
|
|
199
|
-
# 不是 Git 仓库且没有从 GitCommitTask
|
|
200
|
-
|
|
200
|
+
# 不是 Git 仓库且没有从 GitCommitTask 获取到版本号:沿用工程版本号,但补齐为 JPS 要求的 major.minor.patch
|
|
201
|
+
normalize_project_version
|
|
201
202
|
return
|
|
202
203
|
end
|
|
203
204
|
end
|
|
@@ -210,6 +211,35 @@ module Pindo
|
|
|
210
211
|
)
|
|
211
212
|
end
|
|
212
213
|
|
|
214
|
+
# 非 Git 仓库时沿用工程原有版本号,仅补齐格式(如 Xcode 模板默认的 1.0 => 1.0.0),否则 JPS 拒收
|
|
215
|
+
def normalize_project_version
|
|
216
|
+
version, build_number = Pindo::XcodeBuildConfig.current_app_version(project_dir: @project_path)
|
|
217
|
+
normalized = Pindo::XcodeBuildConfig.normalize_marketing_version(version)
|
|
218
|
+
|
|
219
|
+
if normalized.nil?
|
|
220
|
+
Funlog.warning("非Git仓库,保持工程原有版本号 #{version.inspect};JPS 要求 major.minor.patch 格式,上传可能失败")
|
|
221
|
+
return
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if normalized == version.to_s.strip
|
|
225
|
+
Funlog.warning("非Git仓库且未从 GitCommitTask 获取到版本号,保持工程原有版本号 #{normalized}")
|
|
226
|
+
return
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
Funlog.warning("非Git仓库,工程版本号 #{version} 补齐为 #{normalized}")
|
|
230
|
+
Pindo::XcodeBuildConfig.update_ios_project_version(
|
|
231
|
+
project_dir: @project_path,
|
|
232
|
+
version: normalized,
|
|
233
|
+
build_number: build_number.to_s.strip.empty? ? '1' : build_number
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# 当前环境配置仓库中的主 Bundle ID(如 workhard 为 com.eyesofstorms.*),与证书使用同一来源
|
|
238
|
+
def config_bundle_id
|
|
239
|
+
require 'pindo/config/ios_config_parser'
|
|
240
|
+
Pindo::IosConfigParser.instance.bundle_id
|
|
241
|
+
end
|
|
242
|
+
|
|
213
243
|
# 检查配置
|
|
214
244
|
def pull_config
|
|
215
245
|
# 检查 IosConfigParser 单例中是否已加载配置
|
|
@@ -7,6 +7,9 @@ module Pindo
|
|
|
7
7
|
class NugetUploadTask < NugetTask
|
|
8
8
|
|
|
9
9
|
DEFAULT_NUGET_BUCKET_NAME = 'nuget-resource'.freeze
|
|
10
|
+
# NuGet 登记项目:JPS「Unity 组件」项目
|
|
11
|
+
# (原「Nuget 组件库」5e6b36f61b614ed88086373ef874616c 已停止接收 NuGet 登记,返回 90006)
|
|
12
|
+
NUGET_PROJECT_ID = '1b9f111539ba4715abb2011fbfe46fd8'.freeze
|
|
10
13
|
|
|
11
14
|
attr_reader :nupkg_file
|
|
12
15
|
|
|
@@ -132,8 +135,8 @@ module Pindo
|
|
|
132
135
|
puts "✅ 登录成功"
|
|
133
136
|
puts
|
|
134
137
|
|
|
135
|
-
# 2. 使用固定的
|
|
136
|
-
nuget_project_id =
|
|
138
|
+
# 2. 使用固定的 NuGet 登记项目(Unity 组件)
|
|
139
|
+
nuget_project_id = NUGET_PROJECT_ID
|
|
137
140
|
puts
|
|
138
141
|
|
|
139
142
|
# 3. 上传文件到存储
|
|
@@ -12,38 +12,69 @@ module Pindo
|
|
|
12
12
|
@project_obj = Xcodeproj::Project.open(proj_fullname)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
DEFAULT_APP_ICON_NAME = "AppIcon".freeze
|
|
16
|
+
APP_ICON_NAME_SETTING = "ASSETCATALOG_COMPILER_APPICON_NAME".freeze
|
|
17
|
+
|
|
18
|
+
# 查找工程的 App 图标集目录(<图标名>.appiconset),找不到返回 nil(不抛异常)
|
|
16
19
|
def find_xcodeproj_icon_path()
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
icon_dir_name = "#{app_icon_name}.appiconset"
|
|
21
|
+
|
|
22
|
+
# 优先在主 target 自己的 asset catalog 中查找
|
|
23
|
+
icon_path = main_target_asset_catalogs
|
|
24
|
+
.map { |catalog| File.join(catalog, icon_dir_name) }
|
|
25
|
+
.find { |path| File.directory?(path) }
|
|
26
|
+
return icon_path if icon_path
|
|
27
|
+
|
|
28
|
+
icon_path_array = Dir.glob(File.join(@project_obj.project_dir, "**", icon_dir_name))
|
|
29
|
+
if icon_path_array.size > 1
|
|
30
|
+
icon_path = icon_path_array.find{ |filename| File.directory?(filename) && filename.include?("Assets.xcassets/#{icon_dir_name}")}
|
|
31
|
+
elsif icon_path_array.size == 1
|
|
32
|
+
icon_path = icon_path_array.first
|
|
33
|
+
end
|
|
19
34
|
|
|
20
|
-
|
|
35
|
+
return nil if icon_path.nil? || icon_path.empty? || !File.exist?(icon_path)
|
|
36
|
+
icon_path
|
|
37
|
+
end
|
|
21
38
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
if icon_path.nil? || icon_path.empty? || !File.exist?(icon_path)
|
|
34
|
-
icon_path_array = Dir.glob(File.join(project_dir, "**", "AppIcon.appiconset"))
|
|
35
|
-
if icon_path_array.size > 1
|
|
36
|
-
icon_path = icon_path_array.find{ |filename| File.directory?(filename) && filename.include?("Assets.xcassets/AppIcon.appiconset")}
|
|
37
|
-
elsif icon_path_array.size == 1
|
|
38
|
-
icon_path = icon_path_array.first
|
|
39
|
-
end
|
|
39
|
+
# 查找 App 图标集目录;工程没有图标集时(如 Xcode 新模板工程),
|
|
40
|
+
# 在主 target 的 asset catalog 中创建,并设置 ASSETCATALOG_COMPILER_APPICON_NAME
|
|
41
|
+
def ensure_app_icon_path()
|
|
42
|
+
icon_path = find_xcodeproj_icon_path()
|
|
43
|
+
return icon_path if icon_path
|
|
44
|
+
|
|
45
|
+
catalog = main_target_asset_catalogs.first
|
|
46
|
+
if catalog.nil?
|
|
47
|
+
raise Informative, "没有找到Xcode icon 目录:主 target 中既没有 #{app_icon_name}.appiconset,也没有可存放图标的 .xcassets"
|
|
40
48
|
end
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
icon_path = File.join(catalog, "#{app_icon_name}.appiconset")
|
|
51
|
+
FileUtils.mkdir_p(icon_path)
|
|
52
|
+
assign_app_icon_name
|
|
53
|
+
puts " ✓ 工程中没有 App 图标集,已创建: #{icon_path}"
|
|
54
|
+
icon_path
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 主 App target 配置的图标名,未配置时为 AppIcon
|
|
58
|
+
def app_icon_name()
|
|
59
|
+
names = main_app_target&.build_configurations&.map { |config| config.build_settings[APP_ICON_NAME_SETTING] } || []
|
|
60
|
+
name = names.compact.map(&:to_s).find { |value| !value.strip.empty? }
|
|
61
|
+
name || DEFAULT_APP_ICON_NAME
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# 主 App target 使用的 asset catalog(含文件夹同步方式管理的资源)
|
|
65
|
+
def main_target_asset_catalogs()
|
|
66
|
+
target = main_app_target
|
|
67
|
+
return [] if target.nil?
|
|
68
|
+
|
|
69
|
+
referenced = target.resources_build_phase.files_references
|
|
70
|
+
.select { |file| file.respond_to?(:real_path) && file.real_path.to_s.end_with?(".xcassets") }
|
|
71
|
+
.map { |file| file.real_path.to_s }
|
|
72
|
+
|
|
73
|
+
synchronized = synchronized_group_paths(target).flat_map do |group_path|
|
|
74
|
+
Dir.glob(File.join(group_path, "**", "*.xcassets")).sort
|
|
44
75
|
end
|
|
45
76
|
|
|
46
|
-
|
|
77
|
+
(referenced + synchronized).uniq.select { |path| File.directory?(path) }
|
|
47
78
|
end
|
|
48
79
|
|
|
49
80
|
# 工程是否包含 AppIcon 目录(macOS 命令行工具等无 icon 工程返回 false)
|
|
@@ -68,7 +99,7 @@ module Pindo
|
|
|
68
99
|
end
|
|
69
100
|
|
|
70
101
|
def install_icon_res(new_icon_dir:nil)
|
|
71
|
-
icon_path =
|
|
102
|
+
icon_path = ensure_app_icon_path()
|
|
72
103
|
begin
|
|
73
104
|
FileUtils.rm_rf(icon_path)
|
|
74
105
|
FileUtils.mkdir_p(icon_path)
|
|
@@ -265,6 +296,36 @@ module Pindo
|
|
|
265
296
|
|
|
266
297
|
end
|
|
267
298
|
|
|
299
|
+
private
|
|
300
|
+
|
|
301
|
+
def main_app_target()
|
|
302
|
+
@project_obj.targets.find do |target|
|
|
303
|
+
target.respond_to?(:product_type) && target.product_type.to_s.include?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application])
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# 文件夹同步(PBXFileSystemSynchronizedRootGroup)方式管理的 target 目录
|
|
308
|
+
def synchronized_group_paths(target)
|
|
309
|
+
return [] unless target.respond_to?(:file_system_synchronized_groups)
|
|
310
|
+
|
|
311
|
+
target.file_system_synchronized_groups
|
|
312
|
+
.map { |group| Xcodeproj::Project::Object::GroupableHelper.real_path(group).to_s }
|
|
313
|
+
.select { |path| File.directory?(path) }
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# 未配置图标名的 target 补上 ASSETCATALOG_COMPILER_APPICON_NAME,使图标集参与编译
|
|
317
|
+
def assign_app_icon_name()
|
|
318
|
+
target = main_app_target
|
|
319
|
+
return if target.nil?
|
|
320
|
+
|
|
321
|
+
name = app_icon_name
|
|
322
|
+
missing = target.build_configurations.select { |config| config.build_settings[APP_ICON_NAME_SETTING].to_s.strip.empty? }
|
|
323
|
+
return if missing.empty?
|
|
324
|
+
|
|
325
|
+
missing.each { |config| config.build_settings[APP_ICON_NAME_SETTING] = name }
|
|
326
|
+
@project_obj.save
|
|
327
|
+
end
|
|
328
|
+
|
|
268
329
|
end
|
|
269
330
|
|
|
270
331
|
end
|
|
@@ -21,6 +21,11 @@ module Pindo
|
|
|
21
21
|
/func\s+application\s*\([^)]*configurationForConnecting\b/
|
|
22
22
|
).freeze
|
|
23
23
|
|
|
24
|
+
# SwiftUI App 生命周期(@main 入口遵循 App 协议)本身基于 UIScene
|
|
25
|
+
SWIFTUI_APP_PATTERN = /^[ \t]*@main\b[^{]*?\b(?:struct|class|enum)\s+\w+\s*:[^{]*\bApp\b/.freeze
|
|
26
|
+
|
|
27
|
+
SCENE_ADOPTION_SOURCE_PATTERN = Regexp.union(SCENE_CONFIGURATION_PATTERN, SWIFTUI_APP_PATTERN).freeze
|
|
28
|
+
|
|
24
29
|
SOURCE_EXTENSIONS = %w[.m .mm .swift].freeze
|
|
25
30
|
|
|
26
31
|
EXCLUDED_DIRECTORY_NAMES = %w[.git Pods build DerivedData].freeze
|
|
@@ -47,7 +52,7 @@ module Pindo
|
|
|
47
52
|
)
|
|
48
53
|
return if scene_manifest_declared?(info_plist_path)
|
|
49
54
|
return if scene_manifest_generated?(release_config)
|
|
50
|
-
return if
|
|
55
|
+
return if sources_adopt_scene_lifecycle?(project_dir)
|
|
51
56
|
|
|
52
57
|
raise Informative, adoption_error_message(sdk_version, info_plist_path)
|
|
53
58
|
end
|
|
@@ -91,13 +96,19 @@ module Pindo
|
|
|
91
96
|
# Xcode 只在 GENERATE_INFOPLIST_FILE = YES 时采用 INFOPLIST_KEY_* 设置
|
|
92
97
|
def scene_manifest_generated?(release_config)
|
|
93
98
|
settings = release_config.build_settings
|
|
94
|
-
settings
|
|
95
|
-
settings
|
|
99
|
+
iphoneos_setting(settings, 'GENERATE_INFOPLIST_FILE').to_s.casecmp?('YES') &&
|
|
100
|
+
iphoneos_setting(settings, SCENE_MANIFEST_GENERATION_SETTING).to_s.casecmp?('YES')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# iOS 真机构建实际生效的值:[sdk=iphoneos*] 条件设置优先于无条件设置(多平台模板只写条件设置)
|
|
104
|
+
def iphoneos_setting(settings, key)
|
|
105
|
+
_name, conditional = settings.find { |name, _value| name.start_with?("#{key}[sdk=iphoneos") }
|
|
106
|
+
conditional.nil? ? settings[key] : conditional
|
|
96
107
|
end
|
|
97
108
|
|
|
98
|
-
def
|
|
109
|
+
def sources_adopt_scene_lifecycle?(project_dir)
|
|
99
110
|
each_source_file(project_dir).any? do |path|
|
|
100
|
-
File.binread(path).match?(
|
|
111
|
+
File.binread(path).match?(SCENE_ADOPTION_SOURCE_PATTERN)
|
|
101
112
|
end
|
|
102
113
|
end
|
|
103
114
|
|
|
@@ -11,6 +11,18 @@ module Pindo
|
|
|
11
11
|
|
|
12
12
|
class XcodeBuildConfig
|
|
13
13
|
|
|
14
|
+
# 未提供任何 Bundle ID 线索时,工作流推导 Bundle ID 使用的前缀
|
|
15
|
+
DEFAULT_BUNDLE_ID_PREFIX = "com.heroneverdie101".freeze
|
|
16
|
+
|
|
17
|
+
# 从 Bundle ID 提取前缀:通配 ID 去掉 ".*",具体 ID 去掉最后一段
|
|
18
|
+
# @param bundle_id [String, nil] 如 "com.example.*" 或 "com.example.app"
|
|
19
|
+
# @return [String, nil] 如 "com.example";无法提取时返回 nil
|
|
20
|
+
def self.bundle_id_prefix_from(bundle_id)
|
|
21
|
+
value = bundle_id.to_s.strip
|
|
22
|
+
prefix = value.end_with?(".*") ? value.chomp(".*") : value.rpartition(".").first
|
|
23
|
+
prefix.empty? || prefix.include?("*") ? nil : prefix
|
|
24
|
+
end
|
|
25
|
+
|
|
14
26
|
# 使用package_name一次性更新Display Name、Bundle ID和URL Schemes(基于package_name)
|
|
15
27
|
# 优化版本:只读写一次plist文件,提高性能
|
|
16
28
|
# 注意:此函数只添加基于package_name的URL Scheme,基于实际Bundle ID的URL Scheme
|
|
@@ -19,8 +31,9 @@ module Pindo
|
|
|
19
31
|
# @param workflow_packname [String] 工作流的名称(如:"Test Demo")
|
|
20
32
|
# @param project_id [String] JPS项目ID(可选,用于添加快捷操作)
|
|
21
33
|
# @param bundle_id [String] 显式指定的 Bundle ID(可选,来自 --bundleid)
|
|
34
|
+
# @param bundle_id_prefix [String] 工作流推导 Bundle ID 的前缀(可选,如 "com.example")
|
|
22
35
|
# @return [Boolean] 是否成功更新
|
|
23
|
-
def self.update_project_with_workflow(project_dir: nil, workflow_packname: nil, project_id: nil, bundle_id: nil)
|
|
36
|
+
def self.update_project_with_workflow(project_dir: nil, workflow_packname: nil, project_id: nil, bundle_id: nil, bundle_id_prefix: nil)
|
|
24
37
|
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
25
38
|
raise ArgumentError, "Workflow Package Name不能为空" if workflow_packname.nil?
|
|
26
39
|
|
|
@@ -30,10 +43,14 @@ module Pindo
|
|
|
30
43
|
|
|
31
44
|
# Bundle ID 优先使用显式传入的值(--bundleid),未传时按工作流名推导。
|
|
32
45
|
# 通配符(如 com.example.*)不能作为工程的 PRODUCT_BUNDLE_IDENTIFIER,
|
|
33
|
-
# 与 BaseCertOperator#configure_target_build_settings
|
|
46
|
+
# 与 BaseCertOperator#configure_target_build_settings 的处理保持一致,退回推导值,
|
|
47
|
+
# 推导值的前缀依次取 bundle_id_prefix、通配 ID 的前缀、默认前缀。
|
|
34
48
|
explicit_bundle_id = bundle_id.to_s.strip
|
|
35
49
|
use_explicit_bundle_id = !explicit_bundle_id.empty? && !explicit_bundle_id.include?("*")
|
|
36
|
-
|
|
50
|
+
derived_prefix = bundle_id_prefix.to_s.strip
|
|
51
|
+
derived_prefix = bundle_id_prefix_from(explicit_bundle_id).to_s if derived_prefix.empty?
|
|
52
|
+
derived_prefix = DEFAULT_BUNDLE_ID_PREFIX if derived_prefix.empty?
|
|
53
|
+
final_bundle_id = use_explicit_bundle_id ? explicit_bundle_id : "#{derived_prefix}.#{package_scheme}"
|
|
37
54
|
|
|
38
55
|
project_fullname = Dir.glob(File.join(project_dir, "/*.xcodeproj")).max_by {|f| File.mtime(f)}
|
|
39
56
|
return false if project_fullname.nil?
|
|
@@ -315,6 +332,33 @@ module Pindo
|
|
|
315
332
|
# @param version [String] 版本号
|
|
316
333
|
# @param build_number [Integer] Build号
|
|
317
334
|
# @return [Boolean] 是否成功更新
|
|
335
|
+
# 把纯数字版本补齐为 major.minor.patch(JPS 要求的格式),如 "1.0" => "1.0.0"
|
|
336
|
+
# @param version [String, nil] 工程中的版本号
|
|
337
|
+
# @return [String, nil] 补齐后的版本号;含变量、后缀或超过三段时返回 nil
|
|
338
|
+
def self.normalize_marketing_version(version)
|
|
339
|
+
value = version.to_s.strip
|
|
340
|
+
return nil unless value.match?(/\A\d+(\.\d+){0,2}\z/)
|
|
341
|
+
|
|
342
|
+
segments = value.split(".")
|
|
343
|
+
(segments + ["0"] * (3 - segments.size)).join(".")
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# 读取主 App target 当前的版本号与 Build 号(Release 配置优先)
|
|
347
|
+
# @param project_dir [String] 项目目录
|
|
348
|
+
# @return [Array(String, String)] [MARKETING_VERSION, CURRENT_PROJECT_VERSION],取不到时为 nil
|
|
349
|
+
def self.current_app_version(project_dir:)
|
|
350
|
+
xcodeproj_path = find_xcodeproj(project_dir)
|
|
351
|
+
return [nil, nil] if xcodeproj_path.nil?
|
|
352
|
+
|
|
353
|
+
target = Xcodeproj::Project.open(xcodeproj_path).targets.find do |t|
|
|
354
|
+
t.respond_to?(:product_type) && t.product_type.to_s.eql?("com.apple.product-type.application")
|
|
355
|
+
end
|
|
356
|
+
return [nil, nil] if target.nil?
|
|
357
|
+
|
|
358
|
+
config = target.build_configurations.find { |c| c.name == "Release" } || target.build_configurations.first
|
|
359
|
+
[config.build_settings['MARKETING_VERSION'], config.build_settings['CURRENT_PROJECT_VERSION']]
|
|
360
|
+
end
|
|
361
|
+
|
|
318
362
|
def self.update_ios_project_version(project_dir: nil, version: nil, build_number: nil)
|
|
319
363
|
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
320
364
|
raise ArgumentError, "版本号不能为空" if version.nil?
|
|
@@ -177,13 +177,13 @@ module Pindo
|
|
|
177
177
|
|
|
178
178
|
workflow_name: OptionItem.new(
|
|
179
179
|
key: :workflow_name,
|
|
180
|
-
name: '
|
|
181
|
-
description: '
|
|
180
|
+
name: '工作流/工具名称',
|
|
181
|
+
description: '按名称精确选择 Git 工作流或安装包工具(工具可匹配 name、subtitle 或 name(subtitle)),用于同类型多个时消歧(非交互场景使用)',
|
|
182
182
|
type: String,
|
|
183
183
|
env_name: 'PINDO_JPS_WORKFLOW_NAME',
|
|
184
184
|
optional: true,
|
|
185
185
|
cacheable: false,
|
|
186
|
-
example: 'pindo jps config --proj=MyApp --type=ipa --workflow_name="iOS
|
|
186
|
+
example: 'pindo jps config --proj=MyApp --type=ipa --workflow_name="iOS提交包"'
|
|
187
187
|
)
|
|
188
188
|
}
|
|
189
189
|
end
|
data/lib/pindo/version.rb
CHANGED
data/lib/pindo.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pindo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- wade
|
|
@@ -272,6 +272,7 @@ files:
|
|
|
272
272
|
- lib/pindo/base/plaininformative.rb
|
|
273
273
|
- lib/pindo/base/plistbuddyexecutable.rb
|
|
274
274
|
- lib/pindo/base/standarderror.rb
|
|
275
|
+
- lib/pindo/base/xcodeproj_compat.rb
|
|
275
276
|
- lib/pindo/client/applovinclient.rb
|
|
276
277
|
- lib/pindo/client/aws3sclient.rb
|
|
277
278
|
- lib/pindo/client/bossclient.rb
|
|
@@ -535,7 +536,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
535
536
|
- !ruby/object:Gem::Version
|
|
536
537
|
version: 3.0.0
|
|
537
538
|
requirements: []
|
|
538
|
-
rubygems_version: 4.0.
|
|
539
|
+
rubygems_version: 4.0.10
|
|
539
540
|
specification_version: 4
|
|
540
541
|
summary: easy work
|
|
541
542
|
test_files: []
|