stable-cli-rails 0.8.0 → 0.8.1

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: ab8c647151029030baf0e96737f45f20cc6b067b952b032b98eac46e5e14e158
4
- data.tar.gz: 5db558cabc869e1f6d6684d26b1d7b08178679194d09ebdbbd42aa67d828aee7
3
+ metadata.gz: 55fbeca00cb3d5ac0cf360dd86a8022081e3bf8b00af321c8ac8dec7482e7e5b
4
+ data.tar.gz: a1d3fa2c7d8ffb2ae339e75d4ebdd1aa67e4b1e7b88ef0377ce8d06dc170c94d
5
5
  SHA512:
6
- metadata.gz: fe68968ac73be6725262a224ceda502eaee576c2aa3a8822a2742577e9f5d6fc05f1c3092c6f7f1c0f011218aaa851949863e3473e8ea95de9e6fd91b94f31dc
7
- data.tar.gz: e333ac1da62ceb50a7a77ec83d1c145900f90b8888249c4307102bc1dfb0e3661e0bf97c1efe5a33be27b1aa409e639278ca91643989c3c2c0954d763c665533
6
+ metadata.gz: 3e90d7e10d507ea30367e375dc99576f0b9902d77f9d7d2facf45e16fe116fff5eaac586eacec099b664b31f05f25dc33ec83db2d5b89926b0bb1fcf196588c8
7
+ data.tar.gz: 81a7739540c1b55e429867f7c7efd22af8a96559b239710fb290179c67d8ca67e1c6e5fc4448d3f6380cedf61b62fae4c1b737c5f98d532848c63652f2c05dcf
data/lib/stable/cli.rb CHANGED
@@ -156,7 +156,7 @@ module Stable
156
156
  end
157
157
 
158
158
  def port_in_use?(port)
159
- system("lsof -i tcp:#{port} > /dev/null 2>&1")
159
+ Stable::Utils::Platform.port_in_use?(port)
160
160
  end
161
161
  end
162
162
  end
@@ -1,48 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../utils/platform'
4
+
3
5
  module Stable
4
6
  module Commands
5
7
  # List command - displays all registered applications
6
8
  class List
7
- def call
8
- apps = Services::AppRegistry.all
9
+ def call
10
+ apps = Services::AppRegistry.all
9
11
 
10
- if apps.empty?
11
- puts 'No apps registered.'
12
- return
13
- end
12
+ if apps.empty?
13
+ puts 'No apps registered.'
14
+ return
15
+ end
14
16
 
15
- print_header
17
+ print_header
16
18
 
17
- apps.each do |app|
18
- puts format_row(app)
19
- end
19
+ apps.each do |app|
20
+ # Determine status based on whether the app is actually running (port check)
21
+ status = app_running?(app) ? 'running' : 'stopped'
22
+ puts format_row(app, status)
20
23
  end
24
+ end
21
25
 
22
- private
26
+ private
23
27
 
24
- def print_header
25
- puts 'APP DOMAIN PORT RUBY STATUS '
26
- puts '-' * 78
27
- end
28
+ def app_running?(app)
29
+ return false unless app && app[:port]
28
30
 
29
- def format_row(app)
30
- status =
31
- if app[:started_at]
32
- 'running'
33
- else
34
- 'stopped'
35
- end
36
-
37
- format(
38
- '%<name>-18s %<domain>-26s %<port>-8s %<ruby>-10s %<status>-10s',
39
- name: app[:name],
40
- domain: app[:domain],
41
- port: app[:port],
42
- ruby: app[:ruby],
43
- status: status
44
- )
45
- end
31
+ # Check if something is listening on the app's port (cross-platform)
32
+ Stable::Utils::Platform.port_in_use?(app[:port])
33
+ end
34
+
35
+ def format_row(app, status)
36
+ format(
37
+ '%<name>-18s %<domain>-26s %<port>-8s %<ruby>-10s %<status>-10s',
38
+ name: app[:name],
39
+ domain: app[:domain],
40
+ port: app[:port],
41
+ ruby: app[:ruby],
42
+ status: status
43
+ )
44
+ end
45
+
46
+ def print_header
47
+ puts 'APP DOMAIN PORT RUBY STATUS '
48
+ puts '-' * 78
49
+ end
46
50
  end
