pindo 5.19.3 → 5.19.5

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/git_handler.rb +5 -5
  3. data/lib/pindo/command/appstore/iap.rb +2 -2
  4. data/lib/pindo/command/appstore/updateid.rb +1 -1
  5. data/lib/pindo/command/ios/autoresign.rb +70 -29
  6. data/lib/pindo/command/ios/podpush.rb +1 -1
  7. data/lib/pindo/command/unity/packbuild.rb +1 -1
  8. data/lib/pindo/command/utils/copyconfig.rb +1 -1
  9. data/lib/pindo/command/utils/renewbundleid.rb +1 -1
  10. data/lib/pindo/config/build_info_manager.rb +2 -2
  11. data/lib/pindo/module/appstore/itcapp_helper.rb +1 -1
  12. data/lib/pindo/module/build/swark_helper.rb +1 -1
  13. data/lib/pindo/module/cert/cert_helper.rb +1 -1
  14. data/lib/pindo/module/cert/pem_helper.rb +1 -1
  15. data/lib/pindo/module/pgyer/pgyerhelper.rb +24 -21
  16. data/lib/pindo/module/resign/ipa_resign_adapter.rb +14 -0
  17. data/lib/pindo/module/resign/mac_app_resign_adapter.rb +16 -0
  18. data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +1 -1
  19. data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +1 -1
  20. data/lib/pindo/module/task/model/git/git_commit_task.rb +2 -2
  21. data/lib/pindo/module/task/model/resign/ipa_local_resign_task.rb +44 -10
  22. data/lib/pindo/module/utils/git_repo_helper.rb +2 -2
  23. data/lib/pindo/module/xcode/applovin_xcode_helper.rb +1 -1
  24. data/lib/pindo/module/xcode/cocoapods_helper.rb +2 -2
  25. data/lib/pindo/module/xcode/mac_app_resign_helper.rb +243 -0
  26. data/lib/pindo/module/xcode/xcode_build_helper.rb +3 -3
  27. data/lib/pindo/module/xcode/xcode_swark_helper.rb +1 -1
  28. data/lib/pindo/options/core/option_value_parser.rb +12 -2
  29. data/lib/pindo/options/groups/jps_options.rb +2 -1
  30. data/lib/pindo/options/groups/tool_options.rb +18 -0
  31. data/lib/pindo/version.rb +17 -10
  32. data/lib/pindo.rb +12 -0
  33. metadata +8 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a77caec349df71376df7ea433567423ae745f767297fb29377e265f123b63d8
4
- data.tar.gz: 5f8040b7e13937405b635906ad34dcd4466af83292d65b823968d22fce9f3081
3
+ metadata.gz: b7cccaa492d62d373f89fd3af414f3536ca43d47a840cc916b2629445cb581e3
4
+ data.tar.gz: 70f7ad8f931126af81fb7bceae2f54b35d1adc4d34b3991cde6b3a6c6422b950
5
5
  SHA512:
6
- metadata.gz: 9fb90c4d057aab583df7780e4e8880c47507da9c64f9b05dfb48b778c87ae1695cf33711c6b777ad6540284f97a101ba5c28f10e8d6a91dfc6243b6a4a25843f
7
- data.tar.gz: 57f746e6f8a01dcebab6615d1e30de5d7b6a1eb2b221bf8db50a10c66a0aab240279064b86f89bef7e0ccace8186eaf87da42a008a2f50f2a403f0d5ee5ec593
6
+ metadata.gz: 9ac4563509d172649298ca22c6f1459b2805521c2c0aa226f6ba9dacdb95b287814d92c70b5b747ac0be9f783c929f180d430dc72c179b76b764e65922ce30c2
7
+ data.tar.gz: 8383a7f60e3970a37e7e3311a6f930dfc9fb68ca49fc56c48ba868678739ed6215ea63d5f326b86ee690afb747a98bede384378e163229094c0e9a2b972c7be9
@@ -443,7 +443,7 @@ module Pindo
443
443
  return config_repo_dir
444
444
  end
445
445
 
446
- def git_addpush_repo(path:nil, message:"res", commit_file_params:nil)
446
+ def git_addpush_repo(path:nil, message:"chore(config): 提交配置变更", commit_file_params:nil)
447
447
  # gitee.com 仓库自动去除代理;is_gitee_repo? 命令已带 -C,无需 chdir
448
448
  if is_gitee_repo?(local_repo_dir: path)
449
449
  return without_proxy { git_addpush_repo_impl(path: path, message: message, commit_file_params: commit_file_params) }
@@ -452,7 +452,7 @@ module Pindo
452
452
  git_addpush_repo_impl(path: path, message: message, commit_file_params: commit_file_params)
453
453
  end
454
454
 
455
- def git_addpush_repo_impl(path:nil, message:"res", commit_file_params:nil)
455
+ def git_addpush_repo_impl(path:nil, message:"chore(config): 提交配置变更", commit_file_params:nil)
456
456
  current=Dir.pwd
457
457
  Dir.chdir(path)
458
458
  begin
@@ -467,7 +467,7 @@ module Pindo
467
467
  end
468
468
  end
469
469
  if need_commit
470
- git! ['commit', '-m ' + "#{message}"]
470
+ git! ['commit', '-m', message]
471
471
  git! ['push']
472
472
  else
473
473
  # puts "\n#{path}\n!!!仓库中文件未发生变化,无需提交!!!\n"
@@ -478,7 +478,7 @@ module Pindo
478
478
  # puts files_list
479
479
  if !files_list.nil? && files_list.size > 0
480
480
  git! ['add', '-A']
481
- git! ['commit', '-m ' + "#{message}"]
481
+ git! ['commit', '-m', message]
482
482
  git! ['push']
483
483
  else
484
484
  puts "\n#{path}\n!!!仓库中文件未发生变化,无需提交!!!\n"
@@ -620,7 +620,7 @@ module Pindo
620
620
 
621
621
  def handle_commit_all(project_dir, branch, commit_message = nil)
622
622
  # 使用传入的 commit_message,如果没有则使用默认值
623
- message = commit_message || "build: 构建产生提交"
623
+ message = commit_message || "chore(build): 构建产生提交"
624
624
 
625
625
  Funlog.instance.fancyinfo_start("开始提交文件...")
626
626
  git! ['add', '-A']
@@ -150,7 +150,7 @@ module Pindo
150
150
 
151
151
  pindo_dir = File.expand_path(pindo_single_config.pindo_dir)
152
152
  config_repo_dir = File.join(pindo_dir, @bundle_id)
153
- Pindo::GitHandler.git_addpush_repo(path: config_repo_dir, message: "download app iap", commit_file_params: ["fastlane/iap/iap_appstore_download.json"])
153
+ Pindo::GitHandler.git_addpush_repo(path: config_repo_dir, message: "chore(config): 下载应用 iap 配置", commit_file_params: ["fastlane/iap/iap_appstore_download.json"])
154
154
 
155
155
  update_app_share_secrets(app_id: app_id, need_create: false)
156
156
  end
@@ -295,7 +295,7 @@ module Pindo
295
295
  file.close
296
296
  end
297
297
 
298
- Pindo::GitHandler.git_addpush_repo(path: config_repo_dir, message: "update app shared secret", commit_file_params: ["config.json"])
298
+ Pindo::GitHandler.git_addpush_repo(path: config_repo_dir, message: "chore(config): 更新应用 shared secret", commit_file_params: ["config.json"])
299
299
 
300
300
  end
301
301
 
@@ -211,7 +211,7 @@ module Pindo
211
211
  # 只提交 config.json 到 Git 仓库
212
212
  Pindo::GitHandler.git_addpush_repo(
213
213
  path: app_config_dir,
214
- message: "sync capabilities from Apple Developer Portal",
214
+ message: "chore(config): 同步 Apple Developer Portal 的 capabilities",
215
215
  commit_file_params: ["config.json"]
216
216
  )
