stable-cli-rails 0.8.1 → 0.8.2

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: 55fbeca00cb3d5ac0cf360dd86a8022081e3bf8b00af321c8ac8dec7482e7e5b
4
- data.tar.gz: a1d3fa2c7d8ffb2ae339e75d4ebdd1aa67e4b1e7b88ef0377ce8d06dc170c94d
3
+ metadata.gz: 0a7c350e5e5b2858a8a8ceb9e35eb9772c0e3416accf9f8e3546ba072ae59833
4
+ data.tar.gz: ed890e3547579a9361c473a8b884166a87d4454f1b89874e53510138f38e43f9
5
5
  SHA512:
6
- metadata.gz: 3e90d7e10d507ea30367e375dc99576f0b9902d77f9d7d2facf45e16fe116fff5eaac586eacec099b664b31f05f25dc33ec83db2d5b89926b0bb1fcf196588c8
7
- data.tar.gz: 81a7739540c1b55e429867f7c7efd22af8a96559b239710fb290179c67d8ca67e1c6e5fc4448d3f6380cedf61b62fae4c1b737c5f98d532848c63652f2c05dcf
6
+ metadata.gz: 9f86445108712b6eb9cb99a14f5da5df53e14d11d646d3ccc9525dc23efec767ae2a6a2f6cb68c0ac032f75928992dfb0913580900ebae6d2426660051f5191b
7
+ data.tar.gz: 2965495aa69ea21c4c19c7020222a90ee84214d05530a1b6175e9ce6689dda13d94a508cb708c9d2c2447f8b484b6afe127e7ae19c08efcf5dfefea247757d03
data/lib/stable/cli.rb CHANGED
@@ -75,6 +75,11 @@ module Stable
75
75
  Commands::Remove.new(name).call
76
76
  end
77
77
 
78
+ desc 'destroy NAME', 'Permanently delete a Rails app and all its files'
79
+ def destroy(name)
80
+ Commands::Destroy.new(name).call
81
+ end
82
+
78
83
  desc 'start NAME', 'Start a Rails app with its correct Ruby version'
79
84
  def start(name)
80
85
  Commands::Start.new(name).call
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+
5
+ module Stable
6
+ module Commands
7
+ # Destroy command - permanently deletes a Rails application with confirmation
8
+ class Destroy
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+
13
+ def call
14
+ app = Services::AppRegistry.find(@name)
15
+ abort 'App not found' unless app
16
+
17
+ display_warning(app)
18
+ return unless confirm_destruction
19
+
20
+ puts "\nšŸ—‘ļø Destroying #{@name}..."
21
+ perform_destruction(app)
22
+ puts "āœ… Successfully destroyed #{@name}"
23
+ end
24
+
25
+ private
26
+
27
+ def display_warning(app)
28
+ puts "āš ļø WARNING: This will permanently delete the application '#{@name}'"
29
+ puts " Path: #{app[:path]}"
30
+ puts " Domain: #{app[:domain]}"
31
+ puts ' This action CANNOT be undone!'
32
+ puts ''
33
+ end
34
+
35
+ def confirm_destruction
36
+ print "Type '#{@name}' to confirm destruction: "
37
+ confirmation = $stdin.gets&.strip
38
+ puts ''
39
+
40
+ if confirmation == @name
41
+ true
42
+ else
43
+ puts "āŒ Destruction cancelled - confirmation didn't match"
44
+ false
45
+ end
46
+ end
47
+
48
+ def perform_destruction(app)
49
+ # Stop the app if running
50
+ Services::ProcessManager.stop(app)
51
+
52
+ # Remove from infrastructure
53
+ Services::HostsManager.remove(app[:domain])
54
+ Services::CaddyManager.remove(app[:domain])
55
+ Services::AppRegistry.remove(@name)
56
+
57
+ # Clean up RVM gemset
58
+ cleanup_rvm_gemset(app)
59
+
60
+ # Delete the project directory
61
+ delete_project_directory(app[:path])
62
+
63
+ # Reload Caddy
64
+ Services::CaddyManager.reload
65
+ end
66
+
67
+ def cleanup_rvm_gemset(app)
68
+ # Only clean up RVM gemsets on Unix-like systems (macOS/Linux)
69
+ # Windows uses different Ruby version managers
70
+ return unless Stable::Utils::Platform.unix?
71
+
72
+ ruby_version = app[:ruby]
73
+ # Handle different ruby version formats (e.g., "3.4.7", "ruby-3.4.7")
74
+ clean_ruby_version = ruby_version.to_s.sub(/^ruby-/, '')
75
+ gemset_name = "#{clean_ruby_version}@#{@name}"
76
+
77
+ puts " Cleaning up RVM gemset #{gemset_name}..."
78
+ begin
79
+ # Use system to run RVM command to delete the gemset
80
+ system("bash -lc 'source ~/.rvm/scripts/rvm && rvm gemset delete #{gemset_name} --force' 2>/dev/null || true")
81
+ puts " āœ… RVM gemset #{gemset_name} cleaned up"
82
+ rescue StandardError => e
83
+ puts " āš ļø Could not clean up RVM gemset #{gemset_name}: #{e.message}"
84
+ end
85
+ end
86
+
87
+ def delete_project_directory(path)
88
+ if File.exist?(path)
89
+ puts ' Deleting project directory...'
90
+ FileUtils.rm_rf(path)
91
+ else
92
+ puts ' Project directory not found (already deleted?)'
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -6,47 +6,47 @@ module Stable
6
6
  module Commands
