stable-cli-rails 0.7.12 → 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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stable/bootstrap.rb +1 -0
  3. data/lib/stable/cli.rb +2 -6
  4. data/lib/stable/commands/doctor.rb +1 -0
  5. data/lib/stable/commands/list.rb +37 -32
  6. data/lib/stable/commands/new.rb +1 -0
  7. data/lib/stable/commands/remove.rb +1 -0
  8. data/lib/stable/commands/restart.rb +1 -0
  9. data/lib/stable/commands/setup.rb +1 -0
  10. data/lib/stable/commands/start.rb +1 -0
  11. data/lib/stable/commands/stop.rb +1 -0
  12. data/lib/stable/db_manager.rb +22 -7
  13. data/lib/stable/paths.rb +10 -1
  14. data/lib/stable/registry.rb +57 -7
  15. data/lib/stable/scanner.rb +1 -0
  16. data/lib/stable/services/app_creator.rb +12 -10
  17. data/lib/stable/services/app_registry.rb +31 -19
  18. data/lib/stable/services/app_remover.rb +1 -0
  19. data/lib/stable/services/app_restarter.rb +1 -0
  20. data/lib/stable/services/app_starter.rb +28 -4
  21. data/lib/stable/services/app_stopper.rb +1 -0
  22. data/lib/stable/services/caddy_manager.rb +1 -0
  23. data/lib/stable/services/database/base.rb +1 -0
  24. data/lib/stable/services/database/mysql.rb +1 -0
  25. data/lib/stable/services/database/postgres.rb +1 -0
  26. data/lib/stable/services/dependency_checker.rb +58 -9
  27. data/lib/stable/services/hosts_manager.rb +18 -7
  28. data/lib/stable/services/process_manager.rb +50 -7
  29. data/lib/stable/services/ruby.rb +1 -0
  30. data/lib/stable/services/setup_runner.rb +110 -16
  31. data/lib/stable/system/shell.rb +1 -0
  32. data/lib/stable/utils/package_manager.rb +68 -0
  33. data/lib/stable/utils/platform.rb +105 -0
  34. data/lib/stable/utils/prompts.rb +1 -0
  35. data/lib/stable/validators/app_name.rb +1 -0
  36. data/lib/stable.rb +1 -0
  37. metadata +6 -31
@@ -1,23 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../utils/package_manager'
4
+
3
5
  module Stable
4
6
  module Services
7
+ # Service for checking system dependencies and health
5
8
  class DependencyChecker
6
9
  def run