217
217
 
@@ -17,7 +17,7 @@ module Pindo
17
17
  class Autoresign < Ios
18
18
 
19
19
  include Appselect
20
- self.summary = 'IPA包重签名工具'
20
+ self.summary = 'iOS IPA / macOS App 重签名工具'
21
21
 
22
22
  # 启用缓存机制
23
23
  def self.use_cache?
@@ -25,7 +25,7 @@ module Pindo
25
25
  end
26
26
 
27
27
  self.description = <<-DESC
28
- 使用最新的证书重签名IPA包。
28
+ 使用最新的证书重签名 iOS IPA 或 macOS App。
29
29
 
30
30
  支持功能:
31
31
 
@@ -35,6 +35,8 @@ module Pindo
35
35
 
36
36
  * 支持指定IPA路径
37
37
 
38
+ * 使用 --macos 对 macOS .app 目录进行 Developer ID / Development 重签名
39
+
38
40
  * 支持上传到JPS测试平台
39
41
 
40
42
  * 支持发送测试通知
@@ -47,6 +49,10 @@ module Pindo
47
49
 
48
50
  $ pindo ios autoresign demo.ipa # 重签名指定IPA文件
49
51
 
52
+ $ pindo ios autoresign Demo.app --macos # 重签名指定 macOS App
53
+
54
+ $ pindo ios autoresign --app=Demo.app --macos # 使用选项指定 macOS App
55
+
50
56
  $ pindo ios autoresign --adhoc # 使用adhoc证书重签名
51
57
 
52
58
  $ pindo ios autoresign --deploy # 使用发布bundle id
@@ -65,7 +71,7 @@ module Pindo
65
71
  DESC
66
72
 
67
73
  self.arguments = [
68
- CLAide::Argument.new('path/to/demo.ipa', true),
74
+ CLAide::Argument.new('path/to/demo.ipa|path/to/Demo.app', true),
69
75
  ]
70
76
 
71
77
  def self.option_items
@@ -73,11 +79,11 @@ module Pindo
73
79
  items = []
74
80
 
75
81
  # 添加工具参数(ToolOptions)
76
- items.concat(Pindo::Options::ToolOptions.select(:ipa, :deploy))
82
+ items.concat(Pindo::Options::ToolOptions.select(:ipa, :app, :deploy))
77
83
 
78
84
  # 添加构建类型参数(BuildOptions)
79
85
  items.concat(Pindo::Options::BuildOptions.select(
80
- :dev, :adhoc, :develop_id, :build_type
86
+ :bundleid, :dev, :adhoc, :develop_id, :build_type
81
87
  ))
82
88
 
83
89
  # 添加证书相关参数(CertOptions)
@@ -106,9 +112,10 @@ module Pindo
106
112
  @options = initialize_options(argv)
107
113
 
108
114
  # 优先使用选项参数,如果没有则使用位置参数
109
- @args_ipa_file = @options[:ipa] || positional_ipa
115
+ @args_ipa_file = @options[:app] || @options[:ipa] || positional_ipa
110
116
 
111
117
  @args_deploy_flag = @options[:deploy]
118
+ @args_bundle_id = @options[:bundleid]
112
119
 
113
120
  # JPS 参数(从 options 中获取)
114
121
  @args_upload_flag = @options[:upload]
@@ -154,7 +161,8 @@ module Pindo
154
161
 
155
162
  # 处理平台类型
156
163
  @platform_type = @options[:platform]
157
- if @args_macos_flag
164
+ # --app 只用于 macOS,指定即隐含 macOS 平台,无需再显式加 --macos
165
+ if @args_macos_flag || @options[:app]
158
166
  @platform_type = 'macos'
159
167
  end
160
168
 
@@ -165,15 +173,18 @@ module Pindo
165
173
 
166
174
  super
167
175
  @additional_args = argv.remainder!
176
+ unless @additional_args.empty?
177
+ raise Informative, "只允许指定一个安装包路径,多余参数: #{@additional_args.join(' ')}"
178
+ end
168
179
  end
169
180
 
170
181
 
171
182
  def run
172
183
  begin
173
- # 1. 查找 IPA 文件
184
+ # 1. 查找待签名包(iOS IPA,macOS 为 .app)
174
185
  ipa_file_path = find_ipa_file()
175
186
  unless ipa_file_path && File.exist?(ipa_file_path)
176
- raise Informative, "未找到需要重签名的 IPA 文件"
187
+ raise Informative, "未找到需要重签名的安装包"
177
188
  end
178
189
 
179
190
  # 2. 选择 Bundle ID
@@ -187,7 +198,7 @@ module Pindo
187
198
  app_info_obj, workflow_info = prepare_jps_config()
188
199
 
189
200
  # 5. 计算重签名后的 IPA 文件名
190
- resigned_ipa_file = ipa_file_path.gsub(/\.ipa$/, '_resigned.ipa')
201
+ resigned_ipa_file = resigned_package_path(ipa_file_path)
191
202
 
192
203
  # 6. 创建任务
