stable-cli-rails 0.6.8 → 0.7.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55e25d45106e712c1d3e9d23754150d9519e34b38d4464652c4c0a21539a0597
4
- data.tar.gz: a2bb082ffeb11bf916903f7a15cc0da89a5136c33d53b3139038d7d294591f2d
3
+ metadata.gz: 8b8d5b304fbf560e181a416e2087e912c4fe2a26cd358341a263d1b890540069
4
+ data.tar.gz: 6e98c5d24b16f5669f442fab9c313a3ee74c790551a776e40e56773ed901e9d3
5
5
  SHA512:
6
- metadata.gz: 954bb6bd50281fe5a47ed03e2cead5a2c614f7786d17cdc4831c50ab798e0a6fac59dc1b98b76f3d35524be574396813444bbf3d4f60099e7e81b44846658ef4
7
- data.tar.gz: 275d760d16ce88fb018f3aad059330fd05c63911aa19352b67780bd8d8680d64918583ece7e48e2eb452329c468fff3eaac5a7fdee92341425ff792421646c0b
6
+ metadata.gz: 41399496362c590019b87351d9e69364b41e6a3ecf0f10a7906a1c3edbaa508557104bd33cfb855623ee0b1e75e690b4d7834a42bd90025bfc5f23bc28dd2bf3
7
+ data.tar.gz: e4779489d26bd0b472a3a6f545039296bb7481aa87df1dbfa0748acd440f3e78b79f091b318956d6bc92aae8e7179f1f66393f9d800fff5b015a47bb3c2edc45
data/bin/stable CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  #!/usr/bin/env ruby
3
2
  require "stable"
4
3
  Stable::CLI.start(ARGV)
data/lib/stable/cli.rb CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'thor'
4
4
  require 'etc'
5
+ require 'tempfile'
5
6
  require 'fileutils'
7
+ require 'io/console'
6
8
  require_relative 'scanner'
7
9
  require_relative 'registry'
8
10
 
@@ -13,7 +15,7 @@ module Stable
13
15
  def initialize(*)
14
16
  super
15
17
  Stable::Bootstrap.run!
16
- ensure_dependencies!
18
+ Services::SetupRunner.ensure_dependencies!
17
19
  dedupe_registry!
18
20
  end
19
21
 
@@ -26,97 +28,16 @@ module Stable
26
28
  method_option :rails, type: :string, desc: 'Rails version to install (optional)'
27
29
  method_option :port, type: :numeric, desc: 'Port to run Rails app on'
28
30
  method_option :skip_ssl, type: :boolean, default: false, desc: 'Skip HTTPS setup'
31
+ method_option :db, type: :string, desc: 'Database name to create and integrate'
32
+ method_option :postgres, type: :boolean, default: false, desc: 'Use Postgres for the database'
33
+ method_option :mysql, type: :boolean, default: false, desc: 'Use MySQL for the database'
29
34
  def new(name, ruby: RUBY_VERSION, rails: nil, port: nil)
