pindo 5.10.6 → 5.10.9

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/funlog.rb +12 -1
  3. data/lib/pindo/base/pindocontext.rb +3 -0
  4. data/lib/pindo/command/android/autobuild.rb +62 -9
  5. data/lib/pindo/command/android/build.rb +6 -6
  6. data/lib/pindo/command/android/debug.rb +9 -9
  7. data/lib/pindo/command/android.rb +1 -1
  8. data/lib/pindo/command/appstore.rb +1 -1
  9. data/lib/pindo/command/deploy/build.rb +2 -4
  10. data/lib/pindo/command/deploy/cert.rb +4 -5
  11. data/lib/pindo/command/deploy/configproj.rb +3 -3
  12. data/lib/pindo/command/deploy/confusecode.rb +1 -1
  13. data/lib/pindo/command/deploy/confuseproj.rb +1 -1
  14. data/lib/pindo/command/deploy/pem.rb +3 -4
  15. data/lib/pindo/command/dev/autobuild.rb +1 -1
  16. data/lib/pindo/command/dev/build.rb +1 -1
  17. data/lib/pindo/command/dev/feishu.rb +1 -1
  18. data/lib/pindo/command/gplay/pullconfig.rb +48 -0
  19. data/lib/pindo/command/gplay.rb +7 -6
  20. data/lib/pindo/command/ios/adhoc.rb +6 -5
  21. data/lib/pindo/command/ios/autobuild.rb +24 -24
  22. data/lib/pindo/command/ios/build.rb +7 -6
  23. data/lib/pindo/command/ios/debug.rb +1 -0
  24. data/lib/pindo/command/ios.rb +1 -1
  25. data/lib/pindo/command/ipa/import.rb +2 -3
  26. data/lib/pindo/command/ipa/output.rb +2 -3
  27. data/lib/pindo/command/jps/upload.rb +6 -5
  28. data/lib/pindo/command/unity/apk.rb +19 -2
  29. data/lib/pindo/command/unity/autobuild.rb +58 -63
  30. data/lib/pindo/command/unity/initpack.rb +1 -1
  31. data/lib/pindo/command/unity/ipa.rb +17 -14
  32. data/lib/pindo/command/unity/pack.rb +1 -1
  33. data/lib/pindo/command/unity/upload.rb +1 -1
  34. data/lib/pindo/command/unity/web.rb +2 -2
  35. data/lib/pindo/command/utils/icon.rb +1 -1
  36. data/lib/pindo/command/utils/renewcert.rb +1 -2
  37. data/lib/pindo/command/utils/renewproj.rb +9 -10
  38. data/lib/pindo/command/web/autobuild.rb +2 -2
  39. data/lib/pindo/command/web/run.rb +1 -1
  40. data/lib/pindo/command.rb +2 -1
  41. data/lib/pindo/module/android/android_build_config_helper.rb +267 -35
  42. data/lib/pindo/module/android/android_build_helper.rb +300 -0
  43. data/lib/pindo/module/android/android_project_helper.rb +279 -0
  44. data/lib/pindo/module/android/gp_compliance_helper.rb +33 -87
  45. data/lib/pindo/module/android/gradle_helper.rb +524 -255
  46. data/lib/pindo/module/android/java_env_helper.rb +633 -0
  47. data/lib/pindo/module/android/keystore_helper.rb +1118 -0
  48. data/lib/pindo/module/appselect.rb +109 -0
  49. data/lib/pindo/module/appstore/appstore_in_app_purchase.rb +1 -1
  50. data/lib/pindo/module/build/{buildhelper.rb → build_helper.rb} +1 -2
  51. data/lib/pindo/module/build/{commonconfuseproj.rb → confuse_xcodeproj.rb} +2 -2
  52. data/lib/pindo/module/cert/cert_helper.rb +245 -0
  53. data/lib/pindo/module/cert/keychain_helper.rb +152 -0
  54. data/lib/pindo/module/cert/pem_helper.rb +67 -0
  55. data/lib/pindo/module/cert/xcodecerthelper.rb +2 -2
  56. data/lib/pindo/module/pgyer/pgyerhelper.rb +21 -1
  57. data/lib/pindo/module/{build/unityhelper.rb → unity/unity_helper.rb} +0 -17
  58. data/lib/pindo/module/xcode/{xcodereshandler.rb → res/xcode_res_handler.rb} +1 -1
  59. data/lib/pindo/module/xcode/{xcodebuildconfig.rb → xcode_build_config.rb} +7 -6
  60. data/lib/pindo/module/xcode/{xcodebuildhelper.rb → xcode_build_helper.rb} +4 -3
  61. data/lib/pindo/module/xcode/{xcodehelper.rb → xcode_project_helper.rb} +4 -2
  62. data/lib/pindo/module/xcode/{xcodereshelper.rb → xcode_res_helper.rb} +12 -9
  63. data/lib/pindo/version.rb +185 -7
  64. data/lib/pindo.rb +14 -9
  65. metadata +24 -23
  66. data/lib/pindo/module/android/apk_helper.rb +0 -138
  67. data/lib/pindo/module/android/base_helper.rb +0 -964
  68. data/lib/pindo/module/android/build_helper.rb +0 -128
  69. data/lib/pindo/module/android/so_helper.rb +0 -18
  70. data/lib/pindo/module/cert/certhelper.rb +0 -246
  71. data/lib/pindo/module/cert/keychainhelper.rb +0 -150
  72. data/lib/pindo/module/cert/pemhelper.rb +0 -65
  73. /data/lib/pindo/module/android/{androidreshelper.rb → android_res_helper.rb} +0 -0
  74. /data/lib/pindo/module/appstore/{iap_tier.json → appstore_iap_tier.json} +0 -0
  75. /data/lib/pindo/module/build/{swarkhelper.rb → swark_helper.rb} +0 -0
  76. /data/lib/pindo/module/build/{versionhelper.rb → version_helper.rb} +0 -0
  77. /data/lib/pindo/module/cert/{provisioninghelper.rb → provisioning_helper.rb} +0 -0
  78. /data/lib/pindo/module/unity/{nugethelper.rb → nuget_helper.rb} +0 -0
  79. /data/lib/pindo/module/xcode/{xcoderesconstant.rb → res/xcode_res_constant.rb} +0 -0
