pindo 5.13.1 → 5.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/appstore/adhocbuild.rb +256 -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/applovin.rb +24 -182
- 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 +9 -6
- data/lib/pindo/command/ios/cert.rb +27 -20
- data/lib/pindo/command/ios/podupdate.rb +6 -37
- 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_config_helper.rb +9 -5
- 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} +211 -6
- data/lib/pindo/module/pgyer/pgyerhelper.rb +13 -5
- 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 +342 -0
- data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +341 -0
- data/lib/pindo/module/task/model/build/{ios_dev_build_task.rb → ios_build_dev_task.rb} +40 -59
- data/lib/pindo/module/task/model/build/ios_build_task.rb +23 -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/unity/unity_helper.rb +2 -1
- data/lib/pindo/module/xcode/applovin_xcode_helper.rb +271 -0
- data/lib/pindo/module/xcode/cocoapods_helper.rb +153 -0
- 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 +51 -40
- 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
|
@@ -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
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
require_relative 'appstore_task'
|
|
2
|
+
require_relative '../task_config'
|
|
3
|
+
|
|
4
|
+
module Pindo
|
|
5
|
+
module TaskSystem
|
|
6
|
+
# App Store Screenshot 上传任务
|
|
7
|
+
# 上传应用截图到 App Store Connect
|
|
8
|
+
class AppStoreUploadScreenshotTask < AppStoreTask
|
|
9
|
+
attr_reader :app_id, :screenshot_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
|
+
15 # 默认延迟 15 秒
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 初始化 Screenshot 上传任务
|
|
25
|
+
# @param app_id [String] App Store Connect 应用 ID
|
|
26
|
+
# @param screenshot_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 [String] :locale 语言区域(默认 "zh-Hans")
|
|
33
|
+
# @option options [Array<String>] :device_types 设备类型(如 ["iPhone 6.5", "iPad Pro"])
|
|
34
|
+
# @option options [Boolean] :overwrite 是否覆盖已有截图(默认 false)
|
|
35
|
+
def initialize(app_id, screenshot_path, options = {})
|
|
36
|
+
@app_id = app_id # App Store Connect 应用 ID
|
|
37
|
+
@screenshot_path = screenshot_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
|
+
@locale = options[:locale] || "zh-Hans"
|
|
47
|
+
@device_types = options[:device_types] || []
|
|
48
|
+
@overwrite = options[:overwrite] || false
|
|
49
|
+
|
|
50
|
+
# 设置上传任务的优先级为 LOW(截图上传可以最后执行)
|
|
51
|
+
options[:priority] ||= TaskPriority::LOW
|
|
52
|
+
|
|
53
|
+
super("上传 Screenshot 到 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
|
+
unless @screenshot_path && !@screenshot_path.empty?
|
|
64
|
+
@error = "缺少必需参数: screenshot_path"
|
|
65
|
+
return false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 验证截图路径
|
|
69
|
+
unless File.directory?(@screenshot_path)
|
|
70
|
+
@error = "截图路径不存在或不是目录: #{@screenshot_path}"
|
|
71
|
+
return false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 验证认证信息(Apple ID 或 API Key)
|
|
75
|
+
has_apple_id_auth = @apple_id && !@apple_id.empty?
|
|
76
|
+
has_api_key_auth = @api_key_id && @api_issuer_id && @api_key_path
|
|
77
|
+
|
|
78
|
+
unless has_apple_id_auth || has_api_key_auth
|
|
79
|
+
@error = "缺少认证信息:需要提供 Apple ID 或 API Key"
|
|
80
|
+
return false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
protected
|
|
87
|
+
|
|
88
|
+
def do_work
|
|
89
|
+
# 1. 扫描截图文件
|
|
90
|
+
screenshots = scan_screenshots
|
|
91
|
+
|
|
92
|
+
if screenshots.empty?
|
|
93
|
+
puts " 警告: 未找到任何截图文件"
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
app_id: @app_id,
|
|
97
|
+
screenshot_count: 0
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# 2. 上传截图到 App Store Connect
|
|
102
|
+
upload_screenshots(screenshots)
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
success: true,
|
|
106
|
+
app_id: @app_id,
|
|
107
|
+
locale: @locale,
|
|
108
|
+
screenshot_count: screenshots.size
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# 扫描截图文件
|
|
115
|
+
def scan_screenshots
|
|
116
|
+
puts " 扫描截图目录: #{@screenshot_path}"
|
|
117
|
+
|
|
118
|
+
screenshots = []
|
|
119
|
+
|
|
120
|
+
# 支持的截图格式
|
|
121
|
+
valid_extensions = ['.png', '.jpg', '.jpeg']
|
|
122
|
+
|
|
123
|
+
# 扫描目录结构
|
|
124
|
+
# 标准 Fastlane 结构: screenshots/{locale}/{device_type}/*.png
|
|
125
|
+
locale_path = File.join(@screenshot_path, @locale)
|
|
126
|
+
|
|
127
|
+
if File.directory?(locale_path)
|
|
128
|
+
# 有语言目录
|
|
129
|
+
scan_locale_directory(locale_path, screenshots, valid_extensions)
|
|
130
|
+
else
|
|
131
|
+
# 直接扫描根目录
|
|
132
|
+
scan_root_directory(@screenshot_path, screenshots, valid_extensions)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
puts " 找到 #{screenshots.size} 个截图文件"
|
|
136
|
+
screenshots
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# 扫描语言目录
|
|
140
|
+
def scan_locale_directory(locale_path, screenshots, valid_extensions)
|
|
141
|
+
# 遍历设备类型目录
|
|
142
|
+
Dir.glob(File.join(locale_path, "*")).each do |device_dir|
|
|
143
|
+
next unless File.directory?(device_dir)
|
|
144
|
+
|
|
145
|
+
device_type = File.basename(device_dir)
|
|
146
|
+
|
|
147
|
+
# 如果指定了设备类型,跳过不匹配的
|
|
148
|
+
unless @device_types.empty?
|
|
149
|
+
next unless @device_types.include?(device_type)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# 扫描设备目录下的截图
|
|
153
|
+
Dir.glob(File.join(device_dir, "*")).each do |file|
|
|
154
|
+
next unless File.file?(file)
|
|
155
|
+
next unless valid_extensions.include?(File.extname(file).downcase)
|
|
156
|
+
|
|
157
|
+
screenshots << {
|
|
158
|
+
path: file,
|
|
159
|
+
device_type: device_type,
|
|
160
|
+
locale: @locale,
|
|
161
|
+
filename: File.basename(file)
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# 扫描根目录(无语言/设备分类)
|
|
168
|
+
def scan_root_directory(root_path, screenshots, valid_extensions)
|
|
169
|
+
Dir.glob(File.join(root_path, "**", "*")).each do |file|
|
|
170
|
+
next unless File.file?(file)
|
|
171
|
+
next unless valid_extensions.include?(File.extname(file).downcase)
|
|
172
|
+
|
|
173
|
+
screenshots << {
|
|
174
|
+
path: file,
|
|
175
|
+
device_type: 'unknown',
|
|
176
|
+
locale: @locale,
|
|
177
|
+
filename: File.basename(file)
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# 上传截图到 App Store Connect
|
|
183
|
+
def upload_screenshots(screenshots)
|
|
184
|
+
puts "\n开始上传 Screenshot 到 App Store Connect..."
|
|
185
|
+
puts " App ID: #{@app_id}"
|
|
186
|
+
puts " 语言区域: #{@locale}"
|
|
187
|
+
puts " 截图数量: #{screenshots.size}"
|
|
188
|
+
puts " 覆盖模式: #{@overwrite}"
|
|
189
|
+
|
|
190
|
+
# 按设备类型分组
|
|
191
|
+
screenshots_by_device = screenshots.group_by { |s| s[:device_type] }
|
|
192
|
+
|
|
193
|
+
screenshots_by_device.each do |device_type, device_screenshots|
|
|
194
|
+
puts "\n 上传 #{device_type} 截图 (#{device_screenshots.size} 张)..."
|
|
195
|
+
|
|
196
|
+
device_screenshots.each_with_index do |screenshot, index|
|
|
197
|
+
puts " [#{index + 1}/#{device_screenshots.size}] #{screenshot[:filename]}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# 使用 Fastlane deliver 或 API 上传截图
|
|
202
|
+
if use_api_key?
|
|
203
|
+
upload_with_api(screenshots)
|
|
204
|
+
else
|
|
205
|
+
upload_with_fastlane(screenshots)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
puts "✓ Screenshot 上传成功"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# 判断是否使用 API Key 认证
|
|
212
|
+
def use_api_key?
|
|
213
|
+
@api_key_id && @api_issuer_id && @api_key_path
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# 使用 App Store Connect API 上传
|
|
217
|
+
def upload_with_api(screenshots)
|
|
218
|
+
puts " 使用 App Store Connect API 上传..."
|
|
219
|
+
|
|
220
|
+
# 这里需要调用 App Store Connect API Helper
|
|
221
|
+
# require 'pindo/module/appstore/appstore_metadata_connect_api_helper'
|
|
222
|
+
|
|
223
|
+
# 示例实现(需要根据实际 Helper 调整)
|
|
224
|
+
# screenshots.each do |screenshot|
|
|
225
|
+
# AppstoreMetadataConnectApiHelper.upload_screenshot(
|
|
226
|
+
# app_id: @app_id,
|
|
227
|
+
# api_key_id: @api_key_id,
|
|
228
|
+
# api_issuer_id: @api_issuer_id,
|
|
229
|
+
# api_key_path: @api_key_path,
|
|
230
|
+
# locale: screenshot[:locale],
|
|
231
|
+
# device_type: screenshot[:device_type],
|
|
232
|
+
# screenshot_path: screenshot[:path],
|
|
233
|
+
# overwrite: @overwrite
|
|
234
|
+
# )
|
|
235
|
+
# end
|
|
236
|
+
|
|
237
|
+
# 临时占位实现
|
|
238
|
+
puts " TODO: 实现 API 上传逻辑"
|
|
239
|
+
raise "API 上传功能尚未实现,请使用 Fastlane 方式"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# 使用 Fastlane deliver 上传
|
|
243
|
+
def upload_with_fastlane(screenshots)
|
|
244
|
+
puts " 使用 Fastlane deliver 上传..."
|
|
245
|
+
|
|
246
|
+
# 构建 deliver 命令参数
|
|
247
|
+
fastlane_cmd = ["fastlane", "deliver"]
|
|
248
|
+
fastlane_cmd << "--app_identifier" << @app_id
|
|
249
|
+
fastlane_cmd << "--username" << @apple_id if @apple_id
|
|
250
|
+
|
|
251
|
+
# 截图路径
|
|
252
|
+
fastlane_cmd << "--screenshots_path" << @screenshot_path
|
|
253
|
+
|
|
254
|
+
# 覆盖已有截图
|
|
255
|
+
if @overwrite
|
|
256
|
+
fastlane_cmd << "--overwrite_screenshots"
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# 跳过元数据上传
|
|
260
|
+
fastlane_cmd << "--skip_metadata"
|
|
261
|
+
|
|
262
|
+
# 跳过二进制文件上传
|
|
263
|
+
fastlane_cmd << "--skip_binary_upload"
|
|
264
|
+
|
|
265
|
+
# 执行命令
|
|
266
|
+
puts " 执行命令: #{fastlane_cmd.join(' ')}"
|
|
267
|
+
|
|
268
|
+
success = system(*fastlane_cmd)
|
|
269
|
+
|
|
270
|
+
unless success
|
|
271
|
+
raise "上传失败:Fastlane deliver 命令执行出错(退出码: #{$?.exitstatus})"
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|