stable-cli-rails 0.8.4 ā 0.8.5
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 +16 -2
- data/lib/stable/commands/destroy.rb +1 -88
- data/lib/stable/commands/open.rb +1 -41
- data/lib/stable/commands/share.rb +47 -0
- data/lib/stable/commands/unshare.rb +19 -0
- data/lib/stable/commands/upgrade_ruby.rb +1 -143
- data/lib/stable/commands/workdir.rb +33 -0
- data/lib/stable/services/app_destroyer.rb +97 -0
- data/lib/stable/services/app_opener.rb +55 -0
- data/lib/stable/services/app_upgrader.rb +159 -0
- data/lib/stable/services/cli/qrcode.rb +22 -0
- data/lib/stable/services/rails/host_authorization.rb +64 -0
- data/lib/stable/services/tunneling/base_provider.rb +14 -0
- data/lib/stable/services/tunneling/manager.rb +32 -0
- data/lib/stable/services/tunneling/providers/ngrok.rb +81 -0
- data/lib/stable/services/tunneling/providers/stable.rb +17 -0
- metadata +28 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f9eaea11c07941f7a09145d69497c0aff9ee2d85cc2d686fe238b5031e0c554b
|
|
4
|
+
data.tar.gz: c9ba35f2b2a9ae12be1070d9811fc75e66d21c4f751fc99a2ecc0cad59128e7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 866da469383dc2e71c0bc766a813391586be2f0af2447b8a063565c4f30d22b6cb45f113c8a53736bd5b8e6dfb1dcf647b5ee42667b5ad2601b1d703cad31f0b
|
|
7
|
+
data.tar.gz: fec6d17ade09f6bf898eed2b12de5ca060e249b5e3ca5b6cf72fdf78778dd743ff23f147e410767adfd8ae672cd33c640027b639c2c99c9f3dcfad0f5b29a3b1
|
data/lib/stable/cli.rb
CHANGED
|
@@ -136,8 +136,22 @@ module Stable
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
desc 'open APP', 'Open a running app in the browser'
|
|
139
|
-
def open(
|
|
140
|
-
Stable::Commands::Open.new(
|
|
139
|
+
def open(name)
|
|
140
|
+
Stable::Commands::Open.new(name).call
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
desc 'share APP [PROVIDER]', 'Share app via public tunnel'
|
|
144
|
+
method_option :provider, type: :string, default: nil, desc: 'Tunnel provider (ngrok, stable)'
|
|
145
|
+
method_option :qrcode, type: :boolean, default: false, desc: 'Generate a Qr code for the shared URL'
|
|
146
|
+
def share(name, provider = nil, qrcode: false)
|
|
147
|
+
provider ||= options[:provider] || 'ngrok'
|
|
148
|
+
Commands::Share.new(name, provider: provider.to_sym, qrcode: options[:qrcode]).call
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
desc 'workdir APP', 'Open the app folder in a code editor'
|
|
152
|
+
method_option :editor, type: :string, default: 'vscode', desc: 'Code editor to use (vscode, sublime, atom, etc.)'
|
|
153
|
+
def workdir(name)
|
|
154
|
+
Commands::Workdir.new(name, options[:editor]).call
|
|
141
155
|
end
|
|
142
156
|
|
|
143
157
|
private
|
|
@@ -11,94 +11,7 @@ module Stable
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def call
|
|
14
|
-
|
|
15
|
-
abort 'App not found' unless app
|
|
16
|
-
|
|
17
|
-
display_warning(app)
|
|
18
|
-
return unless confirm_destruction
|
|
19
|
-
|
|
20
|
-
puts "\nšļø Destroying #{@name}..."
|
|
21
|
-
perform_destruction(app)
|
|
22
|
-
puts "ā
Successfully destroyed #{@name}"
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def display_warning(app)
|
|
28
|
-
puts "ā ļø WARNING: This will permanently delete the application '#{@name}'"
|
|
29
|
-
puts " Path: #{app[:path]}"
|
|
30
|
-
puts " Domain: #{app[:domain]}"
|
|
31
|
-
puts ' This action CANNOT be undone!'
|
|
32
|
-
puts ''
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def confirm_destruction
|
|
36
|
-
print "Type '#{@name}' to confirm destruction: "
|
|
37
|
-
confirmation = $stdin.gets&.strip
|
|
38
|
-
puts ''
|
|
39
|
-
|
|
40
|
-
if confirmation == @name
|
|
41
|
-
true
|
|
42
|
-
else
|
|
43
|
-
puts "ā Destruction cancelled - confirmation didn't match"
|
|
44
|
-
false
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def perform_destruction(app)
|
|
49
|
-
# Stop the app if running
|
|
50
|
-
Services::ProcessManager.stop(app)
|
|
51
|
-
|
|
52
|
-
# Remove from infrastructure
|
|
53
|
-
Services::HostsManager.remove(app[:domain])
|
|
54
|
-
Services::CaddyManager.remove(app[:domain])
|
|
55
|
-
Services::AppRegistry.remove(@name)
|
|
56
|
-
|
|
57
|
-
# Clean up RVM gemset
|
|
58
|
-
cleanup_rvm_gemset(app)
|
|
59
|
-
|
|
60
|
-
# Delete the project directory
|
|
61
|
-
delete_project_directory(app[:path])
|
|
62
|
-
|
|
63
|
-
# Reload Caddy
|
|
64
|
-
Services::CaddyManager.reload
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def cleanup_rvm_gemset(app)
|
|
68
|
-
# Skip RVM operations in test mode
|
|
69
|
-
return if ENV['STABLE_TEST_MODE']
|
|
70
|
-
|
|
71
|
-
# Only clean up RVM gemsets on Unix-like systems (macOS/Linux)
|
|
72
|
-
# Windows uses different Ruby version managers
|
|
73
|
-
return unless Stable::Utils::Platform.unix?
|
|
74
|
-
|
|
75
|
-
ruby_version = app[:ruby]
|
|
76
|
-
# Handle different ruby version formats (e.g., "3.4.7", "ruby-3.4.7")
|
|
77
|
-
clean_ruby_version = ruby_version.to_s.sub(/^ruby-/, '')
|
|
78
|
-
gemset_name = "#{clean_ruby_version}@#{@name}"
|
|
79
|
-
|
|
80
|
-
puts " Cleaning up RVM gemset #{gemset_name}..."
|
|
81
|
-
begin
|
|
82
|
-
# Use system to run RVM command to delete the gemset
|
|
83
|
-
system("bash -lc 'source ~/.rvm/scripts/rvm && rvm gemset delete #{gemset_name} --force' 2>/dev/null || true")
|
|
84
|
-
puts " ā
RVM gemset #{gemset_name} cleaned up"
|
|
85
|
-
rescue StandardError => e
|
|
86
|
-
puts " ā ļø Could not clean up RVM gemset #{gemset_name}: #{e.message}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def delete_project_directory(path)
|
|
91
|
-
if ENV['STABLE_TEST_MODE']
|
|
92
|
-
puts ' Deleting project directory...'
|
|
93
|
-
return
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
if File.exist?(path)
|
|
97
|
-
puts ' Deleting project directory...'
|
|
98
|
-
FileUtils.rm_rf(path)
|
|
99
|
-
else
|
|
100
|
-
puts ' Project directory not found (already deleted?)'
|
|
101
|
-
end
|
|
14
|
+
Services::AppDestroyer.new(@name).call
|
|
102
15
|
end
|
|
103
16
|
end
|
|
104
17
|
end
|
data/lib/stable/commands/open.rb
CHANGED
|
@@ -9,47 +9,7 @@ module Stable
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def call
|
|
12
|
-
|
|
13
|
-
abort "App '#{@app_name}' not found" unless app
|
|
14
|
-
|
|
15
|
-
abort "App '#{@app_name}' is not running" unless app[:pid] && process_alive?(app[:pid])
|
|
16
|
-
url = build_url(app)
|
|
17
|
-
open_browser(url)
|
|
18
|
-
puts "ā Opened #{url}"
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def build_url(app)
|
|
24
|
-
scheme = app[:skip_ssl] ? 'http' : 'https'
|
|
25
|
-
if app[:domain]
|
|
26
|
-
"#{scheme}://#{app[:domain]}"
|
|
27
|
-
else
|
|
28
|
-
"#{scheme}://127.0.0.1:#{app[:port]}"
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def open_browser(url)
|
|
33
|
-
cmd =
|
|
34
|
-
case RbConfig::CONFIG['host_os']
|
|
35
|
-
when /darwin/
|
|
36
|
-
"open #{url}"
|
|
37
|
-
when /linux/
|
|
38
|
-
"xdg-open #{url}"
|
|
39
|
-
when /mswin|mingw/
|
|
40
|
-
"start #{url}"
|
|
41
|
-
else
|
|
42
|
-
abort 'Unsupported OS'
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
system(cmd) || abort('Failed to open browser')
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def process_alive?(pid)
|
|
49
|
-
Process.kill(0, pid)
|
|
50
|
-
true
|
|
51
|
-
rescue Errno::ESRCH
|
|
52
|
-
false
|
|
12
|
+
Services::AppOpener.new(@app_name).call
|
|
53
13
|
end
|
|
54
14
|
end
|
|
55
15
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Commands
|
|
5
|
+
# Share app's public url
|
|
6
|
+
class Share
|
|
7
|
+
def initialize(app_name, provider: :ngrok, qrcode: false)
|
|
8
|
+
@app_name = app_name
|
|
9
|
+
@provider = provider
|
|
10
|
+
@qrcode = qrcode
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
app = Services::AppRegistry.find(@app_name)
|
|
15
|
+
abort "App '#{@app_name}' not found" unless app
|
|
16
|
+
abort "App '#{@app_name}' is not running" unless running?(app)
|
|
17
|
+
Services::Rails::HostAuthorization.allow_ngrok!(app[:path])
|
|
18
|
+
Services::ProcessManager.stop(app) # stop the app
|
|
19
|
+
Services::ProcessManager.start(app) # restart the app
|
|
20
|
+
|
|
21
|
+
# Pass the real app port here
|
|
22
|
+
url = Services::Tunneling::Manager
|
|
23
|
+
.new(provider: @provider)
|
|
24
|
+
.expose_domain(app[:domain], port: app[:port], skip_ssl: app[:skip_ssl])
|
|
25
|
+
|
|
26
|
+
puts "š Shared #{@app_name} at:"
|
|
27
|
+
puts " #{url}"
|
|
28
|
+
|
|
29
|
+
return unless @qrcode
|
|
30
|
+
|
|
31
|
+
Services::Cli::QrCode.print(url)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def running?(app)
|
|
37
|
+
pid = app[:pid]
|
|
38
|
+
return false unless pid
|
|
39
|
+
|
|
40
|
+
Process.kill(0, pid)
|
|
41
|
+
true
|
|
42
|
+
rescue Errno::ESRCH
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Commands
|
|
5
|
+
# Unshare app's public url
|
|
6
|
+
class UnShare
|
|
7
|
+
def initialize(app_name, provider: :ngrok)
|
|
8
|
+
@app_name = app_name
|
|
9
|
+
@provider = provider
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
app = Services::AppRegistry.find(@app_name)
|
|
14
|
+
abort "App '#{@app_name}' not found" unless app
|
|
15
|
+
Stable::Services::Rails::HostAuthorization.remove_ngrok!(app[:path])
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -12,149 +12,7 @@ module Stable
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call
|
|
15
|
-
|
|
16
|
-
unless app
|
|
17
|
-
puts "No app named #{@name}"
|
|
18
|
-
return
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
current_version = app[:ruby] || RUBY_VERSION
|
|
22
|
-
|
|
23
|
-
puts "#{action(current_version, @version)} #{@name} from Ruby #{current_version} to #{@version}..."
|
|
24
|
-
puts ''
|
|
25
|
-
|
|
26
|
-
# Install the target Ruby version if needed
|
|
27
|
-
platform = Stable::Utils::Platform.current
|
|
28
|
-
|
|
29
|
-
if platform == :windows
|
|
30
|
-
puts 'ā ļø Windows detected - Ruby version managers work differently on Windows'
|
|
31
|
-
puts " Please manually install Ruby #{@version} using RubyInstaller or your preferred method"
|
|
32
|
-
puts ' Recommended: https://rubyinstaller.org/'
|
|
33
|
-
puts ' Then update your PATH to use the new Ruby version'
|
|
34
|
-
puts ''
|
|
35
|
-
puts "After installing Ruby #{@version}, update the app configuration manually:"
|
|
36
|
-
puts " - Edit .ruby-version file to contain: #{@version}"
|
|
37
|
-
puts ' - Run: bundle install (in the app directory)'
|
|
38
|
-
return
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
if Stable::Services::Ruby.rvm_available?
|
|
42
|
-
puts "Ensuring Ruby #{@version} is available..."
|
|
43
|
-
system("bash -lc 'rvm install #{@version}'") unless ENV['STABLE_TEST_MODE']
|
|
44
|
-
elsif Stable::Services::Ruby.rbenv_available?
|
|
45
|
-
puts "Ensuring Ruby #{@version} is available..."
|
|
46
|
-
system("rbenv install #{@version}") unless ENV['STABLE_TEST_MODE']
|
|
47
|
-
else
|
|
48
|
-
puts 'ā No supported Ruby version manager found'
|
|
49
|
-
puts ' On macOS/Linux, install RVM (https://rvm.io/) or rbenv (https://github.com/rbenv/rbenv)'
|
|
50
|
-
puts ' On Windows, use RubyInstaller (https://rubyinstaller.org/)'
|
|
51
|
-
return
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Remove current Ruby environment and install new one fresh
|
|
55
|
-
puts "š Upgrading #{@name} from Ruby #{current_version} to #{@version}..."
|
|
56
|
-
|
|
57
|
-
# 1. Remove current Ruby version/gemset
|
|
58
|
-
cleanup_rvm_gemset(app)
|
|
59
|
-
|
|
60
|
-
# 2. Install new Ruby version
|
|
61
|
-
setup_new_ruby_version(app, @version)
|
|
62
|
-
|
|
63
|
-
puts ''
|
|
64
|
-
puts "ā
#{@name} #{past_tense_action(action(current_version, @version))} to Ruby #{@version}!"
|
|
65
|
-
puts " Old gemset cleared, fresh #{@version}@#{@name} gemset created with gems"
|
|
66
|
-
puts ''
|
|
67
|
-
puts "Start with: stable start #{@name}"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def cleanup_rvm_gemset(app)
|
|
73
|
-
# Skip RVM operations in test mode
|
|
74
|
-
return if ENV['STABLE_TEST_MODE']
|
|
75
|
-
|
|
76
|
-
# Only clean up RVM gemsets on Unix-like systems (macOS/Linux)
|
|
77
|
-
# Windows uses different Ruby version managers
|
|
78
|
-
return unless Stable::Utils::Platform.unix?
|
|
79
|
-
|
|
80
|
-
ruby_version = app[:ruby]
|
|
81
|
-
# Handle different ruby version formats (e.g., "3.4.7", "ruby-3.4.7")
|
|
82
|
-
clean_ruby_version = ruby_version.to_s.sub(/^ruby-/, '')
|
|
83
|
-
gemset_name = "#{clean_ruby_version}@#{@name}"
|
|
84
|
-
|
|
85
|
-
puts " Cleaning up RVM gemset #{gemset_name}..."
|
|
86
|
-
begin
|
|
87
|
-
# Use system to run RVM command to delete the gemset
|
|
88
|
-
system("bash -lc 'source ~/.rvm/scripts/rvm && rvm gemset delete #{gemset_name} --force' 2>/dev/null || true")
|
|
89
|
-
puts " ā
RVM gemset #{gemset_name} cleaned up"
|
|
90
|
-
rescue StandardError => e
|
|
91
|
-
puts " ā ļø Could not clean up RVM gemset #{gemset_name}: #{e.message}"
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def setup_new_ruby_version(app, new_version)
|
|
96
|
-
unless ENV['STABLE_TEST_MODE']
|
|
97
|
-
Stable::Services::Ruby.ensure_version(new_version)
|
|
98
|
-
Stable::Services::Ruby.ensure_rvm!
|
|
99
|
-
|
|
100
|
-
# Create gemset
|
|
101
|
-
Stable::System::Shell.run("bash -lc 'source #{Stable::Services::Ruby.rvm_script} && rvm #{new_version} do rvm gemset create #{@name} || true'")
|
|
102
|
-
|
|
103
|
-
rvm_cmd = Stable::Services::Ruby.rvm_prefix(new_version, @name)
|
|
104
|
-
|
|
105
|
-
# Install Bundler
|
|
106
|
-
Stable::System::Shell.run("bash -lc '#{rvm_cmd} gem install bundler --no-document'")
|
|
107
|
-
|
|
108
|
-
# Run bundle install
|
|
109
|
-
Stable::System::Shell.run(rvm_run('bundle install --jobs=4 --retry=3', chdir: app[:path]))
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Update app configuration
|
|
113
|
-
unless ENV['STABLE_TEST_MODE']
|
|
114
|
-
Dir.chdir(app[:path]) do
|
|
115
|
-
File.write('.ruby-version', "#{new_version}\n")
|
|
116
|
-
File.write('.ruby-gemset', "#{@name}\n")
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Update registry
|
|
121
|
-
Services::AppRegistry.update(@name, ruby: new_version)
|
|
122
|
-
puts " ā
New Ruby #{new_version} environment set up with gems"
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def rvm_run(cmd, chdir: nil)
|
|
126
|
-
cd = chdir ? "cd #{chdir} && " : ''
|
|
127
|
-
"bash -lc '#{cd}source #{Dir.home}/.rvm/scripts/rvm && rvm #{@version}@#{@name} do #{cmd}'"
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def action(current_version, new_version)
|
|
131
|
-
current_parts = current_version.split('.').map(&:to_i)
|
|
132
|
-
new_parts = new_version.split('.').map(&:to_i)
|
|
133
|
-
|
|
134
|
-
if new_parts[0] > current_parts[0] ||
|
|
135
|
-
(new_parts[0] == current_parts[0] && new_parts[1] > current_parts[1]) ||
|
|
136
|
-
(new_parts[0] == current_parts[0] && new_parts[1] == current_parts[1] && new_parts[2] > current_parts[2])
|
|
137
|
-
'Upgrading'
|
|
138
|
-
elsif new_parts[0] < current_parts[0] ||
|
|
139
|
-
(new_parts[0] == current_parts[0] && new_parts[1] < current_parts[1]) ||
|
|
140
|
-
(new_parts[0] == current_parts[0] && new_parts[1] == current_parts[1] && new_parts[2] < current_parts[2])
|
|
141
|
-
'Downgrading'
|
|
142
|
-
else
|
|
143
|
-
'Switching'
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def past_tense_action(action)
|
|
148
|
-
case action
|
|
149
|
-
when 'Upgrading'
|
|
150
|
-
'upgraded'
|
|
151
|
-
when 'Downgrading'
|
|
152
|
-
'downgraded'
|
|
153
|
-
when 'Switching'
|
|
154
|
-
'switched'
|
|
155
|
-
else
|
|
156
|
-
'updated'
|
|
157
|
-
end
|
|
15
|
+
Services::AppUpgrader.new(@name, @version).call
|
|
158
16
|
end
|
|
159
17
|
end
|
|
160
18
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Commands
|
|
5
|
+
# Open App's work director
|
|
6
|
+
class Workdir
|
|
7
|
+
EDITOR_COMMANDS = {
|
|
8
|
+
'vscode' => 'code',
|
|
9
|
+
'sublime' => 'subl',
|
|
10
|
+
'atom' => 'atom'
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(app_name, editor)
|
|
14
|
+
@app_name = app_name
|
|
15
|
+
@editor = editor.downcase
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
app = Services::AppRegistry.find(@app_name)
|
|
20
|
+
abort "App '#{@app_name}' not found" unless app
|
|
21
|
+
abort "App path does not exist: #{app[:path]}" unless Dir.exist?(app[:path])
|
|
22
|
+
|
|
23
|
+
editor_cmd = EDITOR_COMMANDS[@editor] || @editor # support custom editors
|
|
24
|
+
unless system("which #{editor_cmd} > /dev/null 2>&1")
|
|
25
|
+
abort "Editor command not found: #{editor_cmd}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
puts "š Opening #{@app_name} in #{@editor}..."
|
|
29
|
+
system("#{editor_cmd} #{app[:path]}")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Services
|
|
5
|
+
# Service for destroying a Rails application
|
|
6
|
+
class AppDestroyer
|
|
7
|
+
def initialize(name)
|
|
8
|
+
@name = name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
app = AppRegistry.find(@name)
|
|
13
|
+
abort 'App not found' unless app
|
|
14
|
+
|
|
15
|
+
display_warning(app)
|
|
16
|
+
return unless confirm_destruction?
|
|
17
|
+
|
|
18
|
+
puts "\nšļø Destroying #{@name}..."
|
|
19
|
+
perform_destruction(app)
|
|
20
|
+
puts "ā
Successfully destroyed #{@name}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def display_warning(app)
|
|
26
|
+
puts "ā ļø WARNING: This will permanently delete the application '#{@name}'"
|
|
27
|
+
puts " Path: #{app[:path]}"
|
|
28
|
+
puts " Domain: #{app[:domain]}"
|
|
29
|
+
puts ' This action CANNOT be undone!'
|
|
30
|
+
puts ''
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def confirm_destruction?
|
|
34
|
+
print "Type '#{@name}' to confirm destruction: "
|
|
35
|
+
confirmation = $stdin.gets&.strip
|
|
36
|
+
puts ''
|
|
37
|
+
|
|
38
|
+
if confirmation == @name
|
|
39
|
+
true
|
|
40
|
+
else
|
|
41
|
+
puts "ā Destruction cancelled - confirmation didn't match"
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def perform_destruction(app)
|
|
47
|
+
# Stop the app if running
|
|
48
|
+
ProcessManager.stop(app)
|
|
49
|
+
|
|
50
|
+
# Remove from infrastructure
|
|
51
|
+
HostsManager.remove(app[:domain])
|
|
52
|
+
CaddyManager.remove(app[:domain])
|
|
53
|
+
AppRegistry.remove(@name)
|
|
54
|
+
|
|
55
|
+
# Clean up RVM gemset
|
|
56
|
+
cleanup_rvm_gemset(app)
|
|
57
|
+
|
|
58
|
+
# Delete the project directory
|
|
59
|
+
delete_project_directory(app[:path])
|
|
60
|
+
|
|
61
|
+
# Reload Caddy
|
|
62
|
+
CaddyManager.reload
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def cleanup_rvm_gemset(app)
|
|
66
|
+
return if ENV['STABLE_TEST_MODE']
|
|
67
|
+
return unless Utils::Platform.unix?
|
|
68
|
+
|
|
69
|
+
ruby_version = app[:ruby]
|
|
70
|
+
clean_ruby_version = ruby_version.to_s.sub(/^ruby-/, '')
|
|
71
|
+
gemset_name = "#{clean_ruby_version}@#{@name}"
|
|
72
|
+
|
|
73
|
+
puts " Cleaning up RVM gemset #{gemset_name}..."
|
|
74
|
+
begin
|
|
75
|
+
system("bash -lc 'source ~/.rvm/scripts/rvm && rvm gemset delete #{gemset_name} --force' 2>/dev/null || true")
|
|
76
|
+
puts " ā
RVM gemset #{gemset_name} cleaned up"
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
puts " ā ļø Could not clean up RVM gemset #{gemset_name}: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def delete_project_directory(path)
|
|
83
|
+
if ENV['STABLE_TEST_MODE']
|
|
84
|
+
puts ' Deleting project directory...'
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if File.exist?(path)
|
|
89
|
+
puts ' Deleting project directory...'
|
|
90
|
+
FileUtils.rm_rf(path)
|
|
91
|
+
else
|
|
92
|
+
puts ' Project directory not found (already deleted?)'
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Services
|
|
5
|
+
# Service for opening Rails applications in a browser
|
|
6
|
+
class AppOpener
|
|
7
|
+
def initialize(app_name)
|
|
8
|
+
@app_name = app_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
app = AppRegistry.find(@app_name)
|
|
13
|
+
abort "App '#{@app_name}' not found" unless app
|
|
14
|
+
abort "App '#{@app_name}' is not running" unless app[:pid] && process_alive?(app[:pid])
|
|
15
|
+
url = build_url(app)
|
|
16
|
+
open_browser(url)
|
|
17
|
+
puts "ā Opened #{url}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_url(app)
|
|
23
|
+
scheme = app[:skip_ssl] ? 'http' : 'https'
|
|
24
|
+
if app[:domain]
|
|
25
|
+
"#{scheme}://#{app[:domain]}"
|
|
26
|
+
else
|
|
27
|
+
"#{scheme}://127.0.0.1:#{app[:port]}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def open_browser(url)
|
|
32
|
+
cmd =
|
|
33
|
+
case RbConfig::CONFIG['host_os']
|
|
34
|
+
when /darwin/
|
|
35
|
+
"open #{url}"
|
|
36
|
+
when /linux/
|
|
37
|
+
"xdg-open #{url}"
|
|
38
|
+
when /mswin|mingw/
|
|
39
|
+
"start #{url}"
|
|
40
|
+
else
|
|
41
|
+
abort 'Unsupported OS'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
system(cmd) || abort('Failed to open browser')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def process_alive?(pid)
|
|
48
|
+
Process.kill(0, pid)
|
|
49
|
+
true
|
|
50
|
+
rescue Errno::ESRCH
|
|
51
|
+
false
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Services
|
|
5
|
+
# Service for opening Rails applications in a browser
|
|
6
|
+
class AppUpgrader
|
|
7
|
+
def initialize(name, version)
|
|
8
|
+
@name = name
|
|
9
|
+
@version = version
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
app = Services::AppRegistry.find(@name)
|
|
14
|
+
unless app
|
|
15
|
+
puts "No app named #{@name}"
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
current_version = app[:ruby] || RUBY_VERSION
|
|
20
|
+
|
|
21
|
+
puts "#{action(current_version, @version)} #{@name} from Ruby #{current_version} to #{@version}..."
|
|
22
|
+
puts ''
|
|
23
|
+
|
|
24
|
+
# Install the target Ruby version if needed
|
|
25
|
+
platform = Stable::Utils::Platform.current
|
|
26
|
+
|
|
27
|
+
if platform == :windows
|
|
28
|
+
puts 'ā ļø Windows detected - Ruby version managers work differently on Windows'
|
|
29
|
+
puts " Please manually install Ruby #{@version} using RubyInstaller or your preferred method"
|
|
30
|
+
puts ' Recommended: https://rubyinstaller.org/'
|
|
31
|
+
puts ' Then update your PATH to use the new Ruby version'
|
|
32
|
+
puts ''
|
|
33
|
+
puts "After installing Ruby #{@version}, update the app configuration manually:"
|
|
34
|
+
puts " - Edit .ruby-version file to contain: #{@version}"
|
|
35
|
+
puts ' - Run: bundle install (in the app directory)'
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if Stable::Services::Ruby.rvm_available?
|
|
40
|
+
puts "Ensuring Ruby #{@version} is available..."
|
|
41
|
+
system("bash -lc 'rvm install #{@version}'") unless ENV['STABLE_TEST_MODE']
|
|
42
|
+
elsif Stable::Services::Ruby.rbenv_available?
|
|
43
|
+
puts "Ensuring Ruby #{@version} is available..."
|
|
44
|
+
system("rbenv install #{@version}") unless ENV['STABLE_TEST_MODE']
|
|
45
|
+
else
|
|
46
|
+
puts 'ā No supported Ruby version manager found'
|
|
47
|
+
puts ' On macOS/Linux, install RVM (https://rvm.io/) or rbenv (https://github.com/rbenv/rbenv)'
|
|
48
|
+
puts ' On Windows, use RubyInstaller (https://rubyinstaller.org/)'
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Remove current Ruby environment and install new one fresh
|
|
53
|
+
puts "š Upgrading #{@name} from Ruby #{current_version} to #{@version}..."
|
|
54
|
+
|
|
55
|
+
# 1. Remove current Ruby version/gemset
|
|
56
|
+
cleanup_rvm_gemset(app)
|
|
57
|
+
|
|
58
|
+
# 2. Install new Ruby version
|
|
59
|
+
setup_new_ruby_version(app, @version)
|
|
60
|
+
|
|
61
|
+
puts ''
|
|
62
|
+
puts "ā
#{@name} #{past_tense_action(action(current_version, @version))} to Ruby #{@version}!"
|
|
63
|
+
puts " Old gemset cleared, fresh #{@version}@#{@name} gemset created with gems"
|
|
64
|
+
puts ''
|
|
65
|
+
puts "Start with: stable start #{@name}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def cleanup_rvm_gemset(app)
|
|
71
|
+
# Skip RVM operations in test mode
|
|
72
|
+
return if ENV['STABLE_TEST_MODE']
|
|
73
|
+
|
|
74
|
+
# Only clean up RVM gemsets on Unix-like systems (macOS/Linux)
|
|
75
|
+
# Windows uses different Ruby version managers
|
|
76
|
+
return unless Stable::Utils::Platform.unix?
|
|
77
|
+
|
|
78
|
+
ruby_version = app[:ruby]
|
|
79
|
+
# Handle different ruby version formats (e.g., "3.4.7", "ruby-3.4.7")
|
|
80
|
+
clean_ruby_version = ruby_version.to_s.sub(/^ruby-/, '')
|
|
81
|
+
gemset_name = "#{clean_ruby_version}@#{@name}"
|
|
82
|
+
|
|
83
|
+
puts " Cleaning up RVM gemset #{gemset_name}..."
|
|
84
|
+
begin
|
|
85
|
+
# Use system to run RVM command to delete the gemset
|
|
86
|
+
system("bash -lc 'source ~/.rvm/scripts/rvm && rvm gemset delete #{gemset_name} --force' 2>/dev/null || true")
|
|
87
|
+
puts " ā
RVM gemset #{gemset_name} cleaned up"
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
puts " ā ļø Could not clean up RVM gemset #{gemset_name}: #{e.message}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def setup_new_ruby_version(app, new_version)
|
|
94
|
+
unless ENV['STABLE_TEST_MODE']
|
|
95
|
+
Stable::Services::Ruby.ensure_version(new_version)
|
|
96
|
+
Stable::Services::Ruby.ensure_rvm!
|
|
97
|
+
|
|
98
|
+
# Create gemset
|
|
99
|
+
Stable::System::Shell.run("bash -lc 'source #{Stable::Services::Ruby.rvm_script} && rvm #{new_version} do rvm gemset create #{@name} || true'")
|
|
100
|
+
|
|
101
|
+
rvm_cmd = Stable::Services::Ruby.rvm_prefix(new_version, @name)
|
|
102
|
+
|
|
103
|
+
# Install Bundler
|
|
104
|
+
Stable::System::Shell.run("bash -lc '#{rvm_cmd} gem install bundler --no-document'")
|
|
105
|
+
|
|
106
|
+
# Run bundle install
|
|
107
|
+
Stable::System::Shell.run(rvm_run('bundle install --jobs=4 --retry=3', chdir: app[:path]))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Update app configuration
|
|
111
|
+
unless ENV['STABLE_TEST_MODE']
|
|
112
|
+
Dir.chdir(app[:path]) do
|
|
113
|
+
File.write('.ruby-version', "#{new_version}\n")
|
|
114
|
+
File.write('.ruby-gemset', "#{@name}\n")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Update registry
|
|
119
|
+
Services::AppRegistry.update(@name, ruby: new_version)
|
|
120
|
+
puts " ā
New Ruby #{new_version} environment set up with gems"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def rvm_run(cmd, chdir: nil)
|
|
124
|
+
cd = chdir ? "cd #{chdir} && " : ''
|
|
125
|
+
"bash -lc '#{cd}source #{Dir.home}/.rvm/scripts/rvm && rvm #{@version}@#{@name} do #{cmd}'"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def action(current_version, new_version)
|
|
129
|
+
current_parts = current_version.split('.').map(&:to_i)
|
|
130
|
+
new_parts = new_version.split('.').map(&:to_i)
|
|
131
|
+
|
|
132
|
+
if new_parts[0] > current_parts[0] ||
|
|
133
|
+
(new_parts[0] == current_parts[0] && new_parts[1] > current_parts[1]) ||
|
|
134
|
+
(new_parts[0] == current_parts[0] && new_parts[1] == current_parts[1] && new_parts[2] > current_parts[2])
|
|
135
|
+
'Upgrading'
|
|
136
|
+
elsif new_parts[0] < current_parts[0] ||
|
|
137
|
+
(new_parts[0] == current_parts[0] && new_parts[1] < current_parts[1]) ||
|
|
138
|
+
(new_parts[0] == current_parts[0] && new_parts[1] == current_parts[1] && new_parts[2] < current_parts[2])
|
|
139
|
+
'Downgrading'
|
|
140
|
+
else
|
|
141
|
+
'Switching'
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def past_tense_action(action)
|
|
146
|
+
case action
|
|
147
|
+
when 'Upgrading'
|
|
148
|
+
'upgraded'
|
|
149
|
+
when 'Downgrading'
|
|
150
|
+
'downgraded'
|
|
151
|
+
when 'Switching'
|
|
152
|
+
'switched'
|
|
153
|
+
else
|
|
154
|
+
'updated'
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rqrcode'
|
|
4
|
+
|
|
5
|
+
module Stable
|
|
6
|
+
module Services
|
|
7
|
+
module Cli
|
|
8
|
+
# Generate QR code
|
|
9
|
+
class QrCode
|
|
10
|
+
def self.print(url)
|
|
11
|
+
qr = RQRCode::QRCode.new(url)
|
|
12
|
+
|
|
13
|
+
puts
|
|
14
|
+
qr.modules.each do |row|
|
|
15
|
+
puts row.map { |cell| cell ? 'āā' : ' ' }.join
|
|
16
|
+
end
|
|
17
|
+
puts
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Services
|
|
5
|
+
module Rails
|
|
6
|
+
# Authorize ngro host
|
|
7
|
+
class HostAuthorization
|
|
8
|
+
MARKER_BEGIN = '# BEGIN Stable ngrok hosts'
|
|
9
|
+
MARKER_END = '# END Stable ngrok hosts'
|
|
10
|
+
|
|
11
|
+
def self.allow_ngrok!(app_path)
|
|
12
|
+
env_file = File.join(app_path, 'config/environments/development.rb')
|
|
13
|
+
return unless File.exist?(env_file)
|
|
14
|
+
|
|
15
|
+
content = File.read(env_file)
|
|
16
|
+
return if content.include?(MARKER_BEGIN)
|
|
17
|
+
|
|
18
|
+
# Only needed for Rails 6+
|
|
19
|
+
return unless rails_host_authorization_enabled?(app_path)
|
|
20
|
+
|
|
21
|
+
injection = <<~RUBY
|
|
22
|
+
|
|
23
|
+
#{MARKER_BEGIN}
|
|
24
|
+
config.hosts << ".ngrok-free.app"
|
|
25
|
+
config.hosts << ".ngrok.app"
|
|
26
|
+
#{MARKER_END}
|
|
27
|
+
RUBY
|
|
28
|
+
|
|
29
|
+
updated = content.sub(
|
|
30
|
+
/Rails\.application\.configure do\s*\n/,
|
|
31
|
+
"\\0#{injection}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
File.write(env_file, updated)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.remove_ngrok!(app_path)
|
|
38
|
+
env_file = File.join(app_path, 'config/environments/development.rb')
|
|
39
|
+
return unless File.exist?(env_file)
|
|
40
|
+
|
|
41
|
+
content = File.read(env_file)
|
|
42
|
+
return unless content.include?(MARKER_BEGIN)
|
|
43
|
+
|
|
44
|
+
cleaned = content.sub(
|
|
45
|
+
/\n\s*#{Regexp.escape(MARKER_BEGIN)}.*?#{Regexp.escape(MARKER_END)}\n/m,
|
|
46
|
+
"\n"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
File.write(env_file, cleaned)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.rails_host_authorization_enabled?(app_path)
|
|
53
|
+
env_rb = File.join(app_path, 'config/application.rb')
|
|
54
|
+
return false unless File.exist?(env_rb)
|
|
55
|
+
|
|
56
|
+
content = File.read(env_rb)
|
|
57
|
+
content.include?('config.load_defaults 6') ||
|
|
58
|
+
content.include?('config.load_defaults 7') ||
|
|
59
|
+
content.include?('config.load_defaults 8')
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Services
|
|
5
|
+
module Tunneling
|
|
6
|
+
# Tunneling manager
|
|
7
|
+
class Manager
|
|
8
|
+
def initialize(provider:)
|
|
9
|
+
@provider = provider.to_sym
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Expose the app domain using the correct port
|
|
13
|
+
def expose_domain(domain, port:, skip_ssl: false)
|
|
14
|
+
adapter.expose(domain, port: port, skip_ssl: skip_ssl)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def adapter
|
|
20
|
+
case @provider
|
|
21
|
+
when :ngrok
|
|
22
|
+
Providers::Ngrok.new
|
|
23
|
+
when :stable
|
|
24
|
+
Providers::Stable.new
|
|
25
|
+
else
|
|
26
|
+
abort "Unknown tunnel provider: #{@provider}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module Stable
|
|
8
|
+
module Services
|
|
9
|
+
module Tunneling
|
|
10
|
+
module Providers
|
|
11
|
+
# ngrok provider
|
|
12
|
+
class Ngrok
|
|
13
|
+
NGROK_API = 'http://127.0.0.1:4040/api/tunnels'
|
|
14
|
+
MAX_RETRIES = 10
|
|
15
|
+
RETRY_DELAY = 0.5
|
|
16
|
+
|
|
17
|
+
# Expose the app via ngrok on the app's local port
|
|
18
|
+
# skip_ssl is kept for future use
|
|
19
|
+
def expose(domain, port:, skip_ssl: false)
|
|
20
|
+
return stub_url(domain) if ENV['STABLE_TEST_MODE']
|
|
21
|
+
|
|
22
|
+
if (tunnel = existing_tunnel)
|
|
23
|
+
validate_tunnel!(tunnel, port)
|
|
24
|
+
return tunnel['public_url']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
start_ngrok(port)
|
|
28
|
+
wait_for_url || abort('Failed to obtain ngrok URL')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def existing_tunnel
|
|
34
|
+
resp = JSON.parse(Net::HTTP.get(URI(NGROK_API)))
|
|
35
|
+
resp['tunnels']&.find { |t| t['proto'] == 'https' }
|
|
36
|
+
rescue StandardError
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate_tunnel!(tunnel, port)
|
|
41
|
+
target = tunnel.dig('config', 'addr')
|
|
42
|
+
return if target == "http://localhost:#{port}"
|
|
43
|
+
|
|
44
|
+
abort <<~MSG
|
|
45
|
+
An ngrok tunnel is already running:
|
|
46
|
+
|
|
47
|
+
#{tunnel['public_url']} ā #{target}
|
|
48
|
+
|
|
49
|
+
Free ngrok allows only one tunnel at a time.
|
|
50
|
+
Stop it first:
|
|
51
|
+
pkill ngrok
|
|
52
|
+
MSG
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def start_ngrok(port)
|
|
56
|
+
@ngrok_pid = spawn(
|
|
57
|
+
'ngrok', 'http', port.to_s, '--log=stdout',
|
|
58
|
+
out: '/dev/null', err: '/dev/null'
|
|
59
|
+
)
|
|
60
|
+
Process.detach(@ngrok_pid)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def wait_for_url
|
|
64
|
+
MAX_RETRIES.times do
|
|
65
|
+
if (tunnel = existing_tunnel)
|
|
66
|
+
return tunnel['public_url']
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
sleep RETRY_DELAY
|
|
70
|
+
end
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def stub_url(domain)
|
|
75
|
+
"https://#{domain}-stable-share.test"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stable
|
|
4
|
+
module Services
|
|
5
|
+
module Tunneling
|
|
6
|
+
module Providers
|
|
7
|
+
# stable provider
|
|
8
|
+
class Stable
|
|
9
|
+
# skip_ssl is kept for future use
|
|
10
|
+
def expose(*)
|
|
11
|
+
abort 'Stable tunnels are not available yet'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
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.8.
|
|
4
|
+
version: 0.8.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Danny Simfukwe
|
|
@@ -9,6 +9,20 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rqrcode
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: thor
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -45,29 +59,41 @@ files:
|
|
|
45
59
|
- lib/stable/commands/remove.rb
|
|
46
60
|
- lib/stable/commands/restart.rb
|
|
47
61
|
- lib/stable/commands/setup.rb
|
|
62
|
+
- lib/stable/commands/share.rb
|
|
48
63
|
- lib/stable/commands/start.rb
|
|
49
64
|
- lib/stable/commands/stop.rb
|
|
65
|
+
- lib/stable/commands/unshare.rb
|
|
50
66
|
- lib/stable/commands/upgrade_ruby.rb
|
|
67
|
+
- lib/stable/commands/workdir.rb
|
|
51
68
|
- lib/stable/config/paths.rb
|
|
52
69
|
- lib/stable/db_manager.rb
|
|
53
70
|
- lib/stable/paths.rb
|
|
54
71
|
- lib/stable/registry.rb
|
|
55
72
|
- lib/stable/scanner.rb
|
|
56
73
|
- lib/stable/services/app_creator.rb
|
|
74
|
+
- lib/stable/services/app_destroyer.rb
|
|
75
|
+
- lib/stable/services/app_opener.rb
|
|
57
76
|
- lib/stable/services/app_registry.rb
|
|
58
77
|
- lib/stable/services/app_remover.rb
|
|
59
78
|
- lib/stable/services/app_restarter.rb
|
|
60
79
|
- lib/stable/services/app_starter.rb
|
|
61
80
|
- lib/stable/services/app_stopper.rb
|
|
81
|
+
- lib/stable/services/app_upgrader.rb
|
|
62
82
|
- lib/stable/services/caddy_manager.rb
|
|
83
|
+
- lib/stable/services/cli/qrcode.rb
|
|
63
84
|
- lib/stable/services/database/base.rb
|
|
64
85
|
- lib/stable/services/database/mysql.rb
|
|
65
86
|
- lib/stable/services/database/postgres.rb
|
|
66
87
|
- lib/stable/services/dependency_checker.rb
|
|
67
88
|
- lib/stable/services/hosts_manager.rb
|
|
68
89
|
- lib/stable/services/process_manager.rb
|
|
90
|
+
- lib/stable/services/rails/host_authorization.rb
|
|
69
91
|
- lib/stable/services/ruby.rb
|
|
70
92
|
- lib/stable/services/setup_runner.rb
|
|
93
|
+
- lib/stable/services/tunneling/base_provider.rb
|
|
94
|
+
- lib/stable/services/tunneling/manager.rb
|
|
95
|
+
- lib/stable/services/tunneling/providers/ngrok.rb
|
|
96
|
+
- lib/stable/services/tunneling/providers/stable.rb
|
|
71
97
|
- lib/stable/system/shell.rb
|
|
72
98
|
- lib/stable/utils/package_manager.rb
|
|
73
99
|
- lib/stable/utils/platform.rb
|
|
@@ -92,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
92
118
|
- !ruby/object:Gem::Version
|
|
93
119
|
version: '0'
|
|
94
120
|
requirements: []
|
|
95
|
-
rubygems_version:
|
|
121
|
+
rubygems_version: 4.0.3
|
|
96
122
|
specification_version: 4
|
|
97
123
|
summary: Zero-config CLI tool to manage local Rails apps with automatic Caddy and
|
|
98
124
|
HTTPS setup
|