@@ -63,6 +63,33 @@ module Pindo
63
63
  return sub_json
64
64
  end
65
65
 
66
+ # Android Bundle Name 相关函数
67
+ def all_dev_bundle_name
68
+ bundle_names = []
69
+ begin
70
+ config = Pindoconfig.instance.pindo_common_config_json
71
+ if config && config['android_dev_bundle_array']
72
+ bundle_names = config['android_dev_bundle_array']
73
+ end
74
+ rescue => e
75
+ raise Informative, "注意: android_dev_bundle_array 配置不存在或无法加载"
76
+ end
77
+ return bundle_names
78
+ end
79
+
80
+ def all_deploy_bundle_name
81
+ bundle_names = []
82
+ begin
83
+ config = Pindoconfig.instance.pindo_common_config_json
84
+ if config && config['android_deploy_bundle_array']
85
+ bundle_names = config['android_deploy_bundle_array']
86
+ end
87
+ rescue => e
88
+ raise Informative, "注意: android_deploy_bundle_array 配置不存在或无法加载"
89
+ end
90
+ return bundle_names
91
+ end
92
+
66
93
 
67
94
  def all_itc_bundleid
68
95
  all_bundleid = []
@@ -190,6 +217,88 @@ module Pindo
190
217
 
191
218
  end
192
219
 
