pindo 5.13.5 → 5.13.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,196 +4,327 @@ require 'find'
4
4
  require 'fileutils'
5
5
  require 'pindo/base/executable'
6
6
  require 'pindo/config/ios_config_parser'
7
- require 'pindo/module/xcode/ipa_resign_helper'
7
+ require 'pindo/config/build_info_manager'
8
+ require 'pindo/base/pindocontext'
9
+ require 'pindo/module/task/task_manager'
10
+ require 'pindo/module/task/model/ipa_local_resign_task'
11
+ require 'pindo/module/task/model/jps_upload_task'
12
+ require 'pindo/options/options'
8
13
 
9
14
  module Pindo
10
- class Command
11
- class Appstore < Command
12
- class Autoresign < Appstore
15
+ class Command
16
+ class Appstore < Command
17
+ class Autoresign < Appstore
13
18
 
14
- include Appselect
19
+ include Appselect
15
20
 
16
- self.summary = '发布阶段ipa包重签名及上传'
21
+ # 命令的简要说明
22
+ self.summary = 'IPA 包重签名及上传到 JPS 测试平台'
17
23
 
18
- self.description = <<-DESC
19
- 发布阶段ipa包重签名及上传。
24
+ # 启用缓存机制
25
+ def self.use_cache?
26
+ true
27
+ end
20
28
 
21
- 支持功能:
29
+ # 命令的详细说明
30
+ self.description = <<-DESC
31
+ 对 IPA 文件进行重签名,并可选上传到 JPS 测试平台。
22
32
 
23
- * IPA包重签名
33
+ 支持功能:
24
34
 
25
- * 上传到测试平台
35
+ * IPA 文件重签名(支持 dev/adhoc 证书)
26
36
 
27
- * 发送测试通知
37
+ * 自动查找或手动指定 IPA 文件
28
38
 
29
- 使用示例:
39
+ * 上传到 JPS 测试平台
30
40
 
31
- $ pindo appstore autoresign demo.ipa # 仅重签名
41
+ * 发送测试通知到群组
32
42
 
33
- $ pindo appstore autoresign demo.ipa --upload # 重签名并上传
43
+ * 支持 dev/adhoc 构建类型
34
44
 
35
- $ pindo appstore autoresign demo.ipa --send # 重签名、上传并发送通知
36
- DESC
45
+ 使用示例:
37
46
 
38
- self.arguments = [
39
- CLAide::Argument.new('path/to/ipa', true),
40
- ]
47
+ $ pindo appstore autoresign # 自动查找并重签名(adhoc)
41
48
 
42
- def self.options
43
- [
44
- ['--ipa', 'iap file name.'],
45
- ['--proj', '指定哪个项目(忽略大小写空格等等字符): --proj=\"prancksoundv4\"'],
46
- ['--upload', '编译完成后是否上传ipa到JPS: --upload'],
47
- ['--send', '上传到JPS之后是否发送测试信息: --send'],
48
- ['--ipa', '强制指定重签名的ipa包,用法: --ipa=path/to/demo.ipa'],
49
- ['--dev', '使用dev证书,默认使用adhoc证书: --dev'],
50
- ['--test', '使用测试的bundle id, 默认是用发布bundle id,用法:--test'],
51
- ].concat(super)
52
- end
49
+ $ pindo appstore autoresign demo.ipa # 重签名指定 IPA(adhoc)
50
+
51
+ $ pindo appstore autoresign --ipa=demo.ipa # 使用选项指定 IPA
53
52
 
53
+ $ pindo appstore autoresign --type=dev # 使用 dev 证书和测试 Bundle ID
54
54
 
55
- def initialize(argv)
55
+ $ pindo appstore autoresign --upload # 重签名并上传
56
56
 
57
- @args_ipa_file = argv.shift_argument
58
- @args_set_ipa_name = argv.option('ipa')
59
- @args_dev_flag = argv.flag?('dev', false)
60
- @args_test_flag = argv.flag?('test', false)
57
+ $ pindo appstore autoresign --send # 重签名、上传并发送通知
61
58
 
