pindo 5.13.1 → 5.13.3

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/git_handler.rb +692 -0
  3. data/lib/pindo/command/android/autobuild.rb +2 -2
  4. data/lib/pindo/command/appstore/adhocbuild.rb +256 -311
  5. data/lib/pindo/command/appstore/autobuild.rb +203 -0
  6. data/lib/pindo/command/appstore/autoresign.rb +35 -17
  7. data/lib/pindo/command/appstore/bundleid.rb +120 -0
  8. data/lib/pindo/command/appstore/cert.rb +212 -0
  9. data/lib/pindo/command/appstore/configproj.rb +81 -0
  10. data/lib/pindo/command/{deploy → appstore}/getitcinfo.rb +76 -91
  11. data/lib/pindo/command/appstore/iap.rb +788 -24
  12. data/lib/pindo/command/appstore/initconfig.rb +105 -0
  13. data/lib/pindo/command/appstore/itcapp.rb +95 -13
  14. data/lib/pindo/command/{deploy → appstore}/itcinfo.rb +90 -118
  15. data/lib/pindo/command/appstore/pem.rb +136 -0
  16. data/lib/pindo/command/appstore/pullconfig.rb +99 -0
  17. data/lib/pindo/command/appstore/quswark.rb +87 -0
  18. data/lib/pindo/command/appstore/quswauth.rb +67 -0
  19. data/lib/pindo/command/appstore/tag.rb +77 -0
  20. data/lib/pindo/command/appstore.rb +13 -1
  21. data/lib/pindo/command/env/quarkenv.rb +11 -13
  22. data/lib/pindo/command/env/swarkenv.rb +11 -16
  23. data/lib/pindo/command/ios/applovin.rb +24 -182
  24. data/lib/pindo/command/ios/autobuild.rb +64 -43
  25. data/lib/pindo/command/ios/autoresign.rb +34 -19
  26. data/lib/pindo/command/ios/build.rb +9 -6
  27. data/lib/pindo/command/ios/cert.rb +27 -20
  28. data/lib/pindo/command/ios/podupdate.rb +6 -37
  29. data/lib/pindo/command/jps/upload.rb +3 -3
  30. data/lib/pindo/command/unity/autobuild.rb +2 -2
  31. data/lib/pindo/command/utils/clearcert.rb +2 -17
  32. data/lib/pindo/command/{deploy → utils}/fabric.rb +13 -13
  33. data/lib/pindo/command/utils/renewcert.rb +62 -38
  34. data/lib/pindo/command/utils/renewproj.rb +0 -3
  35. data/lib/pindo/command/{deploy → utils}/updateconfig.rb +6 -7
  36. data/lib/pindo/command/utils.rb +2 -0
  37. data/lib/pindo/command/web/autobuild.rb +2 -2
  38. data/lib/pindo/command.rb +30 -3
  39. data/lib/pindo/config/build_info_manager.rb +176 -0
  40. data/lib/pindo/config/ios_config_parser.rb +404 -0
  41. data/lib/pindo/module/android/android_config_helper.rb +9 -5
  42. data/lib/pindo/module/appstore/bundleid_helper.rb +349 -0
  43. data/lib/pindo/module/appstore/itcapp_helper.rb +228 -0
  44. data/lib/pindo/module/build/build_helper.rb +12 -0
  45. data/lib/pindo/module/build/swark_helper.rb +116 -77
  46. data/lib/pindo/module/cert/cert_helper.rb +74 -0
  47. data/lib/pindo/module/cert/pem_helper.rb +72 -0
  48. data/lib/pindo/module/cert/{xcodecerthelper.rb → xcode_cert_helper.rb} +211 -6
  49. data/lib/pindo/module/pgyer/pgyerhelper.rb +13 -5
  50. data/lib/pindo/module/task/model/appstore/appstore_task.rb +18 -0
  51. data/lib/pindo/module/task/model/appstore/appstore_upload_ipa_task.rb +151 -0
  52. data/lib/pindo/module/task/model/appstore/appstore_upload_metadata_task.rb +250 -0
  53. data/lib/pindo/module/task/model/appstore/appstore_upload_screenshot_task.rb +276 -0
  54. data/lib/pindo/module/task/model/build/android_build_adhoc_task.rb +210 -0
  55. data/lib/pindo/module/task/model/build/{android_dev_build_task.rb → android_build_dev_task.rb} +2 -2
  56. data/lib/pindo/module/task/model/build/android_build_gplay_task.rb +210 -0
  57. data/lib/pindo/module/task/model/build/android_build_task.rb +13 -0
  58. data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +342 -0
  59. data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +341 -0
  60. data/lib/pindo/module/task/model/build/{ios_dev_build_task.rb → ios_build_dev_task.rb} +40 -59
  61. data/lib/pindo/module/task/model/build/ios_build_task.rb +23 -0
  62. data/lib/pindo/module/task/model/build/{web_dev_build_task.rb → web_build_dev_task.rb} +1 -1
  63. data/lib/pindo/module/task/model/build_task.rb +15 -12
  64. data/lib/pindo/module/task/model/jps_resign_task.rb +185 -0
  65. data/lib/pindo/module/task/model/{upload_task.rb → jps_upload_task.rb} +3 -3
  66. data/lib/pindo/module/task/model/unity_export_task.rb +3 -1
  67. data/lib/pindo/module/unity/unity_helper.rb +2 -1
  68. data/lib/pindo/module/xcode/applovin_xcode_helper.rb +271 -0
  69. data/lib/pindo/module/xcode/cocoapods_helper.rb +153 -0
  70. data/lib/pindo/module/xcode/ipa_resign_helper.rb +210 -0
  71. data/lib/pindo/module/xcode/{xcodeappconfig.rb → xcode_app_config.rb} +79 -0
  72. data/lib/pindo/module/xcode/xcode_build_config.rb +152 -17
  73. data/lib/pindo/module/xcode/xcode_build_helper.rb +151 -1
  74. data/lib/pindo/module/xcode/xcode_swark_helper.rb +341 -0
  75. data/lib/pindo/options/core/global_options_state.rb +268 -0
  76. data/lib/pindo/options/core/option_configuration.rb +206 -0
  77. data/lib/pindo/options/core/option_initializer.rb +51 -0
  78. data/lib/pindo/options/core/option_item.rb +144 -0
  79. data/lib/pindo/options/core/option_value_parser.rb +54 -0
  80. data/lib/pindo/options/groups/build_options.rb +60 -0
  81. data/lib/pindo/options/groups/jps_options.rb +70 -0
  82. data/lib/pindo/options/groups/option_group.rb +73 -0
  83. data/lib/pindo/options/helpers/bundleid_selector.rb +103 -0
  84. data/lib/pindo/options/options.rb +14 -0
  85. data/lib/pindo/version.rb +1 -1
  86. metadata +51 -40
  87. data/lib/pindo/command/appstore/import.rb +0 -259
  88. data/lib/pindo/command/deploy/build.rb +0 -250
  89. data/lib/pindo/command/deploy/bundleid.rb +0 -259
  90. data/lib/pindo/command/deploy/cert.rb +0 -202
  91. data/lib/pindo/command/deploy/check.rb +0 -93
  92. data/lib/pindo/command/deploy/configproj.rb +0 -120
  93. data/lib/pindo/command/deploy/confusecode.rb +0 -262
  94. data/lib/pindo/command/deploy/confuseproj.rb +0 -122
  95. data/lib/pindo/command/deploy/iap.rb +0 -826
  96. data/lib/pindo/command/deploy/initconfig.rb +0 -138
  97. data/lib/pindo/command/deploy/itcapp.rb +0 -146
  98. data/lib/pindo/command/deploy/pem.rb +0 -55
  99. data/lib/pindo/command/deploy/pullconfig.rb +0 -56
  100. data/lib/pindo/command/deploy/pushconfig.rb +0 -93
  101. data/lib/pindo/command/deploy/quswark.rb +0 -156
  102. data/lib/pindo/command/deploy/quswauth.rb +0 -76
  103. data/lib/pindo/command/deploy/reportbug.rb +0 -145
  104. data/lib/pindo/command/deploy/resign.rb +0 -300
  105. data/lib/pindo/command/deploy/tag.rb +0 -108
  106. data/lib/pindo/command/deploy/uploadipa.rb +0 -73
  107. data/lib/pindo/command/deploy.rb +0 -42
  108. data/lib/pindo/command/dev/autobuild.rb +0 -117
  109. data/lib/pindo/command/dev/build.rb +0 -94
  110. data/lib/pindo/command/dev/debug.rb +0 -112
  111. data/lib/pindo/module/task/model/build/android_release_build_task.rb +0 -29
  112. data/lib/pindo/module/task/model/build/ios_adhoc_build_task.rb +0 -53
  113. data/lib/pindo/module/task/model/build/ios_release_build_task.rb +0 -53
  114. data/lib/pindo/options/appconfigoptions.rb +0 -24
  115. data/lib/pindo/options/deployoptions.rb +0 -372