220
+ def get_selected_dev_bundle_name()
221
+ # 检查环境变量
222
+ env_bundle_name = ENV['PINDO_BUNDLE_NAME']
223
+ if env_bundle_name && !env_bundle_name.empty?
224
+ puts "\n使用环境变量指定的Bundle Name: #{env_bundle_name}"
225
+ puts
226
+ # 保存到缓存
227
+ require_relative '../base/pindocontext'
228
+ context = Pindo::PindoContext.instance
229
+ context.set_selection(Pindo::PindoContext::SelectionKey::ANDROID_BUNDLE_NAME, env_bundle_name)
230
+ return env_bundle_name
231
+ end
232
+
233
+ # 检查缓存
234
+ require_relative '../base/pindocontext'
235
+ context = Pindo::PindoContext.instance
236
+ cached_bundle_name = context.get_selection(Pindo::PindoContext::SelectionKey::ANDROID_BUNDLE_NAME)
237
+
238
+ if cached_bundle_name
239
+ puts "\n使用之前选择的Bundle Name: #{cached_bundle_name}"
240
+ puts
241
+ return cached_bundle_name
242
+ end
243
+
244
+ all_bundle_names = all_dev_bundle_name()
245
+
246
+ if all_bundle_names.nil? || all_bundle_names.empty?
247
+ puts "\n未配置任何开发环境的Android Bundle Name"
248
+ return nil
249
+ end
250
+
251
+ cli = HighLine.new
252
+ menu_choice = "None"
253
+ puts
254
+ cli.choose do |menu|
255
+ menu.header = "可用的Android Bundle Name如下:"
256
+ menu.prompt = "请选择使用的Bundle Name,请输入选项(1/2/3...):"
257
+ for bundle_name in all_bundle_names do
258
+ menu.choice(bundle_name) do |details|
259
+ menu_choice = "#{details}"
260
+ end
261
+ end
262
+ end
263
+
264
+ puts
265
+ puts "选择的Bundle Name是: #{menu_choice}"
266
+ puts
267
+
268
+ # 保存选择到缓存
269
+ context.set_selection(Pindo::PindoContext::SelectionKey::ANDROID_BUNDLE_NAME, menu_choice)
270
+
271
+ return menu_choice
272
+ end
273
+
274
+ def get_selected_deploy_bundle_name()
275
+ all_bundle_names = all_deploy_bundle_name()
276
+
277
+ if all_bundle_names.nil? || all_bundle_names.empty?
278
+ puts "\n未配置任何发布环境的Android Bundle Name"
279
+ return nil
280
+ end
281
+
282
+ cli = HighLine.new
283
+ menu_choice = "None"
284
+ puts
285
+ cli.choose do |menu|
286
+ menu.header = "可用的Android Bundle Name如下:"
287
+ menu.prompt = "请选择使用的Bundle Name,请输入选项(1/2/3...):"
288
+ for bundle_name in all_bundle_names do
289
+ menu.choice(bundle_name) do |details|
290
+ menu_choice = "#{details}"
291
+ end
292
+ end
293
+ end
294
+
295
+ puts
296
+ puts "选择的Bundle Name是: #{menu_choice}"
297
+ puts
298
+
299
+ return menu_choice
300
+ end
301
+
193
302
  def get_deploy_setting_repo(tagname:nil)
194
303
  sub_setting_json = deploy_build_setting_json()
195
304
  return sub_setting_json[tagname]['git_repo_name']
@@ -9,7 +9,7 @@ module Pindo
9
9
 
10
10
  def get_price_tier_dict
11
11
 
12
- iap_tier_file = File.join(File.expand_path('./', __dir__), 'iap_tier.json')
12
+ iap_tier_file = File.join(File.expand_path('./', __dir__), 'appstore_iap_tier.json')
13
13
  iap_tier_json = JSON.parse(File.read(iap_tier_file))
14
14
 
15
15
  return iap_tier_json
@@ -1,14 +1,13 @@
1
1
  require 'singleton'
2
2
  require 'fileutils'
3
3
  require 'xcodeproj' # 用于iOS项目检查
4
- require_relative '../android/base_helper'
4
+ require_relative '../android/android_project_helper'
5
5
  require_relative '../../base/githelper'
6
6
  require_relative '../../base/pindocontext'
7
7
  module Pindo
8
8
 
9
9
  class BuildHelper
10
10
  include Singleton
11
- include BaseAndroidHelper
12
11
  include Pindo::Githelper
13
12
 
14
13
  # 临时缓存,只在内存中,不保存到文件
@@ -6,8 +6,8 @@ require 'json'
6
6
  require 'yaml'
7
7
 
8
8
  module Pindo
9
- module CommonConfuseProj
10
-
9
+ module ConfuseXcodeProj
10
+
11
11
  include Hashhelper
12
12
 
13
13
  def install_symash(clang_path:nil)
