six-updater-web 0.13.2 → 0.13.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/app/controllers/main_controller.rb +4 -1
- data/lib/app/models/sixconfig.rb +138 -64
- data/lib/app/models/system_setting.rb +10 -26
- data/lib/config/six-updater-web.rb +48 -40
- data/lib/init.rb +11 -6
- data/lib/lib/tasks/sync.rake +4 -0
- data/lib/vendor/plugins/six-app_manager/lib/six/appmanager.rb +2 -2
- metadata +3 -3
data/Rakefile
CHANGED
@@ -111,7 +111,10 @@ class MainController < ApplicationController
|
|
111
111
|
begin
|
112
112
|
preset = Sixconfig.find(@system_setting.favorite_preset)
|
113
113
|
rescue
|
114
|
-
|
114
|
+
begin
|
115
|
+
preset = Sixconfig.find(:first) # Auto select preset?
|
116
|
+
rescue
|
117
|
+
end
|
115
118
|
end
|
116
119
|
|
117
120
|
begin
|
data/lib/app/models/sixconfig.rb
CHANGED
@@ -13,7 +13,6 @@ class Sixconfig < ActiveRecord::Base
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def all_mods(srv = self.server)
|
16
|
-
hash = Hash.new
|
17
16
|
m = []
|
18
17
|
m += srv.mods if srv
|
19
18
|
self.mods.each { |mod| m << mod unless m.include?(mod) }
|
@@ -91,12 +90,38 @@ class Sixconfig < ActiveRecord::Base
|
|
91
90
|
yml
|
92
91
|
end
|
93
92
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
93
|
+
def to_updater_yml(setting_override = nil, server_override = nil)
|
94
|
+
setting = if setting_override
|
95
|
+
setting_override
|
96
|
+
else
|
97
|
+
self.appsetting
|
98
|
+
end
|
99
|
+
|
100
|
+
srv = if server_override
|
101
|
+
server_override
|
102
|
+
else
|
103
|
+
self.server
|
104
|
+
end
|
105
|
+
|
106
|
+
setting = Appsetting.new unless setting
|
107
|
+
hash = setting.to_updater_yml
|
108
|
+
hash[:folders] = []
|
109
|
+
|
110
|
+
self.all_mods(srv).each do |mod|
|
111
|
+
y = mod.to_updater_yml
|
112
|
+
hash[:folders] << y unless hash[:folders].include?(y)
|
113
|
+
end
|
114
|
+
|
115
|
+
hash[:server] = srv.to_updater_yml if srv
|
116
|
+
|
117
|
+
hash
|
97
118
|
end
|
98
119
|
|
99
|
-
def self.
|
120
|
+
def self.backtrace(e, log)
|
121
|
+
log.info "ERROR!!! #{e.class}: #{e.message} #{e.backtrace.join("\n")}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.exec
|
100
125
|
# Dirty workaround for Windows six-updater.rb inside BASE_PATH will take precedence over ruby\bin\six-updater(.bat)
|
101
126
|
case RUBY_PLATFORM
|
102
127
|
when /-mingw32$/, /-mswin32$/
|
@@ -107,79 +132,128 @@ class Sixconfig < ActiveRecord::Base
|
|
107
132
|
end
|
108
133
|
|
109
134
|
def self.start_updater(cl)
|
110
|
-
SixUpdaterWeb
|
135
|
+
SixUpdaterWeb.run_program(self.exec, SixUpdaterWeb::BASE_PATH, cl)
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.process_msg(msg, messages, ses, previous_r = nil)
|
139
|
+
entry = nil
|
140
|
+
if msg[/\n$/] #&& ! msg[/.+\r\n$/]
|
141
|
+
# Create new log entry
|
142
|
+
entry = Log.new :logsession_id => ses, :content => msg
|
143
|
+
previous_r = nil
|
144
|
+
else
|
145
|
+
if previous_r
|
146
|
+
# Overwrite previous log entry
|
147
|
+
entry = previous_r
|
148
|
+
else
|
149
|
+
# Create new log entry
|
150
|
+
entry = Log.new :logsession_id => ses
|
151
|
+
previous_r = entry
|
152
|
+
end
|
153
|
+
entry.content = msg
|
154
|
+
end
|
155
|
+
# Add back into messages even when used before, as the content might be changed anyway, but dont add it if still in queue for nect cycle
|
156
|
+
messages << entry unless messages.include?(entry)
|
157
|
+
#p [msg, entry]
|
158
|
+
previous_r
|
111
159
|
end
|
112
160
|
|
113
|
-
|
114
|
-
|
115
|
-
|
161
|
+
# TODO: FIXME, Can't run this in development atm!
|
162
|
+
def self.start_updater_inweb(cli)
|
163
|
+
logger.info "Starting #{exec} with #{cli} from #{SixUpdaterWeb::BASE_PATH}"
|
164
|
+
|
116
165
|
ActiveRecord::Base.connection_pool.clear_stale_cached_connections!
|
117
|
-
Thread.new(
|
118
|
-
#out = []
|
166
|
+
Thread.new(cli, logger) do |cl, log|
|
119
167
|
Dir.chdir SixUpdaterWeb::BASE_PATH do |dir|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
if Time.now - previous > 2
|
168
|
+
# Setup session
|
169
|
+
ses = Logsession.new
|
170
|
+
ses.save
|
171
|
+
ses = ses.id # Workaround for some issue with throwing around this record, in development mode.
|
172
|
+
messages, buff = [], []
|
173
|
+
found_r, previous_r = nil, nil
|
174
|
+
begin
|
175
|
+
status = Open3.popen3("#{exec} #{cl}") do |io_in, io_out, io_err, waitth|
|
176
|
+
previous = Time.now
|
177
|
+
io_out.sync = true
|
178
|
+
io_out.each_byte do |b|
|
132
179
|
begin
|
133
|
-
|
134
|
-
|
135
|
-
|
180
|
+
char = b.chr
|
181
|
+
case RUBY_PLATFORM
|
182
|
+
when /-mingw32$/, /-mswin32$/
|
183
|
+
if found_r
|
184
|
+
# Found \r
|
185
|
+
found_r = false
|
186
|
+
if ["\n"].include?(char)
|
187
|
+
# Line ending: Add to buffer, then process it
|
188
|
+
buff << char
|
189
|
+
msg = buff.join("")
|
190
|
+
buff = []
|
191
|
+
previous_r = process_msg(msg, messages, ses, previous_r)
|
192
|
+
else
|
193
|
+
# Other char; Process buffer, then add it to a new buffer
|
194
|
+
msg = buff.join("")
|
195
|
+
buff = []
|
196
|
+
previous_r = process_msg(msg, messages, ses, previous_r)
|
197
|
+
found_r = true if ["\r"].include?(char)
|
198
|
+
buff << char
|
199
|
+
end
|
200
|
+
else
|
201
|
+
# Default processing
|
202
|
+
buff << char
|
203
|
+
case char
|
204
|
+
when "\r"
|
205
|
+
found_r = true
|
206
|
+
when "\n"
|
207
|
+
msg = buff.join("")
|
208
|
+
buff = []
|
209
|
+
previous_r = process_msg(msg, messages, ses, previous_r)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
else
|
213
|
+
# Default processing
|
214
|
+
buff << char
|
215
|
+
if ["\r","\n"].include?(char)
|
216
|
+
msg = buff.join("")
|
217
|
+
buff = []
|
218
|
+
previous_r = process_msg(msg, messages, ses, previous_r)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
rescue => e
|
222
|
+
backtrace(e, log)
|
223
|
+
end
|
224
|
+
unless messages.empty?
|
225
|
+
# Write the messages buffer to database
|
226
|
+
# only check every 2 seconds
|
227
|
+
if Time.now - previous > 2
|
228
|
+
previous = Time.now
|
229
|
+
done = []
|
230
|
+
begin
|
231
|
+
messages.uniq.each {|msg| msg.save; done << msg}
|
232
|
+
rescue => e
|
233
|
+
backtrace(e, log)
|
234
|
+
end
|
235
|
+
messages -= done
|
136
236
|
end
|
137
|
-
buff2 = []
|
138
|
-
previous = Time.now
|
139
|
-
rescue
|
140
|
-
logger.warn "Updater Thread Exception Caught"
|
141
|
-
logger.debug "#{$!}"
|
142
237
|
end
|
143
238
|
end
|
144
|
-
#out << buffer
|
145
239
|
end
|
240
|
+
rescue => e
|
241
|
+
backtrace(e, log)
|
146
242
|
end
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
243
|
+
|
244
|
+
# Handle last bits in the buffer
|
245
|
+
process_msg(buff.join(""), messages, ses, previous_r) unless buff.empty?
|
246
|
+
|
247
|
+
unless messages.empty?
|
248
|
+
begin
|
249
|
+
messages.each {|msg| msg.save}
|
250
|
+
rescue => e
|
251
|
+
backtrace(e, log)
|
252
|
+
end
|
152
253
|
end
|
153
254
|
end
|
154
255
|
ActiveRecord::Base.connection_pool.clear_stale_cached_connections!
|
155
256
|
end
|
156
257
|
sleep 2
|
157
258
|
end
|
158
|
-
|
159
|
-
def to_updater_yml(setting_override = nil, server_override = nil)
|
160
|
-
setting = if setting_override
|
161
|
-
setting_override
|
162
|
-
else
|
163
|
-
self.appsetting
|
164
|
-
end
|
165
|
-
|
166
|
-
srv = if server_override
|
167
|
-
server_override
|
168
|
-
else
|
169
|
-
self.server
|
170
|
-
end
|
171
|
-
|
172
|
-
setting = Appsetting.new unless setting
|
173
|
-
hash = setting.to_updater_yml
|
174
|
-
hash[:folders] = []
|
175
|
-
|
176
|
-
self.all_mods(srv).each do |mod|
|
177
|
-
y = mod.to_updater_yml
|
178
|
-
hash[:folders] << y unless hash[:folders].include?(y)
|
179
|
-
end
|
180
|
-
|
181
|
-
hash[:server] = srv.to_updater_yml if srv
|
182
|
-
|
183
|
-
hash
|
184
|
-
end
|
185
259
|
end
|
@@ -10,35 +10,24 @@ class SystemSetting < ActiveRecord::Base
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
|
15
|
-
path.gsub!("/", "\\")
|
16
|
-
end
|
17
|
-
|
18
|
-
def app_path
|
19
|
-
"rake"
|
13
|
+
def exec
|
14
|
+
"rake" # .bat on Windows
|
20
15
|
end
|
21
16
|
|
22
17
|
def update_gamespy
|
23
18
|
self.gamespied_at = Time.now
|
24
19
|
self.save
|
25
20
|
cl = "sync:gamespy RAILS_ENV=#{ENV['RAILS_ENV']} BASE_PATH=\'#{SixUpdaterWeb::BASE_PATH}\'" # + cl
|
26
|
-
SixUpdaterWeb.run_program(
|
21
|
+
SixUpdaterWeb.run_program(exec, RAILS_ROOT, cl)
|
27
22
|
end
|
28
23
|
|
29
24
|
def update_gamespy_nogeo
|
30
25
|
self.gamespied_at = Time.now
|
31
26
|
self.save
|
32
27
|
cl = "sync:gamespy RAILS_ENV=#{ENV['RAILS_ENV']} NOGEO=1 BASE_PATH=\'#{SixUpdaterWeb::BASE_PATH}\'" # + cl
|
33
|
-
SixUpdaterWeb.run_program(
|
28
|
+
SixUpdaterWeb.run_program(exec, RAILS_ROOT, cl)
|
34
29
|
end
|
35
30
|
|
36
|
-
#system "tools/ruby/bin/ruby tools/ruby/bin/rake sync:gamespy"
|
37
|
-
#path = File.join(SixUpdaterWeb::BASE_PATH, 'tools', "ruby", "bin")
|
38
|
-
#path = File.join("C:", "Ruby", "Bin")
|
39
|
-
#path.gsub!("/", "\\")
|
40
|
-
# \"#{path}\\rake\"
|
41
|
-
|
42
31
|
def synchronize
|
43
32
|
Six::Network::Panel.setlogger(logger)
|
44
33
|
l = []
|
@@ -61,17 +50,12 @@ class SystemSetting < ActiveRecord::Base
|
|
61
50
|
self.synchronized_at = Time.now
|
62
51
|
self.save
|
63
52
|
content = nil
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
# rescue
|
72
|
-
# logger.debug "#{$!}"
|
73
|
-
# content = "ERROR: Failed synchronization: #{$!}"
|
74
|
-
#end
|
53
|
+
l = self.synchronize
|
54
|
+
if l.size > 0
|
55
|
+
content = "Synchronized #{l.size} records with server-site!" #l.map {|e| e.to_json}.join(", ")
|
56
|
+
else
|
57
|
+
content = "WARNING: No objects received, possibly issue with connection (timeout?), or server site"
|
58
|
+
end
|
75
59
|
content
|
76
60
|
end
|
77
61
|
end
|
@@ -19,17 +19,15 @@ case RUBY_VERSION
|
|
19
19
|
end
|
20
20
|
|
21
21
|
module SixUpdaterWeb
|
22
|
-
VERSION = "0.13.
|
23
|
-
|
22
|
+
VERSION = "0.13.3"
|
24
23
|
COMPONENT = "six-updater-web"
|
25
|
-
SIX_PORT = 3000 unless defined?(SIX_PORT)
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
25
|
+
DEFAULT_IP = "127.0.0.1" unless defined?(DEFAULT_IP)
|
26
|
+
DEFAULT_PORT = 3000 unless defined?(DEFAULT_PORT)
|
27
|
+
|
28
|
+
SIX_HOST = DEFAULT_IP unless defined?(SIX_HOST)
|
29
|
+
SIX_PORT = DEFAULT_PORT unless defined?(SIX_PORT)
|
30
|
+
LOCAL_URL = "http://#{SIX_HOST == "0.0.0.0" ? DEFAULT_IP : SIX_HOST}:#{SIX_PORT}"
|
33
31
|
|
34
32
|
bpath = if ENV['BASE_PATH']
|
35
33
|
OLDLOCATION = "#{ENV['BASE_PATH']}" unless defined?(OLDLOCATION)
|
@@ -43,8 +41,6 @@ module SixUpdaterWeb
|
|
43
41
|
bpath.gsub!("\\", "/")
|
44
42
|
BASE_PATH = bpath
|
45
43
|
TOOL_PATH = File.join(BASE_PATH, 'tools')
|
46
|
-
RUBY_PATH = File.join(TOOL_PATH, "ruby", "bin")
|
47
|
-
#RUB_PATH = "tools/ruby/bin"
|
48
44
|
|
49
45
|
args = ARGV.join(" ")
|
50
46
|
if args[/RAILS_ENV=(\w)/]
|
@@ -57,17 +53,21 @@ module SixUpdaterWeb
|
|
57
53
|
end
|
58
54
|
end
|
59
55
|
|
60
|
-
#case RUBY_VERSION
|
61
|
-
# when /1\.8\.[0-9]/
|
62
|
-
# begin
|
63
|
-
# reg_path = Win32::Registry::HKEY_CURRENT_USER.open("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")['AppData']
|
64
|
-
# rescue
|
65
|
-
# end
|
66
|
-
#end
|
67
|
-
|
68
56
|
rpath, distro = nil, nil
|
69
57
|
case RUBY_PLATFORM
|
70
58
|
when /-mingw32$/, /-mswin32$/
|
59
|
+
ARMA2 = ['SOFTWARE\\Bohemia Interactive Studio\\ArmA 2', 'MAIN']
|
60
|
+
ARMA2_ALT = ['SOFTWARE\\Bohemia Interactive\\ArmA 2', 'InstallPath']
|
61
|
+
ARMA2_STEAM = ['SOFTWARE\\Valve\\Steam\\Common\\ARMA 2', 'InstallPath']
|
62
|
+
|
63
|
+
#case RUBY_VERSION
|
64
|
+
# when /1\.8\.[0-9]/
|
65
|
+
# begin
|
66
|
+
# reg_path = Win32::Registry::HKEY_CURRENT_USER.open("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")['AppData']
|
67
|
+
# rescue
|
68
|
+
# end
|
69
|
+
#end
|
70
|
+
|
71
71
|
TEMP_PATH = if ENV['TEMP']
|
72
72
|
if ENV['TEMP'].size > 0
|
73
73
|
File.directory?(ENV['TEMP']) ? "#{ENV['TEMP']}" : BASE_PATH
|
@@ -77,6 +77,8 @@ module SixUpdaterWeb
|
|
77
77
|
else
|
78
78
|
BASE_PATH
|
79
79
|
end
|
80
|
+
|
81
|
+
# Handles non us-ascii characters on Windows; appdata won't exist etc.
|
80
82
|
HOME_PATH = File.exists?(File.join(ENV['APPDATA'])) ? File.join(ENV['APPDATA']) : TEMP_PATH
|
81
83
|
|
82
84
|
CMD_EXE = File.join(ENV['WINDIR'], 'system32', 'cmd.exe')
|
@@ -106,7 +108,7 @@ module SixUpdaterWeb
|
|
106
108
|
end
|
107
109
|
end
|
108
110
|
else
|
109
|
-
HOME_PATH = "#{ENV['HOME']}"
|
111
|
+
HOME_PATH = "#{ENV['HOME']}" # Unfreezes the string
|
110
112
|
TEMP_PATH = '/tmp'
|
111
113
|
end
|
112
114
|
DATA_PATH = File.join(File.exists?(File.join(BASE_PATH, "legacy.txt")) ? BASE_PATH : HOME_PATH, "six-updater") # COMPONENT)
|
@@ -132,39 +134,43 @@ module SixUpdaterWeb
|
|
132
134
|
end
|
133
135
|
|
134
136
|
def after_initialize
|
135
|
-
if defined?(OLDLOCATION && SPECIAL)
|
137
|
+
if defined?(OLDLOCATION && SPECIAL)
|
138
|
+
# Update data in tables; nah, rather start warning users on main page?
|
139
|
+
#Six::Dbmanager.check
|
140
|
+
|
136
141
|
case RUBY_PLATFORM
|
137
142
|
when /-mingw32$/, /-mswin32$/
|
138
143
|
return unless defined?(OPEN_BROWSER)
|
139
|
-
|
140
|
-
file_to_use = "#{LOCAL_URL}
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
Thread.new() do
|
145
|
+
file_to_use = "#{LOCAL_URL}/main"
|
146
|
+
sleep 4
|
147
|
+
system "start #{file_to_use}"
|
148
|
+
end
|
149
|
+
=begin
|
150
|
+
arguments = nil
|
151
|
+
directory = nil
|
152
|
+
operation = "OPEN"
|
153
|
+
show = nil
|
154
|
+
#begin
|
147
155
|
shell = WIN32OLE.new('Shell.Application')
|
148
156
|
shell.ShellExecute(file_to_use, arguments, directory, operation, show)
|
149
|
-
rescue
|
157
|
+
#rescue
|
150
158
|
#logger.warn "Unable to open browser: #{$!}"
|
151
|
-
end
|
152
|
-
|
159
|
+
#end
|
160
|
+
=end
|
153
161
|
end
|
154
162
|
end
|
155
|
-
|
156
|
-
#Six::Dbmanager.check
|
157
163
|
end
|
158
164
|
|
159
|
-
def run_program(
|
165
|
+
def run_program(exec, startpath, cl)
|
160
166
|
logger.info "Current Path: #{Dir.pwd}, BASE_PATH: #{BASE_PATH}, DATA_PATH: #{DATA_PATH}"
|
161
|
-
logger.info "Starting with #{cl} from #{startpath}"
|
167
|
+
logger.info "Starting with #{cl} from #{startpath}"
|
162
168
|
# TODO: Cleanup
|
163
169
|
startpath = "#{startpath}"
|
164
170
|
begin
|
165
171
|
case RUBY_PLATFORM
|
166
172
|
when /-mingw32$/, /-mswin32$/
|
167
|
-
cl = "\"#{
|
173
|
+
cl = "\"#{exec}\" #{cl}"
|
168
174
|
cl = "/C #{cl}"
|
169
175
|
logger.info "#{CMD_EXE} #{cl}"
|
170
176
|
struct = Process.create(
|
@@ -179,10 +185,12 @@ module SixUpdaterWeb
|
|
179
185
|
)
|
180
186
|
else
|
181
187
|
Dir.chdir(startpath) do
|
182
|
-
# TODO: Properly create seperate process like on windows..
|
188
|
+
# TODO: Properly create seperate process like on windows.. or maybe this is fine just needs 2>&1 >> /dev/null ?
|
189
|
+
# as logfiles deliver the feedback, or rather run integrated. However maybe when running in a desktop environment
|
190
|
+
# maybe it's useful to open a new terminal window/tab ..
|
183
191
|
p = Process.fork do
|
184
|
-
logger.info "#{
|
185
|
-
system "#{
|
192
|
+
logger.info "#{exec} #{cl}"
|
193
|
+
system "#{exec} #{cl}"
|
186
194
|
end
|
187
195
|
end
|
188
196
|
#logger.info "Unimplemented on current platform: #{RUBY_PLATFORM}"
|
data/lib/init.rb
CHANGED
@@ -6,16 +6,19 @@ module SixUpdaterWeb
|
|
6
6
|
DEFAULT_IP = '127.0.0.1'
|
7
7
|
|
8
8
|
OPEN_BROWSER = true
|
9
|
-
|
9
|
+
|
10
|
+
SIX_HOST = if ARGV.to_s[/--bindi?n?g?=([0-9\.]*)/]
|
10
11
|
$1
|
11
12
|
else
|
12
|
-
ARGV << "--
|
13
|
-
|
13
|
+
ARGV << "--binding=#{DEFAULT_IP}"
|
14
|
+
DEFAULT_IP
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
SIX_PORT = if ARGV.to_s[/--port=([0-9]*)/]
|
18
|
+
$1
|
19
|
+
else
|
20
|
+
ARGV << "--port=#{DEFAULT_PORT}"
|
21
|
+
DEFAULT_PORT
|
19
22
|
end
|
20
23
|
|
21
24
|
module_function
|
@@ -54,6 +57,8 @@ Dir.chdir(File.dirname(__FILE__)) do
|
|
54
57
|
system "ruby -rubygems \"_rake.rb\" db:migrate" # goldberg:migrate
|
55
58
|
end
|
56
59
|
|
60
|
+
# Check for required data updates?
|
61
|
+
|
57
62
|
puts ""
|
58
63
|
puts "Starting the web client..."
|
59
64
|
|
data/lib/lib/tasks/sync.rake
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 13
|
8
|
-
-
|
9
|
-
version: 0.13.
|
8
|
+
- 3
|
9
|
+
version: 0.13.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Sickboy
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-29 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|