193
204
  tasks = create_resign_tasks(
@@ -214,21 +225,25 @@ module Pindo
214
225
 
215
226
  # 查找 IPA 文件
216
227
  def find_ipa_file
217
- # 1. 如果通过选项或位置参数指定了 IPA 文件且存在,直接返回
218
- if @args_ipa_file && File.exist?(@args_ipa_file)
219
- puts "\n使用指定的 IPA 文件: #{@args_ipa_file}"
228
+ # 1. 如果通过选项或位置参数指定了安装包,严格校验后直接返回
229
+ if @args_ipa_file
230
+ unless File.exist?(@args_ipa_file)
231
+ raise Informative, "指定的安装包不存在: #{@args_ipa_file}"
232
+ end
233
+ validate_package_path!(@args_ipa_file)
234
+ puts "\n使用指定的安装包: #{@args_ipa_file}"
220
235
  puts "修改时间: #{File.mtime(@args_ipa_file)}"
221
236
  return @args_ipa_file
222
237
  end
223
238
 
224
239
  # 2. 在当前目录查找最新的非 _resigned 的 IPA
225
240
  current_dir = Dir.pwd
226
- ipa_files = Dir.glob(File.join(current_dir, "*.ipa"))
241
+ pattern = macos_platform? ? "*.app" : "*.ipa"
242
+ ipa_files = Dir.glob(File.join(current_dir, pattern))
227
243
 
228
- # 过滤掉 _resigned.ipa 并找到最新的
229
- ipa_file = ipa_files.max_by do |f|
230
- File.basename(f).include?("_resigned") ? Time.local(0, 1, 1) : File.mtime(f)
231
- end
244
+ # 排除已经重签名的产物并找到最新安装包
245
+ ipa_files.reject! { |f| File.basename(f).include?("_resigned") }
246
+ ipa_file = ipa_files.max_by { |f| File.mtime(f) }
232
247
 
233
248
  if ipa_file && File.exist?(ipa_file)
234
249
  puts "\n自动找到 IPA 文件: #{ipa_file}"
@@ -237,12 +252,13 @@ module Pindo
237
252
  end
238
253
 
239
254
  # 3. 如果没有找到,从终端输入
240
- puts "\n⚠️ 未找到 IPA 文件"
241
- puts "请输入 IPA 文件路径(支持相对路径或绝对路径):"
255
+ package_name = macos_platform? ? "macOS .app" : "IPA"
256
+ puts "\n⚠️ 未找到 #{package_name}"
257
+ puts "请输入 #{package_name} 路径(支持相对路径或绝对路径):"
242
258
 
243
- ipa_path = ask("IPA 文件路径: ") do |q|
244
- q.validate = /\.ipa$/i
245
- q.responses[:not_valid] = "❌ 请输入有效的 IPA 文件路径(必须以 .ipa 结尾)"
259
+ ipa_path = ask("安装包路径: ") do |q|
260
+ q.validate = macos_platform? ? /\.app$/i : /\.ipa$/i
261
+ q.responses[:not_valid] = "❌ 请输入有效的 #{package_name} 路径"
246
262
  end
247
263
 
248
264
  # 去除首尾空格
@@ -251,7 +267,8 @@ module Pindo
251
267
  # 验证文件是否存在
252
268
  if ipa_path && !ipa_path.empty?
253
269
  if File.exist?(ipa_path)
254
- puts "✓ 使用输入的 IPA 文件: #{ipa_path}"
270
+ validate_package_path!(ipa_path)
271
+ puts "✓ 使用输入的安装包: #{ipa_path}"
255
272
  puts " 修改时间: #{File.mtime(ipa_path)}"
256
273
  return ipa_path
257
274
  else
@@ -262,8 +279,32 @@ module Pindo
262
279
  nil
263
280
  end
264
281
 
282
+ def macos_platform?
283
+ @platform_type.to_s.downcase == 'macos'
284
+ end
285
+
286
+ def validate_package_path!(path)
287
+ if macos_platform?
288
+ unless File.directory?(path) && path.downcase.end_with?('.app')
289
+ raise Informative, "--macos 模式要求输入 macOS .app 目录: #{path}"
290
+ end
291
+ elsif !File.file?(path) || !path.downcase.end_with?('.ipa')
292
+ raise Informative, "iOS 模式要求输入 .ipa 文件: #{path}"
293
+ end
294
+ end
295
+
296
+ def resigned_package_path(path)
297
+ extension = macos_platform? ? '.app' : '.ipa'
298
+ path.sub(/#{Regexp.escape(extension)}$/i, "_resigned#{extension}")
299
+ end
300
+
265
301
  # 选择 Bundle ID(根据 build_type)
266
302
  def select_bundle_id
303
+ if @args_bundle_id && !@args_bundle_id.empty?
304
+ puts "\n使用指定的 Bundle ID: #{@args_bundle_id}"
305
+ return @args_bundle_id
306
+ end
307
+
267
308
  if @args_deploy_flag || @build_type != 'development'
268
309
  get_selected_deploy_bundleid()
269
310
  else
@@ -274,8 +315,9 @@ module Pindo
274
315
  # 拉取应用配置
275
316
  def pull_app_config(bundle_id)
276
317
  require 'pindo/config/build_info_manager'
318
+ repo_name = bundle_id.sub(/\.\*$/, '')
277
319
  Pindo::BuildInfoManager.share_instance.pull_appconfig_with_reponame(
278
- repo_name: bundle_id,
320
+ repo_name: repo_name,
279
321
  target_dir: Dir.pwd
280
322
  )
281
323
  end
@@ -283,9 +325,8 @@ module Pindo
283
325
  # 准备 JPS 配置
284
326
  def prepare_jps_config
285
327
  if @args_upload_flag
286
- # 根据 Xcode 工程类型动态设置 package_type(macOS 工程使用 'mac',否则使用 'ipa')
287
- is_macos = Pindo::BuildHelper.share_instance.macos_project?(Dir.pwd)
288
- package_type = is_macos ? 'mac' : 'ipa'
328
+ # 以平台类型为唯一判定口径,与 create_resign_tasks 的上传任务保持一致
329
+ package_type = macos_platform? ? 'mac' : 'ipa'
289
330
  app_info_obj, workflow_info = PgyerHelper.share_instace.prepare_upload(
290
331
  working_directory: Dir.pwd,
291
332
  conf: @args_conf,
@@ -327,7 +368,7 @@ module Pindo
327
368
  # 2. 上传任务(仅在需要时创建)
328
369
  if @args_upload_flag && app_info_obj
329
370
  upload_task = Pindo::TaskSystem::JPSUploadTask.new(
330
- 'ipa',
371
+ macos_platform? ? 'mac' : 'ipa',
331
372
  Dir.pwd,
332
373
  resigned_ipa_file,
333
374
  app_info_obj: app_info_obj,
@@ -270,7 +270,7 @@ module Pindo
270
270
  begin FileUtils.mkdir_p(dest_dir) rescue StandardError => e end
271
271
  FileUtils.cp_r(podspec, File.join(dest_dir, File.basename(podspec)))
272
272
  puts "上传到仓库 #{private_source.repo}"
273
- git_addpush_repo(path:private_source.repo, message:"add #{pod_name} #{tag_name}")
273
+ git_addpush_repo(path:private_source.repo, message:"chore(pod): 添加 #{pod_name} #{tag_name}")
274
274
  end
275
275
  end
276
276
 
@@ -188,7 +188,7 @@ module Pindo
188
188
  tag_type: @args_tag_type,
189
189
  tag_pre: @args_tag_pre,
190
190
  process_type: process_type,
191
- commit_message: "chore: packbuild 更新 v#{confirmed_version} Release Notes",
191
+ commit_message: "chore(nuget): 更新 nuget v#{confirmed_version} Release Notes",
192
192
  fixed_version: confirmed_version # 传入确认后的版本号
193
193
  )
194
194
  tasks << git_commit_task
@@ -170,7 +170,7 @@ module Pindo
170
170
  # 只提交 config.json
171
171
  Pindo::GitHandler.git_addpush_repo(
172
172
  path: new_config_dir,
173
- message: "init config from #{@from_bundle_id}"
173
+ message: "chore(config): 初始化 #{@from_bundle_id} 的配置"
174
174
  )
175
175
 
176
176
  puts
@@ -184,7 +184,7 @@ module Pindo
184
184
  # 提交到 Git 仓库
185
185
  Pindo::GitHandler.git_addpush_repo(
186
186
  path: app_config_dir,
187
- message: "sync capabilities from Apple Developer Portal",
187
+ message: "chore(config): 同步 Apple Developer Portal 的 capabilities",
188
188
  commit_file_params: ["config.json"]
189
189
  )
190
190
 
@@ -220,7 +220,7 @@ module Pindo
220
220
  f.write(JSON.pretty_generate(git_repo_json))
221
221
  end
222
222
 
223
- Pindo::GitHandler.git_addpush_repo(path: pindo_setting_dir, message: "add #{repo_name}")
223
+ Pindo::GitHandler.git_addpush_repo(path: pindo_setting_dir, message: "chore(config): 添加 #{repo_name} 仓库")
224
224
  end
225
225
 
226
226
  # 更新应用配置仓库(从模板复制)
@@ -252,7 +252,7 @@ module Pindo
252
252
  end
253
253
 
254
254
  # 提交并推送到远程仓库
255
- Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "init app config")
255
+ Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "chore(config): 初始化应用配置")
256
256
  end
257
257
  end
258
258
 
@@ -186,7 +186,7 @@ module Pindo
186
186
  if File.exist?(app_config_file)
187
187
  modify_appconfig_file(config_file: app_config_file, app_info_detail: app)
188
188
  Pindo::GitHandler.prepare_gitenv()
189
- Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "modify apple id")
189
+ Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "chore(config): 修改 apple id")
190
190
  end
191
191
  rescue StandardError => e
192
192
  puts "更新远程配置仓库失败: #{e.message}"
@@ -127,7 +127,7 @@ module Pindo
127
127
  f.write(JSON.pretty_generate(swark_authorize_json))
128
128
  end
129
129
 
130
- Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "add swark authorize json")
130
+ Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "chore(config): 添加 swark 授权配置")
131
131
  end
132
132
 
133
133
  end
@@ -143,7 +143,7 @@ module Pindo
143
143
 
144
144
  # 提交并推送到远程仓库
145
145
  Pindo::GitHandler.prepare_gitenv()
