pindo 5.12.2 → 5.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/pindo/base/git_handler.rb +692 -0
- data/lib/pindo/command/android/autobuild.rb +2 -2
- data/lib/pindo/command/android.rb +0 -1
- data/lib/pindo/command/appstore/adhocbuild.rb +258 -311
- data/lib/pindo/command/appstore/autobuild.rb +203 -0
- data/lib/pindo/command/appstore/autoresign.rb +35 -17
- data/lib/pindo/command/appstore/bundleid.rb +120 -0
- data/lib/pindo/command/appstore/cert.rb +212 -0
- data/lib/pindo/command/appstore/configproj.rb +81 -0
- data/lib/pindo/command/{deploy → appstore}/getitcinfo.rb +76 -91
- data/lib/pindo/command/appstore/iap.rb +788 -24
- data/lib/pindo/command/appstore/initconfig.rb +105 -0
- data/lib/pindo/command/appstore/itcapp.rb +95 -13
- data/lib/pindo/command/{deploy → appstore}/itcinfo.rb +90 -118
- data/lib/pindo/command/appstore/pem.rb +136 -0
- data/lib/pindo/command/appstore/pullconfig.rb +99 -0
- data/lib/pindo/command/appstore/quswark.rb +87 -0
- data/lib/pindo/command/appstore/quswauth.rb +67 -0
- data/lib/pindo/command/appstore/tag.rb +77 -0
- data/lib/pindo/command/appstore.rb +13 -1
- data/lib/pindo/command/env/quarkenv.rb +11 -13
- data/lib/pindo/command/env/swarkenv.rb +11 -16
- data/lib/pindo/command/ios/autobuild.rb +64 -43
- data/lib/pindo/command/ios/autoresign.rb +34 -19
- data/lib/pindo/command/ios/build.rb +10 -7
- data/lib/pindo/command/ios/cert.rb +27 -20
- data/lib/pindo/command/jps/upload.rb +3 -3
- data/lib/pindo/command/unity/autobuild.rb +2 -2
- data/lib/pindo/command/utils/clearcert.rb +2 -17
- data/lib/pindo/command/{deploy → utils}/fabric.rb +13 -13
- data/lib/pindo/command/utils/renewcert.rb +62 -38
- data/lib/pindo/command/utils/renewproj.rb +0 -3
- data/lib/pindo/command/{deploy → utils}/updateconfig.rb +6 -7
- data/lib/pindo/command/utils.rb +2 -0
- data/lib/pindo/command/web/autobuild.rb +2 -2
- data/lib/pindo/command.rb +30 -3
- data/lib/pindo/config/build_info_manager.rb +176 -0
- data/lib/pindo/config/ios_config_parser.rb +404 -0
- data/lib/pindo/module/android/android_build_helper.rb +110 -0
- data/lib/pindo/module/android/android_config_helper.rb +9 -5
- data/lib/pindo/module/android/gradle_helper.rb +88 -0
- data/lib/pindo/module/appstore/bundleid_helper.rb +349 -0
- data/lib/pindo/module/appstore/itcapp_helper.rb +228 -0
- data/lib/pindo/module/build/build_helper.rb +12 -0
- data/lib/pindo/module/build/swark_helper.rb +116 -77
- data/lib/pindo/module/cert/cert_helper.rb +74 -0
- data/lib/pindo/module/cert/pem_helper.rb +72 -0
- data/lib/pindo/module/cert/{xcodecerthelper.rb → xcode_cert_helper.rb} +208 -6
- data/lib/pindo/module/task/model/appstore/appstore_task.rb +18 -0
- data/lib/pindo/module/task/model/appstore/appstore_upload_ipa_task.rb +151 -0
- data/lib/pindo/module/task/model/appstore/appstore_upload_metadata_task.rb +250 -0
- data/lib/pindo/module/task/model/appstore/appstore_upload_screenshot_task.rb +276 -0
- data/lib/pindo/module/task/model/build/android_build_adhoc_task.rb +210 -0
- data/lib/pindo/module/task/model/build/{android_dev_build_task.rb → android_build_dev_task.rb} +2 -2
- data/lib/pindo/module/task/model/build/android_build_gplay_task.rb +210 -0
- data/lib/pindo/module/task/model/build/android_build_task.rb +13 -0
- data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +197 -0
- data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +367 -0
- data/lib/pindo/module/task/model/build/{ios_dev_build_task.rb → ios_build_dev_task.rb} +37 -27
- data/lib/pindo/module/task/model/build/ios_build_task.rb +13 -0
- data/lib/pindo/module/task/model/build/{web_dev_build_task.rb → web_build_dev_task.rb} +1 -1
- data/lib/pindo/module/task/model/build_task.rb +15 -12
- data/lib/pindo/module/task/model/jps_resign_task.rb +185 -0
- data/lib/pindo/module/task/model/{upload_task.rb → jps_upload_task.rb} +3 -3
- data/lib/pindo/module/task/model/unity_export_task.rb +3 -1
- data/lib/pindo/module/task/pindo_task.rb +19 -10
- data/lib/pindo/module/unity/unity_helper.rb +2 -1
- data/lib/pindo/module/xcode/ipa_resign_helper.rb +210 -0
- data/lib/pindo/module/xcode/{xcodeappconfig.rb → xcode_app_config.rb} +79 -0
- data/lib/pindo/module/xcode/xcode_build_config.rb +152 -17
- data/lib/pindo/module/xcode/xcode_build_helper.rb +151 -1
- data/lib/pindo/module/xcode/xcode_swark_helper.rb +341 -0
- data/lib/pindo/options/core/global_options_state.rb +268 -0
- data/lib/pindo/options/core/option_configuration.rb +206 -0
- data/lib/pindo/options/core/option_initializer.rb +51 -0
- data/lib/pindo/options/core/option_item.rb +144 -0
- data/lib/pindo/options/core/option_value_parser.rb +54 -0
- data/lib/pindo/options/groups/build_options.rb +60 -0
- data/lib/pindo/options/groups/jps_options.rb +70 -0
- data/lib/pindo/options/groups/option_group.rb +73 -0
- data/lib/pindo/options/helpers/bundleid_selector.rb +103 -0
- data/lib/pindo/options/options.rb +14 -0
- data/lib/pindo/version.rb +1 -1
- metadata +49 -41
- data/lib/pindo/command/android/build.rb +0 -186
- data/lib/pindo/command/appstore/import.rb +0 -259
- data/lib/pindo/command/deploy/build.rb +0 -250
- data/lib/pindo/command/deploy/bundleid.rb +0 -259
- data/lib/pindo/command/deploy/cert.rb +0 -202
- data/lib/pindo/command/deploy/check.rb +0 -93
- data/lib/pindo/command/deploy/configproj.rb +0 -120
- data/lib/pindo/command/deploy/confusecode.rb +0 -262
- data/lib/pindo/command/deploy/confuseproj.rb +0 -122
- data/lib/pindo/command/deploy/iap.rb +0 -826
- data/lib/pindo/command/deploy/initconfig.rb +0 -138
- data/lib/pindo/command/deploy/itcapp.rb +0 -146
- data/lib/pindo/command/deploy/pem.rb +0 -55
- data/lib/pindo/command/deploy/pullconfig.rb +0 -56
- data/lib/pindo/command/deploy/pushconfig.rb +0 -93
- data/lib/pindo/command/deploy/quswark.rb +0 -156
- data/lib/pindo/command/deploy/quswauth.rb +0 -76
- data/lib/pindo/command/deploy/reportbug.rb +0 -145
- data/lib/pindo/command/deploy/resign.rb +0 -300
- data/lib/pindo/command/deploy/tag.rb +0 -108
- data/lib/pindo/command/deploy/uploadipa.rb +0 -73
- data/lib/pindo/command/deploy.rb +0 -42
- data/lib/pindo/command/dev/autobuild.rb +0 -117
- data/lib/pindo/command/dev/build.rb +0 -94
- data/lib/pindo/command/dev/debug.rb +0 -112
- data/lib/pindo/module/task/model/build/android_release_build_task.rb +0 -29
- data/lib/pindo/module/task/model/build/ios_adhoc_build_task.rb +0 -53
- data/lib/pindo/module/task/model/build/ios_release_build_task.rb +0 -53
- data/lib/pindo/options/appconfigoptions.rb +0 -24
- data/lib/pindo/options/deployoptions.rb +0 -372
|
@@ -4,8 +4,139 @@ require 'pindo/base/aeshelper'
|
|
|
4
4
|
|
|
5
5
|
module Pindo
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
class XcodeCertHelper
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
|
|
11
|
+
# 标准化构建类型
|
|
12
|
+
# @param build_type [String, Symbol] 构建类型 ('dev', 'adhoc', 'release', 'development', 'appstore')
|
|
13
|
+
# @return [String] 标准化后的证书类型 ('development', 'adhoc', 'appstore')
|
|
14
|
+
def normalize_build_type(build_type)
|
|
15
|
+
case build_type.to_s.downcase
|
|
16
|
+
when 'dev', 'development'
|
|
17
|
+
'development'
|
|
18
|
+
when 'adhoc'
|
|
19
|
+
'adhoc'
|
|
20
|
+
when 'release', 'appstore'
|
|
21
|
+
'appstore'
|
|
22
|
+
else
|
|
23
|
+
raise ArgumentError, "无效的构建类型: #{build_type},支持的类型: dev/development, adhoc, release/appstore"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 安装证书并配置 Xcode 工程
|
|
28
|
+
# @param build_type [String, Symbol] 构建类型 ('dev', 'adhoc', 'release' 或 'development', 'adhoc', 'appstore')
|
|
29
|
+
# @param platform_type [String] 平台类型 ('ios', 'macos')
|
|
30
|
+
# @param project_dir [String] 项目目录
|
|
31
|
+
# @param config_file [String, nil] 配置文件路径(可选,默认从 project_dir 加载)
|
|
32
|
+
# @param skip_xcode_config [Boolean] 是否跳过 Xcode 配置
|
|
33
|
+
# @return [Hash] 返回证书信息 { provisioning_info_array:, team_id: }
|
|
34
|
+
def install_and_config_certs(
|
|
35
|
+
build_type:,
|
|
36
|
+
platform_type: 'ios',
|
|
37
|
+
project_dir: Dir.pwd,
|
|
38
|
+
config_file: nil,
|
|
39
|
+
skip_xcode_config: false
|
|
40
|
+
)
|
|
41
|
+
# 标准化构建类型
|
|
42
|
+
cert_type = normalize_build_type(build_type)
|
|
43
|
+
|
|
44
|
+
# 配置加载
|
|
45
|
+
require 'pindo/config/ios_config_parser'
|
|
46
|
+
config_parser = Pindo::IosConfigParser.instance
|
|
47
|
+
config_path = config_file || File.join(project_dir, 'config.json')
|
|
48
|
+
config_parser.load_config(config_file: config_path)
|
|
49
|
+
|
|
50
|
+
apple_id = config_parser.apple_id
|
|
51
|
+
bundle_id_map = config_parser.get_bundle_id_map
|
|
52
|
+
|
|
53
|
+
# 安装证书
|
|
54
|
+
require 'pindo/config/pindoconfig'
|
|
55
|
+
require 'pindo/base/git_handler'
|
|
56
|
+
require 'pindo/module/cert/cert_helper'
|
|
57
|
+
|
|
58
|
+
pindo_config = Pindoconfig.instance
|
|
59
|
+
|
|
60
|
+
cert_git_url = apple_id.eql?(pindo_config.demo_apple_id) ?
|
|
61
|
+
pindo_config.dev_cert_giturl :
|
|
62
|
+
pindo_config.deploy_cert_giturl
|
|
63
|
+
|
|
64
|
+
cert_reponame = cert_git_url.split("/").last.chomp(".git")
|
|
65
|
+
certs_dir = Pindo::GitHandler.getcode_to_dir(
|
|
66
|
+
reponame: cert_reponame,
|
|
67
|
+
remote_url: cert_git_url,
|
|
68
|
+
path: pindo_config.pindo_dir,
|
|
69
|
+
new_branch: apple_id
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 安装证书和配置文件
|
|
73
|
+
Pindo::CertHelper.install_certs(
|
|
74
|
+
cert_url: cert_git_url,
|
|
75
|
+
certs_dir: certs_dir,
|
|
76
|
+
cert_type: cert_type,
|
|
77
|
+
platform_type: platform_type
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
provisioning_info_array = Pindo::CertHelper.install_provisionfiles(
|
|
81
|
+
cert_url: cert_git_url,
|
|
82
|
+
certs_dir: certs_dir,
|
|
83
|
+
bundle_id_map: bundle_id_map,
|
|
84
|
+
cert_type: cert_type,
|
|
85
|
+
platform_type: platform_type
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
pindo_config.set_cert_info(dict: provisioning_info_array)
|
|
89
|
+
|
|
90
|
+
raise Informative, "未找到证书信息" if provisioning_info_array.nil? || provisioning_info_array.empty?
|
|
91
|
+
|
|
92
|
+
team_id = provisioning_info_array.first["team_id"]
|
|
93
|
+
|
|
94
|
+
# Swark 授权检查
|
|
95
|
+
main_bundle_id = config_parser.bundle_id
|
|
96
|
+
if config_parser.config_json && main_bundle_id
|
|
97
|
+
require 'pindo/module/build/swark_helper'
|
|
98
|
+
Pindo::SwarkHelper.add_swark_authorize_json(
|
|
99
|
+
build_type: cert_type,
|
|
100
|
+
config_json: config_parser.config_json,
|
|
101
|
+
team_id: team_id,
|
|
102
|
+
bundle_id: main_bundle_id,
|
|
103
|
+
deploy_repo_name: main_bundle_id
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# 配置 Xcode 工程 (如果需要)
|
|
108
|
+
unless skip_xcode_config
|
|
109
|
+
project_fullname = Dir.glob(File.join(project_dir, "/*.xcodeproj")).max_by { |f| File.mtime(f) }
|
|
110
|
+
if project_fullname && File.exist?(project_fullname)
|
|
111
|
+
proj_name = File.basename(project_fullname, ".xcodeproj")
|
|
112
|
+
|
|
113
|
+
Funlog.instance.fancyinfo_start("正在给Xcode配置证书...")
|
|
114
|
+
config_project_cert(
|
|
115
|
+
new_proj_name: proj_name,
|
|
116
|
+
new_project_dir: project_dir,
|
|
117
|
+
cert_type: cert_type,
|
|
118
|
+
platform_type: platform_type,
|
|
119
|
+
team_id_vaule: team_id,
|
|
120
|
+
provisioning_info_array: provisioning_info_array
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
config_infoplist_cert(
|
|
124
|
+
new_proj_name: proj_name,
|
|
125
|
+
new_project_dir: project_dir,
|
|
126
|
+
icloud_id: config_parser.icloud_id,
|
|
127
|
+
group_id: config_parser.group_id,
|
|
128
|
+
provisioning_info_array: provisioning_info_array
|
|
129
|
+
)
|
|
130
|
+
Funlog.instance.fancyinfo_success("Xcode配置证书完成!")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
{
|
|
135
|
+
provisioning_info_array: provisioning_info_array,
|
|
136
|
+
team_id: team_id
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
9
140
|
def get_target_name_map
|
|
10
141
|
return {
|
|
11
142
|
"MainTarget" => "bundle_id",
|
|
@@ -28,7 +159,9 @@ module Pindo
|
|
|
28
159
|
|
|
29
160
|
provisioning_info_array = []
|
|
30
161
|
|
|
31
|
-
|
|
162
|
+
# 使用 IosConfigParser 单例获取 bundle_id_map
|
|
163
|
+
config_parser = Pindo::IosConfigParser.instance
|
|
164
|
+
bundle_id_map = config_parser.get_bundle_id_map
|
|
32
165
|
|
|
33
166
|
bundle_id_map.each do |type, bundle_id_temp|
|
|
34
167
|
provisioning_info = {}
|
|
@@ -224,7 +357,29 @@ module Pindo
|
|
|
224
357
|
end
|
|
225
358
|
|
|
226
359
|
if target.product_type.to_s.eql?(Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application]) then
|
|
227
|
-
|
|
360
|
+
# 调用 SwarkHelper 添加权限配置
|
|
361
|
+
# 读取 config.json(如果存在)
|
|
362
|
+
config_json = nil
|
|
363
|
+
config_json_file = File.join(new_project_dir, "config.json")
|
|
364
|
+
if File.exist?(config_json_file)
|
|
365
|
+
config_json = JSON.parse(File.read(config_json_file))
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# 获取 team_id 和 bundle_id
|
|
369
|
+
team_id = provisioning_info_array.first["team_id"] if provisioning_info_array && provisioning_info_array.any?
|
|
370
|
+
bundle_id = provisioning_info_array.first["bundle_id"] if provisioning_info_array && provisioning_info_array.any?
|
|
371
|
+
|
|
372
|
+
if config_json && team_id && bundle_id
|
|
373
|
+
require 'pindo/module/build/swark_helper'
|
|
374
|
+
Pindo::SwarkHelper.add_swark_entitlement(
|
|
375
|
+
entitlements_plist_path: entitlements_plist_path,
|
|
376
|
+
bundle_id_dict: provisioning_info_array,
|
|
377
|
+
config_json: config_json,
|
|
378
|
+
team_id: team_id,
|
|
379
|
+
bundle_id: bundle_id
|
|
380
|
+
)
|
|
381
|
+
end
|
|
382
|
+
|
|
228
383
|
if !icloud_id.nil?
|
|
229
384
|
modify_entitlements_plist(entitlements_plist_path:entitlements_plist_path, icloud_id:icloud_id)
|
|
230
385
|
end
|
|
@@ -315,6 +470,7 @@ module Pindo
|
|
|
315
470
|
end
|
|
316
471
|
|
|
317
472
|
def create_upload_cert_info(apple_id:nil, cert_type:nil, platform_type:nil)
|
|
473
|
+
pindo_single_config = Pindoconfig.instance
|
|
318
474
|
|
|
319
475
|
cert_dest_dir = File.join(Dir.pwd, "cert")
|
|
320
476
|
if !File.exist?(cert_dest_dir)
|
|
@@ -483,6 +639,52 @@ module Pindo
|
|
|
483
639
|
return account_cert_set
|
|
484
640
|
end
|
|
485
641
|
|
|
642
|
+
def get_create_cert_match_values(apple_id:nil, bundle_id_array:nil, build_type:nil, platform_type:nil, renew_flag:false)
|
|
643
|
+
pindo_single_config = Pindoconfig.instance
|
|
644
|
+
|
|
645
|
+
if build_type.eql?("appstore") && (apple_id.eql?(pindo_single_config.demo_apple_id))
|
|
646
|
+
raise Informative, "#{apple_id} 是测试账号,不能创建appstore证书!!!"
|
|
647
|
+
end
|
|
648
|
+
if !build_type.eql?("appstore") && !apple_id.eql?(pindo_single_config.demo_apple_id)
|
|
649
|
+
raise Informative, "账号#{apple_id} 不能创建dev或者adhoc证书!!!"
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
git_url = pindo_single_config.deploy_cert_giturl
|
|
653
|
+
if apple_id.eql?(pindo_single_config.demo_apple_id)
|
|
654
|
+
git_url = pindo_single_config.dev_cert_giturl
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
force_for_new_devices_flag = true
|
|
658
|
+
if build_type.eql?("appstore")
|
|
659
|
+
force_for_new_devices_flag = false
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
if platform_type.downcase.include?("macos") && build_type.eql?("adhoc")
|
|
663
|
+
build_type = "developer_id"
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
values = {
|
|
667
|
+
username:apple_id,
|
|
668
|
+
app_identifier: bundle_id_array,
|
|
669
|
+
type: build_type,
|
|
670
|
+
keychain_password:"goodcert1",
|
|
671
|
+
git_url: git_url,
|
|
672
|
+
readonly:!renew_flag,
|
|
673
|
+
force:renew_flag,
|
|
674
|
+
clone_branch_directly:!renew_flag,
|
|
675
|
+
include_mac_in_profiles:true,
|
|
676
|
+
include_all_certificates:true,
|
|
677
|
+
generate_apple_certs:true,
|
|
678
|
+
shallow_clone:!renew_flag,
|
|
679
|
+
git_branch: apple_id,
|
|
680
|
+
platform:platform_type,
|
|
681
|
+
force_for_new_devices:force_for_new_devices_flag
|
|
682
|
+
}
|
|
683
|
+
return values
|
|
684
|
+
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
end # class << self
|
|
486
688
|
|
|
487
|
-
end
|
|
488
|
-
end
|
|
689
|
+
end # class XcodeCertHelper
|
|
690
|
+
end # module Pindo
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative '../pindo_task'
|
|
2
|
+
|
|
3
|
+
module Pindo
|
|
4
|
+
module TaskSystem
|
|
5
|
+
# App Store 任务基类
|
|
6
|
+
# 所有 App Store 相关任务的抽象基类
|
|
7
|
+
# 子类包括:AppStoreUploadIpaTask、AppStoreUploadMetadataTask、AppStoreUploadScreenshotTask
|
|
8
|
+
class AppStoreTask < PindoTask
|
|
9
|
+
# 空基类,仅用于类型标识
|
|
10
|
+
# 所有具体实现由子类完成
|
|
11
|
+
|
|
12
|
+
# 任务类型
|
|
13
|
+
def self.task_type
|
|
14
|
+
:appstore
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require_relative 'appstore_task'
|
|
2
|
+
require_relative '../task_config'
|
|
3
|
+
|
|
4
|
+
module Pindo
|
|
5
|
+
module TaskSystem
|
|
6
|
+
# App Store IPA 上传任务
|
|
7
|
+
# 上传 IPA 文件到 App Store Connect
|
|
8
|
+
class AppStoreUploadIpaTask < AppStoreTask
|
|
9
|
+
attr_reader :ipa_path, :ipa_file
|
|
10
|
+
|
|
11
|
+
# 重试配置
|
|
12
|
+
def self.default_retry_mode
|
|
13
|
+
RetryMode::DELAYED # 延迟重试
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.default_retry_count
|
|
17
|
+
3 # 默认可以重试 3 次
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.default_retry_delay
|
|
21
|
+
30 # 默认延迟 30 秒(App Store 上传较慢)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 初始化 IPA 上传任务
|
|
25
|
+
# @param ipa_path [String] 搜索 IPA 文件的路径
|
|
26
|
+
# @param ipa_file [String] 要上传的 IPA 文件(nil 表示自动查找)
|
|
27
|
+
# @param options [Hash] 选项
|
|
28
|
+
# @option options [String] :apple_id Apple ID(App Store Connect 账号)
|
|
29
|
+
# @option options [String] :app_password App专用密码或API Key
|
|
30
|
+
# @option options [String] :bundle_id Bundle ID
|
|
31
|
+
# @option options [Boolean] :skip_validation 是否跳过验证(默认 false)
|
|
32
|
+
# @option options [String] :platform 平台(ios/osx,默认 ios)
|
|
33
|
+
def initialize(ipa_path, ipa_file = nil, options = {})
|
|
34
|
+
@ipa_path = ipa_path # 搜索 IPA 文件的路径
|
|
35
|
+
@ipa_file = ipa_file # 要上传的 IPA 文件(nil 表示自动查找)
|
|
36
|
+
|
|
37
|
+
# App Store Connect 配置
|
|
38
|
+
@apple_id = options[:apple_id]
|
|
39
|
+
@app_password = options[:app_password]
|
|
40
|
+
@bundle_id = options[:bundle_id]
|
|
41
|
+
@skip_validation = options[:skip_validation] || false
|
|
42
|
+
@platform = options[:platform] || 'ios'
|
|
43
|
+
|
|
44
|
+
# 设置上传任务的优先级为 LOW,确保在构建任务之后执行
|
|
45
|
+
options[:priority] ||= TaskPriority::LOW
|
|
46
|
+
|
|
47
|
+
super("上传 IPA 到 App Store", options)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate
|
|
51
|
+
# 验证基本参数
|
|
52
|
+
unless @ipa_path && !@ipa_path.empty?
|
|
53
|
+
@error = "缺少必需参数: ipa_path"
|
|
54
|
+
return false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# IPA 文件会在 do_work 中查找,这里不验证
|
|
58
|
+
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
protected
|
|
63
|
+
|
|
64
|
+
def do_work
|
|
65
|
+
# 1. 确定上传的 IPA 文件
|
|
66
|
+
if @ipa_file && !@ipa_file.empty? && File.exist?(@ipa_file)
|
|
67
|
+
# 使用指定文件
|
|
68
|
+
file_to_upload = @ipa_file
|
|
69
|
+
puts " 使用指定的 IPA 文件: #{file_to_upload}"
|
|
70
|
+
else
|
|
71
|
+
# 在 ipa_path 下查找最新的 IPA 文件
|
|
72
|
+
file_to_upload = find_latest_ipa
|
|
73
|
+
unless file_to_upload && File.exist?(file_to_upload)
|
|
74
|
+
# 文件不存在,将重试次数设为 0,不再重试
|
|
75
|
+
@retry_count = 0
|
|
76
|
+
raise "未找到 IPA 文件(路径: #{@ipa_path})"
|
|
77
|
+
end
|
|
78
|
+
puts " 找到 IPA 文件: #{file_to_upload}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 2. 上传到 App Store Connect
|
|
82
|
+
upload_to_appstore(file_to_upload)
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
success: true,
|
|
86
|
+
ipa_path: file_to_upload
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# 在 ipa_path 下查找最新的 IPA 文件
|
|
93
|
+
def find_latest_ipa
|
|
94
|
+
search_pattern = File.join(@ipa_path, "**", "*.ipa")
|
|
95
|
+
ipa_files = Dir.glob(search_pattern)
|
|
96
|
+
|
|
97
|
+
if ipa_files.any?
|
|
98
|
+
# 返回修改时间最新的文件
|
|
99
|
+
latest_ipa = ipa_files.max_by { |f| File.mtime(f) }
|
|
100
|
+
return latest_ipa
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# 上传 IPA 到 App Store Connect
|
|
107
|
+
def upload_to_appstore(ipa_file)
|
|
108
|
+
puts "\n开始上传 IPA 到 App Store Connect..."
|
|
109
|
+
puts " IPA 文件: #{ipa_file}"
|
|
110
|
+
puts " Apple ID: #{@apple_id}" if @apple_id
|
|
111
|
+
puts " Bundle ID: #{@bundle_id}" if @bundle_id
|
|
112
|
+
puts " 平台: #{@platform}"
|
|
113
|
+
puts " 跳过验证: #{@skip_validation}"
|
|
114
|
+
|
|
115
|
+
# 使用 xcrun altool 上传
|
|
116
|
+
upload_with_altool(ipa_file)
|
|
117
|
+
|
|
118
|
+
puts "✓ IPA 上传成功"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# 使用 xcrun altool 上传 IPA
|
|
122
|
+
def upload_with_altool(ipa_file)
|
|
123
|
+
# 构建 altool 命令
|
|
124
|
+
cmd = ["xcrun", "altool"]
|
|
125
|
+
cmd << "--upload-app"
|
|
126
|
+
cmd << "--type" << @platform
|
|
127
|
+
cmd << "--file" << ipa_file
|
|
128
|
+
|
|
129
|
+
# 添加认证参数
|
|
130
|
+
if @apple_id && @app_password
|
|
131
|
+
cmd << "--username" << @apple_id
|
|
132
|
+
cmd << "--password" << @app_password
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# 跳过验证
|
|
136
|
+
if @skip_validation
|
|
137
|
+
cmd << "--skip-validation"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# 执行上传命令
|
|
141
|
+
puts " 执行命令: #{cmd.join(' ')}"
|
|
142
|
+
|
|
143
|
+
success = system(*cmd)
|
|
144
|
+
|
|
145
|
+
unless success
|
|
146
|
+
raise "上传失败:xcrun altool 命令执行出错(退出码: #{$?.exitstatus})"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
require_relative 'appstore_task'
|
|
2
|
+
require_relative '../task_config'
|
|
3
|
+
|
|
4
|
+
module Pindo
|
|
5
|
+
module TaskSystem
|
|
6
|
+
# App Store Metadata 上传任务
|
|
7
|
+
# 上传应用元数据(名称、描述、关键词等)到 App Store Connect
|
|
8
|
+
class AppStoreUploadMetadataTask < AppStoreTask
|
|
9
|
+
attr_reader :app_id, :metadata_path
|
|
10
|
+
|
|
11
|
+
# 重试配置
|
|
12
|
+
def self.default_retry_mode
|
|
13
|
+
RetryMode::DELAYED # 延迟重试
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.default_retry_count
|
|
17
|
+
2 # 默认可以重试 2 次
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.default_retry_delay
|
|
21
|
+
10 # 默认延迟 10 秒
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 初始化 Metadata 上传任务
|
|
25
|
+
# @param app_id [String] App Store Connect 应用 ID
|
|
26
|
+
# @param metadata_path [String] 元数据配置文件路径或目录
|
|
27
|
+
# @param options [Hash] 选项
|
|
28
|
+
# @option options [String] :apple_id Apple ID(App Store Connect 账号)
|
|
29
|
+
# @option options [String] :api_key_id API Key ID(使用 API Key 认证)
|
|
30
|
+
# @option options [String] :api_issuer_id API Issuer ID
|
|
31
|
+
# @option options [String] :api_key_path API Key 文件路径(.p8 文件)
|
|
32
|
+
# @option options [Hash] :metadata 元数据内容(如果不从文件读取)
|
|
33
|
+
# @option options [String] :locale 语言区域(默认 "zh-Hans")
|
|
34
|
+
# @option options [Boolean] :skip_screenshots 是否跳过截图上传(默认 false)
|
|
35
|
+
def initialize(app_id, metadata_path = nil, options = {})
|
|
36
|
+
@app_id = app_id # App Store Connect 应用 ID
|
|
37
|
+
@metadata_path = metadata_path # 元数据配置文件路径或目录
|
|
38
|
+
|
|
39
|
+
# App Store Connect 认证配置
|
|
40
|
+
@apple_id = options[:apple_id]
|
|
41
|
+
@api_key_id = options[:api_key_id]
|
|
42
|
+
@api_issuer_id = options[:api_issuer_id]
|
|
43
|
+
@api_key_path = options[:api_key_path]
|
|
44
|
+
|
|
45
|
+
# 元数据内容
|
|
46
|
+
@metadata = options[:metadata] || {}
|
|
47
|
+
@locale = options[:locale] || "zh-Hans"
|
|
48
|
+
@skip_screenshots = options[:skip_screenshots] || false
|
|
49
|
+
|
|
50
|
+
# 设置上传任务的优先级为 NORMAL
|
|
51
|
+
options[:priority] ||= TaskPriority::NORMAL
|
|
52
|
+
|
|
53
|
+
super("上传 Metadata 到 App Store", options)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate
|
|
57
|
+
# 验证基本参数
|
|
58
|
+
unless @app_id && !@app_id.empty?
|
|
59
|
+
@error = "缺少必需参数: app_id"
|
|
60
|
+
return false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 验证认证信息(Apple ID 或 API Key)
|
|
64
|
+
has_apple_id_auth = @apple_id && !@apple_id.empty?
|
|
65
|
+
has_api_key_auth = @api_key_id && @api_issuer_id && @api_key_path
|
|
66
|
+
|
|
67
|
+
unless has_apple_id_auth || has_api_key_auth
|
|
68
|
+
@error = "缺少认证信息:需要提供 Apple ID 或 API Key"
|
|
69
|
+
return false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# 验证元数据来源
|
|
73
|
+
has_metadata_path = @metadata_path && !@metadata_path.empty?
|
|
74
|
+
has_metadata_content = @metadata && !@metadata.empty?
|
|
75
|
+
|
|
76
|
+
unless has_metadata_path || has_metadata_content
|
|
77
|
+
@error = "缺少元数据:需要提供 metadata_path 或 metadata 内容"
|
|
78
|
+
return false
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
def do_work
|
|
87
|
+
# 1. 加载元数据
|
|
88
|
+
metadata_to_upload = load_metadata
|
|
89
|
+
|
|
90
|
+
# 2. 上传元数据到 App Store Connect
|
|
91
|
+
upload_metadata(metadata_to_upload)
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
success: true,
|
|
95
|
+
app_id: @app_id,
|
|
96
|
+
locale: @locale
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# 加载元数据
|
|
103
|
+
def load_metadata
|
|
104
|
+
# 如果已经有元数据内容,直接使用
|
|
105
|
+
return @metadata unless @metadata.empty?
|
|
106
|
+
|
|
107
|
+
# 从文件或目录加载
|
|
108
|
+
if @metadata_path && File.exist?(@metadata_path)
|
|
109
|
+
if File.directory?(@metadata_path)
|
|
110
|
+
load_metadata_from_directory
|
|
111
|
+
else
|
|
112
|
+
load_metadata_from_file
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
raise "元数据路径不存在: #{@metadata_path}"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# 从目录加载元数据(Fastlane metadata 格式)
|
|
120
|
+
def load_metadata_from_directory
|
|
121
|
+
puts " 从目录加载元数据: #{@metadata_path}"
|
|
122
|
+
|
|
123
|
+
metadata = {}
|
|
124
|
+
locale_path = File.join(@metadata_path, @locale)
|
|
125
|
+
|
|
126
|
+
unless File.directory?(locale_path)
|
|
127
|
+
raise "未找到语言目录: #{locale_path}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# 读取各个元数据文件
|
|
131
|
+
metadata_files = {
|
|
132
|
+
name: 'name.txt',
|
|
133
|
+
subtitle: 'subtitle.txt',
|
|
134
|
+
description: 'description.txt',
|
|
135
|
+
keywords: 'keywords.txt',
|
|
136
|
+
marketing_url: 'marketing_url.txt',
|
|
137
|
+
support_url: 'support_url.txt',
|
|
138
|
+
privacy_url: 'privacy_policy_url.txt',
|
|
139
|
+
promotional_text: 'promotional_text.txt',
|
|
140
|
+
release_notes: 'release_notes.txt'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
metadata_files.each do |key, filename|
|
|
144
|
+
file_path = File.join(locale_path, filename)
|
|
145
|
+
if File.exist?(file_path)
|
|
146
|
+
content = File.read(file_path).strip
|
|
147
|
+
metadata[key] = content unless content.empty?
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
metadata
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# 从 JSON/YAML 文件加载元数据
|
|
155
|
+
def load_metadata_from_file
|
|
156
|
+
puts " 从文件加载元数据: #{@metadata_path}"
|
|
157
|
+
|
|
158
|
+
case File.extname(@metadata_path)
|
|
159
|
+
when '.json'
|
|
160
|
+
require 'json'
|
|
161
|
+
JSON.parse(File.read(@metadata_path))
|
|
162
|
+
when '.yml', '.yaml'
|
|
163
|
+
require 'yaml'
|
|
164
|
+
YAML.load_file(@metadata_path)
|
|
165
|
+
else
|
|
166
|
+
raise "不支持的元数据文件格式: #{@metadata_path}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# 上传元数据到 App Store Connect
|
|
171
|
+
def upload_metadata(metadata)
|
|
172
|
+
puts "\n开始上传 Metadata 到 App Store Connect..."
|
|
173
|
+
puts " App ID: #{@app_id}"
|
|
174
|
+
puts " 语言区域: #{@locale}"
|
|
175
|
+
puts " 元数据项: #{metadata.keys.join(', ')}"
|
|
176
|
+
|
|
177
|
+
# 使用 Fastlane deliver 或 API 上传元数据
|
|
178
|
+
if use_api_key?
|
|
179
|
+
upload_with_api(metadata)
|
|
180
|
+
else
|
|
181
|
+
upload_with_fastlane(metadata)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
puts "✓ Metadata 上传成功"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# 判断是否使用 API Key 认证
|
|
188
|
+
def use_api_key?
|
|
189
|
+
@api_key_id && @api_issuer_id && @api_key_path
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# 使用 App Store Connect API 上传
|
|
193
|
+
def upload_with_api(metadata)
|
|
194
|
+
puts " 使用 App Store Connect API 上传..."
|
|
195
|
+
|
|
196
|
+
# 这里需要调用 App Store Connect API Helper
|
|
197
|
+
# require 'pindo/module/appstore/appstore_metadata_connect_api_helper'
|
|
198
|
+
|
|
199
|
+
# 示例实现(需要根据实际 Helper 调整)
|
|
200
|
+
# AppstoreMetadataConnectApiHelper.upload_metadata(
|
|
201
|
+
# app_id: @app_id,
|
|
202
|
+
# api_key_id: @api_key_id,
|
|
203
|
+
# api_issuer_id: @api_issuer_id,
|
|
204
|
+
# api_key_path: @api_key_path,
|
|
205
|
+
# locale: @locale,
|
|
206
|
+
# metadata: metadata
|
|
207
|
+
# )
|
|
208
|
+
|
|
209
|
+
# 临时占位实现
|
|
210
|
+
puts " TODO: 实现 API 上传逻辑"
|
|
211
|
+
raise "API 上传功能尚未实现,请使用 Fastlane 方式"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# 使用 Fastlane deliver 上传
|
|
215
|
+
def upload_with_fastlane(metadata)
|
|
216
|
+
puts " 使用 Fastlane deliver 上传..."
|
|
217
|
+
|
|
218
|
+
# 构建 deliver 命令参数
|
|
219
|
+
fastlane_cmd = ["fastlane", "deliver"]
|
|
220
|
+
fastlane_cmd << "--app_identifier" << @app_id
|
|
221
|
+
fastlane_cmd << "--username" << @apple_id if @apple_id
|
|
222
|
+
|
|
223
|
+
# 如果有元数据路径,使用路径
|
|
224
|
+
if @metadata_path && File.exist?(@metadata_path)
|
|
225
|
+
fastlane_cmd << "--metadata_path" << @metadata_path
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# 跳过截图
|
|
229
|
+
if @skip_screenshots
|
|
230
|
+
fastlane_cmd << "--skip_screenshots"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# 跳过二进制文件上传
|
|
234
|
+
fastlane_cmd << "--skip_binary_upload"
|
|
235
|
+
|
|
236
|
+
# 自动提交审核(可选)
|
|
237
|
+
# fastlane_cmd << "--submit_for_review"
|
|
238
|
+
|
|
239
|
+
# 执行命令
|
|
240
|
+
puts " 执行命令: #{fastlane_cmd.join(' ')}"
|
|
241
|
+
|
|
242
|
+
success = system(*fastlane_cmd)
|
|
243
|
+
|
|
244
|
+
unless success
|
|
245
|
+
raise "上传失败:Fastlane deliver 命令执行出错(退出码: #{$?.exitstatus})"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|