pindo 5.13.5 → 5.13.7

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.
@@ -220,6 +220,168 @@ module Pindo
220
220
  }
221
221
  end
222
222
 
223
+ # AdHoc 专用工作流选择
224
+ # 筛选条件:
225
+ # 1. package_type 匹配
226
+ # 2. tab_name 必须包含 "提交包"
227
+ #
228
+ # @param project_id [String] 项目ID
229
+ # @param package_type [String] 包类型(ipa/apk/zip/app)
230
+ # @return [Hash] 选择的工作流信息
231
+ def select_workflow_for_adhoc(project_id:, package_type:)
232
+ # 1. 从 JPS API 获取可用工作流列表
233
+ workflow_result = @pgyer_client.get_project_workflows(project_id: project_id)
234
+
235
+ if workflow_result.nil? || workflow_result['data'].nil?
236
+ raise Informative, "获取工作流列表失败"
237
+ end
238
+
239
+ workflows = workflow_result['data']
240
+
241
+ if workflows.empty?
242
+ raise Informative, "该项目没有可用的工作流"
243
+ end
244
+
245
+ # 2. 第一层过滤:根据 package_type
246
+ filtered_by_type = workflows.select do |w|
247
+ if w['packageType']
248
+ case package_type
249
+ when 'ipa'
250
+ w['packageType'] == 'ipa'
251
+ when 'apk'
252
+ w['packageType'] == 'apk'
253
+ when 'zip'
254
+ w['packageType'] == 'zip'
255
+ when 'app'
256
+ w['packageType'] == 'app'
257
+ else
258
+ true
259
+ end
260
+ else
261
+ # 如果没有 packageType 字段,保留(后续通过名称筛选)
262
+ true
263
+ end
264
+ end
265
+
266
+ # 3. 第二层过滤:tab_name 必须包含 "提交包"
267
+ adhoc_workflows = filtered_by_type.select do |w|
268
+ tab_name = w['tabName'] || w['tab_name'] || ''
269
+ tab_name.include?('提交包')
270
+ end
271
+
272
+ if adhoc_workflows.empty?
273
+ raise Informative, "未找到符合条件的 AdHoc 工作流\n" \
274
+ "要求:package_type=#{package_type} 且 tab_name 包含 '提交包'\n" \
275
+ "请检查 JPS 工作流配置"
276
+ end
277
+
278
+ # 4. 根据匹配数量决定选择方式
279
+ workflow = nil
280
+
281
+ if adhoc_workflows.size == 1
282
+ # 只有 1 个匹配,自动选择
283
+ workflow = adhoc_workflows.first
284
+ else
285
+ # 有多个匹配,让用户选择
286
+ require 'highline/import'
287
+ cli = HighLine.new
288
+
289
+ workflow_choices = adhoc_workflows.map do |w|
290
+ tab_name = w['tabName'] || w['tab_name']
291
+ workflow_id = w['id'] || w['workflow_id']
292
+ "#{tab_name} (ID: #{workflow_id})"
293
+ end
294
+
295
+ selected = cli.choose do |menu|
296
+ menu.prompt = "请选择 AdHoc 工作流:"
297
+ menu.choices(*workflow_choices)
298
+ end
299
+
300
+ # 提取选择的工作流
301
+ selected_index = workflow_choices.index(selected)
302
+ workflow = adhoc_workflows[selected_index]
303
+ end
304
+
305
+ # 5. 返回标准化的工作流信息
306
+ {
307
+ workflow_id: workflow['id'] || workflow['workflow_id'],
308
+ tab_name: workflow['tabName'] || workflow['tab_name'],
309
+ package_type: workflow['packageType'] || package_type,
310
+ package_name: workflow['packageName'] || workflow['package_name'] || '',
311
+ manage_type: workflow['manageType'] || workflow['manage_type'] || ''
312
+ }
313
+ end
314
+
315
+ # 获取 AdHoc 上传信息
316
+ # 用于 adhoc 编译命令,不保存配置到 JPSBuildConfig.json
317
+ #
318
+ # @param working_directory [String] 工作目录
319
+ # @param package_type [String] 包类型(ipa/apk/zip/app)
320
+ # @param proj_name [String] 项目名称(可选)
321
+ # @param skip_config_file [Boolean] 是否跳过从 config.json 获取项目名称(默认 false)
322
+ # @return [Array] [app_info_obj, workflow_info]
323
+ def get_adhoc_upload_info(working_directory:, package_type:, proj_name: nil, skip_config_file: false)
324
+ upload_proj_name = proj_name
325
+
326
+ # 1. 项目名称获取逻辑
327
+ # 1.1 如果 proj_name 为空,从环境变量获取
328
+ if upload_proj_name.nil? || upload_proj_name.empty?
329
+ upload_proj_name = ENV['PINDO_PROJECT_NAME']
330
+ end
331
+
332
+ # 1.2 如果仍为空且未禁用,从 IosConfigParser 获取
333
+ if (upload_proj_name.nil? || upload_proj_name.empty?) && !skip_config_file
334
+ require 'pindo/config/ios_config_parser'
335
+ config_parser = Pindo::IosConfigParser.instance
336
+ if config_parser.config_json
337
+ upload_proj_name = config_parser.config_json.dig("project_info", "project_name")
338
+ end
339
+ end
340
+
341
+ # 1.3 如果还是为空,让用户输入
342
+ if upload_proj_name.nil? || upload_proj_name.empty?
343
+ require 'highline/import'
344
+ puts "\n未找到项目名称配置"
345
+ upload_proj_name = ask("请输入 JPS 项目名称: ") do |q|
346
+ q.validate = /\S+/
347
+ q.responses[:not_valid] = "项目名称不能为空"
348
+ end
349
+ upload_proj_name = upload_proj_name.strip if upload_proj_name
350
+ end
351
+
352
+ # 2. 确保已登录
353
+ unless login
354
+ raise Informative, "请先登录 JPS 网站"
355
+ end
356
+
357
+ # 3. 获取 app_info_obj
358
+ app_info_obj = find_app_info_with_obj_list(proj_name: upload_proj_name)
359
+
360
+ unless app_info_obj
361
+ raise Informative, "未找到项目: #{upload_proj_name},请检查项目名称是否正确"
362
+ end
363
+
364
+ # 4. 获取 AdHoc 工作流(调用专用函数)
365
+ workflow_info = select_workflow_for_adhoc(
366
+ project_id: app_info_obj["id"],
367
+ package_type: package_type
368
+ )
369
+
370
+ unless workflow_info
371
+ raise Informative, "未能获取 AdHoc 工作流信息"
372
+ end
373
+
374
+ # 5. 输出最终选择的项目和工作流
375
+ puts "\n✓ 项目: #{app_info_obj['projectName']}"
376
+ puts "✓ 工作流: #{workflow_info[:tab_name]}"
377
+
378
+ # 6. 保存项目名称到实例变量
379
+ @proj_name = upload_proj_name
380
+
381
+ # 7. 返回结果(不保存配置到 JPSBuildConfig.json)
382
+ return app_info_obj, workflow_info
383
+ end
384
+
223
385
  def prepare_upload(working_directory:nil, proj_name:nil, package_type:nil)