146
- Pindo::GitHandler.git_addpush_repo(path: cert_repo_dir, message: "remove #{apple_id} certs")
146
+ Pindo::GitHandler.git_addpush_repo(path: cert_repo_dir, message: "chore(cert): 删除 #{apple_id} 的证书")
147
147
 
148
148
  puts "✓ Git 仓库证书清理完成"
149
149
  end
@@ -65,7 +65,7 @@ module Pindo
65
65
  FileUtils.cp_r(File.join(push_path, "."), push_repo_dir)
66
66
 
67
67
  Pindo::GitHandler.prepare_gitenv()
68
- Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "add push cert")
68
+ Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "chore(cert): 添加推送证书")
69
69
 
70
70
  puts "✓ 证书已上传到配置仓库: #{push_repo_dir}"
71
71
  end
@@ -432,7 +432,7 @@ module Pindo
432
432
 
433
433
  # 确保已登录
434
434
  unless login
435
- raise Informative, "请先登录 JPS 网站"
435
+ raise Informative, "JPS 未登录或登录已失效,请执行 pindo jps login 重新登录"
436
436
  end
437
437
 
438
438
  # 获取 app_info_obj
@@ -506,7 +506,7 @@ module Pindo
506
506
  end
507
507
 
508
508
  unless login
509
- raise Informative, "请先登录JPS网站"
509
+ raise Informative, "JPS 未登录或登录已失效,请执行 pindo jps login 重新登录"
510
510
  end
511
511
 
512
512
  puts "[PINDO_DEBUG] 正在查找项目: #{config_result[:project_name]}" if ENV['PINDO_DEBUG']
@@ -686,7 +686,7 @@ module Pindo
686
686
 
687
687
 
688
688
  else
689
- raise Informative, "请先登录JPS网站"
689
+ raise Informative, "JPS 未登录或登录已失效,请执行 pindo jps login 重新登录"
690
690
  end
691
691
  @proj_name = upload_proj_name
692
692
  app_info_obj
@@ -751,23 +751,7 @@ module Pindo
751
751
  if !ipa_file_upload.nil? &&
752
752
  File.extname(ipa_file_upload).downcase.eql?(".exe")
753
753
 
754
- windows_exe_path = File.expand_path(ipa_file_upload)
755
- windows_build_dir = File.dirname(windows_exe_path)
756
- windows_zip_dir = File.dirname(windows_build_dir)
757
- windows_build_name = File.basename(windows_exe_path, File.extname(windows_exe_path))
758
- windows_exe_name = File.basename(windows_exe_path)
759
- windows_zip_fullname = File.join(windows_zip_dir, windows_build_name + ".zip")
760
-
761
- if File.exist?(windows_zip_fullname)
762
- FileUtils.rm_rf(windows_zip_fullname)
763
- end
764
-
765
- Zip::File.open(windows_zip_fullname, Zip::File::CREATE) do |zipfile|
766
- Dir.chdir(windows_build_dir) do
767
- zipfile.add(windows_exe_name, windows_exe_name)
768
- end
769
- end
770
- ipa_file_upload = windows_zip_fullname
754
+ ipa_file_upload = package_windows_exe_for_upload(ipa_file_upload)
771
755
  end
772
756
  unless !ipa_file_upload.nil? && File.exist?(ipa_file_upload)
773
757
  return
@@ -898,6 +882,25 @@ module Pindo
898
882
 
899
883
  end
900
884
 
885
+ def package_windows_exe_for_upload(exe_file_upload)
886
+ windows_exe_path = File.expand_path(exe_file_upload)
887
+ windows_build_dir = File.dirname(windows_exe_path)
888
+ windows_zip_dir = File.dirname(windows_build_dir)
889
+ windows_build_name = File.basename(windows_exe_path, File.extname(windows_exe_path))
890
+ windows_exe_name = File.basename(windows_exe_path)
891
+ windows_zip_fullname = File.join(windows_zip_dir, windows_build_name + ".zip")
892
+
893
+ if File.exist?(windows_zip_fullname)
894
+ FileUtils.rm_rf(windows_zip_fullname)
895
+ end
896
+
897
+ Zip::File.open(windows_zip_fullname, Zip::File::CREATE) do |zipfile|
898
+ zipfile.add(windows_exe_name, windows_exe_path)
899
+ end
900
+
901
+ windows_zip_fullname
902
+ end
903
+
901
904
  # 上传 media 文件(图片、视频等)到 JPS
902
905
  # 上传成功后自动更新 commit_log 记录
903
906
  #
@@ -1457,7 +1460,7 @@ module Pindo
1457
1460
 
1458
1461
  if app_info_list.nil?
1459
1462
  Funlog.instance.fancyinfo_error("拉取app信息列表失败!")
1460
- raise Informative, "JPS网络数据异常,请检查网络连接或重新登录(pindo repo login)"
1463
+ raise Informative, "JPS网络数据异常,请检查网络连接或重新登录(pindo jps login)"
1461
1464
  end
1462
1465
 
1463
1466
  return app_info_list