30
- port ||= next_free_port
31
- app_path = File.expand_path(name)
32
-
33
- # --- Add app to registry ---
34
- domain = "#{name}.test"
35
- apps = Registry.apps
36
-
37
- app = {
38
- name: name,
39
- path: app_path,
40
- domain: domain,
41
- port: port,
42
- ruby: ruby,
43
- started_at: nil
44
- }
45
-
46
- apps.reject! { |a| a[:name] == name }
47
- apps << app
48
-
49
- abort "Folder already exists: #{app_path}" if File.exist?(app_path)
50
-
51
- # --- Ensure RVM and Ruby ---
52
- ensure_rvm!
53
- puts "Using Ruby #{ruby} with RVM gemset #{name}..."
54
- system("bash -lc 'rvm #{ruby}@#{name} --create do true'") or abort("Failed to create RVM gemset #{name}")
55
- ruby_cmd = rvm_exec(app, ruby)
56
- # --- Install Rails in gemset if needed ---
57
- rails_version = rails || 'latest'
58
- rails_check = system("bash -lc '#{ruby_cmd} gem list -i rails#{rails ? " -v #{rails}" : ''}'")
59
- unless rails_check
60
- puts "Installing Rails #{rails_version} in gemset..."
61
- system("bash -lc '#{ruby_cmd} gem install rails #{rails ? "-v #{rails}" : ''}'") or abort('Failed to install Rails')
62
- end
63
-
64
- # --- Create Rails app ---
65
- puts "Creating Rails app #{name} (Ruby #{ruby})..."
66
- system("bash -lc '#{ruby_cmd} rails new #{app_path}'") or abort('Rails app creation failed')
67
-
68
- # --- Add .ruby-version and .ruby-gemset ---
69
- Dir.chdir(app_path) do
70
- File.write('.ruby-version', "#{ruby}\n")
71
- File.write('.ruby-gemset', "#{name}\n")
72
-
73
- # --- Install gems inside gemset ---
74
- puts 'Running bundle install...'
75
- system("bash -lc '#{ruby_cmd} bundle install --jobs=4 --retry=3'") or abort('bundle install failed')
76
- end
77
-
78
- # --- Host entry & certificate ---
79
- add_host_entry(domain)
80
- generate_cert(domain) unless options[:skip_ssl]
81
- update_caddyfile(domain, port)
82
- ensure_caddy_running!
83
- caddy_reload
84
-
85
- # --- Start Rails server ---
86
- puts "Starting Rails server for #{name} on port #{port}..."
87
- log_file = File.join(app_path, 'log', 'stable.log')
88
- FileUtils.mkdir_p(File.dirname(log_file))
89
-
90
- abort "Port #{port} is already in use. Choose another port." if app_running?({ port: port })
91
-
92
- pid = spawn(
93
- 'bash',
94
- '-lc',
95
- "cd #{app_path} && #{ruby_cmd} bundle exec rails s -p #{port} -b 127.0.0.1",
96
- out: log_file,
97
- err: log_file
98
- )
99
- Process.detach(pid)
100
-
101
- app[:started_at] = Time.now.to_i
102
- Registry.save(apps)
103
-
104
- sleep 1.5
105
-
106
- wait_for_port(port)
107
- puts "✔ #{name} running at https://#{domain}"
35
+ Commands::New.new(name, options).call
108
36
  end
109
37
 
110
38
  desc 'list', 'List detected apps'
111
39
  def list
112
- apps = Registry.apps
113
- if apps.empty?
114
- puts 'No apps found.'
115
- else
116
- apps.each do |app|
117
- puts "#{app[:name]} -> https://#{app[:domain]}"
118
- end
119
- end
40
+ Commands::List.new.call
120
41
  end
121
42
 
122
43
  desc 'add FOLDER', 'Add a Rails app folder'
@@ -129,175 +50,90 @@ module Stable
129
50
 
130
51
  puts "Detected gemset: #{File.read('.ruby-gemset').strip}" if File.exist?('.ruby-gemset')
131
52
 
132
- apps = Registry.apps
133
53
  name = File.basename(folder)
134
54
  domain = "#{name}.test"
135
55
 
136
- if apps.any? { |a| a[:path] == folder }
56
+ if Services::AppRegistry.all.any? { |a| a[:path] == folder }
137
57
  puts "App already exists: #{name}"
138
58
  return
139
59
  end
140
60
 
141
61
  port = next_free_port
142
- ruby = detect_ruby_version(folder)
62
+ ruby = Stable::Services::Ruby.detect_ruby_version(folder)
143
63
 
144
- apps << { name: name, path: folder, domain: domain, port: port, ruby: ruby }
145
- Registry.save(apps)
64
+ app = { name: name, path: folder, domain: domain, port: port, ruby: ruby }
65
+ Services::AppRegistry.add_app(app)
146
66
  puts "Added #{name} -> https://#{domain} (port #{port})"
147
67
 
148
- add_host_entry(domain)
149
- generate_cert(domain)
150
- update_caddyfile(domain, port)
151
- caddy_reload
68
+ Services::HostsManager.add(domain)
69
+ Services::CaddyManager.add_app(name, skip_ssl: options[:skip_ssl])
70
+ Services::CaddyManager.reload
152
71
  end
153
72
 
154
73
  desc 'remove NAME', 'Remove an app by name'
155
74
  def remove(name)
156
- apps = Registry.apps
157
- app = apps.find { |a| a[:name] == name }
158
- if app.nil?
159
- puts "No app found with name #{name}"
160
- return
161
- end
162
-
163
- new_apps = apps.reject { |a| a[:name] == name }
164
- Registry.save(new_apps)
165
- puts "Removed #{name}"
166
-
167
- remove_host_entry(app[:domain])
168
- remove_caddy_entry(app[:domain])
169
- caddy_reload
75
+ Commands::Remove.new(name).call
170
76
  end
171
77
 
172
78
  desc 'start NAME', 'Start a Rails app with its correct Ruby version'
173
79
  def start(name)
174
- app = Registry.apps.find { |a| a[:name] == name }
175
- return puts("No app found with name #{name}") unless app
176
-
177
- port = app[:port] || next_free_port
178
- ruby = app[:ruby]
179
- path = app[:path]
180
-
181
- if app_running?(app)
182
- puts "#{name} is already running on https://#{app[:domain]} (port #{port})"
183
- return
184
- end
185
-
186
- gemset = gemset_for(app)
187
-
188
- rvm_cmd =
189
- if ruby && gemset
190
- system("bash -lc 'rvm #{ruby}@#{gemset} --create do true'")
191
- "rvm #{ruby}@#{gemset} do"
192
- elsif ruby
193
- "rvm #{ruby} do"
194
- end
195
-
196
- puts "Starting #{name} on port #{port}..."
197
-
198
- log_file = File.join(path, 'log', 'stable.log')
199
- FileUtils.mkdir_p(File.dirname(log_file))
200
-
201
- pid = spawn(
202
- 'bash',
203
- '-lc',
204
- <<~CMD,
205
- cd "#{path}"
206
- #{rvm_cmd} bundle exec rails s \
207
- -p #{port} \
208
- -b 127.0.0.1
209
- CMD
210
- out: log_file,
211
- err: log_file
212
- )
213
-
214
- Process.detach(pid)
215
-
216
- wait_for_port(port, timeout: 30)
217
-
218
- app[:started_at] = Time.now.to_i
219
- Registry.save(Registry.apps)
220
-
221
- generate_cert(app[:domain])
222
- update_caddyfile(app[:domain], port)
223
- caddy_reload
80
+ Commands::Start.new(name).call
81
+ end
224
82
 
225
- puts "#{name} started on https://#{app[:domain]}"
83
+ desc 'restart NAME', 'Restart a Rails app'
84
+ def restart(name)
85
+ Commands::Restart.new(name).call
226
86
  end
227
87
 
228
88
  desc 'stop NAME', 'Stop a Rails app (default port 3000)'
229
89
  def stop(name)
230
- app = Registry.apps.find { |a| a[:name] == name }
231
-
232
- output = `lsof -i tcp:#{app[:port]} -t`.strip
233
- if output.empty?
234
- puts "No app running on port #{app[:port]}"
235
- else
236
- output.split("\n").each { |pid| Process.kill('TERM', pid.to_i) }
237
- puts "Stopped #{name}"
238
- end
90
+ Commands::Stop.new(name).call
239
91
  end
240
92
 
241
93
  desc 'setup', 'Sets up Caddy and local trusted certificates'
242
94
  def setup
243
- FileUtils.mkdir_p(Stable::Paths.root)
244
- File.write(Stable::Paths.caddyfile, '') unless File.exist?(Stable::Paths.caddyfile)
245
- ensure_caddy_running!
246
- puts "Caddy home initialized at #{Stable::Paths.root}"
95
+ Commands::Setup.new.call
247
96
  end
248
97
 
249
98
  desc 'caddy reload', 'Reloads Caddy after adding/removing apps'
250
99
  def caddy_reload
251
- if system('which caddy > /dev/null')
252
- system("caddy reload --config #{Stable::Paths.caddyfile}")
253
- puts 'Caddy reloaded'
254
- else
255
- puts 'Caddy not found. Install Caddy first.'
256
- end
100
+ Services::CaddyManager.reload
101
+ puts 'Caddy reloaded'
257
102
  end
258
103
 
259
104
  desc 'secure DOMAIN', 'Generate trusted local HTTPS cert for a specific folder/domain'
260
105
  def secure(domain)
261
- app = Registry.apps.find { |a| a[:domain] == domain }
106
+ apps = Services::AppRegistry.all
107
+ app = apps.find { |a| a[:domain] == domain }
108
+ app ||= apps.find { |a| a[:name] == domain }
109
+ app ||= apps.find { |a| a[:domain] == "#{domain}.test" }
110
+
262
111
  unless app
263
112
  puts "No app found with domain #{domain}"
264
113
  return
265
114
  end
266
- secure_app(domain, app[:path], app[:port])
267
- caddy_reload
268
- puts "Secured https://#{domain}"
115
+
116
+ Services::CaddyManager.add_app(app[:name], skip_ssl: true)
117
+ Services::CaddyManager.reload
118
+ puts "Secured https://#{app[:domain]}"
269
119
  end
270
120
 
271
121
  desc 'doctor', 'Check Stable system health'
272
122
  def doctor
273
- puts "Stable doctor\n\n"
274
-
275
- puts "Ruby version: #{RUBY_VERSION}"
276
- puts "RVM: #{rvm_available? ? 'yes' : 'no'}"
277
- puts "rbenv: #{rbenv_available? ? 'yes' : 'no'}"
278
- puts "Caddy: #{system('which caddy > /dev/null') ? 'yes' : 'no'}"
279
- puts "mkcert: #{system('which mkcert > /dev/null') ? 'yes' : 'no'}"
280
-
281
- Registry.apps.each do |app|
282
- state = boot_state(app)
283
- ruby = app[:ruby] || 'default'
284
- port = app[:port]
285
-
286
- puts "#{app[:name]} → Ruby #{ruby} | port #{port} | #{state}"
287
- end
123
+ Commands::Doctor.new.call
288
124
  end
289
125
 
290
126
  desc 'upgrade-ruby NAME VERSION', 'Upgrade Ruby for an app'
291
127
  def upgrade_ruby(name, version)
292
- app = Registry.apps.find { |a| a[:name] == name }
128
+ app = Services::AppRegistry.find(name)
293
129
  unless app
294
130
  puts "No app named #{name}"
295
131
  return
296
132
  end
297
133
 
298
- if rvm_available?
134
+ if Stable::Services::Ruby.rvm_available?
299
135
  system("bash -lc 'rvm install #{version}'")
300
- elsif rbenv_available?
136
+ elsif Stable::Services::Ruby.rbenv_available?
301
137
  system("rbenv install #{version}")
302
138
  else
303
139
  puts 'No Ruby version manager found'
@@ -305,142 +141,15 @@ module Stable
305
141
  end
306
142
 
307
143
  File.write(File.join(app[:path], '.ruby-version'), version)
308
- app[:ruby] = version
309
- Registry.save(Registry.apps)
144
+ Services::AppRegistry.update(name, ruby: version)
310
145
 
311
146
  puts "#{name} now uses Ruby #{version}"
312
147
  end
313
148
 
314
149
  private
315
150
 
316
- def add_host_entry(domain)
317
- entry = "127.0.0.1\t#{domain}"
318
- hosts = File.read(HOSTS_FILE)
319
- unless hosts.include?(domain)
320
- puts "Adding #{domain} to #{HOSTS_FILE}..."
321
- File.open(HOSTS_FILE, 'a') { |f| f.puts entry }
322
- system('dscacheutil -flushcache; sudo killall -HUP mDNSResponder')
323
- end
324
- rescue Errno::EACCES
325
- ensure_hosts_entry(domain)
326
- end
327
-
328
- def remove_host_entry(domain)
329
- hosts = File.read(HOSTS_FILE)
330
- new_hosts = hosts.lines.reject { |line| line.include?(domain) }.join
331
- File.write(HOSTS_FILE, new_hosts)
332
- system('dscacheutil -flushcache; sudo killall -HUP mDNSResponder')
333
- rescue Errno::EACCES
334
- puts "Permission denied updating #{HOSTS_FILE}. Run 'sudo stable remove #{domain}' to remove hosts entry."
335
- end
336
-
337
- def ensure_hosts_entry(domain)
338
- entry = "127.0.0.1\t#{domain}"
339
-
340
- hosts = File.read(HOSTS_FILE)
341
- return if hosts.include?(domain)
342
-
343
- if Process.uid.zero?
344
- File.open(HOSTS_FILE, 'a') { |f| f.puts entry }
345
- else
346
- system(%(echo "#{entry}" | sudo tee -a #{HOSTS_FILE} > /dev/null))
347
- end
348
-
349
- system('dscacheutil -flushcache; sudo killall -HUP mDNSResponder')
350
- end
351
-
352
- def secure_app(domain, _folder, port)
353
- ensure_certs_dir!
354
-
355
- cert_path = File.join(Stable::Paths.certs_dir, "#{domain}.pem")
356
- key_path = File.join(Stable::Paths.certs_dir, "#{domain}-key.pem")
357
-
358
- # Generate certificates if missing
359
- if system('which mkcert > /dev/null')
360
- unless File.exist?(cert_path) && File.exist?(key_path)
361
- system("mkcert -cert-file #{cert_path} -key-file #{key_path} #{domain}")
362
- end
363
- else
364
- puts 'mkcert not found. Please install mkcert.'
365
- return
366
- end
367
-
368
- # Auto-add Caddy block if not already in Caddyfile
369
- add_caddy_block(domain, cert_path, key_path, port)
370
- caddy_reload
371
- end
372
-
373
- def add_caddy_block(domain, cert, key, port)
374
- caddyfile = Stable::Paths.caddyfile
375
- FileUtils.touch(caddyfile) unless File.exist?(caddyfile)
376
- content = File.read(caddyfile)
377
-
378
- return if content.include?(domain) # don't duplicate
379
-
380
- block = <<~CADDY
381
-
382
- https://#{domain} {
383
- reverse_proxy 127.0.0.1:#{port}
384
- tls #{cert} #{key}
385
- }
386
- CADDY
387
-
388
- File.write(caddyfile, content + block)
389
- system("caddy fmt --overwrite #{caddyfile}")
390
- end
391
-
392
- # Remove Caddyfile entry for the domain
393
- def remove_caddy_entry(domain)
394
- return unless File.exist?(Stable::Paths.caddyfile)
395
-
396
- content = File.read(Stable::Paths.caddyfile)
397
- # Remove block starting with https://<domain> { ... }
398
- regex = %r{
399
- https://#{Regexp.escape(domain)}\s*\{
400
- .*?
401
- \}
402
- }mx
403
-
404
- new_content = content.gsub(regex, '')
405
-
406
- File.write(Stable::Paths.caddyfile, new_content)
407
- end
408
-
409
- def ensure_dependencies!
410
- unless system('which brew > /dev/null')
411
- puts 'Homebrew is required. Install it first: https://brew.sh'
412
- exit 1
413
- end
414
-
415
- unless system('which caddy > /dev/null')
416
- puts 'Installing Caddy...'
417
- system('brew install caddy')
418
- end
419
-
420
- return if system('which mkcert > /dev/null')
421
-
422
- puts 'Installing mkcert...'
423
- system('brew install mkcert nss')
424
- system('mkcert -install')
425
- end
426
-
427
- def ensure_caddy_running!
428
- api_port = 2019
429
-
430
- # Check if Caddy API is reachable
431
- require 'socket'
432
- begin
433
- TCPSocket.new('127.0.0.1', api_port).close
434
- puts 'Caddy already running.'
435
- rescue Errno::ECONNREFUSED
436
- puts 'Starting Caddy in background...'
437
- system("caddy run --config #{Stable::Paths.caddyfile} --adapter caddyfile --watch --resume &")
438
- sleep 3
439
- end
440
- end
441
-
442
151
  def next_free_port
443
- used_ports = Registry.apps.map { |a| a[:port] }
152
+ used_ports = Services::AppRegistry.all.map { |a| a[:port] }
444
153
  port = 3000
445
154
  port += 1 while used_ports.include?(port) || port_in_use?(port)
446
155
  port
@@ -509,7 +218,7 @@ module Stable
509
218
  end
510
219
  end
511
220
 
512
- def wait_for_port(port, timeout: 10)
221
+ def wait_for_port(port, timeout: 20)
513
222
  require 'socket'
514
223
  start = Time.now
515
224
 
@@ -523,62 +232,7 @@ module Stable
523
232
  end
524
233
  end
525
234
 
526
- def ensure_rvm!
527
- return if system('which rvm > /dev/null')
528
-
529
- puts 'RVM not found. Installing RVM...'
530
-
531
- install_cmd = <<~CMD
532
- curl -sSL https://get.rvm.io | bash -s stable
533
- CMD
534
-
535
- abort 'RVM installation failed' unless system(install_cmd)
536
-
537
- # Load RVM into current process
538
- rvm_script = File.expand_path('~/.rvm/scripts/rvm')
539
- abort 'RVM installed but could not be loaded' unless File.exist?(rvm_script)
540
-
541
- ENV['PATH'] = "#{File.expand_path('~/.rvm/bin')}:#{ENV['PATH']}"
542
-
543
- system(%(bash -lc "source #{rvm_script} && rvm --version")) ||
544
- abort('RVM installed but not functional')
545
- end
546
-
547
- def ensure_ruby_installed!(version)
548
- return if system("rvm list strings | grep ruby-#{version} > /dev/null")
549
-
550
- puts "Installing Ruby #{version}..."
551
- system("rvm install #{version}") || abort("Failed to install Ruby #{version}")
552
- end
553
-
554
- def detect_ruby_version(path)
555
- rv = File.join(path, '.ruby-version')
556
- return File.read(rv).strip if File.exist?(rv)
557
-
558
- gemfile = File.join(path, 'Gemfile')
559
- if File.exist?(gemfile)
560
- ruby_line = File.read(gemfile)[/^ruby ['"](.+?)['"]/, 1]
561
- return ruby_line if ruby_line
562
- end
563
-
564
- nil
565
- end
566
-
567
- def rvm_available?
568
- system("bash -lc 'command -v rvm > /dev/null'")
569
- end
570
-
571
- def rbenv_available?
572
- system('command -v rbenv > /dev/null')
573
- end
574
-
575
- def ensure_rvm_ruby!(version)
576
- system("bash -lc 'rvm list strings | grep -q #{version} || rvm install #{version}'")
577
- end
578
-
579
- def ensure_rbenv_ruby!(version)
580
- system("rbenv versions | grep -q #{version} || rbenv install #{version}")
581
- end
235
+ # RVM/ruby helpers moved to Services::Ruby
582
236
 
583
237
  def app_running?(app)
584
238
  return false unless app && app[:port]
@@ -598,26 +252,15 @@ module Stable
598
252
  end
599
253
 
600
254
  def dedupe_registry!
601
- apps = Registry.apps
602
- apps.uniq! { |a| a[:name] }
603
- Registry.save(apps)
255
+ Services::AppRegistry.dedupe
604
256
  end
605
257
 
606
258
  def gemset_for(app)
607
- gemset_file = File.join(app[:path], '.ruby-gemset')
608
- return File.read(gemset_file).strip if File.exist?(gemset_file)
609
-
610
- nil
259
+ Stable::Services::Ruby.gemset_for(app)
611
260
  end
612
261
 
613
262
  def rvm_exec(app, ruby)
614
- gemset = gemset_for(app)
615
-
616
- if gemset
617
- "rvm #{ruby}@#{gemset} do"
618
- else
619
- "rvm #{ruby} do"
620
- end
263
+ Stable::Services::Ruby.rvm_exec(app, ruby)
621
264
  end
622
265
  end
623
266
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stable
4
+ module Commands
5
+ class Doctor
6
+ def call
7
+ puts 'Running Stable health checks...'
8
+ puts
9
+
10
+ checks = Services::DependencyChecker.new.run
11
+
12
+ checks.each do |check|
13
+ print_check(check)
14
+ end
15
+
16
+ puts
17
+ summary(checks)
18
+ end
19
+
20
+ private
21
+
22
+ def print_check(check)
23
+ icon = check[:ok] ? '✔' : '✖'
24
+ puts "#{icon} #{check[:name]}"
25
+ puts " #{check[:message]}" unless check[:ok]
26
+ end
27
+
28
+ def summary(checks)
29
+ failures = checks.count { |c| !c[:ok] }
30
+
31
+ if failures.zero?
32
+ puts 'All checks passed.'
33
+ else
34
+ puts "#{failures} issue(s) detected. Fix the above and re-run `stable doctor`."
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stable
4
+ module Commands
5
+ class List
6
+ def call
7
+ apps = Services::AppRegistry.all
8
+
9
+ if apps.empty?
10
+ puts 'No apps registered.'
11
+ return
12
+ end
13
+
14
+ print_header
15
+
16
+ apps.each do |app|
17
+ puts format_row(app)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def print_header
24
+ puts format(
25
+ '%-18s %-26s %-8s %-10s %-10s',
26
+ 'APP',
27
+ 'DOMAIN',
28
+ 'PORT',
29
+ 'RUBY',
30
+ 'STATUS'
31
+ )
32
+ puts '-' * 78
33
+ end
34
+
35
+ def format_row(app)
36
+ status =
37
+ if app[:started_at]
38
+ 'running'
39
+ else
40
+ 'stopped'
41
+ end
42
+
43
+ format(
44
+ '%-18s %-26s %-8s %-10s %-10s',
45
+ app[:name],
46
+ app[:domain],
47
+ app[:port],
48
+ app[:ruby],
49
+ status
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stable
4
+ module Commands
5
+ class New
6
+ def initialize(name, options)
7
+ @name = name
8
+ @options = options
9
+ end
10
+
11
+ def call
12
+ Services::AppCreator.new(@name, @options).call
13
+ end
14
+ end
15
+ end
16
+ end