stable-cli-rails 0.6.8 → 0.6.9

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: 2d0788683f7e8f99ac96bf00779599a7003e7c2f866a5a124a0fbbbca56c7a1a
4
+ data.tar.gz: 2c4d7c5ae3f0ee878e9f8c85660df787c340782eef1a1ea3c9c60460180c3259
5
5
  SHA512:
6
- metadata.gz: 954bb6bd50281fe5a47ed03e2cead5a2c614f7786d17cdc4831c50ab798e0a6fac59dc1b98b76f3d35524be574396813444bbf3d4f60099e7e81b44846658ef4
7
- data.tar.gz: 275d760d16ce88fb018f3aad059330fd05c63911aa19352b67780bd8d8680d64918583ece7e48e2eb452329c468fff3eaac5a7fdee92341425ff792421646c0b
6
+ metadata.gz: ad3f5ba69aca2cca188f8a138e370a63547cf040a6ad715509d2afe7c6347fc1ed292073b2286d63dc7e446b1728a2bca29eae7db053751325fcdb9b6ac8ac2e
7
+ data.tar.gz: 02670b007fef74d07357f9bbc65526c1ac8256341f4c32c0e18cac37c0e55e45ef6d96efe16b4450f222a800b6e79f185a0f5888bb810d33423d5d9fe9ca61fd
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
 
@@ -26,6 +28,9 @@ 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
35
  port ||= next_free_port
31
36
  app_path = File.expand_path(name)
@@ -75,6 +80,84 @@ module Stable
75
80
  system("bash -lc '#{ruby_cmd} bundle install --jobs=4 --retry=3'") or abort('bundle install failed')
76
81
  end
77
82
 
83
+ # --- Database integration ---
84
+ if options[:db]
85
+ adapter = if options[:postgres]
86
+ :postgresql
87
+ elsif options[:mysql]
88
+ :mysql
89
+ else
90
+ :postgresql
91
+ end
92
+
93
+ adapter == :postgresql ? 'postgresql' : 'mysql2'
94
+ gem_name = adapter == :postgresql ? 'pg' : 'mysql2'
95
+
96
+ gemfile_path = File.join(app_path, 'Gemfile')
97
+ unless File.read(gemfile_path).include?(gem_name)
98
+ File.open(gemfile_path, 'a') do |f|
99
+ f.puts "\n# Added by Stable CLI"
100
+ f.puts "gem '#{gem_name}'"
101
+ end
102
+ puts "✅ Added '#{gem_name}' gem to Gemfile"
103
+ end
104
+
105
+ # Ensure the gem is installed inside the gemset
106
+ system("bash -lc 'cd #{app_path} && rvm #{ruby}@#{name} do bundle install --jobs=4 --retry=3'") or abort('bundle install failed')
107
+
108
+ # --- Database setup ---
109
+ db = Stable::DBManager.new(options[:db], adapter: adapter)
110
+ creds = []
111
+
112
+ case adapter
113
+ when :postgresql
114
+ db.create
115
+ when :mysql
116
+ creds = create_mysql_db(options[:db]) # creates DB and returns creds
117
+ # Make sure mysql2 gem is loaded for Rails
118
+ system("bash -lc 'cd #{app_path} && rvm #{ruby}@#{name} do bundle exec gem list | grep mysql2'") || abort('mysql2 gem not found in gemset')
119
+ end
120
+
121
+ # --- Generate database.yml ---
122
+ db_config_path = File.join(app_path, 'config', 'database.yml')
123
+ base_config = {
124
+ 'adapter' => adapter == :postgresql ? 'postgresql' : 'mysql2',
125
+ 'encoding' => adapter == :postgresql ? 'unicode' : 'utf8mb4',
126
+ 'pool' => 5,
127
+ 'database' => options[:db],
128
+ 'username' => adapter == :mysql ? creds[:user] : nil,
129
+ 'password' => adapter == :mysql ? creds[:password] : nil,
130
+ 'host' => 'localhost',
131
+ 'auth_plugin' => adapter == :mysql ? 'caching_sha2_password' : ''
132
+ }
133
+
134
+ db_configs = {
135
+ 'default' => base_config,
136
+ 'development' => base_config,
137
+ 'test' => base_config.merge('database' => "#{options[:db]}_test"),
138
+ 'production' => base_config.merge('database' => "#{options[:db]}_prod")
139
+ }
140
+
141
+ File.write(db_config_path, db_configs.to_yaml)
142
+ puts "✅ Database '#{db.name}' configured in Rails app"
143
+
144
+ # --- Prepare the database ---
145
+ puts 'Preparing database...'
146
+ system("bash -lc 'cd #{app_path} && rvm #{ruby}@#{name} do bundle exec rails db:prepare'") or abort('Database preparation failed')
147
+ end
148
+
149
+ # --- Generate Caddyfile ---
150
+
151
+ puts 'Refreshing bundle environment...'
152
+ system(
153
+ "bash -lc 'cd #{app_path} && #{ruby_cmd} bundle check || #{ruby_cmd} bundle install'"
154
+ ) or abort('Bundler setup failed')
155
+
156
+ puts 'Preparing database...'
157
+ system(
158
+ "bash -lc 'cd #{app_path} && #{ruby_cmd} bundle exec rails db:prepare'"
159
+ ) or abort('Database preparation failed')
160
+
78
161
  # --- Host entry & certificate ---