7
7
  # List command - displays all registered applications
8
8
  class List
9
- def call
10
- apps = Services::AppRegistry.all
9
+ def call
10
+ apps = Services::AppRegistry.all
11
11
 
12
- if apps.empty?
13
- puts 'No apps registered.'
14
- return
15
- end
12
+ if apps.empty?
13
+ puts 'No apps registered.'
14
+ return
15
+ end
16
16
 
17
- print_header
17
+ print_header
18
18
 
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)
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)
23
+ end
23
24
  end
24
- end
25
25
 
26
- private
26
+ private
27
27
 
28
- def app_running?(app)
29
- return false unless app && app[:port]
28
+ def app_running?(app)
29
+ return false unless app && app[:port]
30
30
 
31
- # Check if something is listening on the app's port (cross-platform)
32
- Stable::Utils::Platform.port_in_use?(app[:port])
33
- 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
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
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
45
 
46
- def print_header
47
- puts 'APP DOMAIN PORT RUBY STATUS '
48
- puts '-' * 78
49
- end
46
+ def print_header
47
+ puts 'APP DOMAIN PORT RUBY STATUS '
48
+ puts '-' * 78
49
+ end
50
50
  end
51
51
  end
52
52
  end
@@ -66,6 +66,21 @@ module Stable
66
66
  def self.remove_app_config(app_name)
67
67
  config_file = Stable::Paths.app_config_file(app_name)
68
68
  FileUtils.rm_f(config_file)
69
+
70
+ # Also remove from legacy apps.yml file for backward compatibility
71
+ remove_from_legacy_file(app_name)
72
+ end
73
+
74
+ def self.remove_from_legacy_file(app_name)
75
+ legacy_file = Stable::Paths.apps_file
76
+ return unless File.exist?(legacy_file)
77
+
78
+ data = YAML.load_file(legacy_file) || []
79
+ filtered_data = data.reject { |entry| entry.is_a?(Hash) && entry['name'] == app_name }
80
+
81
+ return unless filtered_data != data
82
+
83
+ File.write(legacy_file, filtered_data.to_yaml)
69
84
  end
70
85
 
71
86
  def self.parse_config_file(config_file)
@@ -74,12 +74,12 @@ module Stable
74
74
  data = YAML.load_file(legacy_file) || []
75
75
  idx = data.find_index { |app| app['name'] == name || app[:name] == name }
76
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
77
+ return unless idx
78
+
79
+ # Convert symbols to strings for YAML compatibility
80
+ legacy_format = updated_app.transform_keys(&:to_s)
81
+ data[idx] = legacy_format
82
+ File.write(legacy_file, data.to_yaml)
83
83
  end
84
84
 
85
85
  def mark_stopped(name)
@@ -100,9 +100,7 @@ module Stable
100
100
  return false unless app
101
101
 
102
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
103
+ return ProcessManager.pid_alive?(app[:pid]) if app[:pid] && app[:started_at]
106
104
 
107
105
  # Fallback to port checking if no PID info available
108
106
  return false unless app[:port]
@@ -52,7 +52,11 @@ module Stable
52
52
  if pids.empty?
53
53
  puts "No app running on port #{app[:port]}"
54
54
  else
55
- pids.each { |pid| Process.kill('TERM', pid.to_i) rescue nil }
55
+ pids.each do |pid|
56
+ Process.kill('TERM', pid.to_i)
57
+ rescue StandardError
58
+ nil
59
+ end
56
60
  puts "Stopped #{app[:name]} on port #{app[:port]}"
57
61
  end
58
62
 
@@ -89,9 +93,7 @@ module Stable
89
93
  apps.each do |app|
90
94
  next unless app[:started_at] && app[:pid]
91
95
 
92
- unless pid_alive?(app[:pid])
93
- AppRegistry.update(app[:name], started_at: nil, pid: nil)
94
- end
96
+ AppRegistry.update(app[:name], started_at: nil, pid: nil) unless pid_alive?(app[:pid])
95
97
  end
96
98
  end
97
99
 
@@ -68,6 +68,7 @@ module Stable
68
68
  # Use lsof to find PIDs listening on the port
69
69
  output = `lsof -i tcp:#{port} -sTCP:LISTEN -t 2>/dev/null`.strip
70
70
  return [] if output.empty?
71
+
71
72
  output.split("\n").map(&:to_i)
72
73
  when :windows
73
74
  # On Windows, this is more complex. For now, return empty array
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.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Simfukwe
@@ -37,6 +37,7 @@ files:
37
37
  - lib/stable.rb
38
38
  - lib/stable/bootstrap.rb
39
39
  - lib/stable/cli.rb
40
+ - lib/stable/commands/destroy.rb
40
41
  - lib/stable/commands/doctor.rb
41
42
  - lib/stable/commands/list.rb
42
43
  - lib/stable/commands/new.rb