@@ -0,0 +1,245 @@
1
+
2
+
3
+ require 'match'
4
+ require 'openssl'
5
+ require 'pindo/base/aeshelper'
6
+ require 'pindo/module/cert/keychain_helper'
7
+ require 'pindo/module/cert/provisioning_helper'
8
+
9
+
10
+ module Pindo
11
+
12
+ class CertHelper
13
+ # 密码缓存,避免重复获取相同URL的密码
14
+ @@password_cache = {}
15
+
16
+ class << self
17
+ # 获取密码的辅助方法,使用缓存避免重复获取
18
+ def get_cached_password(cert_url)
19
+ unless @@password_cache[cert_url]
20
+ puts "\e[33m[DEBUG] 密码缓存中未找到,从Keychain获取: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
21
+ @@password_cache[cert_url] = AESHelper.fetch_password(keychain_name: cert_url)
22
+ puts "\e[32m[DEBUG] 密码已缓存: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
23
+ else
24
+ puts "\e[32m[DEBUG] 从密码缓存获取: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
25
+ end
26
+ @@password_cache[cert_url]
27
+ end
28
+
29
+ # 清除密码缓存
30
+ def clear_password_cache
31
+ @@password_cache.clear
32
+ end
33
+
34
+ # 清除特定URL的密码缓存
35
+ def clear_password_cache_for_url(cert_url)
36
+ @@password_cache.delete(cert_url)
37
+ end
38
+
39
+ def get_cert_info(cer_certificate)
40
+ # can receive a certificate path or the file data
41
+ begin
42
+ if File.exist?(cer_certificate)
43
+ cer_certificate = File.binread(cer_certificate)
44
+ end
45
+ rescue ArgumentError
46
+ # cert strings have null bytes; suppressing output
47
+ end
48
+
49
+ cert = OpenSSL::X509::Certificate.new(cer_certificate)
50
+
51
+ # openssl output:
52
+ # subject= /UID={User ID}/CN={Certificate Name}/OU={Certificate User}/O={Organisation}/C={Country}
53
+ cert_info = cert.subject.to_s.gsub(/\s*subject=\s*/, "").tr("/", "\n")
54
+ out_array = cert_info.split("\n")
55
+ openssl_keys_to_readable_keys = {
56
+ 'UID' => 'User ID',
57
+ 'CN' => 'Common Name',
58
+ 'OU' => 'Organisation Unit',
59
+ 'O' => 'Organisation',
60
+ 'C' => 'Country',
61
+ 'notBefore' => 'Start Datetime',
62
+ 'notAfter' => 'End Datetime'
63
+ }
64
+
65
+ return out_array.map { |x| x.split(/=+/) if x.include?("=") }
66
+ .compact
67
+ .map { |k, v| [openssl_keys_to_readable_keys.fetch(k, k), v] }
68
+ .push([openssl_keys_to_readable_keys.fetch("notBefore"), cert.not_before])
69
+ .push([openssl_keys_to_readable_keys.fetch("notAfter"), cert.not_after])
70
+ rescue => ex
71
+ raise Informative, "get_cert_info: #{ex}"
72
+ return {}
73
+ end
74
+
75
+ def select_cert_or_key(paths:)
76
+ cert_id_path = ENV['MATCH_CERTIFICATE_ID'] ? paths.find { |path| path.include?(ENV['MATCH_CERTIFICATE_ID']) } : nil
77
+ cert_id_path || paths.last
78
+ end
79
+
80
+ def is_cert_valid?(cer_certificate_path)
81
+ cert = OpenSSL::X509::Certificate.new(File.binread(cer_certificate_path))
82
+ now = Time.now.utc
83
+ return (now <=> cert.not_after) == -1
84
+ end
85
+
86
+ def isMac?
87
+ (/darwin/ =~ RUBY_PLATFORM) != nil
88
+ end
89
+
90
+ def install_certs(cert_url:nil, certs_dir:nil, cert_type:nil, platform_type:nil)
91
+
92
+ cert_git_dir = cert_type.downcase
93
+ if platform_type.downcase.eql?("macos")
94
+ if cert_type.downcase.include?("development")
95
+ cert_git_dir = "development"
96
+ elsif cert_type.downcase.eql?("appstore")
97
+ cert_git_dir = "distribution"
98
+ else
99
+ cert_git_dir = "developer_id_application"
100
+ end
101
+ else
102
+ if !cert_type.downcase.include?("development")
103
+ cert_git_dir = "distribution"
104
+ end
105
+ end
106
+
107
+ certs = Dir[File.join(certs_dir, "certs", cert_git_dir.to_s, "*.cer")]
108
+ keys = Dir[File.join(certs_dir, "certs", cert_git_dir.to_s, "*.p12")]
109
+
110
+ if certs.count == 0 || keys.count == 0
111
+ raise Informative, "No certificates found in #{certs_dir}"
112
+ else
113
+ output_dir = Dir.mktmpdir
114
+
115
+ decrypt_password = CertHelper.get_cached_password(cert_url)
116
+ Funlog.instance.fancyinfo_start("正在安装证书...")
117
+
118
+ cert_path = AESHelper.decrypt_specific_file(src_file: certs.first, password:decrypt_password, output_dir: output_dir)
119
+ if cert_path.nil? || cert_path.empty? || !File.exist?(cert_path)
120
+ AESHelper.delete_password(keychain_name:cert_url)
121
+ # 清除内存中的密码缓存,避免重复使用错误密码
122
+ @@password_cache.delete(cert_url)
123
+ raise Informative, "证书解析失败,密码错误!"
124
+ end
125
+
126
+ key_path = AESHelper.decrypt_specific_file(src_file: keys.first, password:decrypt_password, output_dir: output_dir)
127
+ if key_path.nil? || key_path.empty? || !File.exist?(key_path)
128
+ AESHelper.delete_password(keychain_name:cert_url)
129
+ # 清除内存中的密码缓存,避免重复使用错误密码
130
+ @@password_cache.delete(cert_url)
131
+ raise Informative, "证书解析失败,密码错误!"
132
+ end
133
+
134
+ unless is_cert_valid?(cert_path)
135
+ raise Informative, "证书已经过期,请重新生产新证书!"
136
+ end
137
+
138
+
139
+ if isMac?
140
+
141
+ keychain_name = "login.keychain"
142
+ if FastlaneCore::CertChecker.installed?(cert_path, in_keychain: nil)
143
+ Funlog.instance.fancyinfo_success("证书#{File.basename(cert_path)}已安装,无需重复安装!")
144
+ else
145
+
146
+ cert_password = Pindoconfig.instance.cert_key_password
147
+ keychain = 'login.keychain'
148
+ keychain_path = FastlaneCore::Helper.keychain_path(keychain)
149
+
150
+ KeychainHelper.import_file(cert_path, keychain_path, keychain_password: cert_password, certificate_password:'' )
151
+ KeychainHelper.import_file(key_path, keychain_path, keychain_password: cert_password, certificate_password: '')
152
+
153
+ Funlog.instance.fancyinfo_success("证书'#{File.basename(cert_path)}'安装完成!")
154
+
155
+ end
156
+ else
157
+ Funlog.instance.fancyinfo_error("非Mac电脑不支持安装证书!")
158
+ end
159
+
160
+ end
161
+ end
162
+
163
+ def install_provisionfiles(cert_url:nil, certs_dir:nil, bundle_id_map:nil, cert_type:nil, platform_type:nil)
164
+
165
+ cert_sub_dir = cert_type.downcase
166
+ provision_start_name = "Development"
167
+ provision_extension_name = ".mobileprovision"
168
+
169
+ if platform_type.downcase.include?("macos")
170
+ provision_extension_name = ".provisionprofile"
171
+
172
+ if cert_type.downcase.include?("development")
173
+ provision_start_name = "Development"
174
+ cert_sub_dir = cert_type.downcase
175
+ elsif cert_type.downcase.eql?("appstore")
176
+ provision_start_name = "AppStore"
177
+ cert_sub_dir = "appstore"
178
+ else
179
+ provision_start_name = "Direct"
180
+ cert_sub_dir = "developer_id"
181
+ end
182
+ else
183
+ provision_extension_name = ".mobileprovision"
184
+ if cert_type.downcase.include?("development")
185
+ provision_start_name = "Development"
186
+ cert_sub_dir = cert_type.downcase
187
+ elsif cert_type.downcase.include?("adhoc")
188
+ provision_start_name = "Adhoc"
189
+ cert_sub_dir = "adhoc"
190
+ else
191
+ provision_start_name = "AppStore"
192
+ cert_sub_dir = "appstore"
193
+ end
194
+ end
195
+
196
+ Funlog.instance.fancyinfo_start("正在安装#{provision_start_name} #{platform_type} Provisioning Profiles...")
197
+
198
+ un_exist_files = []
199
+ provisioning_info_array = []
200
+
201
+ # 在循环外获取密码,避免重复添加到Keychain
202
+ decrypt_password = CertHelper.get_cached_password(cert_url)
203
+
204
+ bundle_id_map.each do |type, bundle_id_temp|
205
+ profile_filename = File.join(certs_dir, "profiles", cert_sub_dir, [provision_start_name.to_s, bundle_id_temp].join('_') + provision_extension_name)
206
+ unless File.exist?(profile_filename)
207
+ un_exist_files << profile_filename
208
+ next
209
+ end
210
+ # puts "正在安装 #{bundle_id_temp}..."
211
+ output_dir = Dir.mktmpdir
212
+ file_decrypt = AESHelper.decrypt_specific_file(src_file: profile_filename, password:decrypt_password, output_dir: output_dir)
213
+ destpath = Provisioninghelper.install(file_decrypt)
214
+ parsed_data = Provisioninghelper.parse(destpath)
215
+
216
+ provisioning_info = {}
217
+ provisioning_info['type'] = type
218
+ provisioning_info['bundle_id'] = bundle_id_temp
219
+ provisioning_info['profile_name'] = parsed_data['Name']
220
+ provisioning_info['profile_path'] = destpath
221
+
222
+ cert_info = get_cert_info(parsed_data["DeveloperCertificates"].first.string).to_h
223
+ provisioning_info['signing_identity'] = cert_info["Common Name"]
224
+ provisioning_info['team_id'] = parsed_data["TeamIdentifier"].first
225
+
226
+ # puts JSON.pretty_generate(provisioning_info)
227
+ provisioning_info_array << provisioning_info
228
+ end
229
+
230
+ Funlog.instance.fancyinfo_success("#{provision_start_name} #{platform_type} Provisioning Profiles文件安装完成!")
231
+
232
+ if un_exist_files.size > 0
233
+ Funlog.instance.fancyinfo_error("证书 #{provision_start_name} #{platform_type} Provisioning Profiles文件不存在!")
234
+ raise Informative, "The following profiles do not exist: #{un_exist_files.join(', ')}"
235
+ end
236
+
237
+ return provisioning_info_array
238
+ end
239
+
240
+
241
+ end
242
+
243
+
244
+ end
245
+ end
@@ -0,0 +1,152 @@
1
+
2
+ require 'open3'
3
+ require 'security'
4
+
5
+ module Pindo
6
+
7
+ class KeychainHelper
8
+ class << self
9
+
10
+ def import_file(path, keychain_path, keychain_password: nil, certificate_password: "", skip_set_partition_list: false, output: false)
11
+ # puts path
12
+
13
+ # puts "Could not find file '#{path}'" unless File.exist?(path)
14
+
15
+ password_part = " -P #{certificate_password.shellescape}"
16
+
17
+ command = "security import #{path.shellescape} -k '#{keychain_path.shellescape}'"
18
+ command << password_part
19
+ command << " -T /usr/bin/codesign" # to not be asked for permission when running a tool like `gym` (before Sierra)
20
+ command << " -T /usr/bin/security"
21
+ command << " -T /usr/bin/productbuild" # to not be asked for permission when using an installer cert for macOS
22
+ command << " -T /usr/bin/productsign" # to not be asked for permission when using an installer cert for macOS
23
+ command << " 1> /dev/null" unless output
24
+
25
+ # puts command
26
+ # sensitive_command = command.gsub(password_part, " -P ********")
27
+
28
+ # UI.command(sensitive_command) if output
29
+ Open3.popen3(command) do |stdin, stdout, stderr, thrd|
30
+ # UI.command_output(stdout.read.to_s) if output
31
+ # puts "123"
32
+ # Set partition list only if success since it can be a time consuming process if a lot of keys are installed
33
+ if thrd.value.success? && !skip_set_partition_list
34
+ # puts "安装成功!"
35
+ keychain_password ||= resolve_keychain_password(keychain_path)
36
+ set_partition_list(path, keychain_path, keychain_password: keychain_password, output: output)
37
+ else
38
+ # Output verbose if file is already installed since not an error otherwise we will show the whole error
39
+ err = stderr.read.to_s.strip
40
+
41
+ if err.include?("SecKeychainItemImport") && err.include?("The specified item already exists in the keychain")
42
+ # UI.verbose("'}' is already installed on this machine")
43
+ # puts "证书key已经存在,安装完成!"
44
+ else
45
+ # puts err
46
+ # raise Informative, err
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def help_backticks(command, print: true)
53
+ # UI.command(command) if print
54
+ result = `#{command}`
55
+ # UI.command_output(result) if print
56
+ return result
57
+ end
58
+
59
+ def set_partition_list(path, keychain_path, keychain_password: nil, output: false)
60
+ # When security supports partition lists, also add the partition IDs
61
+ # See https://openradar.appspot.com/28524119
62
+ if help_backticks('security -h | grep set-key-partition-list', print: false).length > 0
63
+ password_part = " -k #{keychain_password.to_s.shellescape}"
64
+
65
+ command = "security set-key-partition-list"
66
+ command << " -S apple-tool:,apple:,codesign:"
67
+ command << " -s" # This is a needed in Catalina to prevent "security: SecKeychainItemCopyAccess: A missing value was detected."
68
+ command << password_part
69
+ command << " #{keychain_path.shellescape}"
70
+ command << " 1> /dev/null" # always disable stdout. This can be very verbose, and leak potentially sensitive info
71
+
72
+ # Showing loading indicator as this can take some time if a lot of keys installed
73
+ # Helper.show_loading_indicator("Setting key partition list... (this can take a minute if there are a lot of keys installed)")
74
+
75
+ # Strip keychain password from command output
76
+ # sensitive_command = command.gsub(password_part, " -k ********")
77
+ # puts command
78
+ # UI.command(sensitive_command) if output
79
+ Open3.popen3(command) do |stdin, stdout, stderr, thrd|
80
+ unless thrd.value.success?
81
+ err = stderr.read.to_s.strip
82
+
83
+ # Inform user when no/wrong password was used as its needed to prevent UI permission popup from Xcode when signing
84
+ if err.include?("SecKeychainItemSetAccessWithPassword")
85
+ keychain_name = File.basename(keychain_path, ".*")
86
+ # puts "password #{keychain_password}"
87
+ # puts "keychain #{keychain_name}"
88
+ # puts "security #{server_name(keychain_name)}"
89
+ Security::InternetPassword.delete(server: server_name(keychain_name))
90
+
91
+ # UI.important("")
92
+ # UI.important("Could not configure imported keychain item (certificate) to prevent UI permission popup when code signing\n" \
93
+ # "Check if you supplied the correct `keychain_password` for keychain: `#{keychain_path}`\n" \
94
+ # "#{err}")
95
+ # UI.important("")
96
+ # UI.important("Please look at the following docs to see how to set a keychain password:")
97
+ # UI.important(" - https://docs.fastlane.tools/actions/sync_code_signing")
98
+ # UI.important(" - https://docs.fastlane.tools/actions/get_certificates")
99
+ # puts "证书密码错误!"
100
+ else
101
+ puts err
102
+ end
103
+ end
104
+ end
105
+
106
+ # Hiding after Open3 finishes
107
+ # Helper.hide_loading_indicator
108
+
109
+ end
110
+ end
111
+
112
+ # https://github.com/fastlane/fastlane/issues/14196
113
+ # Keychain password is needed to set the partition list to
114
+ # prevent Xcode from prompting dialog for keychain password when signing
115
+ # 1. Uses keychain password from login keychain if found
116
+ # 2. Prompts user for keychain password and stores it in login keychain for user later
117
+ def resolve_keychain_password(keychain_path)
118
+ keychain_name = File.basename(keychain_path, ".*")
119
+ server = server_name(keychain_name)
120
+
121
+ # Attempt to find password in keychain for keychain
122
+ item = Security::InternetPassword.find(server: server)
123
+ if item
124
+ keychain_password = item.password
125
+ # UI.important("Using keychain password from keychain item #{server} in #{keychain_path}")
126
+ end
127
+
128
+ if keychain_password.nil?
129
+ # if UI.interactive?
130
+ # UI.important("Enter the password for #{keychain_path}")
131
+ # UI.important("This passphrase will be stored in your local keychain with the name #{server} and used in future runs")
132
+ # UI.important("This prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable")
133
+ keychain_password = FastlaneCore::Helper.ask_password(message: "Password for #{keychain_name} keychain: ", confirm: true, confirmation_message: "Type password for #{keychain_name} keychain again: ")
134
+ Security::InternetPassword.add(server, "", keychain_password)
135
+ # else
136
+ # UI.important("Keychain password for #{keychain_path} was not specified and not found in your keychain. Specify the 'keychain_password' option to prevent the UI permission popup when code signing")
137
+ # keychain_password = ""
138
+ # end
139
+ end
140
+
141
+ return keychain_password
142
+ end
143
+
144
+ # server name used for accessing the macOS keychain
145
+ def server_name(keychain_name)
146
+ ["pindo", "keychain", keychain_name].join("_")
147
+ end
148
+
149
+ private :server_name
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,67 @@
1
+
2
+ module Pindo
3
+
4
+ class PemHelper
5
+ class << self
6
+
7
+ def create_certificate(bundle_id:nil, type:"prod", output_path:"")
8
+
9
+ if bundle_id.empty? || output_path.empty?
10
+ UI.user_error!("bundle id is nil or output_path is nil.")
11
+ end
12
+
13
+ puts "Creating a new push certificate for app '#{bundle_id}'. "
14
+
15
+ csr, pkey = Spaceship::Portal.certificate.create_certificate_signing_request
16
+
17
+ cert = nil
18
+ begin
19
+ if type == "dev"
20
+ cert = Spaceship::Portal.certificate.development_push.create!(csr: csr, bundle_id: bundle_id)
21
+ else
22
+ cert = Spaceship::Portal.certificate.production_push.create!(csr: csr, bundle_id: bundle_id)
23
+ end
24
+ rescue => ex
25
+ if ex.to_s.include?("You already have a current")
26
+ # That's the most common failure probably
27
+ raise Informative, "You already have 2 active push profiles for this application/environment. You'll need to revoke an old certificate to make room for a new one"
28
+ else
29
+ raise ex
30
+ end
31
+ end
32
+
33
+
34
+ x509_certificate = cert.download
35
+ certificate_type = (type == "dev" ? 'development' : 'production')
36
+
37
+ base_base = bundle_id.gsub('.', '_')
38
+ puts base_base
39
+ filename_base = base_base + '_' + type
40
+
41
+
42
+ private_key_path = File.join(output_path, "#{filename_base}.pkey")
43
+ File.write(private_key_path, pkey.to_pem)
44
+ puts "key: #{private_key_path}"
45
+
46
+
47
+ p12_cert_path = File.join(output_path, "#{filename_base}.p12")
48
+ p12 = OpenSSL::PKCS12.create('goodcert1', certificate_type, pkey, x509_certificate)
49
+ File.write(p12_cert_path, p12.to_der)
50
+ puts "p12 : #{p12_cert_path}"
51
+
52
+
53
+ x509_cert_path = File.join(output_path, "#{filename_base}.pem")
54
+ File.write(x509_cert_path, x509_certificate.to_pem + pkey.to_pem)
55
+ puts "pem : #{x509_cert_path}"
56
+
57
+ return x509_cert_path
58
+ end
59
+
60
+ def login(apple_id:nil)
61
+ puts apple_id
62
+ Spaceship::Portal.login(apple_id.to_s)
63
+ Spaceship::Portal.select_team
64
+ end
65
+ end
66
+ end
67
+ end
@@ -346,13 +346,13 @@ module Pindo
346
346
 