79
162
  add_host_entry(domain)
80
163
  generate_cert(domain) unless options[:skip_ssl]
@@ -412,18 +495,37 @@ module Stable
412
495
  exit 1
413
496
  end
414
497
 
498
+ # --- Install Caddy ---
415
499
  unless system('which caddy > /dev/null')
416
500
  puts 'Installing Caddy...'
417
501
  system('brew install caddy')
418
502
  end
419
503
 
420
- return if system('which mkcert > /dev/null')
504
+ # --- Install mkcert ---
505
+ unless system('which mkcert > /dev/null')
506
+ puts 'Installing mkcert...'
507
+ system('brew install mkcert nss')
508
+ system('mkcert -install')
509
+ end
510
+
511
+ # --- Install PostgreSQL ---
512
+ unless system('which psql > /dev/null')
513
+ puts 'Installing PostgreSQL...'
514
+ system('brew install postgresql')
515
+ system('brew services start postgresql')
516
+ end
517
+
518
+ # --- Install MySQL ---
519
+ unless system('which mysql > /dev/null')
520
+ puts 'Installing MySQL...'
521
+ system('brew install mysql')
522
+ system('brew services start mysql')
523
+ end
421
524
 
422
- puts 'Installing mkcert...'
423
- system('brew install mkcert nss')
424
- system('mkcert -install')
525
+ puts ' All dependencies are installed and running.'
425
526
  end
426
527
 
528
+
427
529
  def ensure_caddy_running!
428
530
  api_port = 2019
429
531
 
@@ -509,7 +611,7 @@ module Stable
509
611
  end
510
612
  end
511
613
 
512
- def wait_for_port(port, timeout: 10)
614
+ def wait_for_port(port, timeout: 20)
513
615
  require 'socket'
514
616
  start = Time.now
515
617
 
@@ -619,5 +721,38 @@ module Stable
619
721
  "rvm #{ruby} do"
620
722
  end
621
723
  end
724
+
725
+ def create_mysql_db(db_name)
726
+ socket = %w[
727
+ /opt/homebrew/var/mysql/mysql.sock
728
+ /tmp/mysql.sock
729
+ ].find { |path| File.exist?(path) } || abort('MySQL socket not found')
730
+
731
+ print 'Enter MySQL root username (default: root): '
732
+ root_user = $stdin.gets.chomp
733
+ root_user = 'root' if root_user.empty?
734
+
735
+ print 'Enter MySQL root password (leave blank if none): '
736
+ root_password = $stdin.noecho(&:gets).chomp
737
+ puts
738
+
739
+ password_arg = root_password.empty? ? '' : "-p#{root_password}"
740
+
741
+ sql = <<~SQL
742
+ CREATE DATABASE IF NOT EXISTS #{db_name};
743
+ FLUSH PRIVILEGES;
744
+ SQL
745
+
746
+ require 'tempfile'
747
+ Tempfile.create(['db_setup', '.sql']) do |file|
748
+ file.write(sql)
749
+ file.flush
750
+ system("mysql --protocol=SOCKET --socket=#{socket} -u #{root_user} #{password_arg} < #{file.path}") or
751
+ abort("Failed to create MySQL DB '#{db_name}'")
752
+ end
753
+
754
+ puts "✅ MySQL database '#{db_name}' created/ready"
755
+ { user: root_user, password: root_password }
756
+ end
622
757
  end