@@ -0,0 +1,14 @@
1
+ require 'pindo/module/xcode/ipa_resign_helper'
2
+
3
+ module Pindo
4
+ module Resign
5
+ class IpaResignAdapter
6
+ def resign(package_path:, bundle_id:, **_options)
7
+ Pindo::IpaResignHelper.resign_ipa(
8
+ ipa_file_path: package_path,
9
+ bundle_id: bundle_id
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'pindo/module/xcode/mac_app_resign_helper'
2
+
3
+ module Pindo
4
+ module Resign
5
+ class MacAppResignAdapter
6
+ def resign(package_path:, bundle_id:, signing_identity:, profiles:, **_options)
7
+ Pindo::MacAppResignHelper.resign_app(
8
+ app_path: package_path,
9
+ bundle_id: bundle_id,
10
+ signing_identity: signing_identity,
11
+ profiles: profiles
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -200,7 +200,7 @@ module Pindo
200
200
  git_helper = Pindo::GitRepoHelper.share_instance
201
201
  git_helper.git_addpush_repo(
202
202
  path: app_config_repo_dir,
203
- message: "increate build number",
203
+ message: "chore(config): 递增 build number",
204
204
  commit_file_params: ["config.json"]
205
205
  )
206
206
  end
@@ -173,7 +173,7 @@ module Pindo
173
173
  git_helper = Pindo::GitRepoHelper.share_instance
174
174
  git_helper.git_addpush_repo(
175
175
  path: app_config_repo_dir,
176
- message: "increate build number",
176
+ message: "chore(config): 递增 build number",
177
177
  commit_file_params: ["config.json"]
178
178
  )
179
179
  end
@@ -27,7 +27,7 @@ module Pindo
27
27
  # - tag_pre: tag前缀,默认 'v'
28
28
  # - fixed_version: 外部指定的固定版本号
29
29
  # - process_type: 未提交文件的处理方式 ('skip', 'commit', 'reset', 'stash'),默认 'skip'
30
- # - commit_message: 提交消息,默认 'build: 构建产生提交'
30
+ # - commit_message: 提交消息,默认 'chore(build): 构建产生提交'
31
31
  #
32
32
  # 版本号计算逻辑:
33
33
  # 1. 如果 fixed_version 为 nil 且 HEAD 存在 tag 且工作目录干净,
@@ -38,7 +38,7 @@ module Pindo
38
38
  # GitCommitTask 特有的属性
39
39
  @fixed_version = options[:fixed_version] # 外部指定的固定版本号
40
40
  @process_type = options[:process_type] || Pindo::UncommittedFilesProcessType::SKIP_WITH_TAG # 默认跳过但打tag
41
- @commit_message = options[:commit_message] || 'build: 构建产生提交' # 默认提交消息
41
+ @commit_message = options[:commit_message] || 'chore(build): 构建产生提交' # 默认提交消息
42
42
 
43
43
  options[:project_path] = project_path
44
44
  # 调用基类初始化(基类会设置 release_branch, ver_inc, tag_type, tag_pre 等通用属性)
@@ -3,6 +3,8 @@ require 'pindo/module/task/task_config'
3
3
  require 'pindo/module/cert/cert_helper'
4
4
  require 'pindo/config/ios_config_parser'
5
5
  require 'pindo/module/xcode/ipa_resign_helper'
6
+ require 'pindo/module/resign/ipa_resign_adapter'
7
+ require 'pindo/module/resign/mac_app_resign_adapter'
6
8
 
7
9
  module Pindo
8
10
  module TaskSystem
@@ -62,7 +64,7 @@ module Pindo
62
64
  # 设置任务优先级
63
65
  options[:priority] ||= TaskPriority::HIGH
64
66
 
65
- super("重签名 IPA", options)
67
+ super("本地重签名", options)
66
68
  end
67
69
 
68
70
  # 验证任务参数
@@ -95,14 +97,16 @@ module Pindo
95
97
 
96
98
  # 执行重签名
97
99
  def do_work
98
- # 1. 确定要重签名的 IPA 文件
100
+ # 1. 确定要重签名的安装包
99
101
  ipa_to_resign = determine_ipa_file
100
102
  unless ipa_to_resign && File.exist?(ipa_to_resign)
101
- raise "未找到需要重签名的 IPA 文件"
103
+ raise "未找到需要重签名的安装包"
102
104
  end
103
105
 
106
+ validate_package_type!(ipa_to_resign)
107
+
104
108
  puts "\n▶ 重签名信息"
105
- puts " IPA: #{File.basename(ipa_to_resign)}"
109
+ puts " 安装包: #{File.basename(ipa_to_resign)}"
106
110
  puts " Bundle ID: #{@bundle_id}"
107
111
  puts " 类型: #{@build_type}"
108
112
  cert_info = []
@@ -114,17 +118,23 @@ module Pindo
114
118
  load_config_if_needed
115
119
 
116
120
  # 3. 安装证书
117
- install_certificate
121
+ provisioning_info_array = install_certificate
118
122
 
119
123
  # 4. 执行重签名
120
- resigned_ipa = Pindo::IpaResignHelper.resign_ipa(
121
- ipa_file_path: ipa_to_resign,
122
- bundle_id: @bundle_id
124
+ signing_identity = Array(provisioning_info_array)
125
+ .map { |item| item['signing_identity'] }
126
+ .compact
127
+ .first
128
+ resigned_ipa = resign_adapter.resign(
129
+ package_path: ipa_to_resign,
130
+ bundle_id: @bundle_id,
131
+ signing_identity: signing_identity,
132
+ profiles: provisioning_info_array
123
133
  )
124
134
 
125
135
  # 5. 验证重签名结果
126
136
  unless resigned_ipa && File.exist?(resigned_ipa)
127
- raise "重签名失败:未生成重签名后的 IPA 文件"
137
+ raise "重签名失败:未生成重签名后的安装包"
128
138
  end
129
139
 
130
140
  puts "✅ 重签名完成: #{File.basename(resigned_ipa)}\n"
@@ -146,13 +156,36 @@ module Pindo
146
156
  end
147
157
  end
148
158
 
159
+ def macos_platform?
160
+ @platform_type.to_s.downcase == 'macos'
161
+ end
162
+
163
+ def validate_package_type!(path)
164
+ valid = if macos_platform?
165
+ File.directory?(path) && path.downcase.end_with?('.app')
166
+ else
167
+ File.file?(path) && path.downcase.end_with?('.ipa')
168
+ end
169
+ expected = macos_platform? ? 'macOS .app 目录' : '.ipa 文件'
170
+ raise "当前平台要求输入 #{expected}: #{path}" unless valid
171
+ end
172
+
173
+ def resign_adapter
174
+ if macos_platform?
175
+ Pindo::Resign::MacAppResignAdapter.new
176
+ else
177
+ Pindo::Resign::IpaResignAdapter.new
178
+ end
179
+ end
180
+
149
181
  # 查找最新的 IPA 文件(排除 _resigned.ipa)
150
182
  def find_latest_ipa_file
151
183
  unless File.directory?(@ipa_path)
152
184
  return nil
153
185
  end
154
186
 
155
- ipa_files = Dir.glob(File.join(@ipa_path, "*.ipa"))
187
+ pattern = macos_platform? ? '*.app' : '*.ipa'
188
+ ipa_files = Dir.glob(File.join(@ipa_path, pattern))
156
189
 
157
190
  # 过滤掉 _resigned.ipa 文件
158
191
  ipa_files = ipa_files.reject { |f| File.basename(f).include?("_resigned") }
@@ -204,6 +237,7 @@ module Pindo
204
237
  unless provisioning_info_array && !provisioning_info_array.empty?
205
238
  raise "证书获取失败:未找到有效的证书信息"
206
239
  end
240
+ provisioning_info_array
207
241
  end
208
242
  end
209
243
  end
@@ -57,7 +57,7 @@ module Pindo
57
57
  current_branch = Pindo::GitHandler.git!(%W(-C #{current_git_root_path} rev-parse --abbrev-ref HEAD)).strip
58
58
  Pindo::GitHandler.git!(%W(-C #{current_git_root_path} add cliff.toml))
59
59
  # .gitignore 已由 check_gitignore 自动添加到暂存区,无需重复添加
60
- commit_message = "docs: 添加日志变更配置".encode('UTF-8')
60
+ commit_message = "docs(config): 添加日志变更配置".encode('UTF-8')
61
61
  Pindo::GitHandler.git!(%W(-C #{current_git_root_path} commit -m #{commit_message}))
62
62
  Pindo::GitHandler.git!(%W(-C #{current_git_root_path} push origin #{current_branch}))
63
63
  else
@@ -158,7 +158,7 @@ module Pindo
158
158
  current_branch = Pindo::GitHandler.git!(%W(-C #{git_root_dir} rev-parse --abbrev-ref HEAD)).strip
159
159
 
160
160
  # 提交
161
- commit_message = "chore: 更新 .gitignore 规则"
161
+ commit_message = "chore(config): 更新 .gitignore 规则"
162
162
  Pindo::GitHandler.git!(%W(-C #{git_root_dir} commit -m #{commit_message}))
163
163
  Funlog.instance.fancyinfo_success("已自动提交 .gitignore 更改")
164
164
 
@@ -230,7 +230,7 @@ module Pindo
230
230
  begin
231
231
  Pindo::GitHandler.git_addpush_repo(
232
232
  path: config_dir,
233
- message: "Applovin SKAdNetworkItems",
233
+ message: "chore(config): 更新 Applovin SKAdNetworkItems",
234
234
  commit_file_params: ["applovin_client_config.json"]
235
235
  )
236
236
  puts "✓ 配置已保存到远程仓库"
@@ -187,7 +187,7 @@ module Pindo
187
187
  FileUtils.cp(proj_pod_file, File.join(app_config_dir, "Podfile.lock"))
188
188
  Pindo::GitHandler.git_addpush_repo(
189
189
  path: app_config_dir,
190
- message: "#{app_version} backup podfile.lock",
190
+ message: "chore(pod): 备份 #{app_version} Podfile.lock",
191
191
  commit_file_params: ["Podfile.lock"]
192
192
  )
193
193
 
@@ -218,7 +218,7 @@ module Pindo
218
218
 
219
219
  Pindo::GitHandler.git_addpush_repo(
220
220
  path: app_config_dir,
221
- message: "backup #{app_version} output info",
221
+ message: "chore(build): 备份 #{app_version} 的构建输出信息",
222
222
  commit_file_params: ["build_verify.json"]
223
223
  )
224
224
 
@@ -0,0 +1,243 @@
1
+ require 'fileutils'
2
+ require 'find'
3
+ require 'open3'
4
+ require 'tmpdir'
5
+
6
+ module Pindo
7
+ class MacAppResignHelper
8
+ BUNDLE_EXTENSIONS = %w[.app .appex .framework .xpc .bundle .plugin].freeze
9
+ # 需要内嵌描述文件的可执行 bundle(含独立 Bundle ID),
10
+ # .framework / .bundle / .plugin 属于共享代码,不单独内嵌 profile。
11
+ PROFILE_BUNDLE_EXTENSIONS = %w[.app .appex .xpc].freeze
12
+ MACHO_MAGICS = [
13
+ "\xFE\xED\xFA\xCE", "\xCE\xFA\xED\xFE", "\xFE\xED\xFA\xCF",
14
+ "\xCF\xFA\xED\xFE", "\xCA\xFE\xBA\xBE", "\xBE\xBA\xFE\xCA",
15
+ "\xCA\xFE\xBA\xBF", "\xBF\xBA\xFE\xCA"
16
+ ].map(&:b).freeze
17
+
18
+ class << self
19
+ def resign_app(app_path:, bundle_id:, signing_identity:, profiles: [])
20
+ validate_input!(app_path, signing_identity)
21
+ output_path = resigned_path(app_path)
22
+ FileUtils.rm_rf(output_path)
23
+ FileUtils.cp_r(app_path, output_path, preserve: true)
24
+
25
+ resolved_bundle_id = resolve_bundle_id(output_path, bundle_id)
26
+ puts "\n▶ macOS App 重签名"
27
+ puts " 输入: #{app_path}"
28
+ puts " 输出: #{output_path}"
29
+ puts " Bundle ID: #{bundle_id} → #{resolved_bundle_id}" if bundle_id != resolved_bundle_id
30
+ puts " Bundle ID: #{resolved_bundle_id}" if bundle_id == resolved_bundle_id
31
+ puts " 签名身份: #{signing_identity}"
32
+ update_main_bundle_id(output_path, resolved_bundle_id)
33
+
34
+ Dir.mktmpdir('pindo-macos-entitlements-') do |tmp_dir|
35
+ nested_paths = signable_paths(output_path)
36
+ puts " 嵌套组件: #{nested_paths.length} 个"
37
+ nested_paths.each_with_index do |path, index|
38
+ puts " [#{index + 1}/#{nested_paths.length}] 签名: #{relative_path(path, output_path)}"
39
+ sign_component(path, output_path, signing_identity, profiles, tmp_dir, index)
40
+ end
41
+ puts " 签名主 App: #{File.basename(output_path)}"
42
+ sign_component(output_path, output_path, signing_identity, profiles, tmp_dir, 'main',
43
+ bundle_id: resolved_bundle_id)
44
+ end
45
+
46
+ puts " 验证签名: codesign --verify --strict"
47
+ verify!(output_path)
48
+ puts " ✓ macOS App 签名验证通过"
49
+ output_path
50
+ rescue StandardError
51
+ FileUtils.rm_rf(output_path) if output_path && File.exist?(output_path)
52
+ raise
53
+ end
54
+
55
+ private
56
+
57
+ def validate_input!(app_path, signing_identity)
58
+ unless File.directory?(app_path) && app_path.downcase.end_with?('.app')
59
+ raise Informative, "macOS 重签名要求输入 .app 目录: #{app_path}"
60
+ end
61
+ if signing_identity.to_s.strip.empty?
62
+ raise Informative, 'macOS 重签名未找到有效的 signing identity'
63
+ end
64
+ info_plist = File.join(app_path, 'Contents', 'Info.plist')
65
+ raise Informative, "不是有效的 macOS App,缺少 #{info_plist}" unless File.file?(info_plist)
66
+ end
67
+
68
+ def resigned_path(app_path)
69
+ app_path.sub(/\.app$/i, '_resigned.app')
70
+ end
71
+
72
+ def update_main_bundle_id(app_path, bundle_id)
73
+ return if bundle_id.to_s.empty?
74
+
75
+ plist = File.join(app_path, 'Contents', 'Info.plist')
76
+ success = system('/usr/libexec/PlistBuddy', '-c', "Set :CFBundleIdentifier #{bundle_id}", plist)
77
+ raise Informative, "修改 macOS App Bundle ID 失败: #{plist}" unless success
78
+ end
79
+
80
+ def resolve_bundle_id(app_path, bundle_id)
81
+ return bundle_id unless bundle_id.to_s.include?('*')
82
+
83
+ plist = File.join(app_path, 'Contents', 'Info.plist')
84
+ candidate = plist_value(plist, 'CFBundleName')
85
+ candidate = plist_value(plist, 'CFBundleDisplayName') if candidate.to_s.empty?
86
+ candidate = File.basename(app_path, '.app') if candidate.to_s.empty?
87
+ suffix = candidate.to_s.downcase.gsub(/[^a-z0-9-]+/, '-')
88
+ .gsub(/\A-+|-+\z/, '')
89
+ suffix = 'app' if suffix.empty?
90
+ bundle_id.sub('*', suffix)
91
+ end
92
+
93
+ def plist_value(plist, key)
94
+ stdout, _stderr, status = Open3.capture3(
95
+ '/usr/libexec/PlistBuddy', '-c', "Print :#{key}", plist
96
+ )
97
+ status.success? ? stdout.strip : nil
98
+ end
99
+
100
+ # 对单个可签名组件重签名。
101
+ # 对需要描述文件的 bundle(.app/.appex/.xpc):内嵌匹配的 profile,并
102
+ # 优先使用 profile 内的 entitlements(含新的 team-identifier /
103
+ # application-identifier),从根本上避免沿用旧二进制里的旧身份。
104
+ # Developer ID 场景(profiles 为空)回退到从旧二进制导出的 entitlements。
105
+ def sign_component(path, app_root, identity, profiles, tmp_dir, index, bundle_id: nil)
106
+ entitlements =
107
+ if profile_required?(path)
108
+ component_bundle_id = bundle_id || bundle_identifier(path)
109
+ profile_path = matched_profile_path(component_bundle_id, profiles)
110
+ if profile_path
111
+ embed_profile_file(path, profile_path)
112
+ puts " 描述文件: #{File.basename(profile_path)}"
113
+ profile_entitlements(profile_path, tmp_dir, index) ||
114
+ extract_entitlements(path, tmp_dir, index)
115
+ elsif !Array(profiles).empty?
116
+ raise Informative,
117
+ "未找到匹配 #{component_bundle_id} 的描述文件,无法重签名: #{relative_path(path, app_root)}"
118
+ else
119
+ extract_entitlements(path, tmp_dir, index)
120
+ end
121
+ else
122
+ extract_entitlements(path, tmp_dir, index)
123
+ end
124
+ codesign(path, identity, entitlements)
125
+ end
126
+
127
+ # 仅 .app/.appex/.xpc 这类含 Info.plist 的可执行 bundle 需要描述文件。
128
+ def profile_required?(path)
129
+ return false unless File.directory?(path)
130
+ return false unless PROFILE_BUNDLE_EXTENSIONS.include?(File.extname(path).downcase)
131
+
132
+ !bundle_info_plist(path).nil?
133
+ end
134
+
135
+ def bundle_info_plist(bundle_path)
136
+ [
137
+ File.join(bundle_path, 'Contents', 'Info.plist'),
138
+ File.join(bundle_path, 'Info.plist'),
139
+ File.join(bundle_path, 'Resources', 'Info.plist')
140
+ ].find { |candidate| File.file?(candidate) }
141
+ end
142
+
143
+ def bundle_identifier(bundle_path)
144
+ plist = bundle_info_plist(bundle_path)
145
+ plist && plist_value(plist, 'CFBundleIdentifier')
146
+ end
147
+
148
+ def matched_profile_path(bundle_id, profiles)
149
+ profile = Array(profiles).find do |item|
150
+ profile_bundle_id = item['bundle_id'].to_s
151
+ profile_bundle_id == bundle_id ||
152
+ (profile_bundle_id.end_with?('.*') &&
153
+ bundle_id.to_s.start_with?(profile_bundle_id.delete_suffix('*')))
154
+ end
155
+ profile_path = profile && profile['profile_path']
156
+ profile_path if profile_path && File.file?(profile_path)
157
+ end
158
+
159
+ def embed_profile_file(bundle_path, profile_path)
160
+ contents_dir = File.join(bundle_path, 'Contents')
161
+ dest_dir = File.directory?(contents_dir) ? contents_dir : bundle_path
162
+ FileUtils.cp(profile_path, File.join(dest_dir, 'embedded.provisionprofile'))
163
+ end
164
+
165
+ # 从描述文件解出 Entitlements,写成独立 plist 供 codesign --entitlements 使用。
166
+ def profile_entitlements(profile_path, tmp_dir, index)
167
+ decoded, _stderr, status = Open3.capture3(
168
+ '/usr/bin/security', 'cms', '-D', '-i', profile_path
169
+ )
170
+ return nil unless status.success?
171
+
172
+ profile_plist = File.join(tmp_dir, "profile-#{index}.plist")
173
+ File.write(profile_plist, decoded)
174
+ entitlements_xml, _err, extract_status = Open3.capture3(
175
+ '/usr/bin/plutil', '-extract', 'Entitlements', 'xml1', '-o', '-', profile_plist
176
+ )
177
+ return nil unless extract_status.success? && entitlements_xml.include?('<plist')
178
+
179
+ entitlements_path = File.join(tmp_dir, "profile-entitlements-#{index}.plist")
180
+ File.write(entitlements_path, entitlements_xml)
181
+ entitlements_path
182
+ end
183
+
184
+ def relative_path(path, app_path)
185
+ path.delete_prefix("#{app_path}/")
186
+ end
187
+
188
+ # Sign deepest nested code first. The main app is deliberately excluded
189
+ # here and signed last by resign_app.
190
+ def signable_paths(app_path)
191
+ paths = []
192
+ Find.find(app_path) do |path|
193
+ next if path == app_path
194
+ if File.directory?(path) && BUNDLE_EXTENSIONS.include?(File.extname(path).downcase)
195
+ paths << path
196
+ elsif File.file?(path) && (File.extname(path).downcase == '.dylib' || macho?(path))
197
+ paths << path
198
+ end
199
+ end
200
+ paths.uniq.sort_by { |path| [-path.count(File::SEPARATOR), File.directory?(path) ? 1 : 0] }
201
+ end
202
+
203
+ def macho?(path)
204
+ File.open(path, 'rb') { |file| MACHO_MAGICS.include?(file.read(4)) }
205
+ rescue Errno::EACCES, Errno::ENOENT
206
+ false
207
+ end
208
+
209
+ def extract_entitlements(path, tmp_dir, index)
210
+ stdout, _stderr, status = Open3.capture3(
211
+ '/usr/bin/codesign', '-d', '--entitlements', ':-', path
212
+ )
213
+ return nil unless status.success? && stdout.include?('<plist')
214
+
215
+ entitlements_path = File.join(tmp_dir, "#{index}.plist")
216
+ File.write(entitlements_path, stdout)
217
+ entitlements_path
218
+ end
219
+
220
+ def codesign(path, identity, entitlements)
221
+ command = [
222
+ '/usr/bin/codesign', '--force', '--sign', identity,
223
+ '--timestamp', '--options', 'runtime'
224
+ ]
225
+ command.concat(['--entitlements', entitlements]) if entitlements
226
+ command << path
227
+ _stdout, stderr, status = Open3.capture3(*command)
228
+ return if status.success?
229
+
230
+ raise Informative, "codesign 失败: #{path}\n#{stderr.strip}"
231
+ end
232
+
233
+ def verify!(app_path)
234
+ _stdout, stderr, status = Open3.capture3(
235
+ '/usr/bin/codesign', '--verify', '--strict', '--verbose=2', app_path
236
+ )
237
+ return if status.success?
238
+
239
+ raise Informative, "macOS App 签名验证失败: #{app_path}\n#{stderr.strip}"
240
+ end
241
+ end
242
+ end
243
+ end
@@ -48,7 +48,7 @@ module Pindo
48
48
  file.close
49
49
  end
50
50
 
51
- git_addpush_repo(path:app_config_dir, message:"back release info", commit_file_params:["build_verify.json"])
51
+ git_addpush_repo(path:app_config_dir, message:"chore(build): 回写发布信息", commit_file_params:["build_verify.json"])
52
52
 
53
53
  if File.exist?(src_pod_file)
54
54
  FileUtils.cp_r(src_pod_file, File.join(project_dir, "Podfile.lock"))
@@ -64,7 +64,7 @@ module Pindo
64
64
  begin
65
65
  proj_pod_file = File.join(project_dir, "Podfile.lock")
66
66
  FileUtils.cp_r(proj_pod_file, File.join(app_config_dir, "Podfile.lock"))
67
- git_addpush_repo(path:app_config_dir, message:"#{appversion} backup podfile.lock", commit_file_params:["Podfile.lock"])
67
+ git_addpush_repo(path:app_config_dir, message:"chore(pod): 备份 #{appversion} Podfile.lock", commit_file_params:["Podfile.lock"])
68
68
 
69
69
  bytes = File.binread(proj_pod_file)
70
70
  checksum = Digest::MD5.hexdigest(bytes)
@@ -84,7 +84,7 @@ module Pindo
84
84
  file.write(JSON.pretty_generate(build_verify_json))
85
85
  file.close
86
86
  end
87
- git_addpush_repo(path:app_config_dir, message:"backup #{appversion} output info", commit_file_params:["build_verify.json"])
87
+ git_addpush_repo(path:app_config_dir, message:"chore(build): 备份 #{appversion} 的构建输出信息", commit_file_params:["build_verify.json"])
88
88
 
89
89
  rescue => error
90
90
  raise Informative, "保存Podfile.lock 文件失败!!!"
@@ -333,7 +333,7 @@ module Pindo
333
333
  end
334
334
 
335
335
  # 提交到配置仓库
336
- Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "swark authorize success !!!")
336
+ Pindo::GitHandler.git_addpush_repo(path: app_config_dir, message: "chore(config): 完成 swark 授权")
337
337
 
338
338
  Funlog.instance.fancyinfo_success("Swark 授权完成!")
339
339
  return true
@@ -41,13 +41,23 @@ module Pindo
41
41
  # @param item [OptionItem] 参数定义
42
42
  # @return [Object, nil] 提取的值
43
43
  def self.extract_from_argv(argv, item)
44
+ option_names = [item.key.to_s] + item.aliases.map(&:to_s)
45
+
44
46
  if item.boolean?
45
47
  # Boolean 类型:使用 flag?
46
- argv.flag?(item.key.to_s, nil)
48
+ option_names.each do |option_name|
49
+ value = argv.flag?(option_name, nil)
50
+ return value unless value.nil?
51
+ end
47
52
  else
48
53
  # 其他类型:使用 option
49
- argv.option(item.key.to_s, nil)
54
+ option_names.each do |option_name|
55
+ value = argv.option(option_name, nil)
56
+ return value unless value.nil?
57
+ end
50
58
  end
59
+
60
+ nil
51
61
  end
52
62
  end
53
63
  end
@@ -115,9 +115,10 @@ module Pindo
115
115
  file: OptionItem.new(
116
116
  key: :file,
117
117
  name: '上传文件',
118
- description: '指定要上传的文件路径(支持 ipa/apk/app/exe/html',
118
+ description: '指定要上传的文件路径(支持 ipa/apk/app/exe/html;兼容 --ipa 别名)',
119
119
  type: String,
120
120
  env_name: 'PINDO_JPS_FILE',
121
+ aliases: [:ipa],
121
122
  optional: true,
122
123
  example: 'pindo jps upload --file=path/to/app.ipa'
123
124
  ),
@@ -28,6 +28,24 @@ module Pindo
28
28
  example: 'pindo ios autoresign --ipa=path/to/demo.ipa'
29
29
  ),
30
30
 
31
+ app: OptionItem.new(
32
+ key: :app,
33
+ name: 'macOS App',
34
+ description: '指定要重签名的 macOS .app 目录',
35
+ type: String,
36
+ env_name: 'PINDO_APP_FILE',
37
+ optional: true,
38
+ verify_block: proc do |value|
39
+ unless value.downcase.end_with?('.app')
40
+ raise "App 路径格式错误: #{value},必须以 .app 结尾"
41
+ end
42
+ unless File.directory?(value)
43
+ raise "App 目录不存在: #{value}"
44
+ end
45
+ end,
46
+ example: 'pindo ios autoresign --macos --app=path/to/Demo.app'
47
+ ),
48
+
31
49
  apk: OptionItem.new(
32
50
  key: :apk,
33
51
  name: 'APK 文件',
data/lib/pindo/version.rb CHANGED
@@ -6,7 +6,7 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.19.3"
9
+ VERSION = "5.19.5"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
@@ -135,7 +135,10 @@ module Pindo
135
135
  puts "当前pindo版本 #{Pindo::VERSION}"
136
136
  puts "最新版本 #{pindo_latest_version}"
137
137
  puts "要求最低版本 #{pindo_mini_version}"
138
- raise Informative, "请使用 gem install pindo 升级!"
138
+ puts "请使用 gem install pindo 升级!".red
139
+ # 强制升级必须真正中断:用 exit(1)(SystemExit) 绕过上层通用 rescue,
140
+ # 与 RubyGems API 强制升级路径保持一致;可用 PINDO_SKIP_FORCE_UPDATE=1 紧急跳过
141
+ exit(1)
139
142
  end
140
143
  end
141
144
 
@@ -187,7 +190,7 @@ module Pindo
187
190
  # 创建 HTTP 连接,禁用代理
188
191
  http = Net::HTTP.new(uri.host, uri.port, nil, nil)
189
192
  http.use_ssl = true
190
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE # 忽略 SSL 证书验证
193
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER # 校验 SSL 证书(此连接直连 RubyGems、不走代理,公网有效证书可正常校验)
191
194
  http.open_timeout = 3 # 3秒超时
192
195
  http.read_timeout = 3
193
196
 
@@ -535,24 +538,28 @@ module Pindo
535
538
  current_branch = `git rev-parse --abbrev-ref HEAD`.strip
536
539
  current_branch = 'master' if current_branch.empty?
537
540
 
538
- # 获取远程更新
539
- system("git fetch origin --quiet")
541
+ # 获取远程更新(数组式 system 避免 shell 注入;失败即抛出,不静默吞掉)
542
+ system("git", "fetch", "origin", "--quiet") or
543
+ raise "git fetch 失败: #{repo_name}"
540
544
 
541
545
  # 强制重置到远程分支最新状态(覆盖本地修改)
542
- system("git reset --hard origin/#{current_branch} --quiet")
546
+ system("git", "reset", "--hard", "origin/#{current_branch}", "--quiet") or
547
+ raise "git reset 失败: #{repo_name}"
543
548
 
544
- # 设置上游分支
545
- system("git branch --set-upstream-to=origin/#{current_branch} #{current_branch} --quiet 2>/dev/null")
549
+ # 设置上游分支(失败可忽略)
550
+ system("git", "branch", "--set-upstream-to=origin/#{current_branch}", current_branch, "--quiet", err: File::NULL)
546
551
 
547
552
  # 拉取最新更新
548
- system("git pull --quiet")
553
+ system("git", "pull", "--quiet") or
554
+ raise "git pull 失败: #{repo_name}"
549
555
  end
550
556
  else
551
557
  # 仓库不存在,执行 git clone
552
558
  puts " 📦 克隆: #{repo_name}".cyan if ENV['PINDO_DEBUG']
553
559
  parent_dir = File.dirname(dir)
554
560
  FileUtils.mkdir_p(parent_dir) unless File.exist?(parent_dir)
555
- system("git clone #{url} #{dir} --quiet")
561
+ system("git", "clone", url, dir, "--quiet") or
562
+ raise "git clone 失败: #{repo_name} (#{url})"
556
563
  end
557
564
  end
558
565
  end
data/lib/pindo.rb CHANGED
@@ -21,6 +21,18 @@ module Pindo
21
21
  # 解析参数以确定是否是 help 或 version 命令
22
22
  coerced_argv = CLAide::ARGV.coerce(argv)
23
23
 
24
+ # --verbose 等效 PINDO_DEBUG=1:必须在版本检查、参数初始化等任何逻辑之前设置,
25
+ # 否则子命令 initialize 早期执行的调试日志会因 ENV 未就绪而丢失
26
+ ENV['PINDO_DEBUG'] = '1' if coerced_argv.flag?('verbose')
27
+
28
+ # 依赖库 jpsclient 有自己的调试开关 JPS_DEBUG;pindo 处于调试模式时同步打开它。
29
+ # 仅当 PINDO_DEBUG 为真值时才桥接:PINDO_DEBUG=0/false 因 Ruby 字符串恒为真值本会误开,
30
+ # 这里按 jpsclient 的真值规则排除;且不覆盖用户显式设置的 JPS_DEBUG
31
+ pindo_debug_val = ENV['PINDO_DEBUG'].to_s.strip.downcase
32
+ if !pindo_debug_val.empty? && !%w[0 false no].include?(pindo_debug_val) && !ENV['JPS_DEBUG']
33
+ ENV['JPS_DEBUG'] = '1'
34
+ end
35
+
24
36
  # 显示欢迎界面(除了 help/version 命令和特定情况)
25
37
  show_welcome_banner(argv, coerced_argv)
26
38
 
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.19.3
4
+ version: 5.19.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
@@ -109,20 +109,20 @@ dependencies:
109
109
  requirements:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: '2.4'
112
+ version: '2.7'
113
113
  - - ">="
114
114
  - !ruby/object:Gem::Version
115
- version: 2.4.0
115
+ version: 2.7.0
116
116
  type: :runtime
117
117
  prerelease: false
118
118
  version_requirements: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: '2.4'
122
+ version: '2.7'
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
- version: 2.4.0
125
+ version: 2.7.0
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rqrcode
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -406,6 +406,8 @@ files:
406
406
  - lib/pindo/module/cert/pem_helper.rb
407
407
  - lib/pindo/module/cert/provisioning_helper.rb
408
408
  - lib/pindo/module/pgyer/pgyerhelper.rb
409
+ - lib/pindo/module/resign/ipa_resign_adapter.rb
410
+ - lib/pindo/module/resign/mac_app_resign_adapter.rb
409
411
  - lib/pindo/module/task/core/concurrent_execution_strategy.rb
410
412
  - lib/pindo/module/task/core/dependency_checker.rb
411
413
  - lib/pindo/module/task/core/execution_strategy.rb
@@ -477,6 +479,7 @@ files:
477
479
  - lib/pindo/module/xcode/applovin_xcode_helper.rb
478
480
  - lib/pindo/module/xcode/cocoapods_helper.rb
479
481
  - lib/pindo/module/xcode/ipa_resign_helper.rb
482
+ - lib/pindo/module/xcode/mac_app_resign_helper.rb
480
483
  - lib/pindo/module/xcode/res/xcode_res_constant.rb
481
484
  - lib/pindo/module/xcode/res/xcode_res_handler.rb
482
485
  - lib/pindo/module/xcode/xcode_app_config.rb