stable-cli-rails 0.6.7 → 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 +4 -4
- data/lib/stable/cli.rb +249 -45
- data/lib/stable/db_manager.rb +121 -0
- data/lib/stable.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d0788683f7e8f99ac96bf00779599a7003e7c2f866a5a124a0fbbbca56c7a1a
|
|
4
|
+
data.tar.gz: 2c4d7c5ae3f0ee878e9f8c85660df787c340782eef1a1ea3c9c60460180c3259
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
|
@@ -14,6 +16,7 @@ module Stable
|
|
|
14
16
|
super
|
|
15
17
|
Stable::Bootstrap.run!
|
|
16
18
|
ensure_dependencies!
|
|
19
|
+
dedupe_registry!
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def self.exit_on_failure?
|
|
@@ -25,28 +28,47 @@ module Stable
|
|
|
25
28
|
method_option :rails, type: :string, desc: 'Rails version to install (optional)'
|
|
26
29
|
method_option :port, type: :numeric, desc: 'Port to run Rails app on'
|
|
27
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'
|
|
28
34
|
def new(name, ruby: RUBY_VERSION, rails: nil, port: nil)
|
|
29
35
|
port ||= next_free_port
|
|
30
36
|
app_path = File.expand_path(name)
|
|
31
37
|
|
|
38
|
+
# --- Add app to registry ---
|
|
39
|
+
domain = "#{name}.test"
|
|
40
|
+
apps = Registry.apps
|
|
41
|
+
|
|
42
|
+
app = {
|
|
43
|
+
name: name,
|
|
44
|
+
path: app_path,
|
|
45
|
+
domain: domain,
|
|
46
|
+
port: port,
|
|
47
|
+
ruby: ruby,
|
|
48
|
+
started_at: nil
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
apps.reject! { |a| a[:name] == name }
|
|
52
|
+
apps << app
|
|
53
|
+
|
|
32
54
|
abort "Folder already exists: #{app_path}" if File.exist?(app_path)
|
|
33
55
|
|
|
34
56
|
# --- Ensure RVM and Ruby ---
|
|
35
57
|
ensure_rvm!
|
|
36
58
|
puts "Using Ruby #{ruby} with RVM gemset #{name}..."
|
|
37
|
-
system("bash -lc 'rvm #{ruby}@#{name} --create do true'") or abort(
|
|
38
|
-
|
|
59
|
+
system("bash -lc 'rvm #{ruby}@#{name} --create do true'") or abort("Failed to create RVM gemset #{name}")
|
|
60
|
+
ruby_cmd = rvm_exec(app, ruby)
|
|
39
61
|
# --- Install Rails in gemset if needed ---
|
|
40
62
|
rails_version = rails || 'latest'
|
|
41
|
-
rails_check = system("bash -lc '
|
|
63
|
+
rails_check = system("bash -lc '#{ruby_cmd} gem list -i rails#{rails ? " -v #{rails}" : ''}'")
|
|
42
64
|
unless rails_check
|
|
43
65
|
puts "Installing Rails #{rails_version} in gemset..."
|
|
44
|
-
system("bash -lc '
|
|
66
|
+
system("bash -lc '#{ruby_cmd} gem install rails #{rails ? "-v #{rails}" : ''}'") or abort('Failed to install Rails')
|
|
45
67
|
end
|
|
46
68
|
|
|
47
69
|
# --- Create Rails app ---
|
|
48
70
|
puts "Creating Rails app #{name} (Ruby #{ruby})..."
|
|
49
|
-
system("bash -lc '
|
|
71
|
+
system("bash -lc '#{ruby_cmd} rails new #{app_path}'") or abort('Rails app creation failed')
|
|
50
72
|
|
|
51
73
|
# --- Add .ruby-version and .ruby-gemset ---
|
|
52
74
|
Dir.chdir(app_path) do
|
|
@@ -55,14 +77,86 @@ module Stable
|
|
|
55
77
|
|
|
56
78
|
# --- Install gems inside gemset ---
|
|
57
79
|
puts 'Running bundle install...'
|
|
58
|
-
system("bash -lc '
|
|
80
|
+
system("bash -lc '#{ruby_cmd} bundle install --jobs=4 --retry=3'") or abort('bundle install failed')
|
|
59
81
|
end
|
|
60
82
|
|
|
61
|
-
# ---
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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')
|
|
66
160
|
|
|
67
161
|
# --- Host entry & certificate ---
|
|
68
162
|
add_host_entry(domain)
|
|
@@ -76,15 +170,20 @@ module Stable
|
|
|
76
170
|
log_file = File.join(app_path, 'log', 'stable.log')
|
|
77
171
|
FileUtils.mkdir_p(File.dirname(log_file))
|
|
78
172
|
|
|
173
|
+
abort "Port #{port} is already in use. Choose another port." if app_running?({ port: port })
|
|
174
|
+
|
|
79
175
|
pid = spawn(
|
|
80
176
|
'bash',
|
|
81
177
|
'-lc',
|
|
82
|
-
"cd #{app_path} &&
|
|
178
|
+
"cd #{app_path} && #{ruby_cmd} bundle exec rails s -p #{port} -b 127.0.0.1",
|
|
83
179
|
out: log_file,
|
|
84
180
|
err: log_file
|
|
85
181
|
)
|
|
86
182
|
Process.detach(pid)
|
|
87
183
|
|
|
184
|
+
app[:started_at] = Time.now.to_i
|
|
185
|
+
Registry.save(apps)
|
|
186
|
+
|
|
88
187
|
sleep 1.5
|
|
89
188
|
|
|
90
189
|
wait_for_port(port)
|
|
@@ -111,6 +210,8 @@ module Stable
|
|
|
111
210
|
return
|
|
112
211
|
end
|
|
113
212
|
|
|
213
|
+
puts "Detected gemset: #{File.read('.ruby-gemset').strip}" if File.exist?('.ruby-gemset')
|
|
214
|
+
|
|
114
215
|
apps = Registry.apps
|
|
115
216
|
name = File.basename(folder)
|
|
116
217
|
domain = "#{name}.test"
|
|
@@ -154,47 +255,54 @@ module Stable
|
|
|
154
255
|
desc 'start NAME', 'Start a Rails app with its correct Ruby version'
|
|
155
256
|
def start(name)
|
|
156
257
|
app = Registry.apps.find { |a| a[:name] == name }
|
|
157
|
-
unless app
|
|
158
|
-
puts "No app found with name #{name}"
|
|
159
|
-
return
|
|
160
|
-
end
|
|
258
|
+
return puts("No app found with name #{name}") unless app
|
|
161
259
|
|
|
162
260
|
port = app[:port] || next_free_port
|
|
163
261
|
ruby = app[:ruby]
|
|
262
|
+
path = app[:path]
|
|
164
263
|
|
|
165
|
-
|
|
264
|
+
if app_running?(app)
|
|
265
|
+
puts "#{name} is already running on https://#{app[:domain]} (port #{port})"
|
|
266
|
+
return
|
|
267
|
+
end
|
|
166
268
|
|
|
167
|
-
|
|
168
|
-
FileUtils.mkdir_p(File.dirname(log_file))
|
|
269
|
+
gemset = gemset_for(app)
|
|
169
270
|
|
|
170
|
-
|
|
171
|
-
if ruby
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
ensure_rbenv_ruby!(ruby)
|
|
177
|
-
"RBENV_VERSION=#{ruby}"
|
|
178
|
-
else
|
|
179
|
-
puts 'No Ruby version manager found (rvm or rbenv)'
|
|
180
|
-
return
|
|
181
|
-
end
|
|
271
|
+
rvm_cmd =
|
|
272
|
+
if ruby && gemset
|
|
273
|
+
system("bash -lc 'rvm #{ruby}@#{gemset} --create do true'")
|
|
274
|
+
"rvm #{ruby}@#{gemset} do"
|
|
275
|
+
elsif ruby
|
|
276
|
+
"rvm #{ruby} do"
|
|
182
277
|
end
|
|
183
278
|
|
|
279
|
+
puts "Starting #{name} on port #{port}..."
|
|
280
|
+
|
|
281
|
+
log_file = File.join(path, 'log', 'stable.log')
|
|
282
|
+
FileUtils.mkdir_p(File.dirname(log_file))
|
|
283
|
+
|
|
184
284
|
pid = spawn(
|
|
185
285
|
'bash',
|
|
186
286
|
'-lc',
|
|
187
|
-
|
|
287
|
+
<<~CMD,
|
|
288
|
+
cd "#{path}"
|
|
289
|
+
#{rvm_cmd} bundle exec rails s \
|
|
290
|
+
-p #{port} \
|
|
291
|
+
-b 127.0.0.1
|
|
292
|
+
CMD
|
|
188
293
|
out: log_file,
|
|
189
294
|
err: log_file
|
|
190
295
|
)
|
|
296
|
+
|
|
191
297
|
Process.detach(pid)
|
|
192
298
|
|
|
193
|
-
|
|
299
|
+
wait_for_port(port, timeout: 30)
|
|
300
|
+
|
|
301
|
+
app[:started_at] = Time.now.to_i
|
|
302
|
+
Registry.save(Registry.apps)
|
|
194
303
|
|
|
195
304
|
generate_cert(app[:domain])
|
|
196
305
|
update_caddyfile(app[:domain], port)
|
|
197
|
-
wait_for_port(port)
|
|
198
306
|
caddy_reload
|
|
199
307
|
|
|
200
308
|
puts "#{name} started on https://#{app[:domain]}"
|
|
@@ -254,8 +362,11 @@ module Stable
|
|
|
254
362
|
puts "mkcert: #{system('which mkcert > /dev/null') ? 'yes' : 'no'}"
|
|
255
363
|
|
|
256
364
|
Registry.apps.each do |app|
|
|
257
|
-
|
|
258
|
-
|
|
365
|
+
state = boot_state(app)
|
|
366
|
+
ruby = app[:ruby] || 'default'
|
|
367
|
+
port = app[:port]
|
|
368
|
+
|
|
369
|
+
puts "#{app[:name]} → Ruby #{ruby} | port #{port} | #{state}"
|
|
259
370
|
end
|
|
260
371
|
end
|
|
261
372
|
|
|
@@ -384,18 +495,37 @@ module Stable
|
|
|
384
495
|
exit 1
|
|
385
496
|
end
|
|
386
497
|
|
|
498
|
+
# --- Install Caddy ---
|
|
387
499
|
unless system('which caddy > /dev/null')
|
|
388
500
|
puts 'Installing Caddy...'
|
|
389
501
|
system('brew install caddy')
|
|
390
502
|
end
|
|
391
503
|
|
|
392
|
-
|
|
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
|
|
393
510
|
|
|
394
|
-
|
|
395
|
-
system('
|
|
396
|
-
|
|
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
|
|
524
|
+
|
|
525
|
+
puts '✅ All dependencies are installed and running.'
|
|
397
526
|
end
|
|
398
527
|
|
|
528
|
+
|
|
399
529
|
def ensure_caddy_running!
|
|
400
530
|
api_port = 2019
|
|
401
531
|
|
|
@@ -481,16 +611,17 @@ module Stable
|
|
|
481
611
|
end
|
|
482
612
|
end
|
|
483
613
|
|
|
484
|
-
def wait_for_port(port, timeout:
|
|
614
|
+
def wait_for_port(port, timeout: 20)
|
|
485
615
|
require 'socket'
|
|
486
|
-
|
|
616
|
+
start = Time.now
|
|
617
|
+
|
|
487
618
|
loop do
|
|
488
619
|
TCPSocket.new('127.0.0.1', port).close
|
|
489
|
-
|
|
620
|
+
return
|
|
490
621
|
rescue Errno::ECONNREFUSED
|
|
491
|
-
raise "
|
|
622
|
+
raise "Rails never bound port #{port}. Check log/stable.log" if Time.now - start > timeout
|
|
492
623
|
|
|
493
|
-
sleep 0.
|
|
624
|
+
sleep 0.5
|
|
494
625
|
end
|
|
495
626
|
end
|
|
496
627
|
|
|
@@ -550,5 +681,78 @@ module Stable
|
|
|
550
681
|
def ensure_rbenv_ruby!(version)
|
|
551
682
|
system("rbenv versions | grep -q #{version} || rbenv install #{version}")
|
|
552
683
|
end
|
|
684
|
+
|
|
685
|
+
def app_running?(app)
|
|
686
|
+
return false unless app && app[:port]
|
|
687
|
+
|
|
688
|
+
system("lsof -i tcp:#{app[:port]} -sTCP:LISTEN > /dev/null 2>&1")
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def boot_state(app)
|
|
692
|
+
return 'stopped' unless app_running?(app)
|
|
693
|
+
|
|
694
|
+
if app[:started_at]
|
|
695
|
+
elapsed = Time.now.to_i - app[:started_at]
|
|
696
|
+
return "booting (#{elapsed}s)" if elapsed < 10
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
'running'
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def dedupe_registry!
|
|
703
|
+
apps = Registry.apps
|
|
704
|
+
apps.uniq! { |a| a[:name] }
|
|
705
|
+
Registry.save(apps)
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def gemset_for(app)
|
|
709
|
+
gemset_file = File.join(app[:path], '.ruby-gemset')
|
|
710
|
+
return File.read(gemset_file).strip if File.exist?(gemset_file)
|
|
711
|
+
|
|
712
|
+
nil
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
def rvm_exec(app, ruby)
|
|
716
|
+
gemset = gemset_for(app)
|
|
717
|
+
|
|
718
|
+
if gemset
|
|
719
|
+
"rvm #{ruby}@#{gemset} do"
|
|
720
|
+
else
|
|
721
|
+
"rvm #{ruby} do"
|
|
722
|
+
end
|
|
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
|
|
553
757
|
end
|
|
554
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
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.
|
|
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
|