pindo 4.6.9 → 4.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/pindo/base/aeshelper.rb +75 -0
- data/lib/pindo/base/funlog.rb +89 -0
- data/lib/pindo/base/githelper.rb +35 -18
- data/lib/pindo/base/plaininformative.rb +3 -0
- data/lib/pindo/base/standarderror.rb +1 -0
- data/lib/pindo/base/xcodeconst.rb +251 -0
- data/lib/pindo/client/applovinclient.rb +6 -3
- data/lib/pindo/client/aws3sclient.rb +33 -46
- data/lib/pindo/client/bossclient.rb +1 -1
- data/lib/pindo/client/bossconfigclient.rb +3 -3
- data/lib/pindo/client/pgyerclient.rb +159 -100
- data/lib/pindo/command/appstore/iap.rb +43 -0
- data/lib/pindo/command/appstore/itcapp.rb +41 -0
- data/lib/pindo/command/appstore/metadata.rb +43 -0
- data/lib/pindo/command/appstore/screenshots.rb +43 -0
- data/lib/pindo/command/appstore/upload.rb +40 -0
- data/lib/pindo/command/appstore.rb +17 -0
- data/lib/pindo/command/deploy/build.rb +109 -0
- data/lib/pindo/{deploy → command/deploy}/bundleid.rb +1 -1
- data/lib/pindo/command/deploy/cert.rb +183 -0
- data/lib/pindo/command/deploy/configproj.rb +105 -0
- data/lib/pindo/{deploy → command/deploy}/getitcinfo.rb +1 -1
- data/lib/pindo/{deploy → command/deploy}/iap.rb +30 -9
- data/lib/pindo/{deploy → command/deploy}/itcapp.rb +0 -1
- data/lib/pindo/{deploy → command/deploy}/itcinfo.rb +2 -3
- data/lib/pindo/{deploy → command/deploy}/pem.rb +3 -2
- data/lib/pindo/{deploy → command/deploy}/resign.rb +14 -63
- data/lib/pindo/command/deploy.rb +44 -0
- data/lib/pindo/{dev → command/dev}/autobuild.rb +28 -76
- data/lib/pindo/{dev → command/dev}/autoresign.rb +21 -64
- data/lib/pindo/command/dev/build.rb +94 -0
- data/lib/pindo/{dev → command/dev}/createbuild.rb +0 -2
- data/lib/pindo/{dev → command/dev}/debug.rb +6 -2
- data/lib/pindo/command/dev/pgyercert.rb +75 -0
- data/lib/pindo/command/dev.rb +26 -0
- data/lib/pindo/command/env.rb +17 -0
- data/lib/pindo/{ipa → command/ipa}/autoresign.rb +22 -70
- data/lib/pindo/{ipa → command/ipa}/import.rb +48 -103
- data/lib/pindo/{ipa → command/ipa}/output.rb +43 -135
- data/lib/pindo/command/ipa.rb +16 -0
- data/lib/pindo/{lib → command/lib}/update.rb +23 -14
- data/lib/pindo/command/lib.rb +16 -0
- data/lib/pindo/{pgyer → command/pgyer}/apptest.rb +7 -29
- data/lib/pindo/{pgyer → command/pgyer}/comment.rb +7 -30
- data/lib/pindo/{pgyer → command/pgyer}/download.rb +35 -30
- data/lib/pindo/{pgyer → command/pgyer}/login.rb +3 -4
- data/lib/pindo/command/pgyer/resign.rb +111 -0
- data/lib/pindo/command/pgyer/upload.rb +123 -0
- data/lib/pindo/command/pgyer.rb +18 -0
- data/lib/pindo/{repo.rb → command/repo.rb} +4 -4
- data/lib/pindo/{utils → command/utils}/applovin.rb +43 -33
- data/lib/pindo/{utils → command/utils}/boss.rb +3 -3
- data/lib/pindo/command/utils/icon.rb +81 -0
- data/lib/pindo/{utils → command/utils}/renewproj.rb +1 -0
- data/lib/pindo/command/utils.rb +26 -0
- data/lib/pindo/command.rb +23 -26
- data/lib/pindo/config/pindoconfig.rb +27 -0
- data/lib/pindo/module/appselect.rb +9 -8
- data/lib/pindo/module/build/swarkhelper.rb +95 -0
- data/lib/pindo/module/cert/certhelper.rb +187 -0
- data/lib/pindo/module/cert/keychainhelper.rb +150 -0
- data/lib/pindo/module/{pemcreate.rb → cert/pemhelper.rb} +3 -1
- data/lib/pindo/module/cert/provisioninghelper.rb +137 -0
- data/lib/pindo/module/cert/xcodecerthelper.rb +326 -0
- data/lib/pindo/module/{pgyerhelper.rb → pgyer/pgyerhelper.rb} +246 -36
- data/lib/pindo/module/xcode/xcodeappconfig.rb +188 -0
- data/lib/pindo/module/xcode/xcodebuildconfig.rb +12 -0
- data/lib/pindo/module/xcode/xcodebuildhelper.rb +312 -0
- data/lib/pindo/module/xcode/xcoderesconstant.rb +248 -0
- data/lib/pindo/module/xcode/xcodereshandler.rb +198 -0
- data/lib/pindo/module/xcode/xcodereshelper.rb +119 -0
- data/lib/pindo/options/appconfigoptions.rb +1 -0
- data/lib/pindo/options/deployoptions.rb +38 -42
- data/lib/pindo/version.rb +1 -1
- metadata +110 -97
- data/lib/pindo/deploy/Fastfile +0 -233
- data/lib/pindo/deploy/build.rb +0 -167
- data/lib/pindo/deploy/cert.rb +0 -508
- data/lib/pindo/deploy/configproj.rb +0 -89
- data/lib/pindo/deploy.rb +0 -44
- data/lib/pindo/dev.rb +0 -23
- data/lib/pindo/env/flutter.rb +0 -59
- data/lib/pindo/env/flutter.sh +0 -116
- data/lib/pindo/env.rb +0 -17
- data/lib/pindo/ipa.rb +0 -22
- data/lib/pindo/lib.rb +0 -18
- data/lib/pindo/module/buildconfighelper.rb +0 -13
- data/lib/pindo/module/buildhelper.rb +0 -76
- data/lib/pindo/module/config_project.sh +0 -143
- data/lib/pindo/module/configprojhelper.rb +0 -631
- data/lib/pindo/module/icon_contents.json +0 -116
- data/lib/pindo/module/imessage_icon.json +0 -91
- data/lib/pindo/module/imgset_contents.json +0 -21
- data/lib/pindo/module/launchimg_contents.json +0 -21
- data/lib/pindo/module/xcodebuildpre.rb +0 -258
- data/lib/pindo/pgyer/upload.rb +0 -234
- data/lib/pindo/pgyer.rb +0 -17
- data/lib/pindo/utils/icon.rb +0 -91
- data/lib/pindo/utils/icon.sh +0 -133
- data/lib/pindo/utils/podindex.rb +0 -56
- data/lib/pindo/utils/podindex.sh +0 -30
- data/lib/pindo/utils.rb +0 -29
- /data/lib/pindo/{deploy → command/deploy}/check.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/confusecode.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/confuseproj.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/fabric.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/initconfig.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/pullconfig.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/pushconfig.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/quswark.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/quswauth.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/reportbug.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/tag.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/updateconfig.rb +0 -0
- /data/lib/pindo/{deploy → command/deploy}/uploadipa.rb +0 -0
- /data/lib/pindo/{dev → command/dev}/confusecode.rb +0 -0
- /data/lib/pindo/{dev → command/dev}/confuseproj.rb +0 -0
- /data/lib/pindo/{dev → command/dev}/pub.rb +0 -0
- /data/lib/pindo/{dev → command/dev}/renewcert.rb +0 -0
- /data/lib/pindo/{env → command/env}/dreamstudio.rb +0 -0
- /data/lib/pindo/{env → command/env}/quarkenv.rb +0 -0
- /data/lib/pindo/{env → command/env}/swarkenv.rb +0 -0
- /data/lib/pindo/{env → command/env}/workhard.rb +0 -0
- /data/lib/pindo/{lib → command/lib}/forcepush.rb +0 -0
- /data/lib/pindo/{lib → command/lib}/lint.rb +0 -0
- /data/lib/pindo/{lib → command/lib}/push.rb +0 -0
- /data/lib/pindo/{repo → command/repo}/clone.rb +0 -0
- /data/lib/pindo/{repo → command/repo}/create.rb +0 -0
- /data/lib/pindo/{repo → command/repo}/login.rb +0 -0
- /data/lib/pindo/{repo → command/repo}/search.rb +0 -0
- /data/lib/pindo/{setup.rb → command/setup.rb} +0 -0
- /data/lib/pindo/{upgrade.rb → command/upgrade.rb} +0 -0
- /data/lib/pindo/{utils → command/utils}/clearcert.rb +0 -0
- /data/lib/pindo/{utils → command/utils}/device.rb +0 -0
- /data/lib/pindo/{utils → command/utils}/tgate.rb +0 -0
- /data/lib/pindo/{utils → command/utils}/xcassets.rb +0 -0
- /data/lib/pindo/{utils → command/utils}/xcassets.sh +0 -0
- /data/lib/pindo/module/{appstore_in_app_purchase.rb → appstore/appstore_in_app_purchase.rb} +0 -0
- /data/lib/pindo/module/{appstore_metadata_connect_api_helper.rb → appstore/appstore_metadata_connect_api_helper.rb} +0 -0
- /data/lib/pindo/module/{appstore_metadata_fastlane_helper.rb → appstore/appstore_metadata_fastlane_helper.rb} +0 -0
- /data/lib/pindo/module/{iap_tier.json → appstore/iap_tier.json} +0 -0
- /data/lib/pindo/module/{commonconfuseproj.rb → build/commonconfuseproj.rb} +0 -0
- /data/lib/pindo/module/{xcodehelper.rb → xcode/xcodehelper.rb} +0 -0
@@ -281,6 +281,14 @@ module Pindo
|
|
281
281
|
return url
|
282
282
|
end
|
283
283
|
|
284
|
+
def cert_key_password
|
285
|
+
cert_key_password = ''
|
286
|
+
if !@pindo_user_config_json.nil? && !@pindo_user_config_json["develop_accout_info"].nil?
|
287
|
+
cert_key_password = @pindo_user_config_json["develop_accout_info"]["cert_key_password"]
|
288
|
+
end
|
289
|
+
return cert_key_password
|
290
|
+
end
|
291
|
+
|
284
292
|
def deploy_cert_giturl
|
285
293
|
url = ''
|
286
294
|
if !@pindo_user_config_json.nil? && !@pindo_user_config_json["develop_accout_info"].nil?
|
@@ -288,6 +296,16 @@ module Pindo
|
|
288
296
|
end
|
289
297
|
return url
|
290
298
|
end
|
299
|
+
|
300
|
+
def deploy_cert_decrypt_password
|
301
|
+
pass = ''
|
302
|
+
if !@pindo_user_config_json.nil? && !@pindo_user_config_json["develop_accout_info"].nil?
|
303
|
+
pass = @pindo_user_config_json["develop_accout_info"]["deploy_cert_decrypt_password"]
|
304
|
+
end
|
305
|
+
return pass
|
306
|
+
end
|
307
|
+
|
308
|
+
|
291
309
|
|
292
310
|
def dev_cert_giturl
|
293
311
|
url = ''
|
@@ -297,6 +315,15 @@ module Pindo
|
|
297
315
|
return url
|
298
316
|
end
|
299
317
|
|
318
|
+
|
319
|
+
def dev_cert_decrypt_password
|
320
|
+
pass = ''
|
321
|
+
if !@pindo_user_config_json.nil? && !@pindo_user_config_json["develop_accout_info"].nil?
|
322
|
+
pass = @pindo_user_config_json["develop_accout_info"]["dev_cert_decrypt_password"]
|
323
|
+
end
|
324
|
+
return pass
|
325
|
+
end
|
326
|
+
|
300
327
|
def demo_apple_id
|
301
328
|
url = ''
|
302
329
|
if !@pindo_user_config_json.nil? && !@pindo_user_config_json["develop_accout_info"].nil?
|
@@ -45,7 +45,6 @@ module Pindo
|
|
45
45
|
|
46
46
|
def all_tool_bundleid
|
47
47
|
setting_file = File.join(pindo_single_config.pindo_env_configdir,'bundleid_config.json')
|
48
|
-
puts setting_file
|
49
48
|
setting_json = load_setting(setting_file:setting_file)
|
50
49
|
sub_json = []
|
51
50
|
if !setting_json.nil? && !setting_json['all_tool_bundleid'].nil?
|
@@ -86,10 +85,10 @@ module Pindo
|
|
86
85
|
puts "具体参考文档: https://tower.im/teams/851356/repository_documents/714/"
|
87
86
|
puts ""
|
88
87
|
puts "App Type:"
|
89
|
-
|
88
|
+
puts
|
90
89
|
cli.choose do |menu| # you can also use constants like :blue
|
91
|
-
menu.header = "
|
92
|
-
menu.prompt = "请选择使用的
|
90
|
+
menu.header = "可用的Bundle Id如下:"
|
91
|
+
menu.prompt = "请选择使用的Bundle Id,请输入选项(1/2/3...):"
|
93
92
|
menu.index_suffix = ") "
|
94
93
|
if !sub_setting_json.nil?
|
95
94
|
sub_setting_json.each do |key, items|
|
@@ -115,9 +114,10 @@ module Pindo
|
|
115
114
|
|
116
115
|
cli = HighLine.new
|
117
116
|
menu_choice="None"
|
117
|
+
puts
|
118
118
|
cli.choose do |menu| # you can also use constants like :blue
|
119
|
-
menu.header = "
|
120
|
-
menu.prompt = "请选择使用的
|
119
|
+
menu.header = "可用的Bundle Id如下:"
|
120
|
+
menu.prompt = "请选择使用的Bundle Id,请输入选项(1/2/3...):"
|
121
121
|
if !all_bundleid.nil? && all_bundleid.length > 0
|
122
122
|
for bunld_id in all_bundleid do
|
123
123
|
menu.choice(bunld_id) do |details|
|
@@ -140,10 +140,11 @@ module Pindo
|
|
140
140
|
|
141
141
|
cli = HighLine.new
|
142
142
|
menu_choice="None"
|
143
|
+
puts
|
143
144
|
cli.choose do |menu| # you can also use constants like :blue
|
144
145
|
|
145
|
-
menu.header = "
|
146
|
-
menu.prompt = "请选择使用的
|
146
|
+
menu.header = "可用的Bundle Id如下:"
|
147
|
+
menu.prompt = "请选择使用的Bundle Id,请输入选项(1/2/3...):"
|
147
148
|
if !all_bundleid.nil? && all_bundleid.length > 0
|
148
149
|
for bunld_id in all_bundleid do
|
149
150
|
menu.choice(bunld_id) do |details|
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
require 'xcodeproj'
|
3
|
+
require 'json'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
|
7
|
+
module Pindo
|
8
|
+
module SwarkHelper
|
9
|
+
|
10
|
+
def check_swark_authorize(config_json:nil, buid_type:nil)
|
11
|
+
if !config_json.nil? && config_json['project_info']['xcode_build_type'] && config_json["project_info"]["xcode_build_type"].include?("swark")
|
12
|
+
|
13
|
+
app_config_dir = File.join(File::expand_path(pindo_single_config.pindo_dir), @deploy_identifier)
|
14
|
+
swark_authorize_file = File.join(app_config_dir, "swark_authorize.json")
|
15
|
+
if File.exist?(swark_authorize_file)
|
16
|
+
swark_authorize_json=JSON.parse(File.read(swark_authorize_file))
|
17
|
+
unless swark_authorize_json && swark_authorize_json['swark_authorize_status']
|
18
|
+
show_swark_authorize_error(swark_authorize_json:swark_authorize_json , buid_type:buid_type)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def show_swark_authorize_error(swark_authorize_json:nil, buid_type:nil)
|
25
|
+
|
26
|
+
puts "++++ +++ Need authorize !!! ++++"
|
27
|
+
puts "+++ Need authorize !!! ++++"
|
28
|
+
puts "+++ Need authorize !!! +++++"
|
29
|
+
puts " =========================="
|
30
|
+
puts
|
31
|
+
|
32
|
+
puts "方法一:"
|
33
|
+
command = " 1. swark authorize "
|
34
|
+
command = command + swark_authorize_json["swark_authorize_teamid"] + "." + swark_authorize_json["swark_authorize_bundleid"]
|
35
|
+
puts command
|
36
|
+
command = " 2. 修改swark_authorize.json 中的 swark_authorize_status = true 并且提交"
|
37
|
+
puts
|
38
|
+
|
39
|
+
puts "方法二:"
|
40
|
+
puts " pindo deploy quswark"
|
41
|
+
if buid_type.downcase.to_s.include?("appstore")
|
42
|
+
raise Informative, "Need swark authorize !!!"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def add_swark_entitlement(entitlements_plist_path:nil, bundle_id_dict:nil)
|
48
|
+
if @config_json['project_info']['xcode_build_type'] && @config_json["project_info"]["xcode_build_type"].include?("swark")
|
49
|
+
if File.exist?(entitlements_plist_path)
|
50
|
+
entitlements_plist_dict = Xcodeproj::Plist.read_from_path(entitlements_plist_path)
|
51
|
+
|
52
|
+
key_value = @team_id_vaule
|
53
|
+
key_value = key_value + "." + @bundle_id
|
54
|
+
entitlements_plist_dict['keychain-access-groups'] = [key_value]
|
55
|
+
|
56
|
+
Xcodeproj::Plist.write_to_path(entitlements_plist_dict, entitlements_plist_path)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_swark_authorize_json
|
62
|
+
if @build_type == "appstore" && @config_json['project_info']['xcode_build_type'] && @config_json["project_info"]["xcode_build_type"].include?("swark")
|
63
|
+
|
64
|
+
app_config_dir = clong_buildconfig_repo(repo_name: @deploy_repo_name)
|
65
|
+
swark_authorize_file = File.join(app_config_dir, "swark_authorize.json")
|
66
|
+
if File.exist?(swark_authorize_file)
|
67
|
+
swark_authorize_json=JSON.parse(File.read(swark_authorize_file))
|
68
|
+
if swark_authorize_json && swark_authorize_json['swark_authorize_status']
|
69
|
+
puts "swark already authorize success !!! "
|
70
|
+
else
|
71
|
+
swark_authorize_json = swark_authorize_json || {}
|
72
|
+
swark_authorize_json['swark_authorize_teamid'] = @team_id_vaule
|
73
|
+
swark_authorize_json['swark_authorize_bundleid'] = @bundle_id
|
74
|
+
swark_authorize_json['swark_authorize_status'] = false
|
75
|
+
File.open(swark_authorize_file, "w") do |f|
|
76
|
+
f.write(JSON.pretty_generate(swark_authorize_json))
|
77
|
+
end
|
78
|
+
git_addpush_repo(path:app_config_dir, message:"add swark authorize json")
|
79
|
+
end
|
80
|
+
else
|
81
|
+
swark_authorize_json = swark_authorize_json || {}
|
82
|
+
swark_authorize_json['swark_authorize_teamid'] = @team_id_vaule
|
83
|
+
swark_authorize_json['swark_authorize_bundleid'] = @bundle_id
|
84
|
+
swark_authorize_json['swark_authorize_status'] = false
|
85
|
+
File.open(swark_authorize_file, "w") do |f|
|
86
|
+
f.write(JSON.pretty_generate(swark_authorize_json))
|
87
|
+
end
|
88
|
+
git_addpush_repo(path:app_config_dir, message:"add swark authorize json")
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
|
2
|
+
require 'match'
|
3
|
+
require 'openssl'
|
4
|
+
require 'pindo/base/aeshelper'
|
5
|
+
require 'pindo/module/cert/keychainhelper'
|
6
|
+
require 'pindo/module/cert/provisioninghelper'
|
7
|
+
|
8
|
+
|
9
|
+
module Pindo
|
10
|
+
|
11
|
+
module CertHelper
|
12
|
+
|
13
|
+
def get_cert_info(cer_certificate)
|
14
|
+
# can receive a certificate path or the file data
|
15
|
+
begin
|
16
|
+
if File.exist?(cer_certificate)
|
17
|
+
cer_certificate = File.binread(cer_certificate)
|
18
|
+
end
|
19
|
+
rescue ArgumentError
|
20
|
+
# cert strings have null bytes; suppressing output
|
21
|
+
end
|
22
|
+
|
23
|
+
cert = OpenSSL::X509::Certificate.new(cer_certificate)
|
24
|
+
|
25
|
+
# openssl output:
|
26
|
+
# subject= /UID={User ID}/CN={Certificate Name}/OU={Certificate User}/O={Organisation}/C={Country}
|
27
|
+
cert_info = cert.subject.to_s.gsub(/\s*subject=\s*/, "").tr("/", "\n")
|
28
|
+
out_array = cert_info.split("\n")
|
29
|
+
openssl_keys_to_readable_keys = {
|
30
|
+
'UID' => 'User ID',
|
31
|
+
'CN' => 'Common Name',
|
32
|
+
'OU' => 'Organisation Unit',
|
33
|
+
'O' => 'Organisation',
|
34
|
+
'C' => 'Country',
|
35
|
+
'notBefore' => 'Start Datetime',
|
36
|
+
'notAfter' => 'End Datetime'
|
37
|
+
}
|
38
|
+
|
39
|
+
return out_array.map { |x| x.split(/=+/) if x.include?("=") }
|
40
|
+
.compact
|
41
|
+
.map { |k, v| [openssl_keys_to_readable_keys.fetch(k, k), v] }
|
42
|
+
.push([openssl_keys_to_readable_keys.fetch("notBefore"), cert.not_before])
|
43
|
+
.push([openssl_keys_to_readable_keys.fetch("notAfter"), cert.not_after])
|
44
|
+
rescue => ex
|
45
|
+
raise Informative, "get_cert_info: #{ex}"
|
46
|
+
return {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def select_cert_or_key(paths:)
|
50
|
+
cert_id_path = ENV['MATCH_CERTIFICATE_ID'] ? paths.find { |path| path.include?(ENV['MATCH_CERTIFICATE_ID']) } : nil
|
51
|
+
cert_id_path || paths.last
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_cert_valid?(cer_certificate_path)
|
55
|
+
cert = OpenSSL::X509::Certificate.new(File.binread(cer_certificate_path))
|
56
|
+
now = Time.now.utc
|
57
|
+
return (now <=> cert.not_after) == -1
|
58
|
+
end
|
59
|
+
|
60
|
+
def isMac?
|
61
|
+
(/darwin/ =~ RUBY_PLATFORM) != nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def install_certs(cert_url:nil, certs_dir:nil, cert_type:nil)
|
65
|
+
|
66
|
+
|
67
|
+
if !cert_type.downcase.include?("development")
|
68
|
+
cert_type = "distribution"
|
69
|
+
end
|
70
|
+
|
71
|
+
certs = Dir[File.join(certs_dir, "certs", cert_type.to_s, "*.cer")]
|
72
|
+
keys = Dir[File.join(certs_dir, "certs", cert_type.to_s, "*.p12")]
|
73
|
+
|
74
|
+
if certs.count == 0 || keys.count == 0
|
75
|
+
raise Informative, "No certificates found in #{certs_dir}"
|
76
|
+
else
|
77
|
+
output_dir = Dir.mktmpdir
|
78
|
+
|
79
|
+
decrypt_password = AESHelper.fetch_password(keychain_name:cert_url)
|
80
|
+
Funlog.instance.fancyinfo_start("正在安装证书...")
|
81
|
+
|
82
|
+
cert_path = AESHelper.decrypt_specific_file(src_file: certs.first, password:decrypt_password, output_dir: output_dir)
|
83
|
+
unless cert_path.nil? || File.exist?(cert_path)
|
84
|
+
AESHelper.delete_password(keychain_name:cert_url)
|
85
|
+
raise Informative, "证书解析失败,密码错误!"
|
86
|
+
end
|
87
|
+
|
88
|
+
key_path = AESHelper.decrypt_specific_file(src_file: keys.first, password:decrypt_password, output_dir: output_dir)
|
89
|
+
unless key_path.nil? || File.exist?(key_path)
|
90
|
+
AESHelper.delete_password(keychain_name:cert_url)
|
91
|
+
raise Informative, "证书解析失败,密码错误!"
|
92
|
+
end
|
93
|
+
|
94
|
+
unless is_cert_valid?(cert_path)
|
95
|
+
raise Informative, "证书已经过期,请重新生产新证书!"
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
if isMac?
|
100
|
+
|
101
|
+
keychain_name = "login.keychain"
|
102
|
+
if FastlaneCore::CertChecker.installed?(cert_path, in_keychain: nil)
|
103
|
+
Funlog.instance.fancyinfo_success("证书#{File.basename(cert_path)}已安装,无需重复安装!")
|
104
|
+
else
|
105
|
+
|
106
|
+
cert_password = Pindoconfig.instance.cert_key_password
|
107
|
+
keychain = 'login.keychain'
|
108
|
+
keychain_path = FastlaneCore::Helper.keychain_path(keychain)
|
109
|
+
|
110
|
+
KeychainHelper.import_file(cert_path, keychain_path, keychain_password: cert_password, certificate_password:'' )
|
111
|
+
KeychainHelper.import_file(key_path, keychain_path, keychain_password: cert_password, certificate_password: '')
|
112
|
+
|
113
|
+
|
114
|
+
Funlog.instance.fancyinfo_success("证书'#{File.basename(cert_path)}'安装完成!")
|
115
|
+
end
|
116
|
+
else
|
117
|
+
Funlog.instance.fancyinfo_error("非Mac电脑不支持安装证书!")
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
def install_provisionfiles(cert_url:nil, certs_dir:nil, bundle_id_map:nil, cert_type:nil)
|
123
|
+
|
124
|
+
|
125
|
+
Funlog.instance.fancyinfo_start("正在安装Provisioning Profiles文件...")
|
126
|
+
|
127
|
+
|
128
|
+
if cert_type.downcase.include?("development")
|
129
|
+
cert_type = "Development"
|
130
|
+
elsif cert_type.downcase.include?("adhoc")
|
131
|
+
cert_type = "Adhoc"
|
132
|
+
else
|
133
|
+
cert_type = "AppStore"
|
134
|
+
end
|
135
|
+
|
136
|
+
un_exist_files = []
|
137
|
+
|
138
|
+
provisioning_info_array = []
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
provisioning_info_array = []
|
143
|
+
|
144
|
+
bundle_id_map.each do |type, bundle_id_temp|
|
145
|
+
profile_filename = File.join(certs_dir, "profiles", cert_type.downcase.to_s,[cert_type.to_s, bundle_id_temp].join('_') + '.mobileprovision')
|
146
|
+
unless File.exist?(profile_filename)
|
147
|
+
un_exist_files << profile_filename
|
148
|
+
next
|
149
|
+
end
|
150
|
+
# puts "正在安装 #{bundle_id_temp}..."
|
151
|
+
decrypt_password = AESHelper.fetch_password(keychain_name:cert_url)
|
152
|
+
output_dir = Dir.mktmpdir
|
153
|
+
file_decrypt = AESHelper.decrypt_specific_file(src_file: profile_filename, password:decrypt_password, output_dir: output_dir)
|
154
|
+
destpath = Provisioninghelper.install(file_decrypt)
|
155
|
+
parsed_data = Provisioninghelper.parse(destpath)
|
156
|
+
|
157
|
+
provisioning_info = {}
|
158
|
+
provisioning_info['type'] = type
|
159
|
+
provisioning_info['bundle_id'] = bundle_id_temp
|
160
|
+
provisioning_info['profile_name'] = parsed_data['Name']
|
161
|
+
provisioning_info['profile_path'] = destpath
|
162
|
+
|
163
|
+
|
164
|
+
cert_info = get_cert_info(parsed_data["DeveloperCertificates"].first.string).to_h
|
165
|
+
provisioning_info['signing_identity'] = cert_info["Common Name"]
|
166
|
+
provisioning_info['team_id'] = parsed_data["TeamIdentifier"].first
|
167
|
+
|
168
|
+
# puts JSON.pretty_generate(provisioning_info)
|
169
|
+
provisioning_info_array << provisioning_info
|
170
|
+
end
|
171
|
+
|
172
|
+
Funlog.instance.fancyinfo_success("Provisioning Profiles文件安装完成!")
|
173
|
+
|
174
|
+
if un_exist_files.size > 0
|
175
|
+
Funlog.instance.fancyinfo_error("证书Provisioning Profiles文件不存在!")
|
176
|
+
raise Informative, "The following profiles do not exist: #{un_exist_files.join(', ')}"
|
177
|
+
end
|
178
|
+
|
179
|
+
return provisioning_info_array
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
|
2
|
+
require 'open3'
|
3
|
+
require 'security'
|
4
|
+
|
5
|
+
module Pindo
|
6
|
+
|
7
|
+
module KeychainHelper
|
8
|
+
|
9
|
+
def self.import_file(path, keychain_path, keychain_password: nil, certificate_password: "", skip_set_partition_list: false, output: false)
|
10
|
+
# puts path
|
11
|
+
|
12
|
+
# puts "Could not find file '#{path}'" unless File.exist?(path)
|
13
|
+
|
14
|
+
password_part = " -P #{certificate_password.shellescape}"
|
15
|
+
|
16
|
+
command = "security import #{path.shellescape} -k '#{keychain_path.shellescape}'"
|
17
|
+
command << password_part
|
18
|
+
command << " -T /usr/bin/codesign" # to not be asked for permission when running a tool like `gym` (before Sierra)
|
19
|
+
command << " -T /usr/bin/security"
|
20
|
+
command << " -T /usr/bin/productbuild" # to not be asked for permission when using an installer cert for macOS
|
21
|
+
command << " -T /usr/bin/productsign" # to not be asked for permission when using an installer cert for macOS
|
22
|
+
command << " 1> /dev/null" unless output
|
23
|
+
|
24
|
+
# puts command
|
25
|
+
# sensitive_command = command.gsub(password_part, " -P ********")
|
26
|
+
|
27
|
+
# UI.command(sensitive_command) if output
|
28
|
+
Open3.popen3(command) do |stdin, stdout, stderr, thrd|
|
29
|
+
# UI.command_output(stdout.read.to_s) if output
|
30
|
+
# puts "123"
|
31
|
+
# Set partition list only if success since it can be a time consuming process if a lot of keys are installed
|
32
|
+
if thrd.value.success? && !skip_set_partition_list
|
33
|
+
# puts "安装成功!"
|
34
|
+
keychain_password ||= resolve_keychain_password(keychain_path)
|
35
|
+
set_partition_list(path, keychain_path, keychain_password: keychain_password, output: output)
|
36
|
+
else
|
37
|
+
# Output verbose if file is already installed since not an error otherwise we will show the whole error
|
38
|
+
err = stderr.read.to_s.strip
|
39
|
+
|
40
|
+
if err.include?("SecKeychainItemImport") && err.include?("The specified item already exists in the keychain")
|
41
|
+
# UI.verbose("'}' is already installed on this machine")
|
42
|
+
# puts "证书key已经存在,安装完成!"
|
43
|
+
else
|
44
|
+
# puts err
|
45
|
+
# raise Informative, err
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.help_backticks(command, print: true)
|
52
|
+
# UI.command(command) if print
|
53
|
+
result = `#{command}`
|
54
|
+
# UI.command_output(result) if print
|
55
|
+
return result
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.set_partition_list(path, keychain_path, keychain_password: nil, output: false)
|
59
|
+
# When security supports partition lists, also add the partition IDs
|
60
|
+
# See https://openradar.appspot.com/28524119
|
61
|
+
if help_backticks('security -h | grep set-key-partition-list', print: false).length > 0
|
62
|
+
password_part = " -k #{keychain_password.to_s.shellescape}"
|
63
|
+
|
64
|
+
command = "security set-key-partition-list"
|
65
|
+
command << " -S apple-tool:,apple:,codesign:"
|
66
|
+
command << " -s" # This is a needed in Catalina to prevent "security: SecKeychainItemCopyAccess: A missing value was detected."
|
67
|
+
command << password_part
|
68
|
+
command << " #{keychain_path.shellescape}"
|
69
|
+
command << " 1> /dev/null" # always disable stdout. This can be very verbose, and leak potentially sensitive info
|
70
|
+
|
71
|
+
# Showing loading indicator as this can take some time if a lot of keys installed
|
72
|
+
# Helper.show_loading_indicator("Setting key partition list... (this can take a minute if there are a lot of keys installed)")
|
73
|
+
|
74
|
+
# Strip keychain password from command output
|
75
|
+
# sensitive_command = command.gsub(password_part, " -k ********")
|
76
|
+
# puts command
|
77
|
+
# UI.command(sensitive_command) if output
|
78
|
+
Open3.popen3(command) do |stdin, stdout, stderr, thrd|
|
79
|
+
unless thrd.value.success?
|
80
|
+
err = stderr.read.to_s.strip
|
81
|
+
|
82
|
+
# Inform user when no/wrong password was used as its needed to prevent UI permission popup from Xcode when signing
|
83
|
+
if err.include?("SecKeychainItemSetAccessWithPassword")
|
84
|
+
keychain_name = File.basename(keychain_path, ".*")
|
85
|
+
# puts "password #{keychain_password}"
|
86
|
+
# puts "keychain #{keychain_name}"
|
87
|
+
# puts "security #{server_name(keychain_name)}"
|
88
|
+
Security::InternetPassword.delete(server: server_name(keychain_name))
|
89
|
+
|
90
|
+
# UI.important("")
|
91
|
+
# UI.important("Could not configure imported keychain item (certificate) to prevent UI permission popup when code signing\n" \
|
92
|
+
# "Check if you supplied the correct `keychain_password` for keychain: `#{keychain_path}`\n" \
|
93
|
+
# "#{err}")
|
94
|
+
# UI.important("")
|
95
|
+
# UI.important("Please look at the following docs to see how to set a keychain password:")
|
96
|
+
# UI.important(" - https://docs.fastlane.tools/actions/sync_code_signing")
|
97
|
+
# UI.important(" - https://docs.fastlane.tools/actions/get_certificates")
|
98
|
+
# puts "证书密码错误!"
|
99
|
+
else
|
100
|
+
puts err
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Hiding after Open3 finishes
|
106
|
+
# Helper.hide_loading_indicator
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# https://github.com/fastlane/fastlane/issues/14196
|
112
|
+
# Keychain password is needed to set the partition list to
|
113
|
+
# prevent Xcode from prompting dialog for keychain password when signing
|
114
|
+
# 1. Uses keychain password from login keychain if found
|
115
|
+
# 2. Prompts user for keychain password and stores it in login keychain for user later
|
116
|
+
def self.resolve_keychain_password(keychain_path)
|
117
|
+
keychain_name = File.basename(keychain_path, ".*")
|
118
|
+
server = server_name(keychain_name)
|
119
|
+
|
120
|
+
# Attempt to find password in keychain for keychain
|
121
|
+
item = Security::InternetPassword.find(server: server)
|
122
|
+
if item
|
123
|
+
keychain_password = item.password
|
124
|
+
# UI.important("Using keychain password from keychain item #{server} in #{keychain_path}")
|
125
|
+
end
|
126
|
+
|
127
|
+
if keychain_password.nil?
|
128
|
+
# if UI.interactive?
|
129
|
+
# UI.important("Enter the password for #{keychain_path}")
|
130
|
+
# UI.important("This passphrase will be stored in your local keychain with the name #{server} and used in future runs")
|
131
|
+
# UI.important("This prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable")
|
132
|
+
keychain_password = FastlaneCore::Helper.ask_password(message: "Password for #{keychain_name} keychain: ", confirm: true, confirmation_message: "Type password for #{keychain_name} keychain again: ")
|
133
|
+
Security::InternetPassword.add(server, "", keychain_password)
|
134
|
+
# else
|
135
|
+
# 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")
|
136
|
+
# keychain_password = ""
|
137
|
+
# end
|
138
|
+
end
|
139
|
+
|
140
|
+
return keychain_password
|
141
|
+
end
|
142
|
+
|
143
|
+
# server name used for accessing the macOS keychain
|
144
|
+
def self.server_name(keychain_name)
|
145
|
+
["pindo", "keychain", keychain_name].join("_")
|
146
|
+
end
|
147
|
+
|
148
|
+
private_class_method :server_name
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'plist'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Pindo
|
5
|
+
class Provisioninghelper
|
6
|
+
class << self
|
7
|
+
# @return (Hash) The hash with the data of the provisioning profile
|
8
|
+
# @example
|
9
|
+
# {"AppIDName"=>"My App Name",
|
10
|
+
# "ApplicationIdentifierPrefix"=>["5A997XSAAA"],
|
11
|
+
# "CreationDate"=>#<DateTime: 2015-05-24T20:38:03+00:00 ((2457167j,74283s,0n),+0s,2299161j)>,
|
12
|
+
# "DeveloperCertificates"=>[#<StringIO:0x007f944b9666f8>],
|
13
|
+
# "Entitlements"=>
|
14
|
+
# {"keychain-access-groups"=>["5A997XSAAA.*"],
|
15
|
+
# "get-task-allow"=>false,
|
16
|
+
# "application-identifier"=>"5A997XAAA.net.sunapps.192",
|
17
|
+
# "com.apple.developer.team-identifier"=>"5A997XAAAA",
|
18
|
+
# "aps-environment"=>"production",
|
19
|
+
# "beta-reports-active"=>true},
|
20
|
+
# "ExpirationDate"=>#<DateTime: 2015-11-25T22:45:50+00:00 ((2457352j,81950s,0n),+0s,2299161j)>,
|
21
|
+
# "Name"=>"net.sunapps.192 AppStore",
|
22
|
+
# "TeamIdentifier"=>["5A997XSAAA"],
|
23
|
+
# "TeamName"=>"SunApps GmbH",
|
24
|
+
# "TimeToLive"=>185,
|
25
|
+
# "UUID"=>"1752e382-53bd-4910-a393-aaa7de0005ad",
|
26
|
+
# "Version"=>1}
|
27
|
+
def parse(path, keychain_path = nil)
|
28
|
+
|
29
|
+
|
30
|
+
plist = Plist.parse_xml(decode(path, keychain_path))
|
31
|
+
# puts JSON.pretty_generate(plist)
|
32
|
+
if (plist || []).count > 5
|
33
|
+
plist
|
34
|
+
else
|
35
|
+
raise Informative, "Error parsing provisioning profile at path '#{path}'"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String] The UUID of the given provisioning profile
|
40
|
+
def uuid(path, keychain_path = nil)
|
41
|
+
parse(path, keychain_path).fetch("UUID")
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] The Name of the given provisioning profile
|
45
|
+
def name(path, keychain_path = nil)
|
46
|
+
parse(path, keychain_path).fetch("Name")
|
47
|
+
end
|
48
|
+
|
49
|
+
def bundle_id(path, keychain_path = nil)
|
50
|
+
profile = parse(path, keychain_path)
|
51
|
+
app_id_prefix = profile["ApplicationIdentifierPrefix"].first
|
52
|
+
entitlements = profile["Entitlements"]
|
53
|
+
app_identifier = entitlements["application-identifier"] || entitlements["com.apple.application-identifier"]
|
54
|
+
bundle_id = app_identifier.gsub("#{app_id_prefix}.", "")
|
55
|
+
bundle_id
|
56
|
+
rescue
|
57
|
+
UI.error("Unable to extract the Bundle Id from the provided provisioning profile '#{path}'.")
|
58
|
+
end
|
59
|
+
|
60
|
+
def mac?(path, keychain_path = nil)
|
61
|
+
parse(path, keychain_path).fetch("Platform", []).include?('OSX')
|
62
|
+
end
|
63
|
+
|
64
|
+
def profile_filename(path, keychain_path = nil)
|
65
|
+
basename = uuid(path, keychain_path)
|
66
|
+
basename + profile_extension(path, keychain_path)
|
67
|
+
end
|
68
|
+
|
69
|
+
def profile_extension(path, keychain_path = nil)
|
70
|
+
if mac?(path, keychain_path)
|
71
|
+
".provisionprofile"
|
72
|
+
else
|
73
|
+
".mobileprovision"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def profiles_path
|
78
|
+
path = File.expand_path("~") + "/Library/MobileDevice/Provisioning Profiles/"
|
79
|
+
# If the directory doesn't exist, create it first
|
80
|
+
unless File.directory?(path)
|
81
|
+
FileUtils.mkdir_p(path)
|
82
|
+
end
|
83
|
+
|
84
|
+
return path
|
85
|
+
end
|
86
|
+
|
87
|
+
# Installs a provisioning profile for Xcode to use
|
88
|
+
def install(path, keychain_path = nil)
|
89
|
+
|
90
|
+
|
91
|
+
destination = File.join(profiles_path, profile_filename(path, keychain_path))
|
92
|
+
# puts "Installing provisioning profile... #{destination}"
|
93
|
+
|
94
|
+
if path != destination
|
95
|
+
# copy to Xcode provisioning profile directory
|
96
|
+
FileUtils.copy(path, destination)
|
97
|
+
unless File.exist?(destination)
|
98
|
+
raise Informative, "Failed installation of provisioning profile at location: '#{destination}'"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
destination
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def isMacOS?
|
107
|
+
(/darwin/ =~ RUBY_PLATFORM) != nil
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def decode(path, keychain_path = nil)
|
112
|
+
require 'tmpdir'
|
113
|
+
Dir.mktmpdir('fastlane') do |dir|
|
114
|
+
err = "#{dir}/cms.err"
|
115
|
+
# we want to prevent the error output to mix up with the standard output because of
|
116
|
+
# /dev/null: https://github.com/fastlane/fastlane/issues/6387
|
117
|
+
if isMacOS?
|
118
|
+
if keychain_path.nil?
|
119
|
+
decoded = `security cms -D -i "#{path}" 2> #{err}`
|
120
|
+
else
|
121
|
+
decoded = `security cms -D -i "#{path}" -k "#{keychain_path.shellescape}" 2> #{err}`
|
122
|
+
end
|
123
|
+
else
|
124
|
+
# `security` only works on Mac, fallback to `openssl`
|
125
|
+
# via https://stackoverflow.com/a/14379814/252627
|
126
|
+
decoded = `openssl smime -inform der -verify -noverify -in #{path.shellescape} 2> #{err}`
|
127
|
+
end
|
128
|
+
|
129
|
+
if $?.exitstatus != 0
|
130
|
+
raise Informative, "Failure to decode #{path}"
|
131
|
+
end
|
132
|
+
decoded
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|