62
- @args_upload_flag = argv.flag?('upload', false)
63
- @args_send_flag = argv.flag?('send', false)
64
- @args_proj_name = argv.option('proj')
59
+ $ pindo appstore autoresign --proj="My App" # 指定项目名称
60
+ DESC
65
61
 
66
- if @args_send_flag
67
- @args_upload_flag = true
68
- end
62
+ # 位置参数(向后兼容)
63
+ self.arguments = [
64
+ CLAide::Argument.new('path/to/ipa', true)
65
+ ]
69
66
 
70
- super
71
- @additional_args = argv.remainder!
67
+ # 定义此命令使用的参数项
68
+ def self.option_items
69
+ @option_items ||= begin
70
+ items = []
71
+
72
+ # 添加 --ipa 参数
73
+ items << Pindo::Options::OptionItem.new(
74
+ key: :ipa,
75
+ description: '指定要重签名的 IPA 文件路径',
76
+ type: String,
77
+ optional: true,
78
+ verify_block: proc do |value|
79
+ unless value.end_with?('.ipa')
80
+ raise "IPA 文件路径格式错误: #{value},必须以 .ipa 结尾"
81
+ end
82
+ unless File.exist?(value)
83
+ raise "IPA 文件不存在: #{value}"
72
84
  end
85
+ end,
86
+ example: 'pindo appstore autoresign --ipa=path/to/demo.ipa'
87
+ )
88
+
89
+ # 添加 --type 参数
90
+ items << Pindo::Options::OptionItem.new(
91
+ key: :type,
92
+ description: '构建类型(dev/adhoc,默认 adhoc)。dev=使用dev证书+测试BundleID,adhoc=使用adhoc证书+发布BundleID',
93
+ type: String,
94
+ default_value: 'adhoc',
95
+ optional: true,
96
+ verify_block: proc do |value|
97
+ valid_types = ['dev', 'adhoc']
98
+ unless valid_types.include?(value.to_s.downcase)
99
+ raise "构建类型错误: #{value},必须是 dev 或 adhoc"
100
+ end
101
+ end,
102
+ example: 'pindo appstore autoresign --type=dev'
103
+ )
73
104
 
105
+ # 合并 JPSOptions
106
+ items.concat(Pindo::Options::JPSOptions.select(:proj, :upload, :send))
74
107
 