224
386
  upload_proj_name = proj_name
225
387
  if upload_proj_name.nil? || upload_proj_name.empty?
@@ -508,7 +670,7 @@ module Pindo
508
670
  args_ipa_file_dir = File.expand_path(File::dirname(ipa_file_upload))
509
671
  ipa_file_upload=File.join(args_ipa_file_dir, File.basename(ipa_file_upload))
510
672
  current_project_dir = Dir.pwd
511
- description = get_description_from_git(current_project_dir:current_project_dir)
673
+ # description = get_description_from_git(current_project_dir:current_project_dir)
512
674
  # get_description_from_git 现在会在失败时抛出异常,成功时返回有效的描述
513
675
  # 所以不需要再检查 nil 或空值
514
676
 
@@ -0,0 +1,189 @@
1
+ require_relative '../pindo_task'
2
+ require_relative '../task_config'
3
+
4
+ module Pindo
5
+ module TaskSystem
6
+ # IPA 本地重签名任务
7
+ # 使用新的证书和 Bundle ID 对 IPA 文件进行重签名
8
+ class IpaLocalResignTask < PindoTask
9
+ attr_reader :ipa_path, :ipa_file, :bundle_id, :build_type
10
+
11
+ # 任务类型
12
+ def self.task_type
13
+ :resign
14
+ end
15
+
16
+ # 重试配置
17
+ def self.default_retry_mode
18
+ RetryMode::DELAYED
19
+ end
20
+
21
+ def self.default_retry_count
22
+ 2 # 允许重试 2 次
23
+ end
24
+
25
+ def self.default_retry_delay
26
+ 5 # 延迟 5 秒
27
+ end
28
+
29
+ # 初始化重签名任务
30
+ # @param ipa_path [String] IPA 搜索路径(当 ipa_file 为 nil 时使用)
31
+ # @param ipa_file [String] 指定的 IPA 文件(nil 表示自动查找)
32
+ # @param bundle_id [String] 目标 Bundle ID
33
+ # @param options [Hash] 选项
34
+ # @option options [String] :build_type 构建类型('dev' 或 'adhoc',默认 'adhoc')
35
+ # @option options [String] :project_dir 项目目录(用于加载 config.json)
36
+ def initialize(ipa_path, ipa_file, bundle_id, options = {})
37
+ @ipa_path = ipa_path # IPA 搜索路径
38
+ @ipa_file = ipa_file # 指定的 IPA 文件(nil 表示自动查找)
39
+ @bundle_id = bundle_id # 目标 Bundle ID
40
+ @build_type = options[:build_type] || 'adhoc'
41
+ @project_dir = options[:project_dir] || Dir.pwd
42
+
43
+ # 设置任务优先级
44
+ options[:priority] ||= TaskPriority::HIGH
45
+
46
+ super("重签名 IPA", options)
47
+ end
48
+
49
+ # 验证任务参数
50
+ def validate
51
+ # 验证 Bundle ID
52
+ unless @bundle_id && !@bundle_id.empty?
53
+ @error = "缺少必需参数: bundle_id"
54
+ return false
55
+ end
56
+
57
+ # 验证 build_type
58
+ unless ['dev', 'adhoc'].include?(@build_type)
59
+ @error = "无效的 build_type: #{@build_type}(必须是 'dev' 或 'adhoc')"
60
+ return false
61
+ end
62
+
63
+ # 验证 ipa_path(当 ipa_file 为空时)
64
+ if (@ipa_file.nil? || @ipa_file.empty?)
65
+ unless @ipa_path && !@ipa_path.empty?
66
+ @error = "缺少必需参数: ipa_path(当 ipa_file 未指定时)"
67
+ return false
68
+ end
69
+ end
70
+
71
+ true
72
+ end
73
+
74
+ protected
75
+
76
+ # 执行重签名
77
+ def do_work
78
+ # 1. 确定要重签名的 IPA 文件
79
+ ipa_to_resign = determine_ipa_file
80
+ unless ipa_to_resign && File.exist?(ipa_to_resign)
81
+ raise "未找到需要重签名的 IPA 文件"
82
+ end
83
+
84
+ puts "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
85
+ puts " 正在重签名 IPA"
86
+ puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
87
+ puts " IPA 文件: #{ipa_to_resign}"
88
+ puts " Bundle ID: #{@bundle_id}"
89
+ puts " 构建类型: #{@build_type}"
90
+ puts " 修改时间: #{File.mtime(ipa_to_resign)}"
91
+ puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
92
+
93
+ # 2. 加载配置(如果尚未加载)
94
+ load_config_if_needed
95
+
96
+ # 3. 安装证书
97
+ install_certificate
98
+
99
+ # 4. 执行重签名
100
+ require 'pindo/module/xcode/ipa_resign_helper'
101
+ resigned_ipa = Pindo::IpaResignHelper.resign_ipa(
102
+ ipa_file_path: ipa_to_resign,
103
+ bundle_id: @bundle_id
104
+ )
105
+
106
+ # 5. 验证重签名结果
107
+ unless resigned_ipa && File.exist?(resigned_ipa)
108
+ raise "重签名失败:未生成重签名后的 IPA 文件"
109
+ end
110
+
111
+ puts "\n✅ 重签名成功!"
112
+ puts " 重签名后的 IPA: #{resigned_ipa}"
113
+
114
+ # 6. 返回重签名后的 IPA 路径
115
+ @result = resigned_ipa
116
+ end
117
+
118
+ private
119
+
120
+ # 确定要重签名的 IPA 文件
121
+ def determine_ipa_file
122
+ if @ipa_file && !@ipa_file.empty? && File.exist?(@ipa_file)
123
+ # 使用指定的 IPA 文件
124
+ puts " 使用指定的 IPA 文件: #{@ipa_file}"
125
+ return @ipa_file
126
+ else
127
+ # 在 ipa_path 中查找最新的非 _resigned 的 IPA
128
+ find_latest_ipa_file
129
+ end
130
+ end
131
+
132
+ # 查找最新的 IPA 文件(排除 _resigned.ipa)
133
+ def find_latest_ipa_file
134
+ unless File.directory?(@ipa_path)
135
+ return nil
136
+ end
137
+
138
+ ipa_files = Dir.glob(File.join(@ipa_path, "*.ipa"))
139
+
140
+ # 过滤掉 _resigned.ipa 文件
141
+ ipa_files = ipa_files.reject { |f| File.basename(f).include?("_resigned") }
142
+
143
+ if ipa_files.empty?
144
+ return nil
145
+ end
146
+
147
+ # 返回最新的 IPA 文件
148
+ latest_ipa = ipa_files.max_by { |f| File.mtime(f) }
149
+ puts " 自动查找到 IPA 文件: #{latest_ipa}"
150
+
151
+ latest_ipa
152
+ end
153
+
154
+ # 加载配置(如果尚未加载)
155
+ def load_config_if_needed
156
+ require 'pindo/config/ios_config_parser'
157
+ config_parser = Pindo::IosConfigParser.instance
158
+
159
+ # 如果配置已加载,直接返回
160
+ if config_parser.config_json && !config_parser.config_json.empty?
161
+ puts " 使用已加载的配置"
162
+ return
163
+ end
164
+
165
+ # 加载 config.json
166
+ config_file = File.join(@project_dir, "config.json")
167
+ if File.exist?(config_file)
168
+ puts " 加载配置文件: #{config_file}"
169
+ config_parser.load_config(config_file: config_file)
170
+ else
171
+ raise "配置文件不存在: #{config_file}"
172
+ end
173
+ end
174
+
175
+ # 安装证书
176
+ def install_certificate
177
+ puts "\n📜 安装证书..."
178
+ puts " 证书类型: #{@build_type}"
179
+
180
+ # 调用证书安装命令
181
+ require 'pindo/command/appstore/cert'
182
+ cert_args = ["--build_type=#{@build_type}"]
183
+ Pindo::Command::Appstore::Cert.run(cert_args)
184
+
185
+ puts " ✓ 证书安装完成"
186
+ end
187
+ end
188
+ end
189
+ end
@@ -34,6 +34,7 @@ module Pindo
34
34
  # @option options [Hash] :app_info_obj JPS 应用信息对象(可选,如为 nil 则延迟获取)
