cocoapods-jxedt 0.0.10 → 0.0.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/cocoapods-jxedt/binary/Intergation.rb +18 -13
- data/lib/cocoapods-jxedt/binary/config.rb +67 -0
- data/lib/cocoapods-jxedt/binary/helper/podfile_post_install_hook.rb +29 -0
- data/lib/cocoapods-jxedt/binary/helper/prebuild_sandbox.rb +112 -2
- data/lib/cocoapods-jxedt/binary/hooks/CocoapodsJxedtHook.rb +6 -9
- data/lib/cocoapods-jxedt/binary/hooks/post_install.rb +2 -2
- data/lib/cocoapods-jxedt/binary/hooks/pre_install.rb +33 -9
- data/lib/cocoapods-jxedt/binary/main.rb +1 -0
- data/lib/cocoapods-jxedt/binary/pod-room/framework.rb +40 -0
- data/lib/cocoapods-jxedt/binary/pod-room/xcodebuild_command.rb +79 -1
- data/lib/cocoapods-jxedt/binary/pod-room/xcodebuild_raw.rb +1 -1
- data/lib/cocoapods-jxedt/binary/prebuild.rb +92 -14
- data/lib/cocoapods-jxedt/binary/targets/pod_target.rb +50 -0
- data/lib/cocoapods-jxedt/command/binary/binary.rb +36 -0
- data/lib/cocoapods-jxedt/command/binary/command/build.rb +84 -0
- data/lib/cocoapods-jxedt/command/binary/command/clean.rb +101 -0
- data/lib/cocoapods-jxedt/command/binary/command/fetch.rb +36 -0
- data/lib/cocoapods-jxedt/command/binary/command/push.rb +41 -0
- data/lib/cocoapods-jxedt/command/binary/command/statistics.rb +104 -0
- data/lib/cocoapods-jxedt/command/jxedt.rb +1 -1
- data/lib/cocoapods-jxedt/command/options/options.rb +85 -2
- data/lib/cocoapods-jxedt/gem_version.rb +1 -1
- data/lib/cocoapods-jxedt/git_helper/cache_fetcher.rb +34 -0
- data/lib/cocoapods-jxedt/git_helper/cache_pucher.rb +42 -0
- data/lib/cocoapods-jxedt/git_helper/git_command.rb +48 -0
- data/lib/cocoapods-jxedt/git_helper/zip.rb +22 -0
- metadata +14 -3
- data/lib/cocoapods-jxedt/command/statistics/statistics.rb +0 -98
@@ -10,9 +10,14 @@ module Pod
|
|
10
10
|
self.arguments = [
|
11
11
|
]
|
12
12
|
def self.options
|
13
|
-
[
|
13
|
+
[
|
14
|
+
['--more-config', '获取cocoapods-jxedt插件的完整配置'],
|
15
|
+
['--config', '获取cocoapods-jxedt插件的基础配置']
|
16
|
+
]
|
14
17
|
end
|
15
18
|
def initialize(argv)
|
19
|
+
@config = argv.flag?('config', false)
|
20
|
+
@more_config = argv.flag?('more-config', false)
|
16
21
|
super
|
17
22
|
end
|
18
23
|
|
@@ -22,10 +27,88 @@ module Pod
|
|
22
27
|
|
23
28
|
def run
|
24
29
|
require 'cocoapods-jxedt/binary/config'
|
30
|
+
|
31
|
+
return print_options_config if @more_config
|
32
|
+
return print_base_options_config if @config
|
33
|
+
|
25
34
|
require 'json'
|
26
35
|
|
36
|
+
log_section "🌹 APPLICABLE_DSL_CONFIG"
|
27
37
|
dsl_config = Jxedt::Config::APPLICABLE_DSL_CONFIG
|
28
|
-
puts JSON.pretty_generate(dsl_config)
|
38
|
+
Pod::UI.puts JSON.pretty_generate(dsl_config)
|
39
|
+
|
40
|
+
log_section "🌹 GIT_CACHE_CONFIG"
|
41
|
+
git_config = Jxedt::Config::GIT_CACHE_CONFIG
|
42
|
+
Pod::UI.puts JSON.pretty_generate(git_config)
|
43
|
+
end
|
44
|
+
|
45
|
+
def print_base_options_config
|
46
|
+
config = <<CONFIG
|
47
|
+
use_frameworks! :linkage => :static
|
48
|
+
# use_modular_headers!
|
49
|
+
|
50
|
+
plugin 'cocoapods-jxedt'
|
51
|
+
options = {
|
52
|
+
'all_binary': true, # 所有组件开启binary
|
53
|
+
'keep_source_project': true, # 保留源码pod工程,所在目录`Pods-Source`
|
54
|
+
'excluded_pods': [], # 排除binary的组件名称
|
55
|
+
'framework_header_search_enabled': true, # 兼容头文件引用`#import "xxx.h"`
|
56
|
+
'configurations': ['Release'], # 支持的configurations ['Release', 'Debug']
|
57
|
+
'device_build_enabled': true, # 真机
|
58
|
+
'simulator_build_enabled': false # 模拟器
|
59
|
+
}
|
60
|
+
cocoapods_jxedt_config(options)
|
61
|
+
CONFIG
|
62
|
+
Pod::UI.puts config
|
63
|
+
end
|
64
|
+
|
65
|
+
def print_options_config
|
66
|
+
dsl_config = Jxedt::Config::APPLICABLE_DSL_CONFIG
|
67
|
+
git_config = Jxedt::Config::GIT_CACHE_CONFIG
|
68
|
+
|
69
|
+
config = <<CONFIG
|
70
|
+
use_frameworks! :linkage => :static # 插件是支持使用library的,插件会把编译后的.a生成framework。如果你的工程不支持module,可以删掉或者注释掉这一行。不影响使用
|
71
|
+
# use_modular_headers!
|
72
|
+
|
73
|
+
plugin 'cocoapods-jxedt'
|
74
|
+
|
75
|
+
# 这里默认配置了大部分常用的配置参数,只需要简单修改就可以使用了
|
76
|
+
options = {
|
77
|
+
'all_binary': true, # #{dsl_config[:all_binary]}
|
78
|
+
'binary_dir': '../_Prebuild', # #{dsl_config[:binary_dir]}
|
79
|
+
'binary_switch': true, # #{dsl_config[:binary_switch]}
|
80
|
+
'prebuild_job': true, # #{dsl_config[:prebuild_job]}
|
81
|
+
'keep_source_project': false, # #{dsl_config[:keep_source_project]}
|
82
|
+
'excluded_pods': [], # #{dsl_config[:excluded_pods]}
|
83
|
+
'framework_header_search_enabled': false, # #{dsl_config[:framework_header_search_enabled]}
|
84
|
+
'configurations': ['Release'], # #{dsl_config[:configurations]}
|
85
|
+
'xcframework': false, # #{dsl_config[:xcframework]}
|
86
|
+
'clean_build': true, # #{dsl_config[:clean_build]}
|
87
|
+
'device_build_enabled': true, # #{dsl_config[:device_build_enabled]}
|
88
|
+
'simulator_build_enabled': false, # #{dsl_config[:simulator_build_enabled]}
|
89
|
+
'disable_resource_compilable_pods': false, # #{dsl_config[:disable_resource_compilable_pods]}
|
90
|
+
'build_args': { # 下面是默认的配置,如果没有改动可删除节点
|
91
|
+
'default': ["ONLY_ACTIVE_ARCH=NO", "BUILD_LIBRARY_FOR_DISTRIBUTION=YES"],
|
92
|
+
'device': ["ARCHS='arm64'"],
|
93
|
+
'simulator': ["ARCHS='x86_64'"]
|
94
|
+
},
|
95
|
+
'git_cache': { # 如果不需要git缓存,直接删掉这个节点即可
|
96
|
+
'repo': '', # #{git_config[:repo]}
|
97
|
+
'branch': 'develop', # #{git_config[:branch]}
|
98
|
+
'auto_fetch': true, # #{git_config[:auto_fetch]}
|
99
|
+
'auto_push': false # #{git_config[:auto_push]}
|
100
|
+
}
|
101
|
+
}
|
102
|
+
cocoapods_jxedt_config(options)
|
103
|
+
CONFIG
|
104
|
+
|
105
|
+
Pod::UI.puts config
|
106
|
+
end
|
107
|
+
|
108
|
+
def log_section(message)
|
109
|
+
Pod::UI.puts "-----------------------------------------"
|
110
|
+
Pod::UI.puts message
|
111
|
+
Pod::UI.puts "-----------------------------------------"
|
29
112
|
end
|
30
113
|
end
|
31
114
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Jxedt
|
2
|
+
module CacheFetcher
|
3
|
+
def self.fetch
|
4
|
+
require_relative 'git_command'
|
5
|
+
repo = Jxedt.config.git_remote_repo
|
6
|
+
cache_path = Jxedt.config.git_cache_path
|
7
|
+
branch = Jxedt.config.cache_branch
|
8
|
+
|
9
|
+
commander = Jxedt::GitCommand.new(cache_path)
|
10
|
+
commander.git_fetch(repo, branch)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.sync(binary_hash, to_dir)
|
14
|
+
# fetch
|
15
|
+
fetch
|
16
|
+
|
17
|
+
require_relative 'zip'
|
18
|
+
|
19
|
+
cache_dir = Pathname.new(Jxedt.config.git_cache_path) + "GeneratedFrameworks"
|
20
|
+
to_dir = Pathname.new(to_dir)
|
21
|
+
binary_hash.each do |name, checksum|
|
22
|
+
zip_file = cache_dir + name + "#{checksum}.zip"
|
23
|
+
next unless zip_file.exist?
|
24
|
+
|
25
|
+
file_path = to_dir + name + "#{checksum}.checksum"
|
26
|
+
next if file_path.exist? # 存在同校验和的文件,跳过
|
27
|
+
file_path.parent.rmtree if file_path.parent.exist?
|
28
|
+
|
29
|
+
Jxedt::ZipUtils.unzip(zip_file, :to_dir => to_dir)
|
30
|
+
Pod::UI.puts "✅ 已从缓存下载组件#{name}, 校验和:#{checksum}".yellow
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Jxedt
|
2
|
+
module CachePucher
|
3
|
+
def self.push(output_dir, targets=nil, force_push=false)
|
4
|
+
require_relative 'git_command'
|
5
|
+
require_relative 'zip'
|
6
|
+
|
7
|
+
repo = Jxedt.config.git_remote_repo
|
8
|
+
cache_path = Jxedt.config.git_cache_path
|
9
|
+
branch = Jxedt.config.cache_branch
|
10
|
+
commander = Jxedt::GitCommand.new(cache_path)
|
11
|
+
# fetch
|
12
|
+
commander.git_fetch(repo, branch)
|
13
|
+
|
14
|
+
output_dir = Pathname.new(output_dir)
|
15
|
+
approve, refuse = [], []
|
16
|
+
output_dir.children.each do |child|
|
17
|
+
next unless child.directory?
|
18
|
+
pod_name = child.basename.to_s
|
19
|
+
next if targets && targets.is_a?(Array) && !(targets.include?(pod_name))
|
20
|
+
|
21
|
+
pod_checksum = child.children.select { |ch| ch.extname == '.checksum' }.first
|
22
|
+
pod_checksum = pod_checksum.basename.sub_ext('').to_s unless pod_checksum.nil?
|
23
|
+
next if pod_checksum.nil?
|
24
|
+
|
25
|
+
to_dir = Pathname.new(cache_path) + "GeneratedFrameworks" + pod_name
|
26
|
+
to_dir.mkpath unless to_dir.exist?
|
27
|
+
zip_file = to_dir + "#{pod_checksum}.zip"
|
28
|
+
skip = zip_file.exist? && !force_push
|
29
|
+
next refuse << pod_name if skip
|
30
|
+
|
31
|
+
approve << pod_name
|
32
|
+
Jxedt::ZipUtils.zip(child.to_s, :zip_name => pod_checksum, :to_dir => to_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
# push
|
36
|
+
commander.git_commit_and_push(branch) if approve.size > 0
|
37
|
+
|
38
|
+
Pod::UI.puts "🎉 组件已缓存成功: #{approve}".yellow if approve.size > 0
|
39
|
+
Pod::UI.puts "👻 组件缓存失败,存在相同的校验和: #{refuse}".red if refuse.size > 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Jxedt
|
2
|
+
class GitCommand
|
3
|
+
def initialize(cache_path)
|
4
|
+
@cache_path = File.expand_path(cache_path)
|
5
|
+
prepare_cache_dir
|
6
|
+
end
|
7
|
+
|
8
|
+
def prepare_cache_dir
|
9
|
+
FileUtils.mkdir_p(@cache_path) if @cache_path
|
10
|
+
end
|
11
|
+
|
12
|
+
def git(cmd, ignore_output: false, can_fail: false, git_dir: true)
|
13
|
+
comps = ["git"]
|
14
|
+
comps << "-C" << @cache_path if git_dir
|
15
|
+
comps << cmd
|
16
|
+
comps << "&> /dev/null" if ignore_output
|
17
|
+
comps << "|| true" if can_fail
|
18
|
+
cmd = comps.join(" ")
|
19
|
+
raise "Fail to run command '#{cmd}'" unless system(cmd)
|
20
|
+
end
|
21
|
+
|
22
|
+
def git_clone(cmd, options = {})
|
23
|
+
git("clone #{cmd}", :git_dir => false)
|
24
|
+
end
|
25
|
+
|
26
|
+
def git_fetch(repo, branch)
|
27
|
+
Pod::UI.puts "Fetching cache from #{repo} (branch: #{branch})".green
|
28
|
+
dest_dir = @cache_path
|
29
|
+
if Dir.exist?(dest_dir + "/.git")
|
30
|
+
git("fetch origin #{branch}")
|
31
|
+
git("checkout -f FETCH_HEAD", ignore_output: true)
|
32
|
+
git("branch -D #{branch}", ignore_output: true, can_fail: true)
|
33
|
+
git("checkout -b #{branch}")
|
34
|
+
else
|
35
|
+
FileUtils.rm_rf(dest_dir)
|
36
|
+
git_clone("--depth=1 --branch=#{branch} #{repo} #{dest_dir}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def git_commit_and_push(branch)
|
41
|
+
commit_message = "Update prebuilt cache"
|
42
|
+
git("add .")
|
43
|
+
git("commit -m '#{commit_message}'")
|
44
|
+
git("push origin #{branch}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Jxedt
|
2
|
+
module ZipUtils
|
3
|
+
def self.zip(path, zip_name: nil, to_dir: nil)
|
4
|
+
basename = File.basename(path)
|
5
|
+
zip_name = basename if zip_name.nil?
|
6
|
+
out_path = to_dir.nil? ? "#{zip_name}.zip" : "#{to_dir}/#{zip_name}.zip"
|
7
|
+
cmd = []
|
8
|
+
cmd << "cd" << File.dirname(path)
|
9
|
+
cmd << "&& zip -r --symlinks" << out_path << basename
|
10
|
+
cmd << "&& cd -"
|
11
|
+
`#{cmd.join(" ")}`
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.unzip(path, to_dir: nil)
|
15
|
+
cmd = []
|
16
|
+
cmd << "unzip -nq" << path
|
17
|
+
cmd << "-d" << to_dir unless to_dir.nil?
|
18
|
+
`#{cmd.join(" ")}`
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cocoapods-jxedt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- guojiashuang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cocoapods
|
@@ -75,23 +75,34 @@ files:
|
|
75
75
|
- lib/cocoapods-jxedt/binary/config.rb
|
76
76
|
- lib/cocoapods-jxedt/binary/helper/names.rb
|
77
77
|
- lib/cocoapods-jxedt/binary/helper/podfile_options.rb
|
78
|
+
- lib/cocoapods-jxedt/binary/helper/podfile_post_install_hook.rb
|
78
79
|
- lib/cocoapods-jxedt/binary/helper/prebuild_sandbox.rb
|
79
80
|
- lib/cocoapods-jxedt/binary/helper/target_definition.rb
|
80
81
|
- lib/cocoapods-jxedt/binary/hooks/CocoapodsJxedtHook.rb
|
81
82
|
- lib/cocoapods-jxedt/binary/hooks/post_install.rb
|
82
83
|
- lib/cocoapods-jxedt/binary/hooks/pre_install.rb
|
83
84
|
- lib/cocoapods-jxedt/binary/main.rb
|
85
|
+
- lib/cocoapods-jxedt/binary/pod-room/framework.rb
|
84
86
|
- lib/cocoapods-jxedt/binary/pod-room/xcodebuild_command.rb
|
85
87
|
- lib/cocoapods-jxedt/binary/pod-room/xcodebuild_raw.rb
|
86
88
|
- lib/cocoapods-jxedt/binary/podfile_dsl.rb
|
87
89
|
- lib/cocoapods-jxedt/binary/prebuild.rb
|
88
90
|
- lib/cocoapods-jxedt/binary/targets/pod_target.rb
|
89
91
|
- lib/cocoapods-jxedt/command.rb
|
92
|
+
- lib/cocoapods-jxedt/command/binary/binary.rb
|
93
|
+
- lib/cocoapods-jxedt/command/binary/command/build.rb
|
94
|
+
- lib/cocoapods-jxedt/command/binary/command/clean.rb
|
95
|
+
- lib/cocoapods-jxedt/command/binary/command/fetch.rb
|
96
|
+
- lib/cocoapods-jxedt/command/binary/command/push.rb
|
97
|
+
- lib/cocoapods-jxedt/command/binary/command/statistics.rb
|
90
98
|
- lib/cocoapods-jxedt/command/header/header.rb
|
91
99
|
- lib/cocoapods-jxedt/command/jxedt.rb
|
92
100
|
- lib/cocoapods-jxedt/command/options/options.rb
|
93
|
-
- lib/cocoapods-jxedt/command/statistics/statistics.rb
|
94
101
|
- lib/cocoapods-jxedt/gem_version.rb
|
102
|
+
- lib/cocoapods-jxedt/git_helper/cache_fetcher.rb
|
103
|
+
- lib/cocoapods-jxedt/git_helper/cache_pucher.rb
|
104
|
+
- lib/cocoapods-jxedt/git_helper/git_command.rb
|
105
|
+
- lib/cocoapods-jxedt/git_helper/zip.rb
|
95
106
|
- lib/cocoapods-jxedt/tool.rb
|
96
107
|
- lib/cocoapods_plugin.rb
|
97
108
|
homepage: http://igit.58corp.com/com.wuba.jxedt.ios/cocoapods-jxedt
|
@@ -1,98 +0,0 @@
|
|
1
|
-
module Pod
|
2
|
-
class Command
|
3
|
-
class JxedtCommand < Command
|
4
|
-
class Statistics < JxedtCommand
|
5
|
-
self.summary = '统计二进制使用情况'
|
6
|
-
self.description = <<-DESC
|
7
|
-
统计二进制使用详情
|
8
|
-
DESC
|
9
|
-
self.command = 'statistics'
|
10
|
-
self.arguments = [
|
11
|
-
]
|
12
|
-
def self.options
|
13
|
-
[
|
14
|
-
['--failed', '统计校验失败的二进制'],
|
15
|
-
]
|
16
|
-
end
|
17
|
-
def initialize(argv)
|
18
|
-
@check_failed = argv.flag?('failed')
|
19
|
-
super
|
20
|
-
end
|
21
|
-
|
22
|
-
def validate!
|
23
|
-
super
|
24
|
-
end
|
25
|
-
|
26
|
-
def run
|
27
|
-
podfile = Pod::Config.instance.podfile
|
28
|
-
lockfile = Pod::Config.instance.lockfile
|
29
|
-
help! '请检查命令执行路径,需要在Podfile文件所在目录执行' if podfile.nil? || lockfile.nil?
|
30
|
-
|
31
|
-
require 'cocoapods-jxedt/binary/config'
|
32
|
-
require 'cocoapods-jxedt/binary/helper/podfile_options'
|
33
|
-
|
34
|
-
pods_root = Pathname.new(File.dirname(podfile.defined_in_file)) + "Pods"
|
35
|
-
binary_dir = pods_root + Jxedt.config.binary_dir
|
36
|
-
|
37
|
-
used_binary = []
|
38
|
-
Dir.glob("#{pods_root}/*/_Prebuild") do |file_path|
|
39
|
-
next unless File.directory?(file_path)
|
40
|
-
dir_name = File.dirname(file_path)
|
41
|
-
name = File.basename(dir_name).to_s
|
42
|
-
target_path = binary_dir + name
|
43
|
-
next unless target_path.exist? # 路径不存在,跳过
|
44
|
-
|
45
|
-
new_hash = {}
|
46
|
-
# name
|
47
|
-
new_hash[:name] = name
|
48
|
-
|
49
|
-
# multiple_configuration
|
50
|
-
configuration_enable = target_path.children().select { |path| "#{path.basename}" == 'Debug' || "#{path.basename}" == 'Release' }.count == 2
|
51
|
-
new_hash[:multiple_configuration] = configuration_enable
|
52
|
-
|
53
|
-
# checksum validation
|
54
|
-
checksum_file = target_path.children().select { |path| path.extname == '.checksum' }.first
|
55
|
-
new_hash[:checksum] = checksum_file.basename.to_s.gsub('.checksum', '') unless checksum_file.nil?
|
56
|
-
|
57
|
-
used_binary << new_hash
|
58
|
-
end
|
59
|
-
|
60
|
-
# print
|
61
|
-
index, failed = 0, []
|
62
|
-
used_binary.sort_by {|hash| hash[:name] }.each do |hash|
|
63
|
-
name = hash[:name]
|
64
|
-
|
65
|
-
checksum = lockfile.spec_checksums_hash_key(name)
|
66
|
-
validation_passed = checksum && checksum == hash[:checksum]
|
67
|
-
failed << name unless validation_passed
|
68
|
-
|
69
|
-
# 校验和是否用的 git commitid
|
70
|
-
checkout_options = lockfile.internal_data["CHECKOUT OPTIONS"] || {}
|
71
|
-
is_git_commitid = checkout_options[name] && checkout_options[name][:commit]
|
72
|
-
|
73
|
-
if validation_passed
|
74
|
-
next if @check_failed
|
75
|
-
index += 1
|
76
|
-
log = <<~LOG
|
77
|
-
#{index}). #{name}:
|
78
|
-
multiple configuration: #{hash[:multiple_configuration]}
|
79
|
-
checksum#{"(git commitid)" if is_git_commitid}: #{hash[:checksum]}
|
80
|
-
LOG
|
81
|
-
Pod::UI.puts log
|
82
|
-
else
|
83
|
-
index += 1
|
84
|
-
log = <<~LOG
|
85
|
-
#{index}). #{name}:
|
86
|
-
multiple configuration: #{hash[:multiple_configuration]}
|
87
|
-
checksum: #{hash[:checksum]}
|
88
|
-
checksum in lockfile#{"(git commitid)" if is_git_commitid}: #{checksum}
|
89
|
-
LOG
|
90
|
-
Pod::UI.puts log.red
|
91
|
-
end
|
92
|
-
end
|
93
|
-
Pod::UI.puts "checksum校验失败的组件: #{failed}".red if failed.size > 0
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|