75
- def run
76
-
77
- ipa_file_name = @args_ipa_file
78
- if !@args_set_ipa_name.nil?
79
- ipa_file_name = @args_set_ipa_name
80
- end
81
-
82
- if !ipa_file_name.nil? && File.exist?(ipa_file_name)
83
- else
84
- current_dir = Dir.pwd
85
- # File.basename(f).include?("_resigned") ? Time.local(0, 0, 0) : File.mtime(f)
86
- ipa_file_name = Dir.glob(File.join(current_dir, "/*.ipa")).max_by {|f| File.basename(f).include?("_resigned") ? Time.local(0, 1, 1) : File.mtime(f)}
87
-
88
- end
89
-
90
- if !ipa_file_name.nil? && File.exist?(ipa_file_name)
91
- puts
92
- puts "正在重签名的ipa: #{ipa_file_name}"
93
- puts "时间: #{File.mtime(ipa_file_name)}"
94
- puts
95
- else
96
- return
97
- end
98
-
99
-
100
- mainapp_bundleid = nil
101
-
102
- if @args_test_flag
103
- mainapp_bundleid = get_selected_dev_bundleid()
104
- else
105
- mainapp_bundleid = get_selected_deploy_bundleid()
106
- end
107
- puts "mainapp_bundleid: #{mainapp_bundleid}"
108
-
109
- # 拉取应用配置
110
- require 'pindo/config/build_info_manager'
111
- Pindo::BuildInfoManager.share_instance.pull_appconfig_with_reponame(
112
- repo_name: mainapp_bundleid,
113
- target_dir: Dir.pwd
114
- )
115
-
116
- app_info_obj = nil
117
- workflow_info = nil
118
- if @args_upload_flag || @args_send_flag
119
- # 传入 package_type 获取 workflow_info
120
- app_info_obj, workflow_info = PgyerHelper.share_instace.prepare_upload(
121
- working_directory: Dir.pwd,
122
- proj_name: @args_proj_name,
123
- package_type: 'ipa'
124
- )
125
- end
126
-
127
-
128
- if !ipa_file_name.nil?
129
- # 加载配置获取 Bundle ID
130
- config_file = File.join(Dir.pwd, "config.json")
131
- config_parser = Pindo::IosConfigParser.instance
132
- config_parser.load_config(config_file: config_file)
133
- bundle_id = config_parser.bundle_id
134
-
135
- if bundle_id.nil? || bundle_id.empty?
136
- raise Informative, "无法从配置文件中获取 Bundle ID"
137
- end
138
-
139
- # 确定构建类型并安装证书
140
- build_type = @args_dev_flag ? 'dev' : 'adhoc'
141
- cert_args = build_type == 'adhoc' ? ['--build_type=adhoc'] : []
142
- Pindo::Command::Appstore::Cert::run(cert_args)
143
-
144
- # 使用 IpaResignHelper 进行重签名
145
- ipa_file_upload = Pindo::IpaResignHelper.resign_ipa(
146
- ipa_file_path: ipa_file_name,
147
- bundle_id: bundle_id
148
- )
149
-
150
- if ipa_file_upload.nil?
151
- raise Informative, "重签名失败"
152
- end
153
-
154
- puts "\n重签名成功!"
155
- puts "重签名后的 IPA: #{ipa_file_upload}"
156
- end
157
-
158
- if !ipa_file_upload.nil? && !app_info_obj.nil?
159
-
160
- if !ipa_file_upload.nil? && !app_info_obj.nil?
161
- description = "提交包重签名"
162
- result_data = PgyerHelper.share_instace.start_upload(
163
- app_info_obj: app_info_obj,
164
- ipa_file_upload: ipa_file_upload,
165
- description: description,
166
- workflow_info: workflow_info
167
- )
168
- if !result_data.nil? && !result_data["data"].nil? && !result_data["data"]["id"].nil?
169
- PgyerHelper.share_instace.print_app_version_info(
170
- app_info_obj: app_info_obj,
171
- app_version_info_obj: result_data["data"]
172
- )
173
- # 始终发送给自己
174
- PgyerHelper.share_instace.send_apptest_msg(
175
- app_info_obj: app_info_obj,
176
- app_version_info_obj: result_data["data"],
177
- receiveType: "self"
178
- )
179
-
180
- # 如果有 --send 参数,额外发送到测试群
181
- if @args_send_flag
182
- PgyerHelper.share_instace.send_apptest_msg(
183
- app_info_obj: app_info_obj,
184
- app_version_info_obj: result_data["data"],
185
- chatEnv: "DevTest",
186
- receiveType: "chat"
187
- )
188
- end
189
- end
190
- end
191
-
192
- end
108
+ items
109
+ end
110
+ end
193
111
 
112
+ # 命令的选项列表
113
+ def self.options
114
+ option_items.map { |item| item.to_claide_option }.concat(super)
115
+ end
194
116
 
195
- end
117
+ def initialize(argv)
118
+ # 首先获取位置参数(向后兼容)
119
+ positional_ipa = argv.shift_argument
120
+
121
+ # 使用 Options 模块初始化参数
122
+ @options = initialize_options(argv)
123
+
124
+ # 优先使用选项参数,如果没有则使用位置参数
125
+ @args_ipa_file = @options[:ipa] || positional_ipa
126
+ @args_build_type = (@options[:type] || 'adhoc').downcase
127
+ @args_upload_flag = @options[:send] || @options[:upload]
128
+ @args_send_flag = @options[:send]
129
+ @args_proj_name = @options[:proj]
130
+
131
+ super
132
+ @additional_args = argv.remainder!
133
+ end
134
+
135
+ def validate!
136
+ super
137
+ end
138
+
139
+ def run
140
+ begin
141
+ # 加载 JPS 配置(如果存在)
142
+ context = Pindo::PindoContext.instance
143
+ context.load_and_apply_jps_config(Dir.pwd)
144
+
145
+ # 1. 查找或指定 IPA 文件
146
+ ipa_file_path = find_ipa_file()
147
+
148
+ unless ipa_file_path && File.exist?(ipa_file_path)
149
+ raise Informative, "未找到需要重签名的 IPA 文件"
196
150
  end