47
51
  end
48
52
  end
@@ -192,7 +192,7 @@ module Stable
192
192
  end
193
193
 
194
194
  def port_in_use?(port)
195
- system("lsof -i tcp:#{port} > /dev/null 2>&1")
195
+ Stable::Utils::Platform.port_in_use?(port)
196
196
  end
197
197
 
198
198
  def wait_for_port(port, timeout: 20)
@@ -55,7 +55,31 @@ module Stable
55
55
  return unless app
56
56
 
57
57
  updated_app = app.merge(attrs)
58
- Stable::Registry.save_app_config(name, updated_app)
58
+
59
+ # Check if this is a legacy app (from apps.yml) or new format app
60
+ config_file = Stable::Paths.app_config_file(name)
61
+ if File.exist?(config_file)
62
+ # New format: update individual config file
63
+ Stable::Registry.save_app_config(name, updated_app)
64
+ else
65
+ # Legacy format: update apps.yml file
66
+ update_legacy_app(name, updated_app)
67
+ end
68
+ end
69
+
70
+ def update_legacy_app(name, updated_app)
71
+ legacy_file = Stable::Paths.apps_file
72
+ return unless File.exist?(legacy_file)
73
+
74
+ data = YAML.load_file(legacy_file) || []
75
+ idx = data.find_index { |app| app['name'] == name || app[:name] == name }
76
+
77
+ if idx
78
+ # Convert symbols to strings for YAML compatibility
79
+ legacy_format = updated_app.transform_keys(&:to_s)
80
+ data[idx] = legacy_format
81
+ File.write(legacy_file, data.to_yaml)
82
+ end
59
83
  end
60
84
 
61
85
  def mark_stopped(name)
@@ -21,6 +21,14 @@ module Stable
21
21
 
22
22
  if app_running?(app)
23
23
  puts "#{@name} is already running on https://#{app[:domain]} (port #{port})"
24
+ # Update the registry with the correct PID if it's missing
25
+ if !app[:pid] || !app[:started_at]
26
+ rails_pid = find_rails_pid(port)
27
+ if rails_pid
28
+ AppRegistry.update(app[:name], started_at: Time.now.to_i, pid: rails_pid)
29
+ puts "Updated registry with correct PID (#{rails_pid})"
30
+ end
31
+ end
24
32
  return
25
33
  end
26
34
 
@@ -51,7 +59,9 @@ module Stable
51
59
 
52
60
  wait_for_port(port, timeout: 30)
53
61
 
54
- AppRegistry.update(app[:name], started_at: Time.now.to_i, pid: pid)
62
+ # Find the actual Rails process PID by checking what's listening on the port
63
+ rails_pid = find_rails_pid(port)
64
+ AppRegistry.update(app[:name], started_at: Time.now.to_i, pid: rails_pid)
55
65
 
56
66
  Stable::Services::CaddyManager.add_app(app[:name], skip_ssl: false)
57
67
  Stable::Services::CaddyManager.reload
@@ -69,7 +79,7 @@ module Stable
69
79
  end
70
80
 
71
81
  def port_in_use?(port)
72
- system("lsof -i tcp:#{port} > /dev/null 2>&1")
82
+ Stable::Utils::Platform.port_in_use?(port)
73
83
  end
74
84
 
75
85
  def wait_for_port(port, timeout: 20)
@@ -87,9 +97,22 @@ module Stable
87
97
  end
88
98
 
89
99
  def app_running?(app)
90
- return false unless app && app[:port]
100
+ return false unless app
101
+
102
+ # First check if we have PID info and if the process is alive
103
+ if app[:pid] && app[:started_at]
104
+ return ProcessManager.pid_alive?(app[:pid])
105
+ end
106
+
107
+ # Fallback to port checking if no PID info available
108
+ return false unless app[:port]
109
+
110
+ Stable::Utils::Platform.port_in_use?(app[:port])
111
+ end
91
112
 
92
- system("lsof -i tcp:#{app[:port]} -sTCP:LISTEN > /dev/null 2>&1")
113
+ def find_rails_pid(port)
114
+ pids = Stable::Utils::Platform.find_pids_by_port(port)
115
+ pids.first
93
116
  end
