stable-cli-rails 0.7.10 → 0.7.12
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/bin/stable +3 -1
- data/lib/stable/cli.rb +3 -103
- data/lib/stable/commands/list.rb +1 -8
- data/lib/stable/services/app_creator.rb +113 -57
- data/lib/stable/services/caddy_manager.rb +7 -1
- data/lib/stable/services/database/base.rb +18 -4
- data/lib/stable/services/database/mysql.rb +2 -2
- data/lib/stable/services/database/postgres.rb +1 -1
- data/lib/stable/services/ruby.rb +10 -1
- data/lib/stable/services/setup_runner.rb +1 -1
- data/lib/stable/validators/app_name.rb +49 -0
- data/lib/stable.rb +18 -16
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f092ba86af858aef49a2de7704a7039e3f25accafaf9028254469e9621c754ea
|
|
4
|
+
data.tar.gz: 4ab71b60ae850c74508c790cf0fce5b14c2d12e0c8f2ec793a6b5e5234a95529
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 830108d826c9a63cdbaabf2c539b083795d7cf2772e1d8cea0c91d39dcefcaf06e792e1865793d7c7ab1da428b810228a36bbba34038555bcf20aefe03d2c295
|
|
7
|
+
data.tar.gz: acdf43e4787cf9115eb29b4900f670cb9e2ce0a0fed29ba3f251cf40f00948897a3ed1c1edff3109053031c3ed6d238e4632d28f681fc591548635f9f03951ac
|
data/bin/stable
CHANGED
data/lib/stable/cli.rb
CHANGED
|
@@ -10,8 +10,6 @@ require_relative 'registry'
|
|
|
10
10
|
|
|
11
11
|
module Stable
|
|
12
12
|
class CLI < Thor
|
|
13
|
-
HOSTS_FILE = '/etc/hosts'
|
|
14
|
-
|
|
15
13
|
def initialize(*)
|
|
16
14
|
super
|
|
17
15
|
Stable::Bootstrap.run!
|
|
@@ -31,8 +29,10 @@ module Stable
|
|
|
31
29
|
method_option :db, type: :string, desc: 'Database name to create and integrate'
|
|
32
30
|
method_option :postgres, type: :boolean, default: false, desc: 'Use Postgres for the database'
|
|
33
31
|
method_option :mysql, type: :boolean, default: false, desc: 'Use MySQL for the database'
|
|
32
|
+
method_option :full, type: :boolean, default: false, desc: 'Setup full Rails app common generators'
|
|
34
33
|
def new(name, ruby: RUBY_VERSION, rails: nil, port: nil)
|
|
35
|
-
|
|
34
|
+
safe_name = Validators::AppName.call!(name)
|
|
35
|
+
Commands::New.new(safe_name, options).call
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
desc 'list', 'List detected apps'
|
|
@@ -159,108 +159,8 @@ module Stable
|
|
|
159
159
|
system("lsof -i tcp:#{port} > /dev/null 2>&1")
|
|
160
160
|
end
|
|
161
161
|
|
|
162
|
-
def generate_cert(domain)
|
|
163
|
-
cert_path = File.join(Stable::Paths.certs_dir, "#{domain}.pem")
|
|
164
|
-
key_path = File.join(Stable::Paths.certs_dir, "#{domain}-key.pem")
|
|
165
|
-
FileUtils.mkdir_p(Stable::Paths.certs_dir)
|
|
166
|
-
|
|
167
|
-
return if File.exist?(cert_path) && File.exist?(key_path)
|
|
168
|
-
|
|
169
|
-
if system('which mkcert > /dev/null')
|
|
170
|
-
system("mkcert -cert-file #{cert_path} -key-file #{key_path} #{domain}")
|
|
171
|
-
else
|
|
172
|
-
puts 'mkcert not found. Please install mkcert.'
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def update_caddyfile(domain, port)
|
|
177
|
-
caddyfile = Stable::Paths.caddyfile
|
|
178
|
-
FileUtils.touch(caddyfile) unless File.exist?(caddyfile)
|
|
179
|
-
content = File.read(caddyfile)
|
|
180
|
-
|
|
181
|
-
# remove existing block for domain
|
|
182
|
-
regex = %r{
|
|
183
|
-
https://#{Regexp.escape(domain)}\s*\{
|
|
184
|
-
.*?
|
|
185
|
-
\}
|
|
186
|
-
}mx
|
|
187
|
-
|
|
188
|
-
content = content.gsub(regex, '')
|
|
189
|
-
|
|
190
|
-
# add new block
|
|
191
|
-
cert_path = File.join(Stable::Paths.certs_dir, "#{domain}.pem")
|
|
192
|
-
key_path = File.join(Stable::Paths.certs_dir, "#{domain}-key.pem")
|
|
193
|
-
block = <<~CADDY
|
|
194
|
-
|
|
195
|
-
https://#{domain} {
|
|
196
|
-
reverse_proxy 127.0.0.1:#{port}
|
|
197
|
-
tls #{cert_path} #{key_path}
|
|
198
|
-
}
|
|
199
|
-
CADDY
|
|
200
|
-
|
|
201
|
-
File.write(caddyfile, content + block)
|
|
202
|
-
system("caddy fmt --overwrite #{caddyfile}")
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def ensure_certs_dir!
|
|
206
|
-
certs_dir = Stable::Paths.certs_dir
|
|
207
|
-
FileUtils.mkdir_p(certs_dir)
|
|
208
|
-
|
|
209
|
-
begin
|
|
210
|
-
FileUtils.chown_R(Etc.getlogin, nil, certs_dir)
|
|
211
|
-
rescue StandardError => e
|
|
212
|
-
puts "Could not change ownership: #{e.message}"
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Restrict permissions for security
|
|
216
|
-
Dir.glob("#{certs_dir}/*.pem").each do |pem|
|
|
217
|
-
FileUtils.chmod(0o600, pem)
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def wait_for_port(port, timeout: 20)
|
|
222
|
-
require 'socket'
|
|
223
|
-
start = Time.now
|
|
224
|
-
|
|
225
|
-
loop do
|
|
226
|
-
TCPSocket.new('127.0.0.1', port).close
|
|
227
|
-
return
|
|
228
|
-
rescue Errno::ECONNREFUSED
|
|
229
|
-
raise "Rails never bound port #{port}. Check log/stable.log" if Time.now - start > timeout
|
|
230
|
-
|
|
231
|
-
sleep 0.5
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# RVM/ruby helpers moved to Services::Ruby
|
|
236
|
-
|
|
237
|
-
def app_running?(app)
|
|
238
|
-
return false unless app && app[:port]
|
|
239
|
-
|
|
240
|
-
system("lsof -i tcp:#{app[:port]} -sTCP:LISTEN > /dev/null 2>&1")
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def boot_state(app)
|
|
244
|
-
return 'stopped' unless app_running?(app)
|
|
245
|
-
|
|
246
|
-
if app[:started_at]
|
|
247
|
-
elapsed = Time.now.to_i - app[:started_at]
|
|
248
|
-
return "booting (#{elapsed}s)" if elapsed < 10
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
'running'
|
|
252
|
-
end
|
|
253
|
-
|
|
254
162
|
def dedupe_registry!
|
|
255
163
|
Services::AppRegistry.dedupe
|
|
256
164
|
end
|
|
257
|
-
|
|
258
|
-
def gemset_for(app)
|
|
259
|
-
Stable::Services::Ruby.gemset_for(app)
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def rvm_exec(app, ruby)
|
|
263
|
-
Stable::Services::Ruby.rvm_exec(app, ruby)
|
|
264
|
-
end
|
|
265
165
|
end
|
|
266
166
|
end
|
data/lib/stable/commands/list.rb
CHANGED
|
@@ -15,54 +15,54 @@ module Stable
|
|
|
15
15
|
domain = "#{@name}.test"
|
|
16
16
|
|
|
17
17
|
# --- Register app in registry ---
|
|
18
|
-
Services::AppRegistry.add(
|
|
18
|
+
Services::AppRegistry.add(
|
|
19
|
+
name: @name, path: app_path, domain: domain, port: port,
|
|
20
|
+
ruby: ruby, started_at: nil, pid: nil
|
|
21
|
+
)
|
|
19
22
|
|
|
20
23
|
abort "Folder already exists: #{app_path}" if File.exist?(app_path)
|
|
21
24
|
|
|
22
|
-
# --- Ensure Ruby
|
|
25
|
+
# --- Ensure Ruby version & RVM ---
|
|
23
26
|
Ruby.ensure_version(ruby)
|
|
24
27
|
Ruby.ensure_rvm!
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
# --- Create gemset ---
|
|
30
|
+
System::Shell.run("bash -lc 'source #{Ruby.rvm_script} && rvm #{ruby} do rvm gemset create #{@name} || true'")
|
|
26
31
|
|
|
27
32
|
rvm_cmd = Ruby.rvm_prefix(ruby, @name)
|
|
28
33
|
|
|
29
|
-
# --- Install
|
|
30
|
-
|
|
31
|
-
rails_check_cmd = if rails_version
|
|
32
|
-
"#{rvm_cmd} gem list -i rails -v #{rails_version}"
|
|
33
|
-
else
|
|
34
|
-
"#{rvm_cmd} gem list -i rails"
|
|
35
|
-
end
|
|
34
|
+
# --- Install Bundler ---
|
|
35
|
+
System::Shell.run("bash -lc '#{rvm_cmd} gem install bundler --no-document'")
|
|
36
36
|
|
|
37
|
+
# --- Install Rails if missing ---
|
|
38
|
+
rails_version = @options[:rails]
|
|
39
|
+
rails_check_cmd = rails_version ? "#{rvm_cmd} gem list -i rails -v #{rails_version}" : "#{rvm_cmd} gem list -i rails"
|
|
37
40
|
unless system("bash -lc '#{rails_check_cmd}'")
|
|
38
|
-
puts "Installing Rails #{rails_version || 'latest'}
|
|
41
|
+
puts "Installing Rails #{rails_version || 'latest'}..."
|
|
39
42
|
install_cmd = rails_version ? "#{rvm_cmd} gem install rails -v #{rails_version}" : "#{rvm_cmd} gem install rails"
|
|
40
|
-
system("bash -lc '#{install_cmd}'") or abort(
|
|
43
|
+
system("bash -lc '#{install_cmd}'") or abort("Failed to install Rails #{rails_version || ''}")
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
# --- Create Rails app ---
|
|
44
47
|
puts "Creating Rails app #{@name} (Ruby #{ruby})..."
|
|
45
|
-
System::Shell.run("bash -lc '#{rvm_cmd} rails new #{app_path}
|
|
48
|
+
System::Shell.run("bash -lc '#{rvm_cmd} rails new #{app_path} \
|
|
49
|
+
--skip-importmap \
|
|
50
|
+
--skip-hotwire \
|
|
51
|
+
--skip-javascript \
|
|
52
|
+
--skip-solid'")
|
|
46
53
|
|
|
47
|
-
# --- Write ruby version/gemset
|
|
54
|
+
# --- Write ruby version/gemset ---
|
|
48
55
|
Dir.chdir(app_path) do
|
|
49
56
|
File.write('.ruby-version', "#{ruby}\n")
|
|
50
57
|
File.write('.ruby-gemset', "#{@name}\n")
|
|
51
|
-
|
|
52
|
-
puts 'Running bundle install...'
|
|
53
|
-
System::Shell.run("bash -lc '#{rvm_cmd} bundle install --jobs=4 --retry=3'")
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
# --- Database integration ---
|
|
57
61
|
if @options[:db]
|
|
58
|
-
adapter =
|
|
59
|
-
:mysql
|
|
60
|
-
else
|
|
61
|
-
:postgresql
|
|
62
|
-
end
|
|
63
|
-
|
|
62
|
+
adapter = @options[:mysql] ? :mysql : :postgresql
|
|
64
63
|
gem_name = adapter == :postgresql ? 'pg' : 'mysql2'
|
|
65
64
|
gemfile_path = File.join(app_path, 'Gemfile')
|
|
65
|
+
|
|
66
66
|
unless File.read(gemfile_path).include?(gem_name)
|
|
67
67
|
File.open(gemfile_path, 'a') do |f|
|
|
68
68
|
f.puts "\n# Added by Stable CLI"
|
|
@@ -71,17 +71,85 @@ module Stable
|
|
|
71
71
|
puts "✅ Added '#{gem_name}' gem to Gemfile"
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
#
|
|
75
|
-
System::Shell.run(
|
|
74
|
+
# Use correct Ruby/gemset for bundle install
|
|
75
|
+
System::Shell.run(rvm_run('bundle install --jobs=4 --retry=3', chdir: app_path))
|
|
76
76
|
|
|
77
|
-
# run adapter setup which will write database.yml and prepare
|
|
78
77
|
db_adapter = adapter == :mysql ? Database::MySQL : Database::Postgres
|
|
79
|
-
db_adapter.new(app_name: @name, app_path: app_path).setup
|
|
78
|
+
db_adapter.new(app_name: @name, app_path: app_path, ruby: ruby).setup
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# --- Run bundle & DB prepare ---
|
|
82
|
+
System::Shell.run(rvm_run('bundle install --jobs=4 --retry=3', chdir: app_path))
|
|
83
|
+
System::Shell.run(rvm_run('bundle exec rails db:prepare', chdir: app_path))
|
|
84
|
+
|
|
85
|
+
rails_version = `bash -lc 'cd #{app_path} && #{rvm_cmd} bundle exec rails runner "puts Rails.version"'`.strip.to_f
|
|
86
|
+
|
|
87
|
+
begin
|
|
88
|
+
rails_ver = Gem::Version.new(rails_version)
|
|
89
|
+
rescue ArgumentError
|
|
90
|
+
rails_ver = Gem::Version.new('0.0.0')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# only if Rails >= 7
|
|
94
|
+
if rails_ver >= Gem::Version.new('7.0.0')
|
|
95
|
+
# Gems to re-add
|
|
96
|
+
optional_gems = {
|
|
97
|
+
'importmap-rails' => nil,
|
|
98
|
+
'turbo-rails' => nil,
|
|
99
|
+
'stimulus-rails' => nil,
|
|
100
|
+
'solid_cache' => nil,
|
|
101
|
+
'solid_queue' => nil,
|
|
102
|
+
'solid_cable' => nil
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
gemfile = File.join(app_path, 'Gemfile')
|
|
106
|
+
|
|
107
|
+
# Add gems if not already present
|
|
108
|
+
optional_gems.each do |gem_name, version|
|
|
109
|
+
next if File.read(gemfile).match?(/^gem ['"]#{gem_name}['"]/)
|
|
110
|
+
|
|
111
|
+
File.open(gemfile, 'a') do |f|
|
|
112
|
+
f.puts version ? "gem '#{gem_name}', '#{version}'" : "gem '#{gem_name}'"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Install all new gems
|
|
117
|
+
System::Shell.run(rvm_run('bundle install', chdir: app_path))
|
|
118
|
+
|
|
119
|
+
# Run generators for installed gems
|
|
120
|
+
generators = [
|
|
121
|
+
'importmap:install',
|
|
122
|
+
'turbo:install stimulus:install',
|
|
123
|
+
'solid_cache:install solid_queue:install solid_cable:install'
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
generators.each do |cmd|
|
|
127
|
+
System::Shell.run(rvm_run("bundle exec rails #{cmd}", chdir: app_path))
|
|
128
|
+
end
|
|
80
129
|
end
|
|
81
130
|
|
|
82
|
-
# ---
|
|
83
|
-
|
|
84
|
-
|
|
131
|
+
# --- Add allowed host for Rails < 7.2 ---
|
|
132
|
+
if @options[:rails] && @options[:rails].to_f < 7.2
|
|
133
|
+
env_file = File.join(app_path, 'config/environments/development.rb')
|
|
134
|
+
|
|
135
|
+
if File.exist?(env_file)
|
|
136
|
+
content = File.read(env_file)
|
|
137
|
+
|
|
138
|
+
# Append host config inside the Rails.application.configure block
|
|
139
|
+
updated_content = content.gsub(/Rails\.application\.configure do(.*?)end/mm) do |_match|
|
|
140
|
+
inner = Regexp.last_match(1)
|
|
141
|
+
# Prevent duplicate entries
|
|
142
|
+
unless inner.include?(domain)
|
|
143
|
+
inner += " # allow local .test host for this app\n config.hosts << '#{domain}'\n"
|
|
144
|
+
end
|
|
145
|
+
"Rails.application.configure do#{inner}end"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
File.write(env_file, updated_content)
|
|
149
|
+
else
|
|
150
|
+
warn "Development environment file not found: #{env_file}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
85
153
|
|
|
86
154
|
# --- Hosts, certs, caddy ---
|
|
87
155
|
Services::HostsManager.add(domain)
|
|
@@ -89,50 +157,31 @@ module Stable
|
|
|
89
157
|
Services::CaddyManager.ensure_running!
|
|
90
158
|
Services::CaddyManager.reload
|
|
91
159
|
|
|
92
|
-
# --- Start
|
|
160
|
+
# --- Start Rails server ---
|
|
93
161
|
puts "Starting Rails server for #{@name} on port #{port}..."
|
|
94
162
|
log_file = File.join(app_path, 'log', 'stable.log')
|
|
95
163
|
FileUtils.mkdir_p(File.dirname(log_file))
|
|
96
164
|
|
|
97
165
|
abort "Port #{port} is already in use. Choose another port." if port_in_use?(port)
|
|
98
166
|
|
|
99
|
-
pid = spawn(
|
|
100
|
-
|
|
167
|
+
pid = spawn(
|
|
168
|
+
'bash', '-lc',
|
|
169
|
+
"cd \"#{app_path}\" && #{rvm_cmd} bundle exec rails s -p #{port} -b 127.0.0.1",
|
|
170
|
+
out: log_file, err: log_file
|
|
171
|
+
)
|
|
101
172
|
Process.detach(pid)
|
|
102
|
-
|
|
103
173
|
AppRegistry.update(@name, started_at: Time.now.to_i, pid: pid)
|
|
104
174
|
|
|
105
175
|
sleep 1.5
|
|
106
176
|
wait_for_port(port)
|
|
107
|
-
prefix = @options[:skip_ssl] ? 'http' : 'https'
|
|
108
|
-
display_domain = if @options[:skip_ssl]
|
|
109
|
-
"#{domain}:#{port}"
|
|
110
|
-
else
|
|
111
|
-
domain
|
|
112
|
-
end
|
|
113
177
|
|
|
178
|
+
prefix = @options[:skip_ssl] ? 'http' : 'https'
|
|
179
|
+
display_domain = @options[:skip_ssl] ? "#{domain}:#{port}" : domain
|
|
114
180
|
puts "✔ #{@name} running at #{prefix}://#{display_domain}"
|
|
115
181
|
end
|
|
116
182
|
|
|
117
183
|
private
|
|
118
184
|
|
|
119
|
-
def create_rails_app
|
|
120
|
-
System::Shell.run("rails new #{@name}")
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def setup_database
|
|
124
|
-
return unless @options[:mysql] || @options[:postgres]
|
|
125
|
-
|
|
126
|
-
adapter =
|
|
127
|
-
if @options[:mysql]
|
|
128
|
-
Database::MySQL
|
|
129
|
-
else
|
|
130
|
-
Database::Postgres
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
adapter.new(@name).setup
|
|
134
|
-
end
|
|
135
|
-
|
|
136
185
|
def next_free_port
|
|
137
186
|
used_ports = Services::AppRegistry.all.map { |a| a[:port] }
|
|
138
187
|
port = 3000
|
|
@@ -157,6 +206,13 @@ module Stable
|
|
|
157
206
|
sleep 0.5
|
|
158
207
|
end
|
|
159
208
|
end
|
|
209
|
+
|
|
210
|
+
def rvm_run(cmd, chdir: nil, ruby: nil)
|
|
211
|
+
ruby ||= @options[:ruby] || RUBY_VERSION
|
|
212
|
+
gemset = @name
|
|
213
|
+
cd = chdir ? "cd #{chdir} && " : ''
|
|
214
|
+
"bash -lc '#{cd}source #{Dir.home}/.rvm/scripts/rvm && rvm #{ruby}@#{gemset} do #{cmd}'"
|
|
215
|
+
end
|
|
160
216
|
end
|
|
161
217
|
end
|
|
162
218
|
end
|
|
@@ -131,6 +131,7 @@ module Stable
|
|
|
131
131
|
|
|
132
132
|
until valid_pem?(path)
|
|
133
133
|
raise "Invalid PEM file: #{path}" if Time.now - start > timeout
|
|
134
|
+
|
|
134
135
|
sleep 0.1
|
|
135
136
|
end
|
|
136
137
|
end
|
|
@@ -142,11 +143,16 @@ module Stable
|
|
|
142
143
|
begin
|
|
143
144
|
FileUtils.chown_R(Etc.getlogin, nil, certs_dir)
|
|
144
145
|
rescue StandardError
|
|
146
|
+
nil
|
|
145
147
|
end
|
|
146
148
|
|
|
147
149
|
Dir.glob("#{certs_dir}/*.pem").each do |pem|
|
|
148
150
|
mode = pem.end_with?('-key.pem') ? 0o600 : 0o644
|
|
149
|
-
|
|
151
|
+
begin
|
|
152
|
+
FileUtils.chmod(mode, pem)
|
|
153
|
+
rescue StandardError
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
150
156
|
end
|
|
151
157
|
end
|
|
152
158
|
end
|
|
@@ -4,14 +4,20 @@ module Stable
|
|
|
4
4
|
module Services
|
|
5
5
|
module Database
|
|
6
6
|
class Base
|
|
7
|
-
def initialize(app_name:, app_path:)
|
|
7
|
+
def initialize(app_name:, app_path:, ruby:)
|
|
8
8
|
@app_name = app_name
|
|
9
9
|
@app_path = app_path
|
|
10
|
+
@ruby = ruby
|
|
11
|
+
@database_name = @app_name
|
|
12
|
+
.downcase
|
|
13
|
+
.gsub(/[^a-z0-9_]/, '_')
|
|
14
|
+
.gsub(/_+/, '_')
|
|
15
|
+
.gsub(/^_+|_+$/, '')
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def prepare
|
|
13
19
|
System::Shell.run(
|
|
14
|
-
|
|
20
|
+
bash_rvm('bundle exec rails db:prepare')
|
|
15
21
|
)
|
|
16
22
|
end
|
|
17
23
|
|
|
@@ -22,10 +28,10 @@ module Stable
|
|
|
22
28
|
'default' => base_config(creds),
|
|
23
29
|
'development' => base_config(creds),
|
|
24
30
|
'test' => base_config(creds).merge(
|
|
25
|
-
'database' => "#{@
|
|
31
|
+
'database' => "#{@database_name}_test"
|
|
26
32
|
),
|
|
27
33
|
'production' => base_config(creds).merge(
|
|
28
|
-
'database' => "#{@
|
|
34
|
+
'database' => "#{@database_name}_production"
|
|
29
35
|
)
|
|
30
36
|
}
|
|
31
37
|
|
|
@@ -36,6 +42,14 @@ module Stable
|
|
|
36
42
|
def base_config(_creds)
|
|
37
43
|
raise NotImplementedError
|
|
38
44
|
end
|
|
45
|
+
|
|
46
|
+
def bash_rvm(cmd)
|
|
47
|
+
raise 'ruby not set' unless @ruby
|
|
48
|
+
raise 'app_name not set' unless @app_name
|
|
49
|
+
raise 'app_path not set' unless @app_path
|
|
50
|
+
|
|
51
|
+
"bash -lc 'cd #{@app_path} && source #{Ruby.rvm_script} && rvm #{@ruby}@#{@app_name} do #{cmd}'"
|
|
52
|
+
end
|
|
39
53
|
end
|
|
40
54
|
end
|
|
41
55
|
end
|
|
@@ -13,7 +13,7 @@ module Stable
|
|
|
13
13
|
|
|
14
14
|
def create_database(creds)
|
|
15
15
|
System::Shell.run(
|
|
16
|
-
"mysql -u #{creds[:user]} -p#{creds[:password]} -e 'CREATE DATABASE IF NOT EXISTS #{@
|
|
16
|
+
"mysql -u #{creds[:user]} -p#{creds[:password]} -e 'CREATE DATABASE IF NOT EXISTS #{@database_name};'"
|
|
17
17
|
)
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@ module Stable
|
|
|
24
24
|
'adapter' => 'mysql2',
|
|
25
25
|
'encoding' => 'utf8mb4',
|
|
26
26
|
'pool' => 5,
|
|
27
|
-
'database' => @
|
|
27
|
+
'database' => @database_name,
|
|
28
28
|
'username' => creds[:user],
|
|
29
29
|
'password' => creds[:password],
|
|
30
30
|
'host' => 'localhost'
|
data/lib/stable/services/ruby.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Stable
|
|
|
37
37
|
rvm_script = File.expand_path('~/.rvm/scripts/rvm')
|
|
38
38
|
abort 'RVM installed but could not be loaded' unless File.exist?(rvm_script)
|
|
39
39
|
|
|
40
|
-
ENV['PATH'] = "#{File.expand_path('~/.rvm/bin')}:#{ENV
|
|
40
|
+
ENV['PATH'] = "#{File.expand_path('~/.rvm/bin')}:#{ENV.fetch('PATH', nil)}"
|
|
41
41
|
|
|
42
42
|
system(%(bash -lc "source #{rvm_script} && rvm --version")) || abort('RVM installed but not functional')
|
|
43
43
|
end
|
|
@@ -97,6 +97,15 @@ module Stable
|
|
|
97
97
|
"rvm #{ruby} do"
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
|
+
|
|
101
|
+
def self.clean_rvm_exec(ruby, gemset, cmd)
|
|
102
|
+
<<~CMD
|
|
103
|
+
bash -lc '
|
|
104
|
+
source #{rvm_script};
|
|
105
|
+
rvm #{ruby}@#{gemset} --create do #{cmd}
|
|
106
|
+
'
|
|
107
|
+
CMD
|
|
108
|
+
end
|
|
100
109
|
end
|
|
101
110
|
end
|
|
102
111
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Validators
|
|
5
|
+
class AppName
|
|
6
|
+
VALID_PATTERN = /\A[a-z0-9]+(-[a-z0-9]+)*\z/
|
|
7
|
+
MAX_LENGTH = 63
|
|
8
|
+
|
|
9
|
+
def self.call!(name)
|
|
10
|
+
normalized = normalize(name)
|
|
11
|
+
|
|
12
|
+
unless valid?(normalized)
|
|
13
|
+
raise Thor::Error, <<~MSG
|
|
14
|
+
Invalid app name: "#{name}"
|
|
15
|
+
|
|
16
|
+
Use only:
|
|
17
|
+
- lowercase letters (a-z)
|
|
18
|
+
- numbers (0-9)
|
|
19
|
+
- hyphens (-)
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
- no spaces or underscores
|
|
23
|
+
- cannot start or end with a hyphen
|
|
24
|
+
- max #{MAX_LENGTH} characters
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
stable new my-app
|
|
28
|
+
MSG
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
normalized
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.normalize(name)
|
|
35
|
+
name
|
|
36
|
+
.downcase
|
|
37
|
+
.strip
|
|
38
|
+
.gsub(/\s+/, '-') # spaces → hyphens
|
|
39
|
+
.gsub(/[^a-z0-9-]/, '') # drop invalid chars
|
|
40
|
+
.gsub(/-+/, '-') # collapse hyphens
|
|
41
|
+
.gsub(/\A-|-+\z/, '') # trim hyphens
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.valid?(name)
|
|
45
|
+
name.length <= MAX_LENGTH && VALID_PATTERN.match?(name)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/stable.rb
CHANGED
|
@@ -14,20 +14,22 @@ require_relative 'stable/scanner'
|
|
|
14
14
|
require_relative 'stable/bootstrap'
|
|
15
15
|
require_relative 'stable/db_manager'
|
|
16
16
|
|
|
17
|
-
Dir[File.join(__dir__, 'stable', 'services', '**', '*.rb')].
|
|
18
|
-
Dir[File.join(__dir__, 'stable', 'commands', '**', '*.rb')].
|
|
19
|
-
Dir[File.join(__dir__, 'stable', 'system', '**', '*.rb')].
|
|
20
|
-
Dir[File.join(__dir__, 'stable', 'utils', '**', '*.rb')].
|
|
21
|
-
Dir[File.join(__dir__, 'stable', 'config', '**', '*.rb')].
|
|
17
|
+
Dir[File.join(__dir__, 'stable', 'services', '**', '*.rb')].each { |f| require f }
|
|
18
|
+
Dir[File.join(__dir__, 'stable', 'commands', '**', '*.rb')].each { |f| require f }
|
|
19
|
+
Dir[File.join(__dir__, 'stable', 'system', '**', '*.rb')].each { |f| require f }
|
|
20
|
+
Dir[File.join(__dir__, 'stable', 'utils', '**', '*.rb')].each { |f| require f }
|
|
21
|
+
Dir[File.join(__dir__, 'stable', 'config', '**', '*.rb')].each { |f| require f }
|
|
22
|
+
Dir[File.join(__dir__, 'stable', 'validators', '**', '*.rb')].each { |f| require f }
|
|
22
23
|
|
|
23
|
-
AppRegistry = Stable::Services::AppRegistry unless defined?(
|
|
24
|
-
HostsManager = Stable::Services::HostsManager unless defined?(
|
|
25
|
-
CaddyManager = Stable::Services::CaddyManager unless defined?(
|
|
26
|
-
ProcessManager = Stable::Services::ProcessManager unless defined?(
|
|
27
|
-
AppCreator = Stable::Services::AppCreator unless defined?(
|
|
28
|
-
AppStarter = Stable::Services::AppStarter unless defined?(
|
|
29
|
-
AppStopper = Stable::Services::AppStopper unless defined?(
|
|
30
|
-
AppRemover = Stable::Services::AppRemover unless defined?(
|
|
31
|
-
Database = Stable::Services::Database unless defined?(
|
|
32
|
-
Ruby = Stable::Services::Ruby unless defined?(
|
|
33
|
-
Commands = Stable::Commands unless defined?(
|
|
24
|
+
AppRegistry = Stable::Services::AppRegistry unless defined?(AppRegistry)
|
|
25
|
+
HostsManager = Stable::Services::HostsManager unless defined?(HostsManager)
|
|
26
|
+
CaddyManager = Stable::Services::CaddyManager unless defined?(CaddyManager)
|
|
27
|
+
ProcessManager = Stable::Services::ProcessManager unless defined?(ProcessManager)
|
|
28
|
+
AppCreator = Stable::Services::AppCreator unless defined?(AppCreator)
|
|
29
|
+
AppStarter = Stable::Services::AppStarter unless defined?(AppStarter)
|
|
30
|
+
AppStopper = Stable::Services::AppStopper unless defined?(AppStopper)
|
|
31
|
+
AppRemover = Stable::Services::AppRemover unless defined?(AppRemover)
|
|
32
|
+
Database = Stable::Services::Database unless defined?(Database)
|
|
33
|
+
Ruby = Stable::Services::Ruby unless defined?(Ruby)
|
|
34
|
+
Commands = Stable::Commands unless defined?(Commands)
|
|
35
|
+
Validators = Stable::Validators unless defined?(Validators)
|
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.
|
|
4
|
+
version: 0.7.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Danny Simfukwe
|
|
@@ -94,10 +94,12 @@ files:
|
|
|
94
94
|
- lib/stable/services/setup_runner.rb
|
|
95
95
|
- lib/stable/system/shell.rb
|
|
96
96
|
- lib/stable/utils/prompts.rb
|
|
97
|
+
- lib/stable/validators/app_name.rb
|
|
97
98
|
homepage: https://github.com/dannysimfukwe/stable-rails
|
|
98
99
|
licenses:
|
|
99
100
|
- MIT
|
|
100
|
-
metadata:
|
|
101
|
+
metadata:
|
|
102
|
+
rubygems_mfa_required: 'true'
|
|
101
103
|
rdoc_options: []
|
|
102
104
|
require_paths:
|
|
103
105
|
- lib
|