pindo 5.15.12 → 5.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91922a7d79b8c298a7a6032713b5de285fa0df1c4a429449fbbf718f37c2c937
4
- data.tar.gz: a9279fb764492b9f9deccde186fcd61a2590de82f1c3c9ca7069ce02c022b1e0
3
+ metadata.gz: 83daa2895aaa4a09580db59791876c16ffd319f64711dac735221fd5c75ff4fa
4
+ data.tar.gz: 9a6550b1321ff6a66e8368eee5d982f41fe5aab1f134f6fde50ed73d65b9d286
5
5
  SHA512:
6
- metadata.gz: 11e79e3ae9af23eb59b5d2abc4d06456658c8ecd1d841a0106e8b9833bbba5c86592b5b15025669e800f24da24848ac431898d780163f6b4c5f76bcaf871c01b
7
- data.tar.gz: 885ee1532e0babcdca356261048a6043f2d5f7d480e8cee5eedb245e91ae0282f3e2288aaaacb059d653b012b6bbabfd61992a8e6e166aa89aa9389c19659fb2
6
+ metadata.gz: b2d8500efaf005db9d7b998c6c19a2b6fa04e5012f31b630124f14d408eb18a21eb9594c64305c95c3d75406094e01d6e1c7b8f6de169bf6f42e02438c7c31e5
7
+ data.tar.gz: 0a94ab545c49a3bd383dc324ecd0dd821fd65f024faaec62f96572b7535569e63ed71794aa872eb68e299329542a96c36aa754ac7dfbf524deac4defe387e929
@@ -75,22 +75,8 @@ module Pindo
75
75
  @option_items ||= begin
76
76
  items = []
77
77
 
78
- # 添加 --ipa 参数
79
- items << Pindo::Options::OptionItem.new(
80
- key: :ipa,
81
- description: '指定要重签名的 IPA 文件路径',
82
- type: String,
83
- optional: true,
84
- verify_block: proc do |value|
85
- unless value.end_with?('.ipa')
86
- raise "IPA 文件路径格式错误: #{value},必须以 .ipa 结尾"
87
- end
88
- unless File.exist?(value)
89
- raise "IPA 文件不存在: #{value}"
90
- end
91
- end,
92
- example: 'pindo appstore autoresign --ipa=path/to/demo.ipa'
93
- )
78
+ # 添加工具参数(ToolOptions)
79
+ items.concat(Pindo::Options::ToolOptions.select(:ipa))
94
80
 
95
81
  # 添加证书相关参数(CertOptions)
