honcho 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/bin/honcho +9 -1
- data/lib/honcho/adapters/base.rb +10 -2
- data/lib/honcho/adapters/resque.rb +4 -6
- data/lib/honcho/adapters/sidekiq.rb +4 -6
- data/lib/honcho/app_status.rb +62 -0
- data/lib/honcho/colors.rb +39 -0
- data/lib/honcho/passenger_status.rb +27 -0
- data/lib/honcho/runner.rb +10 -29
- data/lib/honcho/ui_runner/table.rb +53 -0
- data/lib/honcho/ui_runner.rb +159 -0
- data/lib/honcho.rb +1 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a323fb5a455f095cb25c7678de5528b064163773
|
4
|
+
data.tar.gz: f1c5281e293b58abb6ffa79ce3a57f8ef7a8f574
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e89facaef2ec526648f8bcc49ce39d71d4b1c91fde373f60f5c081aaafa1f9cf26278828bbd429ffbd61f9ad022347747ac9deaaf0f35916530bbd5e39b7d19a
|
7
|
+
data.tar.gz: fa05afec855e45ee374c328d664651f6481bd4ae6287d8800a9cf1c324afb26a2a6ee17564d81daf33e4767f141344c15146a4bb5c7119ae381621559d553cb5
|
data/bin/honcho
CHANGED
@@ -13,6 +13,14 @@ OptionParser.new do |opts|
|
|
13
13
|
opts.on('-c', '--config path', 'specify config file path') do |path|
|
14
14
|
options[:config] = path
|
15
15
|
end
|
16
|
+
|
17
|
+
opts.on('--ui', 'enable top-like user interface') do
|
18
|
+
options[:ui] = true
|
19
|
+
end
|
16
20
|
end.parse!
|
17
21
|
|
18
|
-
|
22
|
+
if options[:ui]
|
23
|
+
Honcho::UIRunner.new(options).run
|
24
|
+
else
|
25
|
+
Honcho::Runner.new(options).run
|
26
|
+
end
|
data/lib/honcho/adapters/base.rb
CHANGED
@@ -11,6 +11,10 @@ module Honcho
|
|
11
11
|
|
12
12
|
attr_reader :config, :redis, :runner, :running, :stopping
|
13
13
|
|
14
|
+
def type
|
15
|
+
self.class.name.split(':').last.downcase
|
16
|
+
end
|
17
|
+
|
14
18
|
def check_for_work
|
15
19
|
if run?
|
16
20
|
start
|
@@ -86,6 +90,10 @@ module Honcho
|
|
86
90
|
@running = false
|
87
91
|
end
|
88
92
|
|
93
|
+
def total_count
|
94
|
+
queued_count + busy_count
|
95
|
+
end
|
96
|
+
|
89
97
|
private
|
90
98
|
|
91
99
|
def log(*args)
|
@@ -105,11 +113,11 @@ module Honcho
|
|
105
113
|
end
|
106
114
|
|
107
115
|
def work_to_do?
|
108
|
-
|
116
|
+
queued_count > 0
|
109
117
|
end
|
110
118
|
|
111
119
|
def work_being_done?
|
112
|
-
|
120
|
+
busy_count > 0
|
113
121
|
end
|
114
122
|
end
|
115
123
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
module Honcho
|
2
2
|
module Adapters
|
3
3
|
class Resque < Base
|
4
|
-
|
5
|
-
|
6
|
-
def work_to_do?
|
4
|
+
def queued_count
|
7
5
|
queues = redis.smembers("#{namespace}:queues")
|
8
6
|
counts = queues.map { |q| redis.llen("#{namespace}:queue:#{q}") }
|
9
|
-
counts.
|
7
|
+
counts.inject(&:+) || 0
|
10
8
|
end
|
11
9
|
|
12
|
-
def
|
10
|
+
def busy_count
|
13
11
|
# No way to tell via redis if work is being done in resque? Booo.
|
14
|
-
|
12
|
+
0
|
15
13
|
end
|
16
14
|
|
17
15
|
def namespace
|
@@ -1,20 +1,18 @@
|
|
1
1
|
module Honcho
|
2
2
|
module Adapters
|
3
3
|
class Sidekiq < Base
|
4
|
-
|
5
|
-
|
6
|
-
def work_to_do?
|
4
|
+
def queued_count
|
7
5
|
queues = redis.keys("#{namespace}:queue:*")
|
8
6
|
counts = queues.map { |q| redis.llen(q) }
|
9
|
-
counts.
|
7
|
+
counts.inject(&:+) || 0
|
10
8
|
end
|
11
9
|
|
12
|
-
def
|
10
|
+
def busy_count
|
13
11
|
processes = redis.smembers("#{namespace}:processes")
|
14
12
|
counts = processes.map do |process|
|
15
13
|
redis.hget("#{namespace}:#{process}", 'busy').to_i
|
16
14
|
end
|
17
|
-
counts.
|
15
|
+
counts.inject(&:+) || 0
|
18
16
|
end
|
19
17
|
|
20
18
|
def namespace
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Honcho
|
5
|
+
class AppStatus
|
6
|
+
def initialize(name, path:)
|
7
|
+
@name = name
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def data
|
12
|
+
return {} unless path_exists?
|
13
|
+
threads = [
|
14
|
+
Thread.new { @sha1 = fetch_sha1 },
|
15
|
+
Thread.new { @branch = fetch_branch },
|
16
|
+
Thread.new { @commits_ahead = fetch_commits_ahead }
|
17
|
+
]
|
18
|
+
threads.each(&:join)
|
19
|
+
{
|
20
|
+
sha1: @sha1,
|
21
|
+
branch: @branch,
|
22
|
+
commits_ahead: @commits_ahead
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def fetch_sha1
|
29
|
+
`cd #{@path} && git rev-parse HEAD 2>/dev/null`.strip
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch_branch
|
33
|
+
`cd #{@path} && git symbolic-ref --short HEAD 2>/dev/null`.strip
|
34
|
+
end
|
35
|
+
|
36
|
+
REMOTE_REF_STALE_TIME = 60 * 60 # 1 hour
|
37
|
+
FETCH_ORIGIN_TIMEOUT = 3
|
38
|
+
|
39
|
+
def fetch_commits_ahead
|
40
|
+
remote_branch = `cd #{@path} && git rev-parse --symbolic-full-name --abbrev-ref @{u} 2>/dev/null`.strip
|
41
|
+
remote_ref_path = File.join(@path, ".git/refs/remotes/#{remote_branch}")
|
42
|
+
return 0 unless File.exist?(remote_ref_path)
|
43
|
+
if File.stat(remote_ref_path).mtime < Time.now - REMOTE_REF_STALE_TIME
|
44
|
+
begin
|
45
|
+
status = Timeout.timeout(FETCH_ORIGIN_TIMEOUT) do
|
46
|
+
`cd #{@path} && git fetch origin && git status && touch #{remote_ref_path}`
|
47
|
+
end
|
48
|
+
rescue Timeout::Error
|
49
|
+
status = `cd #{@path} && git status`
|
50
|
+
end
|
51
|
+
else
|
52
|
+
status = `cd #{@path} && git status`
|
53
|
+
end
|
54
|
+
return 0 unless status =~ /Your branch is behind.*by (\d+) commits?/
|
55
|
+
$1.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
def path_exists?
|
59
|
+
File.exist?(@path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module Honcho
|
4
|
+
module Colors
|
5
|
+
COLORS = [
|
6
|
+
[Curses::COLOR_BLUE, Curses::A_NORMAL],
|
7
|
+
[Curses::COLOR_CYAN, Curses::A_NORMAL],
|
8
|
+
[Curses::COLOR_GREEN, Curses::A_NORMAL],
|
9
|
+
[Curses::COLOR_MAGENTA, Curses::A_NORMAL],
|
10
|
+
[Curses::COLOR_RED, Curses::A_NORMAL],
|
11
|
+
[Curses::COLOR_YELLOW, Curses::A_NORMAL],
|
12
|
+
[Curses::COLOR_BLUE, Curses::A_BOLD],
|
13
|
+
[Curses::COLOR_CYAN, Curses::A_BOLD],
|
14
|
+
[Curses::COLOR_GREEN, Curses::A_BOLD],
|
15
|
+
[Curses::COLOR_MAGENTA, Curses::A_BOLD],
|
16
|
+
[Curses::COLOR_RED, Curses::A_BOLD],
|
17
|
+
[Curses::COLOR_YELLOW, Curses::A_BOLD]
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
def assign_colors_for_curses
|
21
|
+
COLORS.each_with_index do |(color, _), index|
|
22
|
+
Curses.init_pair(index + 1, color, Curses::COLOR_BLACK)
|
23
|
+
end
|
24
|
+
apps.keys.each_with_index.each_with_object({}) do |(app, index), hash|
|
25
|
+
(_, quality) = COLORS[index]
|
26
|
+
hash[app] = [index + 1, quality]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def assign_colors_for_ansi
|
31
|
+
colors = COLORS.dup
|
32
|
+
apps.keys.each_with_object({}) do |app, hash|
|
33
|
+
(curses_color_code, curses_color_quality) = colors.shift
|
34
|
+
bold = curses_color_quality == Curses::A_BOLD ? 1 : 0
|
35
|
+
hash[app] = "#{bold};3#{curses_color_code}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Honcho
|
2
|
+
class PassengerStatus
|
3
|
+
def initialize
|
4
|
+
@raw = `passenger-status`
|
5
|
+
rescue
|
6
|
+
raise 'could not execute passenger-status'
|
7
|
+
end
|
8
|
+
|
9
|
+
def data
|
10
|
+
return [] if @raw =~ /not serving any applications/
|
11
|
+
apps = @raw.split(/\-+ Application groups \-+/).last
|
12
|
+
apps.split(/\n\n/).map do |raw_app|
|
13
|
+
next unless (root_match = raw_app.match(/App root: (.+)/))
|
14
|
+
data = {
|
15
|
+
'root' => root_match[1]
|
16
|
+
}
|
17
|
+
data['name'] = data['root'].split('/').last
|
18
|
+
data['workers'] = raw_app.scan(/\* PID.*\n.*/).map do |worker|
|
19
|
+
worker.scan(/([\w ]+?) *: ([\d%MGKhms]+)/).each_with_object({}) do |(key, val), hash|
|
20
|
+
hash[key.strip] = val.strip
|
21
|
+
end
|
22
|
+
end
|
23
|
+
data
|
24
|
+
end.compact.sort_by { |a| a['name'] }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/honcho/runner.rb
CHANGED
@@ -1,28 +1,15 @@
|
|
1
|
-
require 'curses'
|
2
1
|
require 'redis'
|
3
2
|
require 'time'
|
4
3
|
require 'stringio'
|
5
4
|
require 'yaml'
|
6
5
|
require_relative './adapters'
|
6
|
+
require_relative './colors'
|
7
7
|
|
8
8
|
Thread.abort_on_exception = true
|
9
9
|
|
10
10
|
module Honcho
|
11
11
|
class Runner
|
12
|
-
|
13
|
-
red: '0;31',
|
14
|
-
green: '0;32',
|
15
|
-
yellow: '0;33',
|
16
|
-
blue: '0;34',
|
17
|
-
magenta: '0;35',
|
18
|
-
cyan: '0;36',
|
19
|
-
bright_red: '1;31',
|
20
|
-
bright_green: '1;32',
|
21
|
-
bright_yellow: '1;33',
|
22
|
-
bright_blue: '1;34',
|
23
|
-
bright_magenta: '1;35',
|
24
|
-
bright_cyan: '1;36'
|
25
|
-
}.freeze
|
12
|
+
include Colors
|
26
13
|
|
27
14
|
def initialize(options)
|
28
15
|
@config_file_path = options[:config]
|
@@ -30,11 +17,12 @@ module Honcho
|
|
30
17
|
@running = {}
|
31
18
|
@stopping = {}
|
32
19
|
@redis = Redis.new
|
33
|
-
@
|
34
|
-
@
|
20
|
+
@adapters_by_app = build_adapters
|
21
|
+
@adapters = @adapters_by_app.values.flatten
|
22
|
+
@colors = assign_colors_for_ansi
|
35
23
|
end
|
36
24
|
|
37
|
-
attr_reader :config_file_path, :root_path, :adapters, :running, :stopping, :redis, :colors
|
25
|
+
attr_reader :config_file_path, :root_path, :adapters_by_app, :adapters, :running, :stopping, :redis, :colors
|
38
26
|
|
39
27
|
def run
|
40
28
|
trap(:INT) { term_all && exit }
|
@@ -93,19 +81,12 @@ module Honcho
|
|
93
81
|
@label_width ||= apps.keys.map(&:size).max
|
94
82
|
end
|
95
83
|
|
96
|
-
def assign_colors
|
97
|
-
color_values = COLORS.values
|
98
|
-
apps.keys.each_with_object({}) do |app, hash|
|
99
|
-
hash[app] = color_values.shift
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
84
|
def build_adapters
|
104
|
-
apps.
|
105
|
-
config.map do |type, worker_config|
|
85
|
+
apps.each_with_object({}) do |(app, config), hash|
|
86
|
+
hash[app] = config.map do |type, worker_config|
|
106
87
|
build_adapter(app, config, type, worker_config)
|
107
|
-
end
|
108
|
-
end
|
88
|
+
end.compact
|
89
|
+
end
|
109
90
|
end
|
110
91
|
|
111
92
|
def build_adapter(app, config, type, worker_config)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module Honcho
|
4
|
+
module UI
|
5
|
+
class Table
|
6
|
+
def initialize(headings:, width:, top:, left:)
|
7
|
+
@headings = headings
|
8
|
+
@width = width
|
9
|
+
@top = top
|
10
|
+
@left = left
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :width, :headings, :top, :left
|
14
|
+
attr_reader :columns
|
15
|
+
|
16
|
+
def draw(data)
|
17
|
+
draw_headings
|
18
|
+
draw_data(data)
|
19
|
+
Curses.refresh
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def draw_headings
|
25
|
+
@columns = []
|
26
|
+
column = left
|
27
|
+
Curses.setpos(top, left)
|
28
|
+
headings.each_with_index do |heading, index|
|
29
|
+
@columns[index] = column
|
30
|
+
column += heading.size
|
31
|
+
Curses.addstr(heading)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def draw_data(data)
|
36
|
+
data.each_with_index do |row, row_index|
|
37
|
+
row.each_with_index do |(cell, color_index, color_quality), cell_index|
|
38
|
+
cell_start = columns[cell_index]
|
39
|
+
cell_width = (columns[cell_index + 1] || width) - cell_start
|
40
|
+
Curses.setpos(top + row_index + 1, cell_start)
|
41
|
+
if color_index && color_quality
|
42
|
+
Curses.attron(Curses.color_pair(color_index) | color_quality) do
|
43
|
+
Curses.addstr(cell.to_s.ljust(cell_width))
|
44
|
+
end
|
45
|
+
else
|
46
|
+
Curses.addstr(cell.to_s.ljust(cell_width))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'curses'
|
3
|
+
require 'sys-uptime'
|
4
|
+
require_relative './passenger_status'
|
5
|
+
require_relative './ui_runner/table'
|
6
|
+
|
7
|
+
module Honcho
|
8
|
+
class UIRunner < Runner
|
9
|
+
def run
|
10
|
+
setup_curses
|
11
|
+
@colors = assign_colors_for_curses
|
12
|
+
init_log_window
|
13
|
+
draw
|
14
|
+
super
|
15
|
+
ensure
|
16
|
+
unsetup_curses
|
17
|
+
end
|
18
|
+
|
19
|
+
def log(name, message)
|
20
|
+
(color_index, color_quality) = colors[name]
|
21
|
+
@log.attron(Curses.color_pair(color_index) | color_quality) do
|
22
|
+
@log.addstr(name.rjust(label_width))
|
23
|
+
end
|
24
|
+
@log.addstr(': ')
|
25
|
+
@log.addstr(message)
|
26
|
+
@log.refresh
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def init_log_window
|
32
|
+
top = adapters.size + 3
|
33
|
+
@log = Curses::Window.new(lines - top, cols, top, 0)
|
34
|
+
@log.scrollok(true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw
|
38
|
+
draw_queues
|
39
|
+
draw_uptime
|
40
|
+
end
|
41
|
+
|
42
|
+
def draw_queues
|
43
|
+
adapter_names = adapters.map(&:name)
|
44
|
+
max_name_width = adapter_names.map(&:size).max
|
45
|
+
bar_width = cols - max_name_width - 80
|
46
|
+
table = UI::Table.new(
|
47
|
+
headings: [
|
48
|
+
'app'.ljust(max_name_width + 2),
|
49
|
+
'webs ',
|
50
|
+
"req's ",
|
51
|
+
'sidekiq ',
|
52
|
+
'resque ',
|
53
|
+
' ',
|
54
|
+
'work queue'.ljust(bar_width),
|
55
|
+
' '
|
56
|
+
],
|
57
|
+
width: cols - 100,
|
58
|
+
top: 1,
|
59
|
+
left: 2
|
60
|
+
)
|
61
|
+
pstatus = passenger_status
|
62
|
+
data = adapters_by_app.map do |app, adapters|
|
63
|
+
@pstatus_for_app = pstatus[app]
|
64
|
+
count = adapters.map(&:total_count).inject(&:+)
|
65
|
+
sidekiq = adapters.detect { |a| a.type == 'sidekiq' }
|
66
|
+
resque = adapters.detect { |a| a.type == 'resque' }
|
67
|
+
[
|
68
|
+
[app, *colors[app]],
|
69
|
+
[count_web_servers],
|
70
|
+
[count_web_requests],
|
71
|
+
[sidekiq && sidekiq.running? ? 'running' : ''],
|
72
|
+
[resque && resque.running? ? 'running' : ''],
|
73
|
+
['['],
|
74
|
+
[bar(count, bar_width), 2, 0],
|
75
|
+
[']']
|
76
|
+
]
|
77
|
+
end
|
78
|
+
table.draw(data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def count_web_servers
|
82
|
+
return unless @pstatus_for_app
|
83
|
+
return unless (busy_webs = @pstatus_for_app['workers'].select { |s| s['Uptime'] }).any?
|
84
|
+
busy_webs.size
|
85
|
+
end
|
86
|
+
|
87
|
+
def count_web_requests
|
88
|
+
return unless @pstatus_for_app
|
89
|
+
@pstatus_for_app['workers'].map { |s| s['Processed'].to_i }.inject(&:+)
|
90
|
+
end
|
91
|
+
|
92
|
+
def draw_uptime
|
93
|
+
x = cols - 38
|
94
|
+
uptime = `uptime`
|
95
|
+
loadavg = uptime.match(/load averages: (.*)/)[1]
|
96
|
+
Curses.setpos(2, x)
|
97
|
+
Curses.attron(Curses.color_pair(2)) do
|
98
|
+
Curses.addstr('Load average: ')
|
99
|
+
end
|
100
|
+
Curses.addstr(loadavg)
|
101
|
+
time = uptime.match(/up (.*), \d+ users/)[1]
|
102
|
+
Curses.setpos(3, x)
|
103
|
+
Curses.attron(Curses.color_pair(2)) do
|
104
|
+
Curses.addstr('Uptime: ')
|
105
|
+
end
|
106
|
+
Curses.addstr(time)
|
107
|
+
end
|
108
|
+
|
109
|
+
def print(text, y = nil, x = nil)
|
110
|
+
if y && x
|
111
|
+
$stdout.print("\033[#{y};#{x}f#{text}")
|
112
|
+
else
|
113
|
+
$stdout.print(text)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def bar(count, width)
|
118
|
+
('|' * count).ljust(width)[0...width]
|
119
|
+
end
|
120
|
+
|
121
|
+
def check_for_work
|
122
|
+
draw
|
123
|
+
super
|
124
|
+
end
|
125
|
+
|
126
|
+
def lines
|
127
|
+
Curses.lines
|
128
|
+
end
|
129
|
+
|
130
|
+
def cols
|
131
|
+
Curses.cols
|
132
|
+
end
|
133
|
+
|
134
|
+
def setup_curses
|
135
|
+
Curses.init_screen
|
136
|
+
Curses.start_color
|
137
|
+
Curses.cbreak
|
138
|
+
Curses.noecho
|
139
|
+
Curses.curs_set(0)
|
140
|
+
Curses.stdscr.keypad(true)
|
141
|
+
end
|
142
|
+
|
143
|
+
def unsetup_curses
|
144
|
+
Curses.close_screen
|
145
|
+
end
|
146
|
+
|
147
|
+
def show_passenger?
|
148
|
+
return @show_passenger unless @show_passenger.nil?
|
149
|
+
@show_passenger = system('which passenger-status &>/dev/null')
|
150
|
+
end
|
151
|
+
|
152
|
+
def passenger_status
|
153
|
+
return {} unless show_passenger?
|
154
|
+
PassengerStatus.new.data.each_with_object({}) do |app, hash|
|
155
|
+
hash[app['name']] = app
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/lib/honcho.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: honcho
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Morgn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -52,7 +52,12 @@ files:
|
|
52
52
|
- lib/honcho/adapters/base.rb
|
53
53
|
- lib/honcho/adapters/resque.rb
|
54
54
|
- lib/honcho/adapters/sidekiq.rb
|
55
|
+
- lib/honcho/app_status.rb
|
56
|
+
- lib/honcho/colors.rb
|
57
|
+
- lib/honcho/passenger_status.rb
|
55
58
|
- lib/honcho/runner.rb
|
59
|
+
- lib/honcho/ui_runner.rb
|
60
|
+
- lib/honcho/ui_runner/table.rb
|
56
61
|
homepage: https://github.com/seven1m/honcho
|
57
62
|
licenses:
|
58
63
|
- MIT
|