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 +4 -4
- data/lib/pindo/command/appstore/autoresign.rb +2 -16
- data/lib/pindo/command/ios/autobuild.rb +13 -3
- data/lib/pindo/command/ios/autoresign.rb +44 -29
- data/lib/pindo/command/jps/upload.rb +1 -1
- data/lib/pindo/module/pgyer/pgyerhelper.rb +8 -6
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +24 -13
- data/lib/pindo/module/task/model/jps/jps_upload_task.rb +4 -4
- data/lib/pindo/module/xcode/ipa_resign_helper.rb +176 -14
- data/lib/pindo/module/xcode/xcode_build_helper.rb +73 -4
- data/lib/pindo/options/groups/tool_options.rb +44 -0
- data/lib/pindo/options/options.rb +1 -0
- data/lib/pindo/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83daa2895aaa4a09580db59791876c16ffd319f64711dac735221fd5c75ff4fa
|
|
4
|
+
data.tar.gz: 9a6550b1321ff6a66e8368eee5d982f41fe5aab1f134f6fde50ed73d65b9d286
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
#
|
|
79
|
-
items
|
|
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
|
-
#
|
|
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
|
-
|
|
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:
|
|
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 ||=
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
puts "
|
|
204
|
-
|
|
205
|
-
|
|
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:
|
|
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('
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
326
|
-
w['packageType'] == '
|
|
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 '
|
|
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
|
-
|
|
68
|
-
ipa_files = Dir.glob(build_path)
|
|
67
|
+
build_dir = File.join(@project_path, "build")
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
255
|
+
output_file = Pindo::XcodeBuildHelper.build_project(
|
|
247
256
|
project_dir: @project_path,
|
|
248
257
|
icloud_id: nil
|
|
249
258
|
)
|
|
250
259
|
|
|
251
|
-
if
|
|
252
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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' | '
|
|
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' | '
|
|
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 '
|
|
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 '
|
|
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
|
-
|
|
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(
|
|
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 [
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
172
|
+
old_bundle_id = old_bundle_id.strip
|
|
173
|
+
puts "旧 Bundle ID: #{old_bundle_id}"
|
|
132
174
|
|
|
133
175
|
# 修改主应用的 Bundle ID
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
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
|
data/lib/pindo/version.rb
CHANGED
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.
|
|
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:
|
|
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:
|
|
513
|
+
rubygems_version: 4.0.3
|
|
513
514
|
specification_version: 4
|
|
514
515
|
summary: easy work
|
|
515
516
|
test_files: []
|