35
35
  # @option options [Hash] :workflow_info 工作流信息(可选,如为 nil 则延迟获取)
36
36
  # @option options [String] :project_name 项目名称(可选)
37
+ # @option options [String] :upload_desc 上传描述(可选)
37
38
  def initialize(file_type, upload_path, upload_file, options = {})
38
39
  @file_type = file_type # 'ipa' | 'apk' | 'html' | 'app'
39
40
  @upload_path = upload_path # 搜索文件的路径
@@ -43,6 +44,7 @@ module Pindo
43
44
  @app_info_obj = options[:app_info_obj]
44
45
  @workflow_info = options[:workflow_info]
45
46
  @project_name = options[:project_name]
47
+ @upload_desc = options[:upload_desc] # 上传描述
46
48
 
47
49
  # 设置上传任务的优先级为 LOW,确保在构建任务之后执行
48
50
  options[:priority] ||= TaskPriority::LOW
@@ -180,38 +182,80 @@ module Pindo
180
182
  result_data = pgyer_helper.start_upload(
181
183
  app_info_obj: @app_info_obj,
182
184
  ipa_file_upload: file_path,
183
- description: @context[:upload_desc], # 从 context 获取描述
185
+ description: @upload_desc, # 使用上传描述
184
186
  workflow_info: @workflow_info
185
187
  )