151
+
152
+ # 2. 选择 Bundle ID(根据 build_type)
153
+ bundle_id = select_bundle_id()
154
+
155
+ # 3. 拉取应用配置
156
+ pull_app_config(bundle_id)
157
+
158
+ # 4. 准备 JPS 配置(如果需要上传)
159
+ app_info_obj, workflow_info = prepare_jps_config()
160
+
161
+ # 5. 计算重签名后的 IPA 文件名
162
+ resigned_ipa_file = calculate_resigned_ipa_name(ipa_file_path)
163
+
164
+ # 6. 创建任务
165
+ tasks = create_resign_tasks(
166
+ ipa_file_path: ipa_file_path,
167
+ resigned_ipa_file: resigned_ipa_file,
168
+ bundle_id: bundle_id,
169
+ app_info_obj: app_info_obj,
170
+ workflow_info: workflow_info
171
+ )
172
+
173
+ # 7. 执行任务
174
+ task_manager = Pindo::TaskSystem::TaskManager.instance
175
+ task_manager.clear_all
176
+ tasks.each { |task| task_manager.add_task(task) }
177
+ task_manager.start
178
+
179
+ ensure
180
+ # 清除命令状态(如果启用了缓存,这里会自动保存)
181
+ Pindo::Options::GlobalOptionsState.instance.clear
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ # 查找 IPA 文件
188
+ def find_ipa_file
189
+ # 1. 如果通过选项或位置参数指定了 IPA 文件且存在,直接返回
190
+ if @args_ipa_file && File.exist?(@args_ipa_file)
191
+ puts "\n使用指定的 IPA 文件: #{@args_ipa_file}"
192
+ puts "修改时间: #{File.mtime(@args_ipa_file)}"
193
+ return @args_ipa_file
194
+ end
195
+
196
+ # 2. 在当前目录查找最新的非 _resigned 的 IPA
197
+ current_dir = Dir.pwd
198
+ ipa_files = Dir.glob(File.join(current_dir, "*.ipa"))
199
+
200
+ # 过滤掉 _resigned.ipa 并找到最新的
201
+ ipa_file = ipa_files.max_by do |f|
202
+ File.basename(f).include?("_resigned") ? Time.local(0, 1, 1) : File.mtime(f)
203
+ end
204
+
205
+ if ipa_file && File.exist?(ipa_file)
206
+ puts "\n自动找到 IPA 文件: #{ipa_file}"
207
+ puts "修改时间: #{File.mtime(ipa_file)}"
208
+ return ipa_file
209
+ end
210
+
211
+ # 3. 如果没有找到,从终端输入
212
+ puts "\n⚠️ 未找到 IPA 文件"
213
+ puts "请输入 IPA 文件路径(支持相对路径或绝对路径):"
214
+
215
+ ipa_path = ask("IPA 文件路径: ") do |q|
216
+ q.validate = /\.ipa$/i
217
+ q.responses[:not_valid] = "❌ 请输入有效的 IPA 文件路径(必须以 .ipa 结尾)"
218
+ end
219
+
220
+ # 去除首尾空格
221
+ ipa_path = ipa_path.strip if ipa_path
222
+
223
+ # 验证文件是否存在
224
+ if ipa_path && !ipa_path.empty?
225
+ if File.exist?(ipa_path)
226
+ puts "✓ 使用输入的 IPA 文件: #{ipa_path}"
227
+ puts " 修改时间: #{File.mtime(ipa_path)}"
228
+ return ipa_path
229
+ else
230
+ puts "❌ IPA 文件不存在: #{ipa_path}"
231
+ end
232
+ end
233
+
234
+ nil
235
+ end
236
+
237
+ # 选择 Bundle ID(根据 build_type)
238
+ def select_bundle_id
239
+ bundle_id = if @args_build_type == 'dev'
240
+ # dev 类型使用测试 Bundle ID
241
+ get_selected_dev_bundleid()
242
+ else
243
+ # adhoc 类型使用发布 Bundle ID
244
+ get_selected_deploy_bundleid()
245
+ end
246
+
247
+ puts "目标 Bundle ID: #{bundle_id}"
248
+ bundle_id
249
+ end
250
+
251
+ # 拉取应用配置
252
+ def pull_app_config(bundle_id)
253
+ puts "\n拉取应用配置: #{bundle_id}"
254
+
255
+ require 'pindo/config/build_info_manager'
256
+ success = Pindo::BuildInfoManager.share_instance.pull_appconfig_with_reponame(
257
+ repo_name: bundle_id,
258
+ target_dir: Dir.pwd
259
+ )
260
+
261
+ unless success
262
+ raise Informative, "拉取配置失败: #{bundle_id}"
263
+ end
197
264
  end