347
347
 
348
348
  keys = Dir[File.join(certs_dir, "certs", cert_sub_dir, "*.p12")]
349
- decrypt_password = CertHelper.get_cached_password(cert_git_url)
349
+ decrypt_password = Pindo::CertHelper.get_cached_password(cert_git_url)
350
350
  output_dir = Dir.mktmpdir
351
351
  key_path = AESHelper.decrypt_specific_file(src_file: keys.first, password:decrypt_password, output_dir: output_dir)
352
352
  if key_path.nil? || key_path.empty? || !File.exist?(key_path)
353
353
  AESHelper.delete_password(keychain_name:cert_git_url)
354
354
  # 清除内存中的密码缓存,避免重复使用错误密码
355
- CertHelper.clear_password_cache_for_url(cert_git_url)
355
+ Pindo::CertHelper.clear_password_cache_for_url(cert_git_url)
356
356
  raise Informative, "证书解析失败,密码错误!"
357
357
  end
358
358
 
@@ -535,14 +535,34 @@ module Pindo
535
535
 
536
536
  # aws_client = AWSS3Client.new
537
537
  aws_client = JPSClient::UploadClient.new(@pgyer_client)
538
+
539
+ # 开始上传主文件,记录开始时间
540
+ file_size_mb = File.size(ipa_file_upload) / (1024.0 * 1024.0)
541
+ puts "\n开始上传文件 (#{file_size_mb.round(2)} MB)..."
542
+ upload_start_time = Time.now
543
+
538
544
  upload_res = aws_client.upload_file(binary_file:ipa_file_upload)