186
188
 
187
- # 解析上传结果
188
- if result_data && result_data["data"] && result_data["data"]["id"]
189
- app_version_info = result_data["data"]
189
+ # 验证上传结果
190
+ unless result_data && result_data["data"] && result_data["data"]["id"]
191
+ raise "上传失败:未获取到有效的返回数据"
192
+ end
190
193
 
191
- # 打印应用版本信息
192
- pgyer_helper.print_app_version_info(
193
- app_info_obj: @app_info_obj,
194
- app_version_info_obj: app_version_info
195
- )
194
+ # ========== 上传成功,后续操作失败不应导致重试 ==========
195
+ # 将重试次数设为 0,即使后续操作失败也不会重复上传
196
+ @retry_count = 0
196
197
 
197
- # 发送消息给自己
198
- pgyer_helper.send_apptest_msg(
199
- app_info_obj: @app_info_obj,
200
- app_version_info_obj: app_version_info,
201
- receiveType: "self"
202
- )
198
+ app_version_info = result_data["data"]
203
199
 
204
- # 如果需要发送到测试群
205
- if @context[:send_to_chat]
200
+ # 处理上传成功后的操作
201
+ handle_post_upload_actions(app_version_info)
202
+ end
203
+
204
+ # 处理上传成功后的操作
205
+ # @param app_version_info [Hash] 应用版本信息
206
+ def handle_post_upload_actions(app_version_info)
207
+ puts "\n 📋 开始处理上传后续操作..."
208
+
209
+ begin
210
+ pgyer_helper = PgyerHelper.share_instace
211
+
212
+ # 打印应用版本信息(失败只警告,不中断)
213
+ begin
214
+ puts " 📝 打印应用版本信息..."
215
+ pgyer_helper.print_app_version_info(
216
+ app_info_obj: @app_info_obj,
217
+ app_version_info_obj: app_version_info
218
+ )
219
+ puts " ✓ 应用版本信息打印成功"
220
+ rescue => e
221
+ puts " ⚠️ 打印应用版本信息失败: #{e.message}"
222
+ end
223
+
224
+ # 发送消息给自己(失败只警告,不中断)
225
+ begin
226
+ puts " 📨 发送消息给自己..."
206
227
  pgyer_helper.send_apptest_msg(
207
228
  app_info_obj: @app_info_obj,
208
229
  app_version_info_obj: app_version_info,
209
- chatEnv: "DevTest",
210
- receiveType: "chat"
230
+ receiveType: "self"
211
231
  )
