stable-cli-rails 0.7.10 → 0.7.11
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 +2 -103
- data/lib/stable/commands/list.rb +1 -8
- data/lib/stable/services/app_creator.rb +6 -5
- data/lib/stable/services/caddy_manager.rb +7 -1
- data/lib/stable/services/database/base.rb +7 -2
- data/lib/stable/services/database/mysql.rb +2 -2
- data/lib/stable/services/database/postgres.rb +1 -1
- data/lib/stable/services/ruby.rb +1 -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: fe9114ae5eae07811ec911b967cef33620ebf62c7df4f30c202f3743f7d6bea5
|
|
4
|
+
data.tar.gz: 91920b483e2c26e58d1ef7ca7c786ea5cf91a4849ac16ec276fee4cdbf72d878
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbdc4b736300bd5a395f67b9138e3aa7410a0184d4f5ea2be6f5e3445934fcf57588b526e5b957ee0c551247ab3013febd29eaffd96cc7bde43f982df91e3b53
|
|
7
|
+
data.tar.gz: 19e8dd338cd34dd6621159785171c970468d43b0f423048c4c3fc10bd4ab4851cedd59091a03e1748acf990fa1853a7b1da33bbadba552bae186a71a69799f3e
|
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!
|
|
@@ -32,7 +30,8 @@ module Stable
|
|
|
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'
|
|
34
32
|
def new(name, ruby: RUBY_VERSION, rails: nil, port: nil)
|
|
35
|
-
|
|
33
|
+
safe_name = Validators::AppName.call!(name)
|
|
34
|
+
Commands::New.new(safe_name, options).call
|
|
36
35
|
end
|
|
37
36
|
|
|
38
37
|
desc 'list', 'List detected apps'
|
|
@@ -159,108 +158,8 @@ module Stable
|
|
|
159
158
|
system("lsof -i tcp:#{port} > /dev/null 2>&1")
|
|
160
159
|
end
|
|
161
160
|
|
|
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
161
|
def dedupe_registry!
|
|
255
162
|
Services::AppRegistry.dedupe
|
|
256
163
|
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
164
|
end
|
|
266
165
|
end
|
data/lib/stable/commands/list.rb
CHANGED
|
@@ -15,7 +15,8 @@ module Stable
|
|
|
15
15
|
domain = "#{@name}.test"
|
|
16
16
|
|
|
17
17
|
# --- Register app in registry ---
|
|
18
|
-
Services::AppRegistry.add(name: @name, path: app_path, domain: domain, port: port, ruby: ruby, started_at: nil,
|
|
18
|
+
Services::AppRegistry.add(name: @name, path: app_path, domain: domain, port: port, ruby: ruby, started_at: nil,
|
|
19
|
+
pid: nil)
|
|
19
20
|
|
|
20
21
|
abort "Folder already exists: #{app_path}" if File.exist?(app_path)
|
|
21
22
|
|
|
@@ -106,10 +107,10 @@ module Stable
|
|
|
106
107
|
wait_for_port(port)
|
|
107
108
|
prefix = @options[:skip_ssl] ? 'http' : 'https'
|
|
108
109
|
display_domain = if @options[:skip_ssl]
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
"#{domain}:#{port}"
|
|
111
|
+
else
|
|
112
|
+
domain
|
|
113
|
+
end
|
|
113
114
|
|
|
114
115
|
puts "✔ #{@name} running at #{prefix}://#{display_domain}"
|
|
115
116
|
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
|
|
@@ -7,6 +7,11 @@ module Stable
|
|
|
7
7
|
def initialize(app_name:, app_path:)
|
|
8
8
|
@app_name = app_name
|
|
9
9
|
@app_path = app_path
|
|
10
|
+
@database_name = @app_name
|
|
11
|
+
.downcase
|
|
12
|
+
.gsub(/[^a-z0-9_]/, '_')
|
|
13
|
+
.gsub(/_+/, '_')
|
|
14
|
+
.gsub(/^_+|_+$/, '')
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
def prepare
|
|
@@ -22,10 +27,10 @@ module Stable
|
|
|
22
27
|
'default' => base_config(creds),
|
|
23
28
|
'development' => base_config(creds),
|
|
24
29
|
'test' => base_config(creds).merge(
|
|
25
|
-
'database' => "#{@
|
|
30
|
+
'database' => "#{@database_name}_test"
|
|
26
31
|
),
|
|
27
32
|
'production' => base_config(creds).merge(
|
|
28
|
-
'database' => "#{@
|
|
33
|
+
'database' => "#{@database_name}_production"
|
|
29
34
|
)
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -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
|
|
@@ -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.11
|
|
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
|