623
758
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stable
4
+ class DBManager
5
+ attr_reader :name, :adapter
6
+
7
+ def initialize(name, adapter:)
8
+ @name = name
9
+ @adapter = adapter
10
+ end
11
+
12
+ # Main entry
13
+ def create
14
+ case adapter
15
+ when :postgresql
16
+ ensure_postgres_database
17
+ when :mysql
18
+ ensure_mysql_database
19
+ else
20
+ abort "Unsupported database adapter: #{adapter}"
21
+ end
22
+ end
23
+
24
+ # Rails database.yml config
25
+ def rails_config
26
+ case adapter
27
+ when :postgresql
28
+ {
29
+ 'adapter' => 'postgresql',
30
+ 'encoding' => 'unicode',
31
+ 'database' => name,
32
+ 'username' => app_user,
33
+ 'password' => nil,
34
+ 'host' => 'localhost',
35
+ 'pool' => 5
36
+ }
37
+ when :mysql
38
+ {
39
+ 'adapter' => 'mysql2',
40
+ 'encoding' => 'utf8mb4',
41
+ 'database' => name,
42
+ 'username' => app_user,
43
+ 'password' => '',
44
+ 'host' => 'localhost',
45
+ 'socket' => mysql_socket,
46
+ 'pool' => 5
47
+ }
48
+ else
49
+ abort "Unsupported adapter for Rails config: #{adapter}"
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # -------------------- PostgreSQL --------------------
56
+
57
+ def ensure_postgres_database
58
+ exists = system(%(psql -lqt | cut -d \\| -f 1 | grep -w #{name} >/dev/null))
59
+
60
+ if exists
61
+ puts "⚠ Postgres database '#{name}' already exists. Skipping creation."
62
+ else
63
+ system("createdb #{name}") or abort("Failed to create Postgres DB '#{name}'")
64
+ end
65
+ end
66
+
67
+ # -------------------- MySQL --------------------
68
+
69
+ def ensure_mysql_database
70
+ ensure_mysql_root_auth!
71
+
72
+ # Create DB and user
73
+ socket = mysql_socket
74
+ user = app_user
75
+
76
+ system(%(
77
+ mysql --protocol=SOCKET --socket=#{socket} -u root <<SQL
78
+ CREATE DATABASE IF NOT EXISTS #{name};
79
+ CREATE USER IF NOT EXISTS '#{user}'@'localhost' IDENTIFIED BY '';
80
+ GRANT ALL PRIVILEGES ON #{name}.* TO '#{user}'@'localhost';
81
+ FLUSH PRIVILEGES;
82
+ SQL
83
+ )) or abort("Failed to create MySQL DB '#{name}'")
84
+
85
+ puts "✅ MySQL database '#{name}' ready"
86
+ end
87
+
88
+ # Ensure root can connect via socket
89
+ def ensure_mysql_root_auth!
90
+ ok = system("mysql -u root -e 'SELECT 1' >/dev/null 2>&1")
91
+ return if ok
92
+
93
+ puts '⚠ Fixing MySQL root authentication (requires sudo)...'
94
+ socket = mysql_socket
95
+
96
+ system(%(
97
+ sudo mysql --protocol=SOCKET --socket=#{socket} <<SQL
98
+ ALTER USER 'root'@'localhost' IDENTIFIED BY '';
99
+ FLUSH PRIVILEGES;
100
+ SQL
101
+ )) or abort('Failed to repair MySQL root authentication')
102
+ end
103
+
104
+ # Detect MySQL socket on macOS / Linux
105
+ def mysql_socket
106
+ paths = [
107
+ '/opt/homebrew/var/mysql/mysql.sock', # Homebrew macOS
108
+ '/tmp/mysql.sock', # Default
109
+ '/var/run/mysqld/mysqld.sock' # Linux default
110
+ ]
111
+
112
+ paths.each { |p| return p if File.exist?(p) }
113
+ abort 'MySQL socket not found. Is MySQL running?'
114
+ end
115
+
116
+ # Default Rails DB user
117
+ def app_user
118
+ ENV['USER'] || 'stable'
119
+ end
120
+ end
121
+ end
data/lib/stable.rb CHANGED
@@ -12,3 +12,4 @@ require_relative 'stable/cli'
12
12
  require_relative 'stable/registry'
13
13
  require_relative 'stable/scanner'
14
14
  require_relative 'stable/bootstrap'
15
+ require_relative 'stable/db_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.6.8
4
+ version: 0.6.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Simfukwe
@@ -50,6 +50,7 @@ files:
50
50
  - lib/stable.rb
51
51
  - lib/stable/bootstrap.rb
52
52
  - lib/stable/cli.rb
53
+ - lib/stable/db_manager.rb
53
54
  - lib/stable/paths.rb
54
55
  - lib/stable/registry.rb
55
56
  - lib/stable/scanner.rb