232
+ puts " ✓ 消息发送成功"
233
+ rescue => e
234
+ puts " ⚠️ 发送消息给自己失败: #{e.message}"
212
235
  end
213
- else
214
- raise "上传失败:未获取到有效的返回数据"
236
+
237
+ # 如果需要发送到测试群(失败只警告,不中断)
238
+ if @context[:send_to_chat]
239
+ begin
240
+ puts " 📢 发送消息到测试群..."
241
+ pgyer_helper.send_apptest_msg(
242
+ app_info_obj: @app_info_obj,
243
+ app_version_info_obj: app_version_info,
244
+ chatEnv: "DevTest",
245
+ receiveType: "chat"
246
+ )
247
+ puts " ✓ 测试群消息发送成功"
248
+ rescue => e
249
+ puts " ⚠️ 发送消息到测试群失败: #{e.message}"
250
+ end
251
+ end
252
+
253
+ puts " ✅ 上传后续操作全部完成\n"
254
+ rescue => e
255
+ # 外层错误保护:任何未预期的错误都不应影响上传任务的成功状态
256
+ puts " ⚠️ 上传后续操作发生未预期错误: #{e.message}"
257
+ puts " ⚠️ 错误堆栈: #{e.backtrace.first(3).join("\n ")}" if e.backtrace
258
+ puts " ℹ️ 上传已成功,后续操作失败不影响上传结果\n"
215
259
  end