@@ -0,0 +1,271 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+ require 'faraday'
4
+ require 'xcodeproj'
5
+ require 'pindo/base/git_handler'
6
+ require 'pindo/config/pindoconfig'
7
+
8
+ module Pindo
9
+ # Applovin Xcode 辅助类
10
+ # 封装 Applovin 相关的操作,包括:
11
+ # - 获取 SKAdNetwork IDs
12
+ # - 更新 Info.plist 文件
13
+ # - 运行 Applovin 安装脚本
14
+ class ApplovinXcodeHelper
15
+
16
+ class << self
17
+ # 更新 Xcode 工程的 Applovin 配置
18
+ # @param project_dir [String] 项目目录,默认为当前目录
19
+ # @param appstore_mode [Boolean] 是否为 AppStore 模式
20
+ # @param install_script [Boolean] 是否运行安装脚本
21
+ # @param upload_config [Boolean] 是否保存配置到远程
22
+ # @return [Boolean] 是否成功
23
+ def update_applovin_config(project_dir: nil, appstore_mode: false, install_script: false, upload_config: false)
24
+ project_dir ||= Dir.pwd
25
+
26
+ # 1. 获取 SKAdNetwork IDs
27
+ skadnetwork_ids = fetch_skadnetwork_ids(upload_config: upload_config)
28
+
29
+ if skadnetwork_ids.nil? || skadnetwork_ids.empty?
30
+ puts "⚠ 未能获取 SKAdNetwork IDs"
31
+ return false
32
+ end
33
+
34
+ puts "✓ 获取到 #{skadnetwork_ids.size} 条 SKAdNetwork IDs"
35
+
36
+ # 2. 更新 Info.plist
37
+ update_info_plist(project_dir: project_dir, skadnetwork_ids: skadnetwork_ids)
38
+
39
+ # 3. 运行安装脚本(可选)
40
+ if install_script
41
+ run_applovin_install_script(project_dir: project_dir, appstore_mode: appstore_mode)
42
+ end
43
+
44
+ true
45
+ end
46
+
47
+ # 获取 SKAdNetwork IDs
48
+ # @param upload_config [Boolean] 是否保存配置到远程
49
+ # @return [Array<String>] SKAdNetwork IDs 数组
50
+ def fetch_skadnetwork_ids(upload_config: false)
51
+ skadnetwork_ids = []
52
+
53
+ puts "正在请求 Applovin 数据..."
54
+
55
+ # 1. 请求 Applovin SKAdNetwork IDs
56
+ applovin_data = request_applovin_skadnetwork_ids
57
+ if applovin_data && applovin_data["skadnetwork_ids"]
58
+ applovin_data["skadnetwork_ids"].each do |network_id|
59
+ skadnetwork_ids << network_id["skadnetwork_id"]
60
+ end
61
+ end
62
+
63
+ # 2. 请求 Applovin Mediation SKAdNetwork IDs
64
+ mediation_data = request_applovin_mediation_skadnetwork_ids
65
+ if mediation_data && mediation_data["mediationSKAdnetworkIdsSpecs"]
66
+ mediation_data["mediationSKAdnetworkIdsSpecs"].each do |media_item|
67
+ skadnetwork_ids += media_item["skAdnetworkIds"]
68
+ end
69
+ end
70
+
71
+ # 3. 去重
72
+ skadnetwork_ids = skadnetwork_ids.uniq
73
+
74
+ puts "✓ 从网络获取到 #{skadnetwork_ids.size} 条数据"
75
+
76
+ # 4. 如果数据太少,从配置文件读取
77
+ if skadnetwork_ids.size <= 50
78
+ puts "网络数据太少,从缓存中获取..."
79
+ cached_ids = load_cached_skadnetwork_ids
80
+ skadnetwork_ids += cached_ids
81
+ skadnetwork_ids = skadnetwork_ids.uniq
82
+ puts "✓ 从缓存获取到 #{skadnetwork_ids.size} 条数据"
83
+ elsif upload_config
84
+ # 5. 保存到配置文件
85
+ save_skadnetwork_ids_to_config(skadnetwork_ids)
86
+ end
87
+
88
+ skadnetwork_ids
89
+ end
90
+
91
+ # 更新 Info.plist 文件
92
+ # @param project_dir [String] 项目目录
93
+ # @param skadnetwork_ids [Array<String>] SKAdNetwork IDs
94
+ def update_info_plist(project_dir:, skadnetwork_ids:)
95
+ puts "正在更新 Xcode Info.plist..."
96
+
97
+ # 1. 查找 Info.plist 文件
98
+ plist_file = find_info_plist(project_dir: project_dir)
99
+
100
+ unless File.exist?(plist_file)
101
+ puts "⚠ 未找到 Info.plist 文件: #{plist_file}"
102
+ return false
103
+ end
104
+
105
+ # 2. 读取 Info.plist
106
+ info_plist_dict = Xcodeproj::Plist.read_from_path(plist_file)
107
+
108
+ # 3. 更新配置
109
+ info_plist_dict["NSAdvertisingAttributionReportEndpoint"] ||= "https://postbacks-app.com"
110
+
111
+ if !info_plist_dict.has_key?("NSAppTransportSecurity")
112
+ info_plist_dict["NSAppTransportSecurity"] = {}
113
+ info_plist_dict["NSAppTransportSecurity"]["NSAllowsArbitraryLoads"] = true
114
+ end
115
+
116
+ # 4. 更新 SKAdNetworkItems
117
+ if skadnetwork_ids && skadnetwork_ids.size > 0
118
+ info_plist_dict["SKAdNetworkItems"] = []
119
+ skadnetwork_ids.each do |network_id|
120
+ item = { "SKAdNetworkIdentifier" => network_id }
121
+ info_plist_dict["SKAdNetworkItems"] << item
122
+ end
123
+ end
124
+
125
+ # 5. 写回 Info.plist
126
+ Xcodeproj::Plist.write_to_path(info_plist_dict, plist_file)
127
+
128
+ puts "✓ Info.plist 更新完成"
129
+ true
130
+ end
131
+
132
+ # 运行 Applovin 安装脚本
133
+ # @param project_dir [String] 项目目录
134
+ # @param appstore_mode [Boolean] 是否为 AppStore 模式
135
+ def run_applovin_install_script(project_dir:, appstore_mode: false)
136
+ xcodeproj_file = Dir.glob(File.join(project_dir, "/*.xcodeproj")).max_by { |f| File.mtime(f) }
137
+ return unless xcodeproj_file && File.exist?(xcodeproj_file)
138
+
139
+ pindo_common_dir = Pindoconfig.instance.pindo_common_configdir
140
+
141
+ # 选择脚本文件
142
+ script_name = appstore_mode ? "AppLovinQualityServiceSetup-ios.rb" : "AppLovinQualityServiceSetup-ios-dev.rb"
143
+ source_script = File.join(pindo_common_dir, script_name)
144
+
145
+ return unless File.exist?(source_script)
146
+
147
+ # 复制脚本到项目目录
148
+ project_script = File.join(project_dir, "AppLovinQualityServiceSetup-ios.rb")
149
+ FileUtils.cp_r(source_script, project_script)
150
+
151
+ # 运行脚本
152
+ puts "正在运行 Applovin 安装脚本..."
153
+ system("ruby #{project_script}")
154
+ puts "✓ Applovin 安装脚本执行完成"
155
+ end
156
+
157
+ private
158
+
159
+ # 请求 Applovin SKAdNetwork IDs
160
+ # @return [Hash, nil] JSON 数据
161
+ def request_applovin_skadnetwork_ids
162
+ begin
163
+ url = "https://dash.applovin.com/docs/v1/skadnetwork_ids.json"
164
+ conn = Faraday.new
165
+ response = conn.get do |req|
166
+ req.url url
167
+ req.headers['Content-Type'] = 'application/json'
168
+ end
169
+
170
+ JSON.parse(response.body)
171
+ rescue => e
172
+ puts "⚠ 请求 Applovin SKAdNetwork IDs 失败: #{e.message}"
173
+ nil
174
+ end
175
+ end
176
+
177
+ # 请求 Applovin Mediation SKAdNetwork IDs
178
+ # @return [Hash, nil] JSON 数据
179
+ def request_applovin_mediation_skadnetwork_ids
180
+ begin
181
+ url = "https://dash.applovin.com/docs/v1/mediation_sk_adnetwork_ids"
182
+ conn = Faraday.new
183
+ response = conn.get do |req|
184
+ req.url url
185
+ req.headers['Content-Type'] = 'application/json'
186
+ end
187
+
188
+ JSON.parse(response.body)
189
+ rescue => e
190
+ puts "⚠ 请求 Applovin Mediation SKAdNetwork IDs 失败: #{e.message}"
191
+ nil
192
+ end
193
+ end
194
+
195
+ # 从配置文件加载缓存的 SKAdNetwork IDs
196
+ # @return [Array<String>] SKAdNetwork IDs
197
+ def load_cached_skadnetwork_ids
198
+ config_file = File.join(Pindoconfig.instance.pindo_common_configdir, "applovin_client_config.json")
199
+ return [] unless File.exist?(config_file)
200
+
201
+ begin
202
+ config_json = JSON.parse(File.read(config_file))
203
+ config_json["SKAdNetworkItems"] || []
204
+ rescue => e
205
+ puts "⚠ 读取缓存配置失败: #{e.message}"
206
+ []
207
+ end
208
+ end
209
+
210
+ # 保存 SKAdNetwork IDs 到配置文件
211
+ # @param skadnetwork_ids [Array<String>] SKAdNetwork IDs
212
+ def save_skadnetwork_ids_to_config(skadnetwork_ids)
213
+ config_dir = Pindoconfig.instance.pindo_common_configdir
214
+ config_file = File.join(config_dir, "applovin_client_config.json")
215
+
216
+ # 读取原有配置
217
+ origin_json = {}
218
+ if File.exist?(config_file)
219
+ origin_json = JSON.parse(File.read(config_file))
220
+ end
221
+
222
+ # 更新配置
223
+ origin_json["SKAdNetworkItems"] = skadnetwork_ids
224
+
225
+ # 写入文件
226
+ File.write(config_file, JSON.pretty_generate(origin_json))
227
+
228
+ # 提交到 Git
229
+ begin
230
+ Pindo::GitHandler.git_addpush_repo(
231
+ path: config_dir,
232
+ message: "Applovin SKAdNetworkItems",
233
+ commit_file_params: ["applovin_client_config.json"]
234
+ )
235
+ puts "✓ 配置已保存到远程仓库"
236
+ rescue => e
237
+ puts "⚠ 提交配置到 Git 失败: #{e.message}"
238
+ end
239
+ end
240
+
241
+ # 查找 Info.plist 文件路径
242
+ # @param project_dir [String] 项目目录
243
+ # @return [String] Info.plist 文件路径
244
+ def find_info_plist(project_dir:)
245
+ # 默认路径
246
+ default_plist = File.join(project_dir, "AppEntry/Info.plist")
247
+
248
+ # 查找 .xcodeproj 文件
249
+ xcodeproj_file = Dir.glob(File.join(project_dir, "/*.xcodeproj")).max_by { |f| File.mtime(f) }
250
+ return default_plist unless xcodeproj_file
251
+
252
+ # 从 Xcode 工程中查找 Info.plist
253
+ begin
254
+ proj_name = File.basename(xcodeproj_file, ".xcodeproj")
255
+ project_obj = Xcodeproj::Project.open(xcodeproj_file)
256
+
257
+ project_obj.targets.each do |target|
258
+ if target.product_type.to_s == "com.apple.product-type.application"
259
+ temp_info = target.build_configurations.first.build_settings['INFOPLIST_FILE']
260
+ return File.join(project_dir, temp_info) if temp_info
261
+ end
262
+ end
263
+ rescue => e
264
+ puts "⚠ 解析 Xcode 工程失败: #{e.message}"
265
+ end
266
+
267
+ default_plist
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,153 @@
1
+ require 'cocoapods'
2
+ require 'fileutils'
3
+ require 'pindo/base/git_handler'
4
+ require 'pindo/config/pindoconfig'
5
+
6
+ module Pindo
7
+ # CocoaPods 操作辅助类
8
+ # 封装 CocoaPods 相关的操作,包括:
9
+ # - 更新私有索引库
10
+ # - 同步 podspec
11
+ # - 执行 pod install
12
+ class CocoaPodsHelper
13
+
14
+ class << self
15
+ # 更新私有 Pod 索引库
16
+ # @param install [Boolean] 是否执行 pod install
17
+ # @param project_dir [String] 项目目录,默认为当前目录
18
+ # @return [Boolean] 是否成功
19
+ def update_pod_repo(install: false, project_dir: nil)
20
+ project_dir ||= Dir.pwd
21
+
22
+ # 获取私有 Pod 索引地址
23
+ pod_index_url = get_private_pod_index_url
24
+ raise "私有Pod索引地址未知!!" if pod_index_url.nil?
25
+
26
+ # 更新私有索引库
27
+ update_private_repo(pod_index_url)
28
+
29
+ # 如果需要,执行 pod install
30
+ if install
31
+ install_pods(project_dir)
32
+ end
33
+
34
+ true
35
+ end
36
+
37
+ # 执行 pod install
38
+ # @param project_dir [String] 项目目录
39
+ # @return [Boolean] 是否成功
40
+ def install_pods(project_dir)
41
+ podfile_path = File.join(project_dir, "Podfile")
42
+ return false unless File.exist?(podfile_path)
43
+
44
+ begin
45
+ puts "正在执行 pod install..."
46
+ Dir.chdir(project_dir) do
47
+ Pod::Command::Install.run(['--clean-install'])
48
+ end
49
+ true
50
+ rescue => e
51
+ puts "Pod install 失败: #{e.message}"
52
+ false
53
+ end
54
+ end
55
+
56
+ # 清理并重新安装 CocoaPods
57
+ # @param project_dir [String] 项目目录
58
+ def deintegrate_and_install(project_dir)
59
+ return unless File.exist?(File.join(project_dir, "Podfile"))
60
+
61
+ Dir.chdir(project_dir) do
62
+ begin
63
+ # 清理现有 Pods
64
+ FileUtils.rm_rf("Podfile.lock") if File.exist?("Podfile.lock")
65
+ FileUtils.rm_rf("Pods") if File.exist?("Pods")
66
+
67
+ puts "正在执行 pod deintegrate..."
68
+ system 'pod deintegrate'
69
+
70
+ puts "正在执行 pod install..."
71
+ system 'pod install'
72
+
73
+ # 检查 pod install 是否成功
74
+ unless File.exist?("Podfile.lock")
75
+ raise "pod install 失败!!先 pod install 完成后再编译!"
76
+ end
77
+ rescue => error
78
+ puts error.to_s
79
+ raise "pod install 失败!!先 pod install 完成后再编译!"
80
+ end
81
+ end
82
+ end
83
+
84
+ # 检查是否使用了特定的 Pod
85
+ # @param project_dir [String] 项目目录
86
+ # @param pod_names [Array<String>] Pod 名称列表
87
+ # @return [Boolean] 是否使用了任一指定的 Pod
88
+ def using_pods?(project_dir, pod_names)
89
+ pod_lock_file = File.join(project_dir, "Podfile.lock")
90
+ return false unless File.exist?(pod_lock_file)
91
+
92
+ begin
93
+ pod_lock_json = Pod::YAMLHelper.load_file(pod_lock_file)
94
+ pods_array = []
95
+
96
+ if !pod_lock_json["SPEC REPOS"].nil?
97
+ pod_lock_json["SPEC REPOS"].each do |source, pod_items|
98
+ pods_array += pod_items
99
+ end
100
+ end
101
+
102
+ pod_names.any? { |name| pods_array.include?(name) }
103
+ rescue => e
104
+ puts "检查 Podfile.lock 失败: #{e.message}"
105
+ false
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # 获取私有 Pod 索引地址
112
+ # @return [String, nil] 索引地址
113
+ def get_private_pod_index_url
114
+ config = Pindoconfig.instance
115
+ pod_array = config.pod_repo_dict
116
+ return nil if pod_array.nil?
117
+
118
+ pod_array['podindex']
119
+ end
120
+
121
+ # 更新私有索引库
122
+ # @param pod_index_url [String] 索引库地址
123
+ def update_private_repo(pod_index_url)
124
+ sources = Pod::Config.instance.sources_manager.all
125
+ repos_path = File.expand_path("~/.cocoapods/repos")
126
+
127
+ puts "私有Pod地址: #{pod_index_url}"
128
+ puts "私有Pod目录: ~/.cocoapods"
129
+
130
+ # 查找已存在的私有源
131
+ private_source = sources.select { |s| s.git? && s.url.to_s.eql?(pod_index_url) }.first
132
+
133
+ if !private_source.nil?
134
+ # 更新已存在的私有源
135
+ repo_name = File.basename(private_source.repo)
136
+ Pindo::GitHandler.getcode_to_dir(
137
+ reponame: repo_name,
138
+ remote_url: pod_index_url,
139
+ path: repos_path
140
+ )
141
+ else
142
+ # 添加新的私有源
143
+ repository_name = pod_index_url.split("/").last.chomp(".git")
144
+ Pindo::GitHandler.getcode_to_dir(
145
+ reponame: repository_name,
146
+ remote_url: pod_index_url,
147
+ path: repos_path
148
+ )
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,210 @@
1
+ require 'fileutils'
2
+ require 'sigh'
3
+ require 'zip'
4
+ require 'pindo/config/pindoconfig'
5
+
6
+ module Pindo
7
+ class IpaResignHelper
8
+
9
+ class << self
10
+
11
+ # 重签名 IPA 文件的主入口方法
12
+ # @param ipa_file_path [String] IPA 文件路径(可选,如果为 nil 则自动查找当前目录下的 IPA)
13
+ # @param bundle_id [String] 新的 Bundle ID
14
+ # @return [String, nil] 重签名后的 IPA 文件路径,失败返回 nil
15
+ def resign_ipa(ipa_file_path: nil, bundle_id:)
16
+ ipa_path = find_ipa_file(ipa_file_path)
17
+
18
+ if ipa_path.nil? || ipa_path.empty?
19
+ puts "未找到 IPA 文件"
20
+ return nil
21
+ end
22
+
23
+ # 准备重签名的 IPA(修改 Bundle ID)
24
+ resigned_ipa_path = prepare_resign_ipa(ipa_name: ipa_path, bundle_id: bundle_id)
25
+
26
+ if resigned_ipa_path.nil?
27
+ puts "准备重签名 IPA 失败"
28
+ return nil
29
+ end
30
+
31
+ # 使用新证书签名
32
+ sign_ipa_with_new_cert(ipa_name: resigned_ipa_path, bundle_id: bundle_id)
33
+
34
+ resigned_ipa_path
35
+ end
36
+
37
+ private
38
+
39
+ # 查找 IPA 文件
40
+ # @param ipa_file_path [String, nil] 指定的 IPA 文件路径
41
+ # @return [String, nil] IPA 文件的完整路径
42
+ def find_ipa_file(ipa_file_path)
43
+ if ipa_file_path
44
+ return File.expand_path(ipa_file_path)
45
+ end
46
+
47
+ # 自动查找当前目录下的 IPA 文件
48
+ current_dir = Dir.pwd
49
+ Dir.foreach(current_dir) do |file|
50
+ if file =~ /(.*).ipa/
51
+ return File.join(current_dir, file)
52
+ end
53
+ end
54
+
55
+ nil
56
+ end
57
+
58
+ # 准备重签名的 IPA(修改 Bundle ID)
59
+ # @param ipa_name [String] 原始 IPA 文件路径
60
+ # @param bundle_id [String] 新的 Bundle ID
61
+ # @return [String, nil] 准备好的 IPA 文件路径
62
+ def prepare_resign_ipa(ipa_name:, bundle_id:)
63
+ ipa_base_name = File.basename(ipa_name, ".ipa")
64
+ ipa_dir = File.dirname(ipa_name)
65
+
66
+ resign_ipa_name = ipa_base_name + "_resigned.ipa"
67
+ resign_ipa_full_name = File.join(ipa_dir, resign_ipa_name)
68
+ FileUtils.cp_r(ipa_name, resign_ipa_full_name)
69
+ puts resign_ipa_full_name
70
+
71
+ tmp_time = "tmp_" + Time.now.strftime('%y%m%d_%H%M%S')
72
+ tmp_dir = File.join(ipa_dir, tmp_time)
73
+ FileUtils.mkdir(tmp_dir) unless File.exist?(tmp_dir)
74
+
75
+ # 解压 IPA
76
+ command = "unzip -q \"#{resign_ipa_full_name}\" -d #{tmp_dir}"
77
+ puts command
78
+ system command
79
+
80
+ payload_path = File.join(tmp_dir, "Payload")
81
+ if File.exist?(payload_path)
82
+ FileUtils.rm_rf(resign_ipa_full_name)
83
+ end
84
+
85
+ # 查找 .app 目录
86
+ modify_content_path = ""
87
+ Dir.foreach(payload_path) do |file|
88
+ if file =~ /(.*).app/
89
+ modify_content_path = File.join(payload_path, file)
90
+ break
91
+ end
92
+ end
93
+
94
+ # 修改 Info.plist 中的 Bundle ID
95
+ modify_ipa_info_plist(modify_content_path: modify_content_path, bundle_id: bundle_id)
96
+
97
+ # 重新打包 IPA
98
+ current_dir = Dir.pwd
99
+ if File.exist?(payload_path)
100
+ Dir.chdir(tmp_dir)
101
+ command = "zip -qry \"#{resign_ipa_full_name}\" * "
102
+ puts command
103
+ system command
104
+ Dir.chdir(current_dir)
105
+ end
106
+
107
+ # 清理临时目录
108
+ FileUtils.rm_rf(tmp_dir) if File.exist?(resign_ipa_full_name)
109
+
110
+ resign_ipa_full_name
111
+ end
112
+
113
+ # 修改 IPA 中的 Info.plist 文件
114
+ # @param modify_content_path [String] .app 目录路径
115
+ # @param bundle_id [String] 新的 Bundle ID
116
+ def modify_ipa_info_plist(modify_content_path:, bundle_id:)
117
+ main_info_plist = File.join(modify_content_path, "Info.plist")
118
+ puts main_info_plist
119
+
120
+ # 确保 PlistBuddy 可用
121
+ unless File.exist?("/usr/local/bin/PlistBuddy")
122
+ command = 'ln -s /usr/libexec/PlistBuddy /usr/local/bin/PlistBuddy'
123
+ system command
124
+ end
125
+
126
+ # 读取旧的 Bundle ID
127
+ old_bundle_id_command = '/usr/local/bin/PlistBuddy -c "Print :CFBundleIdentifier" ' + main_info_plist
128
+ puts old_bundle_id_command
129
+ old_bundle_id = ""
130
+ IO.popen(old_bundle_id_command) { |f| old_bundle_id = f.gets }
131
+ puts old_bundle_id
132
+
133
+ # 修改主应用的 Bundle ID
134
+ exchange_bundleid_command = '/usr/local/bin/PlistBuddy -c "Set :CFBundleIdentifier ' + bundle_id + '" ' + main_info_plist
135
+ puts exchange_bundleid_command
136
+ system exchange_bundleid_command
137
+
138
+ # 修改插件扩展的 Bundle ID
139
+ plugin_path = File.join(modify_content_path, "PlugIns")
140
+ if File.exist?(plugin_path)
141
+ Dir.foreach(plugin_path) do |file|
142
+ next unless file =~ /(.*).appex/
143
+
144
+ appex_path = File.join(plugin_path, file)
145
+ tmp_info_plist = File.join(appex_path, "Info.plist")
146
+
147
+ old_bundle_id_command = '/usr/local/bin/PlistBuddy -c "Print :CFBundleIdentifier" ' + tmp_info_plist
148
+ puts old_bundle_id_command
149
+ old_plugin_bundle_id = ""
150
+ IO.popen(old_bundle_id_command) { |f| old_plugin_bundle_id = f.gets }
151
+ puts "old_bundle_id = #{old_plugin_bundle_id}"
152
+
153
+ # 根据插件类型修改 Bundle ID
154
+ update_plugin_bundle_id(tmp_info_plist, old_plugin_bundle_id, bundle_id)
155
+ end
156
+ end
157
+ end
158
+
159
+ # 更新插件的 Bundle ID
160
+ # @param plist_path [String] 插件的 Info.plist 路径
161
+ # @param old_bundle_id [String] 旧的 Bundle ID
162
+ # @param new_base_bundle_id [String] 新的基础 Bundle ID
163
+ def update_plugin_bundle_id(plist_path, old_bundle_id, new_base_bundle_id)
164
+ plugin_types = [
165
+ ".content", ".service", ".imessage", ".keyboard",
166
+ ".siri", ".widget", ".siriui", ".extension",
167
+ ".extensionad", ".extensionporn"
168
+ ]
169
+
170
+ plugin_types.each do |suffix|
171
+ if old_bundle_id.include?(suffix)
172
+ new_plugin_bundle_id = new_base_bundle_id + suffix
173
+ exchange_bundleid_command = '/usr/local/bin/PlistBuddy -c "Set :CFBundleIdentifier ' + new_plugin_bundle_id + '" ' + plist_path
174
+ puts exchange_bundleid_command
175
+ system exchange_bundleid_command
176
+ break
177
+ end
178
+ end
179
+ end
180
+
181
+ # 使用新证书签名 IPA
182
+ # @param ipa_name [String] IPA 文件路径
183
+ # @param bundle_id [String] Bundle ID
184
+ def sign_ipa_with_new_cert(ipa_name:, bundle_id:)
185
+ require 'pindo/config/pindoconfig'
186
+
187
+ profil_info_array = Pindo::PindoSingleConfig.instance.get_cert_info
188
+ bundle_id_signing_identity = profil_info_array.first['signing_identity']
189
+
190
+ profile_dict = build_resign_profile_dict(profil_info_array: profil_info_array)
191
+
192
+ # 调用 Sigh::Resign 进行重签名
193
+ # 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)
195
+ end
196
+
197
+ # 构建重签名用的 profile 字典
198
+ # @param profil_info_array [Array<Hash>] 证书信息数组
199
+ # @return [Hash] Bundle ID 到 profile 路径的映射
200
+ def build_resign_profile_dict(profil_info_array:)
201
+ resign_dict = {}
202
+ profil_info_array.each do |profil_info|
203
+ resign_dict[profil_info["bundle_id"]] = profil_info["profile_path"]
204
+ end
205
+ resign_dict
206
+ end
207
+
208
+ end
209
+ end
210
+ end