265
+
266
+ # 准备 JPS 配置
267
+ def prepare_jps_config
268
+ if @args_upload_flag || @args_send_flag
269
+ # 只使用命令行参数的 proj_name,不从 config.json 获取
270
+ # skip_config_file: true 会跳过从 config.json 读取项目名称
271
+ # 这样如果用户没有指定 --proj,会直接提示用户输入
272
+ proj_name = @args_proj_name
273
+
274
+ app_info_obj, workflow_info = PgyerHelper.share_instace.get_adhoc_upload_info(
275
+ working_directory: Dir.pwd,
276
+ package_type: 'ipa',
277
+ proj_name: proj_name,
278
+ skip_config_file: true
279
+ )
280
+ return [app_info_obj, workflow_info]
281
+ end
282
+
283
+ [nil, nil]
284
+ end
285
+
286
+ # 计算重签名后的 IPA 文件名
287
+ # 例如:SlimeSavior.ipa → SlimeSavior_resigned.ipa
288
+ def calculate_resigned_ipa_name(ipa_file_path)
289
+ ipa_file_path.gsub(/\.ipa$/, '_resigned.ipa')
290
+ end
291
+
292
+ # 创建任务列表
293
+ def create_resign_tasks(ipa_file_path:, resigned_ipa_file:, bundle_id:, app_info_obj:, workflow_info:)
294
+ tasks = []
295
+
296
+ # 1. 重签名任务
297
+ resign_task = Pindo::TaskSystem::IpaLocalResignTask.new(
298
+ Dir.pwd, # ipa_path(搜索路径)
299
+ ipa_file_path, # ipa_file(明确指定)
300
+ bundle_id, # bundle_id
301
+ build_type: @args_build_type, # dev 或 adhoc
302
+ project_dir: Dir.pwd
303
+ )
304
+ tasks << resign_task
305
+
306
+ # 2. 上传任务(仅在需要时创建)
307
+ if @args_upload_flag && app_info_obj
308
+ upload_task = Pindo::TaskSystem::JPSUploadTask.new(
309
+ 'ipa',
310
+ Dir.pwd, # upload_path(搜索路径)
311
+ resigned_ipa_file, # upload_file(明确指定重签名后的文件)
312
+ app_info_obj: app_info_obj,
313
+ workflow_info: workflow_info,
314
+ project_name: @args_proj_name,
315
+ upload_desc: "重签名包", # 上传描述
316
+ context: {
317
+ send_to_chat: @args_send_flag
318
+ },
319
+ dependencies: [resign_task.id] # 依赖重签名任务
320
+ )
321
+ tasks << upload_task
322
+ end
323
+
324
+ tasks
325
+ end
326
+
327
+ end
198
328
  end
