pindo 5.18.11 → 5.18.13
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/command/android/install.rb +277 -0
- data/lib/pindo/command/android.rb +1 -0
- data/lib/pindo/command/ios/install.rb +241 -0
- data/lib/pindo/command/ios.rb +1 -0
- data/lib/pindo/options/groups/tool_options.rb +18 -0
- data/lib/pindo/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78ef4b7b03d0ace852b44bc331e1a08436a66caebccc3e4d1ab20a8755e29e27
|
|
4
|
+
data.tar.gz: 0fed70934343961eadced5a2e1c32f8e3adc8c0b78e53cd7d3c5a6b63fd96476
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70479926ca3d908458fbfb7815c064ca82c0b414113263a3f45bbe9b189a28076a30f806af552cb4560cc6c45a13be8cdc82f8caca33afe2809dd2b9cc4ef3f3
|
|
7
|
+
data.tar.gz: b170b9a3f9b9c0708827633b88e8e78d08886e1b77dc66b1ad12d80368d4896487d83c04493ab1ebe1efc7579b6f4f063a3b41794d19b35ce144b3a748027af4
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
module Pindo
|
|
2
|
+
class Command
|
|
3
|
+
class Android < Command
|
|
4
|
+
class Install < Android
|
|
5
|
+
|
|
6
|
+
self.summary = '安装 APK 到已连接的 Android 设备'
|
|
7
|
+
|
|
8
|
+
self.description = <<-DESC
|
|
9
|
+
自动检测已连接的 Android 设备,将 APK 安装到设备上。
|
|
10
|
+
|
|
11
|
+
支持三种方式指定 APK 文件:
|
|
12
|
+
1. 命令行参数直接传入文件路径
|
|
13
|
+
2. --apk 选项指定文件路径
|
|
14
|
+
3. 不指定时自动搜索当前项目中最新的 APK 文件
|
|
15
|
+
|
|
16
|
+
如果检测到多台可用设备,会列出设备列表让用户选择。
|
|
17
|
+
|
|
18
|
+
示例:
|
|
19
|
+
$ pindo and install # 自动搜索 APK 并安装
|
|
20
|
+
$ pindo and install path/to/app.apk # 指定 APK 文件安装
|
|
21
|
+
$ pindo and install --apk=path/to/app.apk # 通过选项指定 APK 文件
|
|
22
|
+
DESC
|
|
23
|
+
|
|
24
|
+
self.arguments = [
|
|
25
|
+
CLAide::Argument.new('path/to/demo.apk', false),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
def self.option_items
|
|
29
|
+
@option_items ||= Pindo::Options::OptionGroup.merge(
|
|
30
|
+
Pindo::Options::ToolOptions.select(:apk)
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.options
|
|
35
|
+
option_items.map(&:to_claide_option).concat(super)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(argv)
|
|
39
|
+
@args_apk_file = argv.shift_argument
|
|
40
|
+
@options = initialize_options(argv)
|
|
41
|
+
|
|
42
|
+
apk_option = @options[:apk] rescue nil
|
|
43
|
+
if !apk_option.nil?
|
|
44
|
+
@args_apk_file = apk_option
|
|
45
|
+
end
|
|
46
|
+
if @args_apk_file && !@args_apk_file.empty?
|
|
47
|
+
@args_apk_file = @args_apk_file.strip.gsub(/\"/, '')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
super(argv)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate!
|
|
54
|
+
super
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run
|
|
58
|
+
# 1. 查找 adb
|
|
59
|
+
@adb_path = find_adb
|
|
60
|
+
puts "ADB: #{@adb_path}"
|
|
61
|
+
|
|
62
|
+
# 2. 查找 APK 文件
|
|
63
|
+
apk_file = find_apk_file
|
|
64
|
+
puts "APK 文件: #{apk_file}"
|
|
65
|
+
|
|
66
|
+
# 3. 检测可用设备
|
|
67
|
+
devices = detect_available_devices
|
|
68
|
+
if devices.empty?
|
|
69
|
+
raise Informative, "未检测到可用的 Android 设备,请确认设备已连接并开启 USB 调试"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# 4. 选择设备
|
|
73
|
+
device = select_device(devices)
|
|
74
|
+
puts "目标设备: #{device[:brand]} #{device[:model]} (#{device[:serial]})"
|
|
75
|
+
|
|
76
|
+
# 5. 安装 APK
|
|
77
|
+
install_apk_to_device(apk_file, device)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# 查找 adb 路径
|
|
83
|
+
def find_adb
|
|
84
|
+
# 1. PATH 中查找
|
|
85
|
+
adb_in_path = `which adb 2>/dev/null`.strip
|
|
86
|
+
return adb_in_path unless adb_in_path.empty?
|
|
87
|
+
|
|
88
|
+
# 2. 通过 SDK 路径查找(复用 android_project_helper 的逻辑)
|
|
89
|
+
sdk_dir = find_android_sdk_dir
|
|
90
|
+
if sdk_dir
|
|
91
|
+
adb = File.join(sdk_dir, "platform-tools", "adb")
|
|
92
|
+
return adb if File.executable?(adb)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
raise Informative, "未找到 adb,请确认已安装 Android SDK 并配置 ANDROID_HOME 环境变量"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 查找 Android SDK 目录
|
|
99
|
+
def find_android_sdk_dir
|
|
100
|
+
# Android Studio 默认路径
|
|
101
|
+
default_sdk = File.expand_path("~/Library/Android/sdk")
|
|
102
|
+
return default_sdk if File.directory?(default_sdk)
|
|
103
|
+
|
|
104
|
+
# 环境变量
|
|
105
|
+
sdk_dir = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT']
|
|
106
|
+
return sdk_dir if sdk_dir && File.directory?(sdk_dir)
|
|
107
|
+
|
|
108
|
+
# 从当前项目的 local.properties 读取
|
|
109
|
+
project_dir = Dir.pwd
|
|
110
|
+
["local.properties", "Unity/local.properties"].each do |props_file|
|
|
111
|
+
props_path = File.join(project_dir, props_file)
|
|
112
|
+
if File.exist?(props_path)
|
|
113
|
+
content = File.read(props_path)
|
|
114
|
+
if content =~ /sdk\.dir\s*=\s*(.+)/
|
|
115
|
+
dir = $1.strip
|
|
116
|
+
return dir if File.directory?(dir)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# 查找 APK 文件
|
|
125
|
+
def find_apk_file
|
|
126
|
+
# 优先使用用户指定的文件
|
|
127
|
+
if @args_apk_file && !@args_apk_file.empty?
|
|
128
|
+
apk_path = File.expand_path(@args_apk_file)
|
|
129
|
+
unless File.exist?(apk_path)
|
|
130
|
+
raise Informative, "APK 文件不存在: #{apk_path}"
|
|
131
|
+
end
|
|
132
|
+
return apk_path
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# 自动搜索
|
|
136
|
+
project_dir = Dir.pwd
|
|
137
|
+
apk_file = nil
|
|
138
|
+
|
|
139
|
+
require 'pindo/module/build/build_helper'
|
|
140
|
+
build_helper = Pindo::BuildHelper.share_instance
|
|
141
|
+
project_type = build_helper.project_type(project_dir)
|
|
142
|
+
|
|
143
|
+
case project_type
|
|
144
|
+
when :android
|
|
145
|
+
apk_file = find_android_apk(project_dir)
|
|
146
|
+
when :unity
|
|
147
|
+
apk_file = find_unity_android_apk(project_dir)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# 兜底搜索
|
|
151
|
+
if apk_file.nil?
|
|
152
|
+
search_paths = [
|
|
153
|
+
File.join(project_dir, "build", "**", "*.apk"),
|
|
154
|
+
File.join(project_dir, "*.apk")
|
|
155
|
+
]
|
|
156
|
+
search_paths.each do |pattern|
|
|
157
|
+
found = Dir.glob(pattern).max_by { |f| File.mtime(f) }
|
|
158
|
+
if found
|
|
159
|
+
apk_file = found
|
|
160
|
+
break
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if apk_file.nil?
|
|
166
|
+
raise Informative, "未找到 APK 文件,请指定文件路径: pindo and install path/to/app.apk"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
apk_file
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# 在 Android 工程中查找最新 APK
|
|
173
|
+
def find_android_apk(project_dir)
|
|
174
|
+
build_path = File.join(project_dir, "build", "**", "*.apk")
|
|
175
|
+
Dir.glob(build_path).max_by { |f| File.mtime(f) }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# 在 Unity 工程中查找最新 Android APK
|
|
179
|
+
def find_unity_android_apk(project_dir)
|
|
180
|
+
apk_files = []
|
|
181
|
+
["GoodPlatform/BaseAndroid/build", "GoodPlatform/Android/build"].each do |sub_dir|
|
|
182
|
+
dir = File.join(project_dir, sub_dir)
|
|
183
|
+
if File.exist?(dir)
|
|
184
|
+
apk_files.concat(Dir.glob(File.join(dir, "**", "*.apk")))
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
apk_files.max_by { |f| File.mtime(f) } if apk_files.any?
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# 检测已连接的 Android 设备
|
|
191
|
+
def detect_available_devices
|
|
192
|
+
output = `"#{@adb_path}" devices 2>/dev/null`.to_s
|
|
193
|
+
devices = []
|
|
194
|
+
|
|
195
|
+
output.split("\n").each do |line|
|
|
196
|
+
next if line.start_with?("List of devices")
|
|
197
|
+
next if line.strip.empty?
|
|
198
|
+
next if line.start_with?("*")
|
|
199
|
+
|
|
200
|
+
parts = line.strip.split(/\s+/)
|
|
201
|
+
next if parts.size < 2
|
|
202
|
+
|
|
203
|
+
serial = parts[0]
|
|
204
|
+
state = parts[1]
|
|
205
|
+
|
|
206
|
+
# 只保留 device 状态(排除 offline、unauthorized、no permissions 等)
|
|
207
|
+
next unless state == "device"
|
|
208
|
+
|
|
209
|
+
brand = get_device_prop(serial, "ro.product.brand")
|
|
210
|
+
model = get_device_prop(serial, "ro.product.model")
|
|
211
|
+
android_version = get_device_prop(serial, "ro.build.version.release")
|
|
212
|
+
|
|
213
|
+
devices << {
|
|
214
|
+
serial: serial,
|
|
215
|
+
brand: brand.capitalize,
|
|
216
|
+
model: model,
|
|
217
|
+
android_version: android_version
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
devices
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# 获取设备属性
|
|
225
|
+
def get_device_prop(serial, prop)
|
|
226
|
+
`"#{@adb_path}" -s #{serial} shell getprop #{prop} 2>/dev/null`.strip
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# 选择设备
|
|
230
|
+
def select_device(devices)
|
|
231
|
+
if devices.size == 1
|
|
232
|
+
return devices.first
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
puts "\n检测到 #{devices.size} 台可用设备:"
|
|
236
|
+
devices.each_with_index do |device, index|
|
|
237
|
+
puts " [#{index + 1}] #{device[:brand]} #{device[:model]} (Android #{device[:android_version]}) - #{device[:serial]}"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
selection = nil
|
|
241
|
+
loop do
|
|
242
|
+
print "\n请选择设备 [1-#{devices.size}]: "
|
|
243
|
+
input = $stdin.gets
|
|
244
|
+
if input.nil?
|
|
245
|
+
raise Informative, "用户取消操作"
|
|
246
|
+
end
|
|
247
|
+
input = input.strip
|
|
248
|
+
num = input.to_i
|
|
249
|
+
if num >= 1 && num <= devices.size
|
|
250
|
+
selection = num - 1
|
|
251
|
+
break
|
|
252
|
+
end
|
|
253
|
+
puts "无效输入,请输入 1-#{devices.size} 之间的数字"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
devices[selection]
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# 安装 APK 到设备
|
|
260
|
+
def install_apk_to_device(apk_file, device)
|
|
261
|
+
puts "\n正在安装到 #{device[:brand]} #{device[:model]}..."
|
|
262
|
+
|
|
263
|
+
output = `"#{@adb_path}" -s #{device[:serial]} install -r "#{apk_file}" 2>&1`
|
|
264
|
+
exit_code = $?.exitstatus
|
|
265
|
+
|
|
266
|
+
if exit_code == 0 && output.include?("Success")
|
|
267
|
+
puts "安装成功!"
|
|
268
|
+
else
|
|
269
|
+
puts output unless output.strip.empty?
|
|
270
|
+
raise Informative, "安装失败"
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
module Pindo
|
|
2
|
+
class Command
|
|
3
|
+
class Ios < Command
|
|
4
|
+
class Install < Ios
|
|
5
|
+
|
|
6
|
+
self.summary = '安装 IPA 到已连接的 iOS 真机设备'
|
|
7
|
+
|
|
8
|
+
self.description = <<-DESC
|
|
9
|
+
自动检测已连接的 iPhone/iPad 设备,将 IPA 安装到设备上。
|
|
10
|
+
|
|
11
|
+
支持三种方式指定 IPA 文件:
|
|
12
|
+
1. 命令行参数直接传入文件路径
|
|
13
|
+
2. --ipa 选项指定文件路径
|
|
14
|
+
3. 不指定时自动搜索当前项目中最新的 IPA 文件
|
|
15
|
+
|
|
16
|
+
如果检测到多台可用设备,会列出设备列表让用户选择。
|
|
17
|
+
|
|
18
|
+
示例:
|
|
19
|
+
$ pindo ios install # 自动搜索 IPA 并安装
|
|
20
|
+
$ pindo ios install path/to/app.ipa # 指定 IPA 文件安装
|
|
21
|
+
$ pindo ios install --ipa=path/to/app.ipa # 通过选项指定 IPA 文件
|
|
22
|
+
DESC
|
|
23
|
+
|
|
24
|
+
self.arguments = [
|
|
25
|
+
CLAide::Argument.new('path/to/demo.ipa', false),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
def self.option_items
|
|
29
|
+
@option_items ||= Pindo::Options::OptionGroup.merge(
|
|
30
|
+
Pindo::Options::ToolOptions.select(:ipa)
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.options
|
|
35
|
+
option_items.map(&:to_claide_option).concat(super)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(argv)
|
|
39
|
+
@args_ipa_file = argv.shift_argument
|
|
40
|
+
@options = initialize_options(argv)
|
|
41
|
+
|
|
42
|
+
ipa_option = @options[:ipa]
|
|
43
|
+
if !ipa_option.nil?
|
|
44
|
+
@args_ipa_file = ipa_option
|
|
45
|
+
end
|
|
46
|
+
if @args_ipa_file && !@args_ipa_file.empty?
|
|
47
|
+
@args_ipa_file = @args_ipa_file.strip.gsub(/\"/, '')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
super(argv)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate!
|
|
54
|
+
super
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run
|
|
58
|
+
# 1. 查找 IPA 文件
|
|
59
|
+
ipa_file = find_ipa_file
|
|
60
|
+
puts "IPA 文件: #{ipa_file}"
|
|
61
|
+
|
|
62
|
+
# 2. 检测可用设备
|
|
63
|
+
devices = detect_available_devices
|
|
64
|
+
if devices.empty?
|
|
65
|
+
raise Informative, "未检测到可用的 iPhone/iPad 设备,请确认设备已连接并信任此电脑"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 3. 选择设备
|
|
69
|
+
device = select_device(devices)
|
|
70
|
+
puts "目标设备: #{device[:name]} (#{device[:model]})"
|
|
71
|
+
|
|
72
|
+
# 4. 安装 IPA
|
|
73
|
+
install_ipa_to_device(ipa_file, device)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# 查找 IPA 文件
|
|
79
|
+
def find_ipa_file
|
|
80
|
+
# 优先使用用户指定的文件
|
|
81
|
+
if @args_ipa_file && !@args_ipa_file.empty?
|
|
82
|
+
ipa_path = File.expand_path(@args_ipa_file)
|
|
83
|
+
unless File.exist?(ipa_path)
|
|
84
|
+
raise Informative, "IPA 文件不存在: #{ipa_path}"
|
|
85
|
+
end
|
|
86
|
+
return ipa_path
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# 自动搜索
|
|
90
|
+
project_dir = Dir.pwd
|
|
91
|
+
ipa_file = nil
|
|
92
|
+
|
|
93
|
+
# 检测项目类型并搜索
|
|
94
|
+
require 'pindo/module/build/build_helper'
|
|
95
|
+
build_helper = Pindo::BuildHelper.share_instance
|
|
96
|
+
project_type = build_helper.project_type(project_dir)
|
|
97
|
+
|
|
98
|
+
case project_type
|
|
99
|
+
when :ios
|
|
100
|
+
ipa_file = find_ios_ipa(project_dir)
|
|
101
|
+
when :unity
|
|
102
|
+
ipa_file = find_unity_ios_ipa(project_dir)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# 兜底:在当前目录和 build 目录查找
|
|
106
|
+
if ipa_file.nil?
|
|
107
|
+
search_paths = [
|
|
108
|
+
File.join(project_dir, "build", "*.ipa"),
|
|
109
|
+
File.join(project_dir, "*.ipa")
|
|
110
|
+
]
|
|
111
|
+
search_paths.each do |pattern|
|
|
112
|
+
found = Dir.glob(pattern).max_by { |f| File.mtime(f) }
|
|
113
|
+
if found
|
|
114
|
+
ipa_file = found
|
|
115
|
+
break
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if ipa_file.nil?
|
|
121
|
+
raise Informative, "未找到 IPA 文件,请指定文件路径: pindo ios install path/to/app.ipa"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
ipa_file
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# 在 iOS 工程中查找最新 IPA
|
|
128
|
+
def find_ios_ipa(project_dir)
|
|
129
|
+
build_path = File.join(project_dir, "build", "*.ipa")
|
|
130
|
+
Dir.glob(build_path).max_by { |f| File.mtime(f) }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# 在 Unity 工程中查找最新 iOS IPA
|
|
134
|
+
def find_unity_ios_ipa(project_dir)
|
|
135
|
+
ipa_files = []
|
|
136
|
+
["GoodPlatform/BaseiOS/build", "GoodPlatform/iOS/build"].each do |sub_dir|
|
|
137
|
+
dir = File.join(project_dir, sub_dir)
|
|
138
|
+
if File.exist?(dir)
|
|
139
|
+
ipa_files.concat(Dir.glob(File.join(dir, "*.ipa")))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
ipa_files.max_by { |f| File.mtime(f) } if ipa_files.any?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# 检测可用的 iPhone/iPad 设备
|
|
146
|
+
def detect_available_devices
|
|
147
|
+
require 'json'
|
|
148
|
+
require 'tempfile'
|
|
149
|
+
|
|
150
|
+
# 使用 JSON 输出避免文本解析对齐问题
|
|
151
|
+
json_tmpfile = Tempfile.new(['devicectl', '.json'])
|
|
152
|
+
json_path = json_tmpfile.path
|
|
153
|
+
json_tmpfile.close
|
|
154
|
+
|
|
155
|
+
system("xcrun devicectl list devices --json-output \"#{json_path}\" >/dev/null 2>&1")
|
|
156
|
+
|
|
157
|
+
unless File.exist?(json_path) && File.size(json_path) > 0
|
|
158
|
+
json_tmpfile.unlink rescue nil
|
|
159
|
+
raise Informative, "无法执行 xcrun devicectl,请确认 Xcode 已安装"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
json_data = JSON.parse(File.read(json_path))
|
|
163
|
+
json_tmpfile.unlink rescue nil
|
|
164
|
+
|
|
165
|
+
devices = []
|
|
166
|
+
device_list = json_data.dig('result', 'devices') || []
|
|
167
|
+
|
|
168
|
+
device_list.each do |device|
|
|
169
|
+
tunnel_state = device.dig('connectionProperties', 'tunnelState') || ''
|
|
170
|
+
device_type = device.dig('hardwareProperties', 'deviceType') || ''
|
|
171
|
+
name = device.dig('deviceProperties', 'name') || ''
|
|
172
|
+
identifier = device['identifier'] || ''
|
|
173
|
+
model = device.dig('hardwareProperties', 'marketingName') || ''
|
|
174
|
+
product_type = device.dig('hardwareProperties', 'productType') || ''
|
|
175
|
+
|
|
176
|
+
# 只保留 available(tunnelState 不是 unavailable)的 iPhone/iPad
|
|
177
|
+
next if tunnel_state == 'unavailable'
|
|
178
|
+
next unless device_type == 'iPhone' || device_type == 'iPad'
|
|
179
|
+
|
|
180
|
+
devices << {
|
|
181
|
+
name: name,
|
|
182
|
+
identifier: identifier,
|
|
183
|
+
model: "#{model} (#{product_type})",
|
|
184
|
+
device_type: device_type
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
devices
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# 选择设备
|
|
192
|
+
def select_device(devices)
|
|
193
|
+
if devices.size == 1
|
|
194
|
+
return devices.first
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# 多台设备,让用户选择
|
|
198
|
+
puts "\n检测到 #{devices.size} 台可用设备:"
|
|
199
|
+
devices.each_with_index do |device, index|
|
|
200
|
+
puts " [#{index + 1}] #{device[:name]} - #{device[:model]}"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
selection = nil
|
|
204
|
+
loop do
|
|
205
|
+
print "\n请选择设备 [1-#{devices.size}]: "
|
|
206
|
+
input = $stdin.gets
|
|
207
|
+
if input.nil?
|
|
208
|
+
raise Informative, "用户取消操作"
|
|
209
|
+
end
|
|
210
|
+
input = input.strip
|
|
211
|
+
num = input.to_i
|
|
212
|
+
if num >= 1 && num <= devices.size
|
|
213
|
+
selection = num - 1
|
|
214
|
+
break
|
|
215
|
+
end
|
|
216
|
+
puts "无效输入,请输入 1-#{devices.size} 之间的数字"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
devices[selection]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# 安装 IPA 到设备
|
|
223
|
+
def install_ipa_to_device(ipa_file, device)
|
|
224
|
+
puts "\n正在安装到 #{device[:name]}..."
|
|
225
|
+
|
|
226
|
+
cmd = "xcrun devicectl device install app --device #{device[:identifier]} \"#{ipa_file}\" 2>&1"
|
|
227
|
+
output = `#{cmd}`
|
|
228
|
+
exit_code = $?.exitstatus
|
|
229
|
+
|
|
230
|
+
if exit_code == 0
|
|
231
|
+
puts "安装成功!"
|
|
232
|
+
else
|
|
233
|
+
puts output unless output.strip.empty?
|
|
234
|
+
raise Informative, "安装失败 (exit code: #{exit_code})"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
data/lib/pindo/command/ios.rb
CHANGED
|
@@ -28,6 +28,24 @@ module Pindo
|
|
|
28
28
|
example: 'pindo ios autoresign --ipa=path/to/demo.ipa'
|
|
29
29
|
),
|
|
30
30
|
|
|
31
|
+
apk: OptionItem.new(
|
|
32
|
+
key: :apk,
|
|
33
|
+
name: 'APK 文件',
|
|
34
|
+
description: '指定要安装的 APK 文件路径',
|
|
35
|
+
type: String,
|
|
36
|
+
env_name: 'PINDO_APK_FILE',
|
|
37
|
+
optional: true,
|
|
38
|
+
verify_block: proc do |value|
|
|
39
|
+
unless value.end_with?('.apk')
|
|
40
|
+
raise "APK 文件路径格式错误: #{value},必须以 .apk 结尾"
|
|
41
|
+
end
|
|
42
|
+
unless File.exist?(value)
|
|
43
|
+
raise "APK 文件不存在: #{value}"
|
|
44
|
+
end
|
|
45
|
+
end,
|
|
46
|
+
example: 'pindo and install --apk=path/to/demo.apk'
|
|
47
|
+
),
|
|
48
|
+
|
|
31
49
|
deploy: OptionItem.new(
|
|
32
50
|
key: :deploy,
|
|
33
51
|
name: '使用发布证书',
|
data/lib/pindo/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pindo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.18.
|
|
4
|
+
version: 5.18.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- wade
|
|
@@ -284,6 +284,7 @@ files:
|
|
|
284
284
|
- lib/pindo/command/android.rb
|
|
285
285
|
- lib/pindo/command/android/autobuild.rb
|
|
286
286
|
- lib/pindo/command/android/autoresign.rb
|
|
287
|
+
- lib/pindo/command/android/install.rb
|
|
287
288
|
- lib/pindo/command/android/keystore.rb
|
|
288
289
|
- lib/pindo/command/appstore.rb
|
|
289
290
|
- lib/pindo/command/appstore/adhocbuild.rb
|
|
@@ -325,6 +326,7 @@ files:
|
|
|
325
326
|
- lib/pindo/command/ios/build.rb
|
|
326
327
|
- lib/pindo/command/ios/cert.rb
|
|
327
328
|
- lib/pindo/command/ios/fixproj.rb
|
|
329
|
+
- lib/pindo/command/ios/install.rb
|
|
328
330
|
- lib/pindo/command/ios/podlint.rb
|
|
329
331
|
- lib/pindo/command/ios/podpush.rb
|
|
330
332
|
- lib/pindo/command/ios/podupdate.rb
|