216
260
  end
217
261
  end
@@ -184,7 +184,7 @@ module Pindo
184
184
  def sign_ipa_with_new_cert(ipa_name:, bundle_id:)
185
185
  require 'pindo/config/pindoconfig'
186
186
 
187
- profil_info_array = Pindo::PindoSingleConfig.instance.get_cert_info
187
+ profil_info_array = Pindo::Pindoconfig.instance.get_cert_info
188
188
  bundle_id_signing_identity = profil_info_array.first['signing_identity']
189
189
 
190
190
  profile_dict = build_resign_profile_dict(profil_info_array: profil_info_array)
@@ -356,7 +356,7 @@ module Pindo
356
356
 
357
357
  end
358
358
 
359
- # 修复 Xcode 16 链接器兼容性问题
359
+ # 修复Xcode 26 链接器兼容性问题
360
360
  # 自动移除 -ld_classic 和 -ld64 标志
361
361
  def fix_xcode16_linker_flags(project_dir: nil)
362
362
  begin
@@ -428,11 +428,11 @@ module Pindo
428
428
  end
429
429
 
430
430
  if fixed_count > 0
431
- puts "✅ 修复 Xcode 16 链接器兼容性配置".green
431
+ puts "✅ 修复Xcode 26 链接器兼容性配置".green
432
432
  end
433
433
 
434
434
  rescue => error
435
- puts "⚠️ 修复链接器标志时出现错误: #{error.message}".yellow
435
+ puts "⚠️ 修复Xcode 26 链接器标志时出现错误: #{error.message}".yellow
436
436
  # 不中断构建流程
437
437
  end
438
438
  end
data/lib/pindo/version.rb CHANGED
@@ -6,7 +6,7 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.13.5"
9
+ VERSION = "5.13.7"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
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.13.5
4
+ version: 5.13.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
@@ -355,6 +355,7 @@ files:
355
355
  - lib/pindo/command/ios/autoresign.rb
356
356
  - lib/pindo/command/ios/build.rb
357
357
  - lib/pindo/command/ios/cert.rb
358
+ - lib/pindo/command/ios/fixproj.rb
358
359
  - lib/pindo/command/ios/podlint.rb
359
360
  - lib/pindo/command/ios/podpush.rb
360
361
  - lib/pindo/command/ios/podupdate.rb
@@ -441,6 +442,7 @@ files:
441
442
  - lib/pindo/module/task/model/build/web_build_dev_task.rb
442
443
  - lib/pindo/module/task/model/build_task.rb
443
444
  - lib/pindo/module/task/model/git_tag_task.rb
445
+ - lib/pindo/module/task/model/ipa_local_resign_task.rb
444
446
  - lib/pindo/module/task/model/jps_resign_task.rb
445
447
  - lib/pindo/module/task/model/jps_upload_task.rb
446
448
  - lib/pindo/module/task/model/unity_export_task.rb