329
+ end
199
330
  end
@@ -0,0 +1,92 @@
1
+ require 'fileutils'
2
+ require 'pindo/module/xcode/xcode_build_helper'
3
+
4
+ module Pindo
5
+ class Command
6
+ class Ios < Command
7
+ class Fixproj < Ios
8
+
9
+ # 命令的简要说明
10
+ self.summary = '修复 Xcode 项目问题'
11
+
12
+ # 命令的详细说明,包含用法示例
13
+ self.description = <<-DESC
14
+ 修复 Xcode 项目的常见问题。
15
+
16
+ 支持功能:
17
+
18
+ * 修复 Xcode 16 的 linker flags 问题
19
+
20
+ * 删除 Unity-iPhone 项目中的 Firebase Crashlytics 脚本
21
+
22
+ 使用示例:
23
+
24
+ $ pindo ios fixproj # 在当前目录修复项目
25
+
26
+ $ pindo ios fixproj path/to/project # 修复指定目录的项目
27
+ DESC
28
+
29
+ # 定义位置参数
30
+ self.arguments = [
31
+ CLAide::Argument.new('path/to/project', true)
32
+ ]
33
+
34
+ # 命令的选项列表
35
+ def self.options
36
+ [
37
+ # 暂无选项
38
+ ].concat(super)
39
+ end
40
+
41
+ def initialize(argv)
42
+ # 获取位置参数(项目路径)
43
+ @project_path = argv.shift_argument
44
+
45
+ super(argv)
46
+ end
47
+
48
+ def validate!
49
+ super
50
+ end
51
+
52
+ def run
53
+ # 确定项目路径
54
+ project_dir = @project_path && !@project_path.empty? ? @project_path : Dir.pwd
55
+ project_dir = File.expand_path(project_dir)
56
+
57
+ unless File.directory?(project_dir)
58
+ raise Informative, "项目路径不存在: #{project_dir}"
59
+ end
60
+
61
+ puts "\n开始修复 Xcode 项目问题..."
62
+ puts "项目路径: #{project_dir}\n"
63
+
64
+ # 1. 修复 Xcode 16 的 linker flags 问题
65
+ puts "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
66
+ puts " 修复 Xcode 16 Linker Flags"
67
+ puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
68
+
69
+ begin
70
+ Pindo::XcodeBuildHelper.fix_xcode16_linker_flags(project_dir: project_dir)
71
+ rescue => e
72
+ puts " ⚠️ 修复 Xcode 16 linker flags 失败: #{e.message}"
73
+ end
74
+
75
+ # 2. 删除 Unity-iPhone 项目中的 Firebase Crashlytics 脚本
76
+ puts "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
77
+ puts " 删除 Firebase Crashlytics 脚本"
78
+ puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
79
+
80
+ begin
81
+ Pindo::XcodeBuildHelper.delete_libtarget_firebase_shell(project_dir)
82
+ rescue => e
83
+ puts " ⚠️ 删除 Firebase Crashlytics 脚本失败: #{e.message}"
84
+ end
85
+
86
+ puts "\n✅ 项目修复完成!\n"
87
+ end
88
+
89
+ end
90
+ end
91
+ end
92
+ end
@@ -7,6 +7,7 @@ require 'pindo/command/ios/applovin'
7
7
  require 'pindo/command/ios/podlint'
8
8
  require 'pindo/command/ios/podpush'
9
9
  require 'pindo/command/ios/podupdate'
10
+ require 'pindo/command/ios/fixproj'
10
11
 
11
12
  module Pindo
12
13
  class Command
@@ -394,8 +394,8 @@ module Pindo
394
394
  app_info_obj: app_info_obj,
395
395
  workflow_info: workflow_info,
396
396
  project_name: @args_proj_name,
397
+ upload_desc: @args_upload_desc,
397
398
  context: {
398
- upload_desc: @args_upload_desc,
399
399
  send_to_chat: @args_send_flag
400
400
  }
401
401
  )