96
82
  items.concat(Pindo::Options::CertOptions.select(
@@ -327,9 +327,14 @@ module Pindo
327
327
  # 5. 上传和消息发送任务(如果需要)
328
328
  upload_task = nil # 声明变量以便后续绑定任务使用
329
329
  if @args_upload_flag
330
- # 5.1 创建上传任务
330
+ # 检测平台类型(iOS 或 macOS)
331
+ is_macos = Pindo::BuildHelper.share_instance.macos_project?(ios_project_path)
332
+ # JPSUploadTask 支持的文件类型:'ipa' | 'apk' | 'html' | 'mac'
333
+ file_type = is_macos ? 'mac' : 'ipa'
334
+
335
+ # 5.1 创建上传任务(根据平台类型设置 file_type)
331
336
  upload_task = Pindo::TaskSystem::JPSUploadTask.new(
332
- 'ipa',
337
+ file_type,
333
338
  File.join(ios_project_path, 'build'),
334
339
  nil,
335
340
  app_info_obj: config[:app_info_obj],
@@ -437,10 +442,15 @@ module Pindo
437
442
  load_config_file(pindo_project_dir, bundle_id)
438
443
 
439
444
  # 获取 JPS 配置
445
+ # 根据 Xcode 工程类型动态设置 package_type
446
+ # JPSUploadTask 支持的文件类型:'ipa' | 'apk' | 'html' | 'mac'
447
+ is_macos = Pindo::BuildHelper.share_instance.macos_project?(pindo_project_dir)
448
+ package_type = is_macos ? 'mac' : 'ipa'
449
+
440
450
  app_info_obj, workflow_info = PgyerHelper.share_instace.prepare_upload(
441
451
  working_directory: pindo_project_dir,
442
452
  proj_name: @args_proj_name,
443
- package_type: 'ipa'
453
+ package_type: package_type
444
454
  )
445
455
 
446
456
  {
@@ -69,40 +69,53 @@ module Pindo
69
69
  ]
70
70
 
71
71
  def self.option_items
72
- @option_items ||= Pindo::Options::CertOptions.select(
73
- :dev, :adhoc, :develop_id, :build_type,
74
- :platform, :macos, :cert_mode, :storage, :match
75
- )
72
+ @option_items ||= begin
73
+ items = []
74
+
75
+ # 添加工具参数(ToolOptions)
76
+ items.concat(Pindo::Options::ToolOptions.select(:ipa, :deploy))
77
+
78
+ # 添加证书相关参数(CertOptions)
79
+ items.concat(Pindo::Options::CertOptions.select(
80
+ :dev, :adhoc, :develop_id, :build_type,
81
+ :platform, :macos, :cert_mode, :storage, :match
82
+ ))
83
+
84
+ # 合并 JPSOptions
85
+ items.concat(Pindo::Options::JPSOptions.select(:proj, :upload, :send))
86
+
87
+ items
88
+ end
76
89
  end
77
90
 
78
91
  def self.options
79
- [
80
- ['--ipa', '指定要重签名的IPA文件路径'],
81
- ['--deploy', '使用发布证书和bundle id重签名(默认使用开发证书)'],
82
- ['--proj', '指定上传到测试平台的项目名称'],
83
- ['--upload', '上传重签名后的IPA到测试平台'],
84
- ['--send', '上传成功后发送测试通知'],
85
- ].concat(option_items.map { |item| item.to_claide_option }).concat(super)
92
+ option_items.map { |item| item.to_claide_option }.concat(super)
86
93
  end
87
94
 
88
95
 
89
96
  def initialize(argv)
90
97
 
91
- # autoresign 自有参数(手动解析)
92
- @args_ipa_file = argv.shift_argument
93
- @args_set_ipa_name = argv.option('ipa')
94
- @args_deploy_flag = argv.flag?('deploy', false)
95
- @args_upload_flag = argv.flag?('upload', false)
96
- @args_send_flag = argv.flag?('send', false)
97
- @args_proj_name = argv.option('proj')
98
+ # 首先获取位置参数(向后兼容)
99
+ positional_ipa = argv.shift_argument
98
100
 
101
+ # 使用 Options 模块初始化参数
102
+ @options = initialize_options(argv)
103
+
104
+ # 优先使用选项参数,如果没有则使用位置参数
105
+ @args_ipa_file = @options[:ipa] || positional_ipa
106
+
107
+ @args_deploy_flag = @options[:deploy]
108
+
109
+ # JPS 参数(从 options 中获取)
110
+ @args_upload_flag = @options[:upload]
111
+ @args_send_flag = @options[:send]
112
+ @args_proj_name = @options[:proj]
113
+
114
+ # 如果设置了 send,自动启用 upload
99
115
  if @args_send_flag
100
116
  @args_upload_flag = true
101
117
  end
102
118
 
103
- # 使用 Options 模块初始化证书参数
104
- @options = initialize_options(argv)
105
-
106
119
  # 证书快捷参数
107
120
  @args_adhoc_flag = @options[:adhoc]
108
121
  @args_dev_flag = @options[:dev]
@@ -197,13 +210,12 @@ module Pindo
197
210
 
198
211
  # 查找 IPA 文件
199
212
  def find_ipa_file
200
- # 1. 如果通过选项或位置参数指定了 IPA 文件且存在,直接返回
201
- ipa_file_name = @args_set_ipa_name || @args_ipa_file
202
- if ipa_file_name && File.exist?(ipa_file_name)
203
- puts "\n使用指定的 IPA 文件: #{ipa_file_name}"
204
- puts "修改时间: #{File.mtime(ipa_file_name)}"
205
- return ipa_file_name
206
- end
213
+ # 1. 如果通过选项或位置参数指定了 IPA 文件且存在,直接返回
214
+ if @args_ipa_file && File.exist?(@args_ipa_file)
215
+ puts "\n使用指定的 IPA 文件: #{@args_ipa_file}"
216
+ puts "修改时间: #{File.mtime(@args_ipa_file)}"
217
+ return @args_ipa_file
218
+ end
207
219
 
208
220
  # 2. 在当前目录查找最新的非 _resigned 的 IPA
209
221
  current_dir = Dir.pwd
@@ -268,10 +280,13 @@ module Pindo
268
280
  def prepare_jps_config
269
281
  if @args_upload_flag
270
282
  proj_name = @args_proj_name
283
+ # 根据 Xcode 工程类型动态设置 package_type(macOS 工程使用 'mac',否则使用 'ipa')
284
+ is_macos = Pindo::BuildHelper.share_instance.macos_project?(Dir.pwd)
285
+ package_type = is_macos ? 'mac' : 'ipa'
271
286
  app_info_obj, workflow_info = PgyerHelper.share_instace.prepare_upload(
272
287
  working_directory: Dir.pwd,
273
288
  proj_name: proj_name,
274
- package_type: 'ipa'
289
+ package_type: package_type
275
290
  )
276
291
  return [app_info_obj, workflow_info]
277
292
  end
@@ -264,7 +264,7 @@ module Pindo
264
264
  # macOS 工程查找 .app 文件
265
265
  macos_file = find_macos_package(project_dir)
266
266
  if macos_file && confirm_file(macos_file)
267
- tasks << create_upload_task('app', File.dirname(macos_file), macos_file)
267
+ tasks << create_upload_task('mac', File.dirname(macos_file), macos_file)
268
268
  end
269
269
  else
270
270
  # iOS 工程查找 .ipa 文件
@@ -126,7 +126,7 @@ module Pindo
126
126
  when 'ipa' then 'ipa_workflow'
127
127
  when 'apk' then 'apk_workflow'
128
128
  when 'zip' then 'webgl_workflow'
129
- when 'app' then 'macos_workflow'
129
+ when 'mac' then 'macos_workflow'
130
130
  when 'exe' then 'win_workflow'
131
131
  else
132
132
  puts "[PINDO_DEBUG] ❌ 不支持的 package_type: #{package_type}" if ENV['PINDO_DEBUG']
@@ -195,6 +195,8 @@ module Pindo
195
195
  raise Informative, "该项目没有可用的工作流"
196
196
  end
197
197
 
198
+ puts "package_type #{package_type} manage_type: #{manage_type}"
199
+
198
200
  Funlog.instance.fancyinfo_success("获取工作流列表成功,共 #{workflows.size} 个工作流")
199
201
 
200
202
  # 2. 根据 manage_type 或 package_type 过滤工作流
@@ -219,7 +221,7 @@ module Pindo
219
221
  w['packageType'] == 'apk'
220
222
  when 'zip'
221
223
  w['packageType'] == 'zip'
222
- when 'app'
224
+ when 'mac'
223
225
  w['packageType'] == 'mac'
224
226
  when 'exe'
225
227
  w['packageType'] == 'exe'
@@ -236,7 +238,7 @@ module Pindo
236
238
  when 'ipa' then 'iOS IPA'
237
239
  when 'apk' then 'Android APK'
238
240
  when 'zip' then 'WebGL'
239
- when 'app' then 'macOS App'
241
+ when 'mac' then 'macOS App'
240
242
  when 'exe' then 'Windows EXE'
241
243
  else package_type
242
244
  end
@@ -322,8 +324,8 @@ module Pindo
322
324
  w['packageType'] == 'apk'
323
325
  when 'zip'
324
326
  w['packageType'] == 'zip'
325
- when 'app'
326
- w['packageType'] == 'app'
327
+ when 'mac'
328
+ w['packageType'] == 'mac'
327
329
  else
328
330
  true
329
331
  end
@@ -1978,7 +1980,7 @@ module Pindo
1978
1980
  when 'ipa' then 'ipa_workflow'
1979
1981
  when 'apk' then 'apk_workflow'
1980
1982
  when 'zip' then 'webgl_workflow'
1981
- when 'app' then 'macos_workflow'
1983
+ when 'mac' then 'macos_workflow'
1982
1984
  when 'exe' then 'win_workflow'
1983
1985
  else raise Informative, "不支持的 package_type: #{package_type}"
1984
1986
  end
@@ -64,15 +64,24 @@ module Pindo
64
64
  end
65
65
 
66
66
  def find_output
67
- build_path = File.join(@project_path, "build", "*.{ipa,app}")
68
- ipa_files = Dir.glob(build_path)
67
+ build_dir = File.join(@project_path, "build")
69
68
 
70
- if ipa_files.any?
71
- latest_ipa = ipa_files.max_by { |f| File.mtime(f) }
72
- puts " 找到 IPA 文件: #{latest_ipa}"
73
- latest_ipa
69
+ # 先查找 .ipa 文件(iOS)
70
+ ipa_files = Dir.glob(File.join(build_dir, "*.ipa"))
71
+
72
+ # 再查找 .app 目录(macOS)
73
+ app_files = Dir.glob(File.join(build_dir, "*.app")).select { |f| File.directory?(f) }
74
+
75
+ # 合并所有输出文件
76
+ all_outputs = ipa_files + app_files
77
+
78
+ if all_outputs.any?
79
+ latest_output = all_outputs.max_by { |f| File.mtime(f) }
80
+ output_type = latest_output.end_with?('.ipa') ? 'IPA' : 'APP'
81
+ puts " 找到 #{output_type} 文件: #{latest_output}"
82
+ latest_output
74
83
  else
75
- puts " 警告: 未找到 IPA 文件"
84
+ puts " 警告: 未找到 IPA 或 APP 文件"
76
85
  nil
77
86
  end
78
87
  end
@@ -238,22 +247,24 @@ module Pindo
238
247
  )
239
248
  end
240
249
 
241
- # 编译 iOS 工程
250
+ # 编译 iOS/macOS 工程
242
251
  def build_ios_project
243
252
  # Dir.chdir(@project_path) # Removed for thread safety
244
253
 
245
254
  # 使用 XcodeBuildHelper 进行构建
246
- ipa_file = Pindo::XcodeBuildHelper.build_project(
255
+ output_file = Pindo::XcodeBuildHelper.build_project(
247
256
  project_dir: @project_path,
248
257
  icloud_id: nil
249
258
  )
250
259
 
251
- if ipa_file.nil?
252
- raise Informative, "构建失败:未生成 IPA 文件"
260
+ if output_file.nil?
261
+ platform_name = @macos_flag ? "macOS .app" : "iOS .ipa"
262
+ raise Informative, "构建失败:未生成 #{platform_name} 文件"
253
263
  end
254
264
 
255
- puts "构建成功:#{ipa_file}"
256
- ipa_file
265
+ output_type = @macos_flag ? ".app" : ".ipa"
266
+ puts "构建成功 (#{output_type}):#{output_file}"
267
+ output_file
257
268
  end
258
269
  end
259
270
  end
@@ -15,7 +15,7 @@ module Pindo
15
15
  end
16
16
 
17
17
  # 初始化上传任务
18
- # @param file_type [String] 文件类型:'ipa' | 'apk' | 'html' | 'app'
18
+ # @param file_type [String] 文件类型:'ipa' | 'apk' | 'html' | 'mac'
19
19
  # @param upload_path [String] 搜索文件的路径
20
20
  # @param upload_file [String] 要上传的文件(nil 表示自动查找)
21
21
  # @param options [Hash] 选项
@@ -24,7 +24,7 @@ module Pindo
24
24
  # @option options [String] :project_name 项目名称(可选)
25
25
  # @option options [String] :upload_desc 上传描述(可选)
26
26
  def initialize(file_type, upload_path, upload_file, options = {})
27
- @file_type = file_type # 'ipa' | 'apk' | 'html' | 'app'
27
+ @file_type = file_type # 'ipa' | 'apk' | 'html' | 'mac'
28
28
  @upload_path = upload_path # 搜索文件的路径
29
29
  @upload_file = upload_file # 要上传的文件(nil 表示自动查找)
30
30
  @upload_desc = options[:upload_desc] # 上传描述
@@ -39,7 +39,7 @@ module Pindo
39
39
  "上传 APK包"
40
40
  when 'html'
41
41
  "上传 WebGL包"
42
- when 'app'
42
+ when 'mac'
43
43
  "上传 macOS App包"
44
44
  else
45
45
  "上传 #{file_type.upcase}"
@@ -147,7 +147,7 @@ module Pindo
147
147
  File.join(@upload_path, "**", "*.apk")
148
148
  when 'html'
149
149
  File.join(@upload_path, "**", "*.html")
150
- when 'app'
150
+ when 'mac'
151
151
  File.join(@upload_path, "**", "*.app")
152
152
  else
153
153
  raise "不支持的文件类型: #{@file_type}"
@@ -10,7 +10,7 @@ module Pindo
10
10
 
11
11
  # 重签名 IPA 文件的主入口方法
12
12
  # @param ipa_file_path [String] IPA 文件路径(可选,如果为 nil 则自动查找当前目录下的 IPA)
13
- # @param bundle_id [String] 新的 Bundle ID
13
+ # @param bundle_id [String] 新的 Bundle ID(可以是通配符,如 com.xxx.*)
14
14
  # @return [String, nil] 重签名后的 IPA 文件路径,失败返回 nil
15
15
  def resign_ipa(ipa_file_path: nil, bundle_id:)
16
16
  ipa_path = find_ipa_file(ipa_file_path)
@@ -21,17 +21,25 @@ module Pindo
21
21
  end
22
22
 
23
23
  # 准备重签名的 IPA(修改 Bundle ID)
24
- resigned_ipa_path = prepare_resign_ipa(ipa_name: ipa_path, bundle_id: bundle_id)
24
+ resign_result = prepare_resign_ipa(ipa_name: ipa_path, bundle_id: bundle_id)
25
25
 
26
- if resigned_ipa_path.nil?
26
+ if resign_result.nil? || resign_result['resigned_ipa_path'].nil?
27
27
  puts "准备重签名 IPA 失败"
28
28
  return nil
29
29
  end
30
30
 
31
+ puts "\n重签名信息:"
32
+ puts " 原始 Bundle ID: #{resign_result['old_bundle_id']}"
33
+ puts " 新的 Bundle ID: #{resign_result['new_bundle_id']}"
34
+ puts " Display Name: #{resign_result['display_name']}"
35
+
31
36
  # 使用新证书签名
32
- sign_ipa_with_new_cert(ipa_name: resigned_ipa_path, bundle_id: bundle_id)
37
+ sign_ipa_with_new_cert(
38
+ ipa_name: resign_result['resigned_ipa_path'],
39
+ bundle_id: resign_result['new_bundle_id']
40
+ )
33
41
 
34
- resigned_ipa_path
42
+ resign_result['resigned_ipa_path']
35
43
  end
36
44
 
37
45
  private
@@ -57,8 +65,14 @@ module Pindo
57
65
 
58
66
  # 准备重签名的 IPA(修改 Bundle ID)
59
67
  # @param ipa_name [String] 原始 IPA 文件路径
60
- # @param bundle_id [String] 新的 Bundle ID
61
- # @return [String, nil] 准备好的 IPA 文件路径
68
+ # @param bundle_id [String] 新的 Bundle ID(可以是通配符,如 com.xxx.*)
69
+ # @return [Hash, nil] 包含重签名信息的 Hash:
70
+ # {
71
+ # 'resigned_ipa_path' => 重签名后的 IPA 路径,
72
+ # 'old_bundle_id' => 原始 Bundle ID,
73
+ # 'new_bundle_id' => 新的 Bundle ID(通配符时是计算后的实际值),
74
+ # 'display_name' => Display Name
75
+ # }
62
76
  def prepare_resign_ipa(ipa_name:, bundle_id:)
63
77
  ipa_base_name = File.basename(ipa_name, ".ipa")
64
78
  ipa_dir = File.dirname(ipa_name)
@@ -91,8 +105,29 @@ module Pindo
91
105
  end
92
106
  end
93
107
 
108
+ # 读取原始 Info.plist 信息
109
+ info_plist_path = File.join(modify_content_path, "Info.plist")
110
+ old_bundle_id = read_plist_value(info_plist_path, "CFBundleIdentifier")
111
+ display_name = read_plist_value(info_plist_path, "CFBundleDisplayName")
112
+
113
+ # 如果 Display Name 为空,使用 Bundle Name
114
+ if display_name.nil? || display_name.strip.empty?
115
+ display_name = read_plist_value(info_plist_path, "CFBundleName") || "app"
116
+ end
117
+
118
+ # 计算实际的新 Bundle ID
119
+ new_bundle_id = if bundle_id.include?('*')
120
+ # 通配符证书:从 Display Name 生成 Bundle ID
121
+ generated_id = generate_bundle_id_from_wildcard(bundle_id, display_name)
122
+ puts "通配符证书:#{bundle_id} + 「#{display_name}」 → #{generated_id}"
123
+ generated_id
124
+ else
125
+ # 普通证书:直接使用指定的 Bundle ID
126
+ bundle_id
127
+ end
128
+
94
129
  # 修改 Info.plist 中的 Bundle ID
95
- modify_ipa_info_plist(modify_content_path: modify_content_path, bundle_id: bundle_id)
130
+ modify_ipa_info_plist(modify_content_path: modify_content_path, bundle_id: new_bundle_id)
96
131
 
97
132
  # 重新打包 IPA
98
133
  current_dir = Dir.pwd
@@ -107,12 +142,18 @@ module Pindo
107
142
  # 清理临时目录
108
143
  FileUtils.rm_rf(tmp_dir) if File.exist?(resign_ipa_full_name)
109
144
 
110
- resign_ipa_full_name
145
+ # 返回详细信息
146
+ {
147
+ 'resigned_ipa_path' => resign_ipa_full_name,
148
+ 'old_bundle_id' => old_bundle_id.strip,
149
+ 'new_bundle_id' => new_bundle_id,
150
+ 'display_name' => display_name.strip
151
+ }
111
152
  end
112
153
 
113
154
  # 修改 IPA 中的 Info.plist 文件
114
155
  # @param modify_content_path [String] .app 目录路径
115
- # @param bundle_id [String] 新的 Bundle ID
156
+ # @param bundle_id [String] 新的 Bundle ID(实际的值,不包含通配符)
116
157
  def modify_ipa_info_plist(modify_content_path:, bundle_id:)
117
158
  main_info_plist = File.join(modify_content_path, "Info.plist")
118
159
  puts main_info_plist
@@ -128,13 +169,21 @@ module Pindo
128
169
  puts old_bundle_id_command
129
170
  old_bundle_id = ""
130
171
  IO.popen(old_bundle_id_command) { |f| old_bundle_id = f.gets }
131
- puts old_bundle_id
172
+ old_bundle_id = old_bundle_id.strip
173
+ puts "旧 Bundle ID: #{old_bundle_id}"
132
174
 
133
175
  # 修改主应用的 Bundle ID
134
- exchange_bundleid_command = '/usr/local/bin/PlistBuddy -c "Set :CFBundleIdentifier ' + bundle_id + '" ' + main_info_plist
176
+ new_main_bundle_id = bundle_id
177
+
178
+ exchange_bundleid_command = '/usr/local/bin/PlistBuddy -c "Set :CFBundleIdentifier ' + new_main_bundle_id + '" ' + main_info_plist
135
179
  puts exchange_bundleid_command
136
180
  system exchange_bundleid_command
137
181
 
182
+ # 如果 Bundle ID 发生变化,更新 URL Schemes
183
+ if old_bundle_id != new_main_bundle_id
184
+ update_url_schemes(main_info_plist, old_bundle_id, new_main_bundle_id)
185
+ end
186
+
138
187
  # 修改插件扩展的 Bundle ID
139
188
  plugin_path = File.join(modify_content_path, "PlugIns")
140
189
  if File.exist?(plugin_path)
@@ -180,7 +229,7 @@ module Pindo
180
229
 
181
230
  # 使用新证书签名 IPA
182
231
  # @param ipa_name [String] IPA 文件路径
183
- # @param bundle_id [String] Bundle ID
232
+ # @param bundle_id [String] Bundle ID(实际的值,不包含通配符)
184
233
  def sign_ipa_with_new_cert(ipa_name:, bundle_id:)
185
234
  require 'pindo/config/pindoconfig'
186
235
 
@@ -191,7 +240,8 @@ module Pindo
191
240
 
192
241
  # 调用 Sigh::Resign 进行重签名
193
242
  # resign(ipa, signing_identity, provisioning_profiles, entitlements, version, display_name, short_version, bundle_version, new_bundle_id, use_app_entitlements, keychain_path)
194
- Sigh::Resign.resign(ipa_name, bundle_id_signing_identity, profile_dict, nil, nil, nil, nil, nil, bundle_id, true, nil)
243
+ # 传入 nil resign.sh 使用 provisioning profile 中的 Bundle ID
244
+ Sigh::Resign.resign(ipa_name, bundle_id_signing_identity, profile_dict, nil, nil, nil, nil, nil, nil, true, nil)
195
245
  end
196
246
 
197
247
  # 构建重签名用的 profile 字典
@@ -205,6 +255,118 @@ module Pindo
205
255
  resign_dict
206
256
  end
207
257
 
258
+ # 读取 plist 文件的值
259
+ # @param plist_path [String] plist 文件路径
260
+ # @param key [String] 要读取的键名
261
+ # @return [String, nil] 键对应的值
262
+ def read_plist_value(plist_path, key)
263
+ # 确保 PlistBuddy 可用
264
+ unless File.exist?("/usr/local/bin/PlistBuddy")
265
+ system 'ln -s /usr/libexec/PlistBuddy /usr/local/bin/PlistBuddy'
266
+ end
267
+
268
+ command = "/usr/local/bin/PlistBuddy -c \"Print :#{key}\" \"#{plist_path}\" 2>/dev/null"
269
+ value = `#{command}`.strip
270
+ value.empty? ? nil : value
271
+ end
272
+
273
+ # 从通配符 Bundle ID 和 Display Name 生成实际的 Bundle ID
274
+ # @param wildcard_bundle_id [String] 通配符 Bundle ID(如 com.xxx.*)
275
+ # @param display_name [String] Display Name
276
+ # @return [String] 实际的 Bundle ID(如 com.xxx.myapp)
277
+ def generate_bundle_id_from_wildcard(wildcard_bundle_id, display_name)
278
+ # 去除通配符部分:com.xxx.* → com.xxx
279
+ prefix = wildcard_bundle_id.gsub(/\.\*$/, '')
280
+
281
+ # 清理 Display Name:转小写、去除特殊字符
282
+ sanitized_name = sanitize_display_name(display_name)
283
+
284
+ # 组合:com.xxx + . + myapp = com.xxx.myapp
285
+ "#{prefix}.#{sanitized_name}"
286
+ end
287
+
288
+ # 清理 Display Name:转小写、去除特殊字符
289
+ # @param display_name [String] 原始 Display Name(如 "My App!")
290
+ # @return [String] 清理后的名称(如 "myapp")
291
+ def sanitize_display_name(display_name)
292
+ # 转小写
293
+ sanitized = display_name.downcase
294
+
295
+ # 替换空格为空字符串
296
+ sanitized = sanitized.gsub(/\s+/, '')
297
+
298
+ # 只保留字母、数字、下划线
299
+ sanitized = sanitized.gsub(/[^a-z0-9_]/, '')
300
+
301
+ # 如果清理后为空,使用默认值
302
+ sanitized.empty? ? 'app' : sanitized
303
+ end
304
+
305
+ # 更新 Info.plist 中的 URL Schemes(基于 Bundle ID 生成的 schemes)
306
+ # 使用全文替换方式:将旧 scheme 值替换为新 scheme 值
307
+ # @param plist_path [String] Info.plist 文件路径
308
+ # @param old_bundle_id [String] 旧的 Bundle ID
309
+ # @param new_bundle_id [String] 新的 Bundle ID
310
+ def update_url_schemes(plist_path, old_bundle_id, new_bundle_id)
311
+ # 生成基于 Bundle ID 的 URL Scheme(去除非字母数字字符,转小写)
312
+ old_scheme = old_bundle_id.gsub(/[^a-zA-Z0-9]/, '').downcase
313
+ new_scheme = new_bundle_id.gsub(/[^a-zA-Z0-9]/, '').downcase
314
+
315
+ # 如果 scheme 相同,无需替换
316
+ if old_scheme == new_scheme
317
+ puts "\n✓ URL Scheme 无变化,跳过更新"
318
+ return
319
+ end
320
+
321
+ puts "\n更新 URL Schemes:"
322
+ puts " 旧 Scheme: #{old_scheme} (基于 #{old_bundle_id})"
323
+ puts " 新 Scheme: #{new_scheme} (基于 #{new_bundle_id})"
324
+
325
+ # 将 binary plist 转换为 XML 格式
326
+ convert_command = "plutil -convert xml1 \"#{plist_path}\" 2>/dev/null"
327
+ unless system(convert_command)
328
+ puts " ✗ 转换 plist 为 XML 失败"
329
+ return
330
+ end
331
+
332
+ # 检查文件中是否存在旧的 scheme
333
+ check_command = "grep -q \"#{old_scheme}\" \"#{plist_path}\""
334
+ scheme_exists = system(check_command)
335
+
336
+ unless scheme_exists
337
+ puts " ✓ 没有找到需要更新的 URL Scheme"
338
+ # 转换回 binary 格式
339
+ convert_back_command = "plutil -convert binary1 \"#{plist_path}\" 2>/dev/null"
340
+ system(convert_back_command)
341
+ return
342
+ end
343
+
344
+ # 使用 sed 进行全文替换
345
+ # 使用临时文件避免 sed -i 在不同系统上的兼容性问题
346
+ temp_file = "#{plist_path}.tmp"
347
+ sed_command = "sed 's/#{old_scheme}/#{new_scheme}/g' \"#{plist_path}\" > \"#{temp_file}\" && mv \"#{temp_file}\" \"#{plist_path}\""
348
+
349
+ if system(sed_command)
350
+ # 统计替换次数
351
+ count_command = "grep -o \"#{new_scheme}\" \"#{plist_path}\" | wc -l"
352
+ replace_count = `#{count_command}`.strip.to_i
353
+
354
+ # 转换回 binary 格式
355
+ convert_back_command = "plutil -convert binary1 \"#{plist_path}\" 2>/dev/null"
356
+ system(convert_back_command)
357
+
358
+ puts " ✓ 已替换 #{replace_count} 处 URL Scheme: #{old_scheme} → #{new_scheme}"
359
+ else
360
+ puts " ✗ 替换 URL Scheme 失败"
361
+ # 清理临时文件
362
+ FileUtils.rm_f(temp_file) if File.exist?(temp_file)
363
+
364
+ # 转换回 binary 格式(即使失败也要转回)
365
+ convert_back_command = "plutil -convert binary1 \"#{plist_path}\" 2>/dev/null"
366
+ system(convert_back_command)
367
+ end
368
+ end
369
+
208
370
  end
209
371
  end
210
372
  end
@@ -471,11 +471,26 @@ module Pindo
471
471
  config = FastlaneCore::Configuration.create(Gym::Options.available_options, gym_options)
472
472
  Gym::Manager.new.work(config)
473
473
 
474
- # 查找生成的 IPA 文件
475
- build_path = File.join(build_dir, "*.ipa")
476
- ipa_file = Dir.glob(build_path).max_by { |f| File.mtime(f) }
474
+ # 检测平台类型
475
+ require 'xcodeproj'
476
+ project_obj = Xcodeproj::Project.open(project_fullname)
477
+ project_build_platform = project_obj.root_object.build_configuration_list.get_setting("SDKROOT")["Release"]
478
+ is_macos = !project_build_platform.nil? && project_build_platform.eql?("macosx")
479
+
480
+ # 根据平台查找输出文件
481
+ if is_macos
482
+ # macOS 平台查找 .app 文件
483
+ build_path = File.join(build_dir, "*.app")
484
+ output_file = Dir.glob(build_path).select { |f| File.directory?(f) }.max_by { |f| File.mtime(f) }
485
+ puts "macOS 平台: 查找 .app 文件" if output_file
486
+ else
487
+ # iOS 平台查找 .ipa 文件
488
+ build_path = File.join(build_dir, "*.ipa")
489
+ output_file = Dir.glob(build_path).max_by { |f| File.mtime(f) }
490
+ puts "iOS 平台: 查找 .ipa 文件" if output_file
491
+ end
477
492
 
478
- ipa_file
493
+ output_file
479
494
  end
480
495
 
481
496
  # 获取 Gym 构建参数
@@ -566,6 +581,9 @@ module Pindo
566
581
  if !project_build_platform.nil? && project_build_platform.eql?("macosx")
567
582
  values[:output_name] = nil
568
583
  values[:destination] = "generic/platform=macosx"
584
+ # macOS 应用不需要导出 .ipa/.pkg,跳过打包步骤
585
+ values[:skip_package_ipa] = true
586
+ values[:skip_package_pkg] = true
569
587
  end
570
588
 
571
589
  # 如果使用 CocoaPods
@@ -584,6 +602,57 @@ module Pindo
584
602
  values[:export_options][:iCloudContainerEnvironment] = icloud_env
585
603
  end
586
604
 
605
+ # # 为所有 target 配置 provisioning profile(包括主应用和扩展)
606
+ # provisioning_profiles = {}
607
+ # project_obj.targets.each do |target|
608
+ # # 获取 Release 配置(export 时使用的配置)
609
+ # release_config = target.build_configurations.find { |config| config.name == 'Release' } || target.build_configurations.first
610
+
611
+ # # 获取 target 的 Bundle ID(从 resolved_build_setting 获取实际值)
612
+ # bundle_id = release_config.resolve_build_setting('PRODUCT_BUNDLE_IDENTIFIER')
613
+
614
+ # # 如果 resolve_build_setting 返回 nil,尝试直接获取
615
+ # if bundle_id.nil?
616
+ # bundle_id = release_config.build_settings['PRODUCT_BUNDLE_IDENTIFIER']
617
+ # end
618
+
619
+ # puts "[DEBUG] Target: #{target.name}, Bundle ID: #{bundle_id}"
620
+
621
+ # # 跳过没有 Bundle ID 的 target
622
+ # next if bundle_id.nil? || bundle_id.to_s.empty? || bundle_id.to_s.include?('$(')
623
+
624
+ # # 获取 target 的 provisioning profile specifier
625
+ # profile_specifier = release_config.build_settings['PROVISIONING_PROFILE_SPECIFIER']
626
+
627
+ # # 如果没有找到,尝试查找 SDK 特定的配置(如 PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*])
628
+ # if profile_specifier.nil? || profile_specifier.to_s.empty?
629
+ # release_config.build_settings.each do |key, value|
630
+ # if key.to_s.start_with?('PROVISIONING_PROFILE_SPECIFIER')
631
+ # profile_specifier = value
632
+ # puts "[DEBUG] Found SDK-specific profile: #{key} = #{value}"
633
+ # break
634
+ # end
635
+ # end
636
+ # end
637
+
638
+ # puts "[DEBUG] Profile Specifier: #{profile_specifier}"
639
+
640
+ # # 跳过没有 provisioning profile 的 target
641
+ # next if profile_specifier.nil? || profile_specifier.to_s.empty?
642
+
643
+ # # 添加到 provisioning profiles 映射
644
+ # provisioning_profiles[bundle_id.to_s] = profile_specifier.to_s
645
+ # puts "[DEBUG] Added: #{bundle_id} => #{profile_specifier}"
646
+ # end
647
+
648
+ # puts "[DEBUG] Total provisioning profiles: #{provisioning_profiles.inspect}"
649
+
650
+ # # 只有在找到 provisioning profiles 时才添加配置
651
+ # if !provisioning_profiles.empty?
652
+ # values[:export_options][:provisioningProfiles] = provisioning_profiles
653
+ # puts "[DEBUG] Setting export_options[:provisioningProfiles] = #{provisioning_profiles.inspect}"
654
+ # end
655
+
587
656
  values
588
657
  end
589
658
 
@@ -0,0 +1,44 @@
1
+ require 'pindo/options/core/option_item'
2
+ require 'pindo/options/groups/option_group'
3
+
4
+ module Pindo
5
+ module Options
6
+ # 工具参数组
7
+ # 定义通用工具类参数(如重签名、上传等工具的参数)
8
+ module ToolOptions
9
+ extend OptionGroup
10
+
11
+ def self.all_options
12
+ @all_options ||= {
13
+ ipa: OptionItem.new(
14
+ key: :ipa,
15
+ name: 'IPA 文件',
16
+ description: '指定要重签名的 IPA 文件路径',
17
+ type: String,
18
+ env_name: 'PINDO_IPA_FILE',
19
+ optional: true,
20
+ verify_block: proc do |value|
21
+ unless value.end_with?('.ipa')
22
+ raise "IPA 文件路径格式错误: #{value},必须以 .ipa 结尾"
23
+ end
24
+ unless File.exist?(value)
25
+ raise "IPA 文件不存在: #{value}"
26
+ end
27
+ end,
28
+ example: 'pindo ios autoresign --ipa=path/to/demo.ipa'
29
+ ),
30
+
31
+ deploy: OptionItem.new(
32
+ key: :deploy,
33
+ name: '使用发布证书',
34
+ description: '使用发布证书和 bundle id 重签名(默认使用开发证书)',
35
+ type: OptionItem::Boolean,
36
+ env_name: 'PINDO_DEPLOY',
37
+ optional: true,
38
+ example: 'pindo ios autoresign --deploy'
39
+ )
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -14,3 +14,4 @@ require 'pindo/options/groups/build_options'
14
14
  require 'pindo/options/groups/jps_options'
15
15
  require 'pindo/options/groups/unity_options'
16
16
  require 'pindo/options/groups/git_options'
17
+ require 'pindo/options/groups/tool_options'
data/lib/pindo/version.rb CHANGED
@@ -6,7 +6,7 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.15.12"
9
+ VERSION = "5.16.0"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pindo
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.15.12
4
+ version: 5.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: claide
@@ -486,6 +486,7 @@ files:
486
486
  - lib/pindo/options/groups/jps_options.rb
487
487
  - lib/pindo/options/groups/option_group.rb
488
488
  - lib/pindo/options/groups/task_options.rb
489
+ - lib/pindo/options/groups/tool_options.rb
489
490
  - lib/pindo/options/groups/unity_options.rb
490
491
  - lib/pindo/options/helpers/bundleid_selector.rb
491
492
  - lib/pindo/options/helpers/git_constants.rb
@@ -509,7 +510,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
509
510
  - !ruby/object:Gem::Version
510
511
  version: 3.0.0
511
512
  requirements: []
512
- rubygems_version: 3.6.3
513
+ rubygems_version: 4.0.3
513
514
  specification_version: 4
514
515
  summary: easy work
515
516
  test_files: []