pindo 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/pindocontext.rb +42 -31
  3. data/lib/pindo/client/aws3sclient.rb +2 -2
  4. data/lib/pindo/client/httpclient.rb +1 -2
  5. data/lib/pindo/command/android/autobuild.rb +40 -18
  6. data/lib/pindo/command/android/build.rb +37 -16
  7. data/lib/pindo/command/android/debug.rb +34 -20
  8. data/lib/pindo/command/appstore/iap.rb +15 -1
  9. data/lib/pindo/command/appstore/itcapp.rb +15 -1
  10. data/lib/pindo/command/appstore/metadata.rb +15 -1
  11. data/lib/pindo/command/appstore/screenshots.rb +15 -1
  12. data/lib/pindo/command/appstore/upload.rb +15 -1
  13. data/lib/pindo/command/deploy/build.rb +35 -20
  14. data/lib/pindo/command/deploy/bundleid.rb +15 -1
  15. data/lib/pindo/command/deploy/cert.rb +19 -2
  16. data/lib/pindo/command/deploy/check.rb +15 -1
  17. data/lib/pindo/command/deploy/configproj.rb +15 -1
  18. data/lib/pindo/command/deploy/confusecode.rb +15 -1
  19. data/lib/pindo/command/deploy/confuseproj.rb +11 -1
  20. data/lib/pindo/command/deploy/fabric.rb +10 -1
  21. data/lib/pindo/command/deploy/getitcinfo.rb +11 -1
  22. data/lib/pindo/command/deploy/iap.rb +12 -1
  23. data/lib/pindo/command/deploy/initconfig.rb +11 -1
  24. data/lib/pindo/command/deploy/itcinfo.rb +12 -1
  25. data/lib/pindo/command/deploy/pullconfig.rb +11 -1
  26. data/lib/pindo/command/deploy/pushconfig.rb +11 -1
  27. data/lib/pindo/command/deploy/quswark.rb +12 -1
  28. data/lib/pindo/command/deploy/quswauth.rb +10 -1
  29. data/lib/pindo/command/deploy/reportbug.rb +11 -1
  30. data/lib/pindo/command/deploy/resign.rb +12 -1
  31. data/lib/pindo/command/deploy/updateconfig.rb +11 -1
  32. data/lib/pindo/command/deploy/uploadipa.rb +11 -1
  33. data/lib/pindo/command/env/dreamstudio.rb +13 -1
  34. data/lib/pindo/command/env/quarkenv.rb +13 -1
  35. data/lib/pindo/command/env/swarkenv.rb +13 -1
  36. data/lib/pindo/command/env/workhard.rb +13 -1
  37. data/lib/pindo/command/gplay/iap.rb +21 -5
  38. data/lib/pindo/command/gplay/itcapp.rb +19 -5
  39. data/lib/pindo/command/gplay/metadata.rb +23 -5
  40. data/lib/pindo/command/gplay/screenshots.rb +23 -5
  41. data/lib/pindo/command/gplay/upload.rb +21 -5
  42. data/lib/pindo/command/gplay.rb +8 -8
  43. data/lib/pindo/command/ios/adhoc.rb +18 -3
  44. data/lib/pindo/command/ios/autobuild.rb +22 -7
  45. data/lib/pindo/command/ios/autoresign.rb +18 -3
  46. data/lib/pindo/command/ios/build.rb +18 -4
  47. data/lib/pindo/command/ipa/autoresign.rb +35 -4
  48. data/lib/pindo/command/ipa/import.rb +19 -1
  49. data/lib/pindo/command/ipa/output.rb +39 -4
  50. data/lib/pindo/command/{pgyer → jps}/apptest.rb +35 -24
  51. data/lib/pindo/command/jps/bind.rb +191 -0
  52. data/lib/pindo/command/{pgyer → jps}/comment.rb +19 -19
  53. data/lib/pindo/command/{pgyer → jps}/download.rb +20 -20
  54. data/lib/pindo/command/{pgyer → jps}/login.rb +9 -9
  55. data/lib/pindo/command/{pgyer → jps}/resign.rb +40 -25
  56. data/lib/pindo/command/{pgyer → jps}/upload.rb +60 -43
  57. data/lib/pindo/command/jps.rb +18 -0
  58. data/lib/pindo/command/lib/forcepush.rb +15 -1
  59. data/lib/pindo/command/lib/push.rb +15 -1
  60. data/lib/pindo/command/lib/update.rb +15 -1
  61. data/lib/pindo/command/repo/clone.rb +11 -1
  62. data/lib/pindo/command/repo/create.rb +13 -1
  63. data/lib/pindo/command/repo/login.rb +14 -3
  64. data/lib/pindo/command/repo/search.rb +14 -2
  65. data/lib/pindo/command/unity/apk.rb +14 -28
  66. data/lib/pindo/command/unity/autobuild.rb +13 -13
  67. data/lib/pindo/command/unity/ipa.rb +15 -29
  68. data/lib/pindo/command/unity/web.rb +15 -15
  69. data/lib/pindo/command/utils/boss.rb +19 -2
  70. data/lib/pindo/command/utils/clearcert.rb +18 -2
  71. data/lib/pindo/command/utils/device.rb +19 -2
  72. data/lib/pindo/command/utils/icon.rb +20 -2
  73. data/lib/pindo/command/utils/renewcert.rb +28 -3
  74. data/lib/pindo/command/utils/renewproj.rb +25 -2
  75. data/lib/pindo/command/utils/tgate.rb +28 -3
  76. data/lib/pindo/command/utils/xcassets.rb +20 -2
  77. data/lib/pindo/command/web/autobuild.rb +18 -3
  78. data/lib/pindo/command.rb +8 -2
  79. data/lib/pindo/module/pgyer/pgyerhelper.rb +185 -85
  80. data/lib/pindo/version.rb +1 -1
  81. metadata +30 -12
  82. data/lib/pindo/client/pgyer_feishu_oauth_cli.rb +0 -669
  83. data/lib/pindo/client/pgyerclient.rb +0 -466
  84. data/lib/pindo/client/pgyeruploadclient.rb +0 -517
  85. data/lib/pindo/command/pgyer.rb +0 -18