94
117
  end
95
118
  end
@@ -36,20 +36,23 @@ module Stable
36
36
 
37
37
  Process.detach(pid)
38
38
 
39
- AppRegistry.update(app[:name], started_at: Time.now.to_i, pid: pid)
39
+ # Wait a moment for Rails to start, then find the actual Rails PID
40
+ sleep 2
41
+ rails_pid = find_rails_pid(app[:port])
40
42
 
41
- pid
43
+ AppRegistry.update(app[:name], started_at: Time.now.to_i, pid: rails_pid || pid)
44
+
45
+ rails_pid || pid
42
46
  end
43
47
 
44
48
  def self.stop(app)
45
- pid = app[:pid]
46
- return unless pid
49
+ return unless app[:port]
47
50
 
48
- output = `lsof -i tcp:#{app[:port]} -t`.strip
49
- if output.empty?
51
+ pids = Stable::Utils::Platform.find_pids_by_port(app[:port])
52
+ if pids.empty?
50
53
  puts "No app running on port #{app[:port]}"
51
54
  else
52
- output.split("\n").each { |pid| Process.kill('TERM', pid.to_i) }
55
+ pids.each { |pid| Process.kill('TERM', pid.to_i) rescue nil }
53
56
  puts "Stopped #{app[:name]} on port #{app[:port]}"
54
57
  end
55
58
 
@@ -57,6 +60,45 @@ module Stable
57
60
  rescue Errno::ESRCH
58
61
  AppRegistry.update(app[:name], started_at: nil, pid: nil)
59
62
  end
63
+
64
+ # Check if a process with the given PID is still running
65
+ def self.pid_alive?(pid)
66
+ return false unless pid
67
+
68
+ # Use a cross-platform method to check if PID exists
69
+ if RUBY_PLATFORM =~ /mingw|mswin|win32/
70
+ # Windows: use tasklist
71
+ system("tasklist /FI \"PID eq #{pid}\" 2>NUL | find /I \"#{pid}\" >NUL")
72
+ else
73
+ # Unix-like systems: check /proc or use ps
74
+ begin
75
+ Process.kill(0, pid)
76
+ true
77
+ rescue Errno::ESRCH
78
+ false
79
+ rescue Errno::EPERM
80
+ # Process exists but we don't have permission to signal it
81
+ true
82
+ end
83
+ end
84
+ end
85
+
86
+ # Validate and clean up stale app statuses
87
+ def self.validate_app_statuses
88
+ apps = AppRegistry.all
89
+ apps.each do |app|
90
+ next unless app[:started_at] && app[:pid]
91
+
92
+ unless pid_alive?(app[:pid])
93
+ AppRegistry.update(app[:name], started_at: nil, pid: nil)
94
+ end
95
+ end
96
+ end
97
+
98
+ def self.find_rails_pid(port)
99
+ pids = Stable::Utils::Platform.find_pids_by_port(port)
100
+ pids.first
101
+ end
60
102
  end
61
103
  end
62
104
  end
@@ -49,6 +49,35 @@ module Stable
49
49
  Dir.home
50
50
  end
51
51
 
52
+ def port_in_use?(port)
53
+ case current
54
+ when :macos, :linux
55
+ # Use lsof on Unix-like systems
56
+ system("lsof -i tcp:#{port} -sTCP:LISTEN > /dev/null 2>&1")
57
+ when :windows
58
+ # Use netstat on Windows
59
+ system("netstat -an | findstr :#{port} > nul 2>&1")
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ def find_pids_by_port(port)
66
+ case current
67
+ when :macos, :linux
68
+ # Use lsof to find PIDs listening on the port
69
+ output = `lsof -i tcp:#{port} -sTCP:LISTEN -t 2>/dev/null`.strip
70
+ return [] if output.empty?
71
+ output.split("\n").map(&:to_i)
72
+ when :windows
73
+ # On Windows, this is more complex. For now, return empty array
74
+ # Could potentially parse netstat output in the future
75
+ []
76
+ else
77
+ []
78
+ end
79
+ end
80
+
52
81
  private
53
82
 
54
83
  def detect_linux_package_manager
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stable-cli-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Simfukwe