7
- [
8
- check('Homebrew', 'brew'),
10
+ platform = Stable::Utils::Platform.current
11
+ package_manager_name = Stable::Utils::PackageManager.name
12
+
13
+ checks = [
14
+ check(package_manager_name, package_manager_command(platform)),
9
15
  check('Caddy', 'caddy'),
10
16
  check('mkcert', 'mkcert'),
11
- check('RVM', 'rvm'),
17
+ check_ruby_manager,
12
18
  check_caddy_running,
13
19
  check_certs_dir,
14
20
  check_apps_registry
15
21
  ]
22
+
23
+ checks.compact
16
24
  end
17
25
 
18
26
  private
19
27
 
20
28
  def check(name, command)
29
+ return nil if command.nil? # Skip checks that don't apply to this platform
30
+
21
31
  ok = system("which #{command} > /dev/null 2>&1")
22
32
  {
23
33
  name: name,
@@ -26,8 +36,47 @@ module Stable
26
36
  }
27
37
  end
28
38
 
39
+ def package_manager_command(platform)
40
+ case platform
41
+ when :macos
42
+ 'brew'
43
+ when :linux
44
+ case Stable::Utils::Platform.package_manager
45
+ when :apt
46
+ 'apt'
47
+ when :yum
48
+ 'yum'
49
+ when :pacman
50
+ 'pacman'
51
+ end
52
+ when :windows
53
+ nil # No package manager check for Windows
54
+ end
55
+ end
56
+
57
+ def check_ruby_manager
58
+ managers = [%w[rvm RVM], %w[rbenv rbenv], %w[chruby chruby]]
59
+
60
+ manager = managers.find do |cmd, _name|
61
+ system("which #{cmd} > /dev/null 2>&1")
62
+ end
63
+
64
+ if manager
65
+ { name: manager[1], ok: true, message: nil }
66
+ else
67
+ { name: 'Ruby version manager', ok: false, message: 'No Ruby version manager found (RVM, rbenv, or chruby)' }
68
+ end
69
+ end
70
+
29
71
  def check_caddy_running
30
- ok = system('pgrep caddy > /dev/null 2>&1')
72
+ platform = Stable::Utils::Platform.current
73
+ cmd = if platform == :windows
74
+ 'tasklist /FI "IMAGENAME eq caddy.exe" 2>NUL | find /I "caddy.exe" >NUL'
75
+ else
76
+ 'pgrep caddy > /dev/null 2>&1'
77
+ end
78
+
79
+ ok = system(cmd)
31
80
  {
32
81
  name: 'Caddy running',
33
82
  ok: ok,
@@ -36,7 +85,7 @@ module Stable
36
85
  end
37
86
 
38
87
  def check_certs_dir
39
- path = File.expand_path('~/StableCaddy/certs')
88
+ path = Stable::Paths.certs_dir
40
89
  ok = Dir.exist?(path)
41
90
  {
42
91
  name: 'Certificates directory',
@@ -46,12 +95,12 @@ module Stable
46
95
  end
47
96
 
48
97
  def check_apps_registry
49
- path = File.expand_path('~/StableCaddy/apps.yml')
50
- ok = File.exist?(path)
98
+ path = Stable::Paths.projects_dir
99
+ ok = Dir.exist?(path)
51
100
  {
52
- name: 'Apps registry',
101
+ name: 'Projects directory',
53
102
  ok: ok,
54
- message: ok ? nil : 'Missing apps registry. Run `stable setup`'
103
+ message: ok ? nil : 'Missing projects directory. Run `stable setup`'
55
104
  }
56
105
  end
57
106
  end
@@ -1,29 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../utils/platform'
4
+
3
5
  module Stable
4
6
  module Services
7
+ # Service for managing hosts file entries
5
8
  class HostsManager
6
- HOSTS_FILE = '/etc/hosts'
9
+ def self.hosts_file
10
+ Stable::Utils::Platform.hosts_file
11
+ end
7
12
 
8
13
  def self.remove(domain)
9
- lines = File.read(HOSTS_FILE).lines
14
+ hosts_file = hosts_file()
15
+ lines = File.read(hosts_file).lines
10
16
  filtered = lines.reject { |l| l.include?(domain) }
11
17
 
12
18
  return if lines == filtered
13
19
 
14
- File.write('/tmp/hosts', filtered.join)
15
- system("sudo mv /tmp/hosts #{HOSTS_FILE}")
20
+ if Process.uid.zero?
21
+ File.write(hosts_file, filtered.join)
22
+ else
23
+ File.write('/tmp/hosts', filtered.join)
24
+ system("sudo mv /tmp/hosts #{hosts_file}")
25
+ end
16
26
  end
17
27
 
18
28
  def self.add(domain)
29
+ hosts_file = hosts_file()
19
30
  entry = "127.0.0.1\t#{domain}\n"
20
- hosts = File.read(HOSTS_FILE)
31
+ hosts = File.read(hosts_file)
21
32
  return if hosts.include?(domain)
22
33
 
23
34
  if Process.uid.zero?
24
- File.open(HOSTS_FILE, 'a') { |f| f.puts entry }
35
+ File.open(hosts_file, 'a') { |f| f.puts entry }
25
36
  else
26
- system(%(echo "#{entry}" | sudo tee -a #{HOSTS_FILE} > /dev/null))
37
+ system(%(echo "#{entry}" | sudo tee -a #{hosts_file} > /dev/null))
27
38
  end
28
39
  end
29
40
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Stable
4
4
  module Services
5
+ # Service for managing application processes
5
6
  class ProcessManager
6
7
  def self.start(target)
7
8
  app = target.is_a?(String) ? AppRegistry.fetch(target) : target
@@ -35,20 +36,23 @@ module Stable
35
36
 
36
37
  Process.detach(pid)
37
38
 
38
- 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])
39
42
 
40
- pid
43
+ AppRegistry.update(app[:name], started_at: Time.now.to_i, pid: rails_pid || pid)
44
+
45
+ rails_pid || pid
41
46
  end
42
47
 
43
48
  def self.stop(app)
44
- pid = app[:pid]
45
- return unless pid
49
+ return unless app[:port]
46
50
 
47
- output = `lsof -i tcp:#{app[:port]} -t`.strip
48
- if output.empty?
51
+ pids = Stable::Utils::Platform.find_pids_by_port(app[:port])
52
+ if pids.empty?
49
53
  puts "No app running on port #{app[:port]}"
50
54
  else
51
- output.split("\n").each { |pid| Process.kill('TERM', pid.to_i) }
55
+ pids.each { |pid| Process.kill('TERM', pid.to_i) rescue nil }
52
56
  puts "Stopped #{app[:name]} on port #{app[:port]}"
53
57
  end
54
58
 
@@ -56,6 +60,45 @@ module Stable
56
60
  rescue Errno::ESRCH
57
61
  AppRegistry.update(app[:name], started_at: nil, pid: nil)
58
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
59
102
  end
60
103
  end
61
104
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Stable
4
4
  module Services
5
+ # Ruby version management utilities
5
6
  module Ruby
6
7
  def self.ensure_version(version)
7
8
  return if version.nil? || version.to_s.strip.empty?
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../utils/package_manager'
4
+
3
5
  module Stable
4
6
  module Services
7
+ # Service for setting up the Stable environment and dependencies
5
8
  class SetupRunner
6
9
  def call
7
10
  ensure_directories
@@ -25,8 +28,8 @@ module Stable
25
28
  end
26
29
 
27
30
  def ensure_apps_registry
28
- path = Stable::Paths.apps_file
29
- File.write(path, {}.to_yaml) unless File.exist?(path)
31
+ path = Stable::Paths.projects_dir
32
+ FileUtils.mkdir_p(path)
30
33
  end
31
34
 
32
35
  def ensure_caddyfile
@@ -41,42 +44,133 @@ module Stable
41
44
  end
42
45
 
43
46
  def ensure_dependencies!
44
- unless system('which brew > /dev/null')
45
- puts 'Homebrew is required. Install it first: https://brew.sh'
47
+ platform = Stable::Utils::Platform.current
48
+
49
+ unless Stable::Utils::PackageManager.available?
50
+ puts "#{Stable::Utils::PackageManager.name} package manager is required."
51
+ show_platform_installation_instructions(platform)
46
52
  exit 1
47
53
  end
48
54
 
49
55
  # --- Install Caddy ---
50
- unless system('which caddy > /dev/null')
56
+ unless system('which caddy > /dev/null 2>&1')
51
57
  puts 'Installing Caddy...'
52
- system('brew install caddy')
58
+ install_package('caddy')
53
59
  end
54
60
 
55
61
  # --- Install mkcert ---
56
- unless system('which mkcert > /dev/null')
62
+ unless system('which mkcert > /dev/null 2>&1')
57
63
  puts 'Installing mkcert...'
58
- system('brew install mkcert nss')
64
+ install_mkcert(platform)
59
65
  end
60
66
 
61
- # Always ensure mkcert CA is installed
62
- system('mkcert -install')
67
+ # Always ensure mkcert CA is installed (skip on Windows for now)
68
+ unless platform == :windows
69
+ begin
70
+ system('mkcert -install')
71
+ rescue StandardError
72
+ nil
73
+ end
74
+ end
63
75
 
64
76
  # --- Install PostgreSQL ---
65
- unless system('which psql > /dev/null')
77
+ unless system('which psql > /dev/null 2>&1')
66
78
  puts 'Installing PostgreSQL...'
67
- system('brew install postgresql')
68
- system('brew services start postgresql')
79
+ install_postgres(platform)
69
80
  end
70
81
 
71
82
  # --- Install MySQL ---
72
- unless system('which mysql > /dev/null')
83
+ unless system('which mysql > /dev/null 2>&1')
73
84
  puts 'Installing MySQL...'
74
- system('brew install mysql')
75
- system('brew services start mysql')
85
+ install_mysql(platform)
76
86
  end
77
87
 
78
88
  puts '✅ All dependencies are installed and running.'
79
89
  end
90
+
91
+ def install_package(package)
92
+ cmd = Stable::Utils::PackageManager.install_command(package)
93
+ system(cmd) or abort("Failed to install #{package}")
94
+ end
95
+
96
+ def install_mkcert(platform)
97
+ case platform
98
+ when :macos
99
+ install_package('mkcert nss')
100
+ when :linux
101
+ case Stable::Utils::Platform.package_manager
102
+ when :apt
103
+ system('sudo apt update && sudo apt install -y wget')
104
+ system('wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64')
105
+ system('chmod +x mkcert && sudo mv mkcert /usr/local/bin/')
106
+ when :yum
107
+ system('wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64')
108
+ system('chmod +x mkcert && sudo mv mkcert /usr/local/bin/')
109
+ end
110
+ when :windows
111
+ puts 'Please install mkcert manually on Windows: https://github.com/FiloSottile/mkcert'
112
+ end
113
+ end
114
+
115
+ def install_postgres(platform)
116
+ case platform
117
+ when :macos
118
+ install_package('postgresql')
119
+ begin
120
+ system('brew services start postgresql')
121
+ rescue StandardError
122
+ nil
123
+ end
124
+ when :linux
125
+ install_package('postgresql postgresql-contrib')
126
+ start_service('postgresql')
127
+ when :windows
128
+ puts 'Please install PostgreSQL manually on Windows from: https://www.postgresql.org/download/windows/'
129
+ end
130
+ end
131
+
132
+ def install_mysql(platform)
133
+ case platform
134
+ when :macos
135
+ install_package('mysql')
136
+ begin
137
+ system('brew services start mysql')
138
+ rescue StandardError
139
+ nil
140
+ end
141
+ when :linux
142
+ install_package('mysql-server')
143
+ start_service('mysql')
144
+ when :windows
145
+ puts 'Please install MySQL manually on Windows from: https://dev.mysql.com/downloads/mysql/'
146
+ end
147
+ end
148
+
149
+ def start_service(service)
150
+ cmd = Stable::Utils::PackageManager.service_start_command(service)
151
+ begin
152
+ system(cmd)
153
+ rescue StandardError
154
+ nil
155
+ end
156
+ end
157
+
158
+ def show_platform_installation_instructions(platform)
159
+ case platform
160
+ when :macos
161
+ puts 'Homebrew is required. Install it first: https://brew.sh'
162
+ when :linux
163
+ puts 'Please install a package manager:'
164
+ puts ' Ubuntu/Debian: sudo apt update && sudo apt install -y build-essential'
165
+ puts ' CentOS/RHEL: sudo yum install -y gcc gcc-c++ make'
166
+ puts ' Arch: sudo pacman -S base-devel'
167
+ when :windows
168
+ puts 'Please install dependencies manually on Windows.'
169
+ puts 'Required: Caddy, mkcert, PostgreSQL, MySQL'
170
+ else
171
+ puts 'Unsupported platform detected.'
172
+ end
173
+ end
80
174
  end
81
175
  end
82
176
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Stable
4
4
  module System
5
+ # Shell command execution utilities
5
6
  class Shell
6
7
  def self.run(cmd)
7
8
  puts "→ #{cmd}"
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'platform'
4
+
5
+ module Stable
6
+ module Utils
7
+ # Cross-platform package manager utilities
8
+ module PackageManager
9
+ class << self
10
+ def install_command(package)
11
+ case Platform.package_manager
12
+ when :brew
13
+ "brew install #{package}"
14
+ when :apt
15
+ "sudo apt update && sudo apt install -y #{package}"
16
+ when :yum
17
+ "sudo yum install -y #{package}"
18
+ when :pacman
19
+ "sudo pacman -S --noconfirm #{package}"
20
+ else
21
+ raise "Unsupported package manager on #{Platform.current} platform"
22
+ end
23
+ end
24
+
25
+ def service_start_command(service)
26
+ case Platform.current
27
+ when :macos
28
+ "brew services start #{service}"
29
+ when :linux
30
+ "sudo systemctl start #{service}" if systemctl_available?
31
+ else
32
+ raise "Service management not supported on #{Platform.current} platform"
33
+ end
34
+ end
35
+
36
+ def available?
37
+ pm = Platform.package_manager
38
+ if pm == :yum
39
+ system('which yum > /dev/null 2>&1') || system('which dnf > /dev/null 2>&1')
40
+ else
41
+ cmd = package_manager_commands.dig(pm, 0)
42
+ cmd ? system("#{cmd} > /dev/null 2>&1") : false
43
+ end
44
+ end
45
+
46
+ def name
47
+ pm = Platform.package_manager
48
+ package_manager_commands[pm]&.last || 'Unknown'
49
+ end
50
+
51
+ private
52
+
53
+ def package_manager_commands
54
+ {
55
+ brew: ['which brew', 'Homebrew'],
56
+ apt: ['which apt', 'APT'],
57
+ yum: ['which yum', 'YUM/DNF'],
58
+ pacman: ['which pacman', 'Pacman']
59
+ }
60
+ end
61
+
62
+ def systemctl_available?
63
+ system('which systemctl > /dev/null 2>&1')
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stable
4
+ module Utils
5
+ # Platform detection utilities for cross-platform compatibility
6
+ module Platform
7
+ class << self
8
+ def macos?
9
+ !!(RUBY_PLATFORM =~ /darwin/)
10
+ end
11
+
12
+ def linux?
13
+ !!(RUBY_PLATFORM =~ /linux/)
14
+ end
15
+
16
+ def windows?
17
+ !!(RUBY_PLATFORM =~ /mingw|mswin|win32/)
18
+ end
19
+
20
+ def unix?
21
+ !windows?
22
+ end
23
+
24
+ def current
25
+ return :macos if macos?
26
+ return :linux if linux?
27
+ return :windows if windows?
28
+
29
+ :unknown
30
+ end
31
+
32
+ def package_manager
33
+ return :brew if macos?
34
+ return detect_linux_package_manager if linux?
35
+
36
+ :unknown
37
+ end
38
+
39
+ def hosts_file
40
+ return '/etc/hosts' if unix?
41
+ return 'C:\Windows\System32\drivers\etc\hosts' if windows?
42
+
43
+ '/etc/hosts' # fallback
44
+ end
45
+
46
+ def home_directory
47
+ return ENV.fetch('USERPROFILE', nil) if windows?
48
+
49
+ Dir.home
50
+ end
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
+
81
+ private
82
+
83
+ def detect_linux_package_manager
84
+ return :apt if apt_available?
85
+ return :yum if yum_available?
86
+ return :pacman if pacman_available?
87
+
88
+ :unknown
89
+ end
90
+
91
+ def apt_available?
92
+ system('which apt > /dev/null 2>&1')
93
+ end
94
+
95
+ def yum_available?
96
+ system('which yum > /dev/null 2>&1') || system('which dnf > /dev/null 2>&1')
97
+ end
98
+
99
+ def pacman_available?
100
+ system('which pacman > /dev/null 2>&1')
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -4,6 +4,7 @@ require 'io/console'
4
4
 
5
5
  module Stable
6
6
  module Utils
7
+ # User interaction utilities for prompting input
7
8
  class Prompts
8
9
  def self.mysql_root_credentials
9
10
  print 'Enter MySQL root username (default: root): '
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Stable
4
4
  module Validators
5
+ # Validates and normalizes Rails application names
5
6
  class AppName
6
7
  VALID_PATTERN = /\A[a-z0-9]+(-[a-z0-9]+)*\z/
7
8
  MAX_LENGTH = 63
data/lib/stable.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Main Stable CLI module - provides cross-platform Rails application management
3
4
  module Stable
4
5
  def self.root
5
6
  Paths.root
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.7.12
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Simfukwe
@@ -23,36 +23,9 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: 1.2.2
26
- - !ruby/object:Gem::Dependency
27
- name: rake
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '13.0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '13.0'
40
- - !ruby/object:Gem::Dependency
41
- name: rspec
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '3.0'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '3.0'
54
- description: 'Stable CLI: manage local Rails apps with automatic Caddy, HTTPS, and
55
- simple start/stop commands.'
26
+ description: Stable is a cross-platform CLI tool to manage local Rails applications
27
+ with automatic Caddy setup, local trusted HTTPS certificates, and easy start/stop
28
+ functionality. Supports macOS, Linux, and Windows.
56
29
  email:
57
30
  - dannysimfukwe@gmail.com
58
31
  executables:
@@ -93,6 +66,8 @@ files:
93
66
  - lib/stable/services/ruby.rb
94
67
  - lib/stable/services/setup_runner.rb
95
68
  - lib/stable/system/shell.rb
69
+ - lib/stable/utils/package_manager.rb
70
+ - lib/stable/utils/platform.rb
96
71
  - lib/stable/utils/prompts.rb
97
72
  - lib/stable/validators/app_name.rb
98
73
  homepage: https://github.com/dannysimfukwe/stable-rails