@@ -1,517 +0,0 @@
1
- require 'uri'
2
- require 'json'
3
- require 'faraday'
4
- require 'securerandom'
5
- require 'pindo/base/aeshelper'
6
- require 'typhoeus'
7
- require 'thread'
8
-
9
- module Pindo
10
-
11
-
12
- class PgyerUploadClient
13
-
14
-
15
- attr_accessor :token
16
-
17
- attr_accessor :region
18
- attr_accessor :bucket_name
19
-
20
- attr_accessor :default_url
21
- attr_accessor :attach_url
22
-
23
- attr_accessor :upload_binary_file
24
- attr_accessor :file_size
25
- attr_accessor :progress_bar
26
-
27
-
28
- def initialize()
29
-
30
- begin
31
-
32
- @pgyer_token_file = File.join(File::expand_path(Pindoconfig.instance.pindo_dir), ".pgyer_token")
33
- config_file = File.join(File::expand_path(Pindoconfig.instance.pindo_common_configdir), "pgyer_client_config.json")
34
- config_json = JSON.parse(File.read(config_file))
35
-
36
- @region = config_json["region"]
37
- @bucket_name = config_json["bucket_name"]
38
- @default_url = config_json["default_url"]
39
- @attach_url = config_json["attach_url"]
40
-
41
- @use_local_wechat_url = config_json["use_local_wechat_url"]
42
- @baseurl = config_json["pgyerapps_base_url"]
43
- @request_config = config_json["pgyerapps_req_config"]
44
- @pgyer_aes_key = config_json["pgyerapps_aes_key"]
45
-
46
- @token = load_token
47
-
48
- # 添加互斥锁用于线程安全
49
- @upload_eTags_mutex = Mutex.new
50
- @tasks_queue_mutex = Mutex.new
51
- @active_tasks_mutex = Mutex.new
52
- @upload_failed_mutex = Mutex.new # 为上传失败状态添加互斥锁
53
- @upload_failed = false
54
-
55
- rescue => error
56
- raise Informative, "PgyerUploadClient 初始化失败!"
57
- end
58
-
59
- end
60
-
61
- def load_token()
62
- token = nil
63
- if File.exist?(@pgyer_token_file)
64
- begin
65
- data = File.read(@pgyer_token_file)
66
-
67
- data_string = AESHelper::aes_128_ecb_decrypt(@pgyer_aes_key, data)
68
- temp_token = data_string
69
- temp_token = JSON.parse(data_string)
70
- if !temp_token.nil? && !temp_token["token"].nil? && !temp_token["username"].nil?
71
- token = temp_token
72
- end
73
- rescue => error
74
- raise Informative, "PgyerUploadClient 加载pgyer token 失败!!!"
75
- end
76
- end
77
- return token
78
- end
79
-
80
- def upload_file(binary_file:nil, isAttach:false)
81
-
82
- raise Informative, "上传文件不能为空" if binary_file.nil? || !File.exist?(binary_file)
83
-
84
- @upload_binary_file = binary_file
85
- @file_size = File.size(@upload_binary_file)
86
- @progress_bar = PgyerUploadProgressBar.new(upload_total_size:@file_size)
87
- @upload_failed = false # 重置上传失败标志
88
-
89
- extension = File.extname(@upload_binary_file)
90
- filename = File.basename(@upload_binary_file)
91
- size_level = 1024 * 1024
92
- file_bytes = File.binread(@upload_binary_file)
93
- checksum = Digest::MD5.hexdigest(file_bytes)
94
- file_uuid = SecureRandom.uuid
95
- total_m = sprintf("%.2f", 1.00 * @file_size / 1024 /1024 )
96
-
97
-
98
- upload_path = @default_url
99
- content_disposition = nil
100
- if isAttach
101
- upload_path = @default_url + @attach_url
102
- content_disposition = "attachment; filename=#{filename}"
103
- end
104
-
105
- upload_path_key = upload_path + file_uuid + extension
106
-
107
- task_num = 10
108
-
109
- puts "文件路径: #{@upload_binary_file}"
110
- puts "文件大小: #{total_m}M"
111
- puts "上传路径: #{upload_path_key}"
112
- puts
113
- puts "切片大小: 5MB"
114
-
115
- upload_result = nil
116
-
117
- begin
118
- file_size_param = 1.00 * @file_size / 1024 /1024
119
- result_data = post_upload_url_req(upload_path_key:upload_path_key, file_ceil_size:file_size_param.ceil)
120
-
121
- if result_data.nil? || !result_data.has_key?("data") || !result_data["data"].has_key?("uploadId")
122
- raise Informative, "获取上传ID失败,请检查网络或服务器状态"
123
- end
124
-
125
- upload_id = result_data["data"]["uploadId"]
126
- # 创建统一的任务队列
127
- @tasks_queue = Queue.new
128
- @worker_threads = []
129
- upload_params_list = result_data["data"]["uploadParamsList"]
130
- upload_item_num = upload_params_list.length
131
- @expected_parts = upload_item_num # 保存预期的分片数量
132
- task_num = upload_item_num
133
- retry_count = 5
134
-
135
- # 合理限制线程数
136
- if task_num < 2
137
- task_num = 2
138
- end
139
-
140
- if task_num > 30
141
- task_num = 30
142
- end
143
-
144
- # 根据系统CPU核心数自动调整线程数
145
- available_cores = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 4
146
- task_num = [task_num, available_cores * 2].min
147
-
148
- # 设置重试次数并将所有任务加入队列
149
- upload_params_list.each do |item|
150
- item["retryCount"] = retry_count
151
- @tasks_queue.push(item)
152
- end
153
-
154
- puts "切分个数: #{upload_item_num}"
155
- puts "线程个数: #{task_num}"
156
- puts "重试次数: #{retry_count}"
157
- puts
158
-
159
- Funlog.instance.fancyinfo_start("开始上传...")
160
- @upload_eTags = []
161
- @active_tasks = 0 # 跟踪活动任务数量
162
-
163
- continuous_upload_data_req(concurrency:task_num)
164
-
165
- # 检查上传是否全部成功
166
- if upload_failed? || @upload_eTags.length != @expected_parts
167
- upload_result = nil
168
- Funlog.instance.fancyinfo_error("文件#{@upload_binary_file} 上传失败! 😭😭😭")
169
- return upload_result
170
- end
171
-
172
- result_data = post_upload_finish_req(upload_path_key:upload_path_key, upload_id:upload_id, eTags:@upload_eTags)
173
-
174
- if result_data && result_data["code"] == 200
175
- upload_result = upload_path_key
176
- Funlog.instance.fancyinfo_success("文件#{@upload_binary_file} 上传成功! 😎😎😎")
177
- else
178
- upload_result = nil
179
- error_msg = result_data && result_data["msg"] ? result_data["msg"] : "未知错误"
180
- Funlog.instance.fancyinfo_error("文件#{@upload_binary_file} 上传失败: #{error_msg} 😭😭😭")
181
- end
182
-
183
- rescue => e
184
- upload_result = nil
185
- Funlog.instance.fancyinfo_error("文件上传过程发生异常: #{e.message} 😭😭😭")
186
- ensure
187
- # 确保所有工作线程都被清理
188
- cleanup_worker_threads
189
- end
190
-
191
- return upload_result
192
-
193
- end
194
-
195
- # 安全地检查上传失败状态
196
- def upload_failed?
197
- @upload_failed_mutex.synchronize { @upload_failed }
198
- end
199
-
200
- # 安全地设置上传失败状态
201
- def set_upload_failed(error_msg = nil)
202
- @upload_failed_mutex.synchronize do
203
- @upload_failed = true
204
- Funlog.instance.fancyinfo_error("上传失败: #{error_msg}") if error_msg
205
- end
206
- end
207
-
208
- # 清理所有工作线程
209
- def cleanup_worker_threads
210
- @worker_threads.each do |thread|
211
- # 尝试安全终止线程
212
- thread.exit if thread.alive?
213
- end
214
- @worker_threads.clear
215
- end
216
-
217
- def continuous_upload_data_req(concurrency:1)
218
- # 初始化活动任务计数和条件变量
219
- @active_tasks = 0
220
- @task_complete_cv = ConditionVariable.new
221
-
222
- # 初始化连续上传,最多启动concurrency个并发任务
223
- start_tasks = [concurrency, @tasks_queue.size].min
224
- start_tasks.times { schedule_next_task }
225
-
226
- # 设置超时保护
227
- timeout_seconds = 300 # 5分钟超时
228
- start_time = Time.now
229
-
230
- # 等待所有任务完成
231
- @tasks_queue_mutex.synchronize do
232
- while (@active_tasks > 0 || !@tasks_queue.empty?) && !upload_failed?
233
- # 添加超时保护
234
- remaining_time = timeout_seconds - (Time.now - start_time)
235
- if remaining_time <= 0
236
- set_upload_failed("上传任务超时")
237
- break
238
- end
239
-
240
- # 等待任务完成通知,最多等待30秒
241
- @task_complete_cv.wait(@tasks_queue_mutex, [remaining_time, 30].min)
242
- end
243
- end
244
-
245
- # 检查所有分片是否都上传成功
246
- if @upload_eTags.length != @expected_parts && !upload_failed?
247
- set_upload_failed("部分分片上传失败,已上传#{@upload_eTags.length}/#{@expected_parts}")
248
- end
249
- end
250
-
251
- def schedule_next_task
252
- # 检查是否应该停止调度
253
- return if upload_failed?
254
-
255
- # 尝试从队列中获取下一个任务
256
- @tasks_queue_mutex.synchronize do
257
- unless @tasks_queue.empty?
258
- upload_params_item = @tasks_queue.pop
259
- @active_tasks_mutex.synchronize { @active_tasks += 1 }
260
-
261
- # 异步处理任务,不阻塞主线程
262
- worker_thread = Thread.new do
263
- begin
264
- process_upload_task(upload_params_item)
265
- rescue => e
266
- # 捕获并记录任务处理过程中的异常
267
- set_upload_failed("处理分片#{upload_params_item["partNo"]}时出错: #{e.message}")
268
- ensure
269
- # 任务完成后,减少活动任务计数并通知等待线程
270
- @active_tasks_mutex.synchronize { @active_tasks -= 1 }
271
-
272
- # 如果队列不为空,调度下一个任务
273
- schedule_next_task if !upload_failed?
274
-
275
- # 通知等待线程任务已完成
276
- @tasks_queue_mutex.synchronize { @task_complete_cv.broadcast }
277
- end
278
- end
279
-
280
- # 保存线程引用以便后续清理
281
- @worker_threads << worker_thread
282
- end
283
- end
284
- end
285
-
286
- def process_upload_task(upload_params_item)
287
- upload_url = upload_params_item["signedUrl"]
288
- part_no = upload_params_item["partNo"]
289
-
290
- file_size_ele = 1024 * 1024 * 5 #5M
291
- start_position = file_size_ele * (part_no - 1)
292
- if part_no * file_size_ele > @file_size
293
- read_length = @file_size - start_position
294
- else
295
- read_length = file_size_ele
296
- end
297
-
298
- file = File.open(@upload_binary_file, "rb")
299
- begin
300
- file.seek(start_position)
301
- put_data = file.read(read_length)
302
-
303
- request = create_req(upload_url:upload_url, body_data:put_data, read_length:read_length)
304
-
305
- # 设置上传进度回调
306
- upload_size_last = 0
307
- request.on_progress do |dltotal, dlnow, ultotal, ulnow|
308
- if ulnow && ulnow > upload_size_last
309
- upload_size_last = ulnow
310
- @progress_bar.update_upload_index(upload_part:part_no, upload_size:ulnow)
311
- @progress_bar.update_upload_progress()
312
- end
313
- end
314
-
315
- # 设置请求超时
316
- request.options[:timeout] = 300 # 5分钟超时
317
-
318
- # 执行请求并等待完成
319
- response = request.run
320
-
321
- # 处理响应结果
322
- if response.success?
323
- @progress_bar.complete_upload_index(upload_part:part_no, complete_size:read_length)
324
- etag = response.headers["ETag"]
325
- if etag.nil? || etag.empty?
326
- raise "服务器返回的ETag为空"
327
- end
328
- eTag_item = { partNumber: part_no, tag: etag}
329
- @upload_eTags_mutex.synchronize { @upload_eTags << eTag_item }
330
- else
331
- @progress_bar.delete_upload_index(upload_part:part_no)
332
- upload_params_item["retryCount"] = upload_params_item["retryCount"] - 1
333
- if upload_params_item["retryCount"] > 0
334
- # 重试任务
335
- @tasks_queue_mutex.synchronize { @tasks_queue.push(upload_params_item) }
336
- else
337
- set_upload_failed("文件#{@upload_binary_file} 分片#{part_no}上传失败: HTTP #{response.code}")
338
- end
339
- end
340
- ensure
341
- file.close
342
- end
343
- end
344
-
345
- def create_req(upload_url:nil, body_data:nil, read_length:nil)
346
-
347
- request = nil
348
- proxy_options = {
349
- proxy: ENV['http_proxy'] || ENV['https_proxy'],
350
- proxyuserpwd: "#{ENV['HTTP_PROXY_USER']}:#{ENV['HTTP_PROXY_PASSWORD']}"
351
- }
352
-
353
- if proxy_options[:proxy]
354
- request = Typhoeus::Request.new(
355
- upload_url,
356
- method: :put,
357
- proxy: proxy_options[:proxy],
358
- proxyuserpwd: proxy_options[:proxyuserpwd],
359
- :body => body_data,
360
- :headers => {
361
- 'Content-Type' => 'application/octet-stream',
362
- 'token' => @token['token'],
363
- 'Content-Length' => read_length.to_s
364
- }
365
- )
366
- else
367
- request = Typhoeus::Request.new(
368
- upload_url,
369
- method: :put,
370
- :body => body_data,
371
- :headers => {
372
- 'Content-Type' => 'application/octet-stream',
373
- 'token' => @token['token'],
374
- 'Content-Length' => read_length.to_s
375
- }
376
- )
377
-
378
- end
379
- return request
380
- end
381
-
382
- def post_upload_finish_req(upload_path_key:nil, upload_id:nil, eTags:nil)
383
-
384
- boss_url = @baseurl + @request_config["multi_signed_url_upload"]
385
-
386
- body_params = {
387
- functionName:"finish",
388
- fileKey:upload_path_key,
389
- uploadId:upload_id,
390
- tags:eTags
391
- }
392
-
393
- begin
394
- con = HttpClient.create_instance_with_proxy
395
- res = con.post do |req|
396
- req.url boss_url
397
- req.headers['Content-Type'] = 'application/json'
398
- req.headers['token'] = @token["token"]
399
- req.body = body_params.to_json
400
- req.options.timeout = 120 # 设置2分钟超时
401
- end
402
-
403
- result_data = nil
404
- if !res.body.nil?
405
- result_data = JSON.parse(res.body)
406
- end
407
-
408
- return result_data
409
- rescue => e
410
- Funlog.instance.fancyinfo_error("完成上传请求失败: #{e.message}")
411
- return nil
412
- end
413
- end
414
-
415
-
416
- def post_upload_url_req(upload_path_key:nil, file_ceil_size:nil)
417
-
418
- boss_url = @baseurl + @request_config["multi_signed_url_upload"]
419
-
420
- body_params = {
421
- functionName:"start",
422
- fileKey:upload_path_key,
423
- fileSize:file_ceil_size
424
- }
425
-
426
- begin
427
- con = HttpClient.create_instance_with_proxy
428
- res = con.post do |req|
429
- req.url boss_url
430
- req.headers['Content-Type'] = 'application/json'
431
- req.headers['token'] = @token["token"]
432
- req.body = body_params.to_json
433
- req.options.timeout = 60 # 设置1分钟超时
434
- end
435
-
436
- result_data = nil
437
- if !res.body.nil?
438
- result_data = JSON.parse(res.body)
439
- end
440
-
441
- return result_data
442
- rescue => e
443
- Funlog.instance.fancyinfo_error("获取上传URL失败: #{e.message}")
444
- return nil
445
- end
446
- end
447
-
448
- class PgyerUploadProgressBar
449
-
450
- attr_accessor :draw_char
451
- attr_accessor :complete_size
452
- attr_accessor :upload_total_size
453
- attr_accessor :last_update_time
454
- attr_accessor :update_ing_size
455
- attr_accessor :is_done
456
-
457
- def initialize(upload_total_size:nil, draw_char:'>')
458
- @upload_total_size = upload_total_size
459
- @draw_char = draw_char
460
- @last_update_time = (Time.now.to_f * 1000).to_i #毫秒
461
-
462
- @complete_size = 0
463
- @update_ing_size = {}
464
- @is_done = false
465
-
466
- # 添加互斥锁来保护进度条更新
467
- @mutex = Mutex.new
468
- end
469
-
470
- def update_upload_index(upload_part:nil, upload_size:nil)
471
- @mutex.synchronize do
472
- @update_ing_size[upload_part] = upload_size
473
- end
474
- end
475
-
476
- def delete_upload_index(upload_part:nil)
477
- @mutex.synchronize do
478
- @update_ing_size[upload_part] = 0
479
- end
480
- end
481
-
482
- def complete_upload_index(upload_part:nil, complete_size:nil)
483
- @mutex.synchronize do
484
- @complete_size = @complete_size + complete_size
485
- @update_ing_size[upload_part] = 0
486
- end
487
- end
488
-
489
- def update_upload_progress()
490
- time_now = (Time.now.to_f * 1000).to_i #毫秒
491
- if time_now - @last_update_time > 80
492
- @mutex.synchronize do
493
- @last_update_time = time_now
494
- total_num = @upload_total_size
495
- index_num = @complete_size
496
- @update_ing_size.each do |key, value|
497
- index_num = index_num + value
498
- end
499
-
500
- progress_str = sprintf("%.2f", 100.0 * index_num / total_num )
501
- total_size = sprintf("%.2f", 1.00 * total_num / 1024 /1024 )
502
- upload_size = sprintf("%.2f", 1.00 * index_num / 1024 /1024 )
503
- index = 40.0 * index_num / total_num
504
- upload_message = "已上传:#{upload_size}MB|#{progress_str}\%【" + (@draw_char * (index/1).floor).ljust(40.0, '_') + "】Total:#{total_size}MB"
505
- Funlog.instance.fancyinfo_update(upload_message)
506
- if index_num == total_num && !@is_done
507
- @is_done = true
508
- Funlog.instance.fancyinfo_success(upload_message)
509
- end
510
- end
511
- end
512
- end
513
-
514
- end
515
-
516
- end
517
- end
@@ -1,18 +0,0 @@
1
-
2
- require 'pindo/command/pgyer/login'
3
- require 'pindo/command/pgyer/upload'
4
- require 'pindo/command/pgyer/download'
5
- require 'pindo/command/pgyer/comment'
6
- require 'pindo/command/pgyer/apptest'
7
- require 'pindo/command/pgyer/resign'
8
-
9
- module Pindo
10
- class Command
11
-
12
- class Pgyer < Command
13
- self.abstract_command = true
14
- self.summary = 'pgyer网站的相关命令'
15
- end
16
-
17
- end
18
- end