545
+
546
+ # 计算上传耗时
547
+ upload_duration = Time.now - upload_start_time
548
+ upload_speed = file_size_mb / upload_duration
549
+ puts "✅ 文件上传完成! 耗时: #{upload_duration.round(2)} 秒, 平均速度: #{upload_speed.round(2)} MB/s"
550
+
539
551
  attach_key_url = nil
540
552
  attachFileUrls = []
541
553
 
542
554
  begin
543
555
  if !addtach_file.nil? && File.exist?(addtach_file)
544
- puts "存在附件, 继续上传附件..."
556
+ attach_size_mb = File.size(addtach_file) / (1024.0 * 1024.0)
557
+ puts "\n存在附件, 继续上传附件 (#{attach_size_mb.round(2)} MB)..."
558
+ attach_start_time = Time.now
559
+
545
560
  attach_key_url = aws_client.upload_file(binary_file:addtach_file, isAttach:true)
561
+
562
+ # 计算附件上传耗时
563
+ attach_duration = Time.now - attach_start_time
564
+ attach_speed = attach_size_mb / attach_duration
565
+ puts "✅ 附件上传完成! 耗时: #{attach_duration.round(2)} 秒, 平均速度: #{attach_speed.round(2)} MB/s"
546
566
  end
547
567
 
548
568
  if !attach_key_url.nil?