stable-cli-rails 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55fbeca00cb3d5ac0cf360dd86a8022081e3bf8b00af321c8ac8dec7482e7e5b
4
- data.tar.gz: a1d3fa2c7d8ffb2ae339e75d4ebdd1aa67e4b1e7b88ef0377ce8d06dc170c94d
3
+ metadata.gz: adfc3784b0a153d9a5368139267d77c0cfa8a32b71d72e8da43ae5a454220f6a
4
+ data.tar.gz: 861823110de96d0e4d1c8297f13ff2f9630a25649d201f921aaa64f43849888c
5
5
  SHA512:
6
- metadata.gz: 3e90d7e10d507ea30367e375dc99576f0b9902d77f9d7d2facf45e16fe116fff5eaac586eacec099b664b31f05f25dc33ec83db2d5b89926b0bb1fcf196588c8
7
- data.tar.gz: 81a7739540c1b55e429867f7c7efd22af8a96559b239710fb290179c67d8ca67e1c6e5fc4448d3f6380cedf61b62fae4c1b737c5f98d532848c63652f2c05dcf
6
+ metadata.gz: 9651b1cd3c7127223a5587f31e70175fdf2be8e9ccd8c3e17347b73a1364b9404eda928d7e314513bf5a416b8acf90c31a14eb2d6e7ec719d6f7576887dd933d
7
+ data.tar.gz: 9ab4288661598a9349301e1fa1f96007b0eafdc78eb66886e26f9b555ecf871d55f07fdb9b2ce529d59aff25ee287e655e68a3a5bce001d38c6621eae6c73736
data/lib/stable/cli.rb CHANGED
@@ -13,6 +13,8 @@ module Stable
13
13
  class CLI < Thor
14
14
  def initialize(*)
15
15
  super
16
+ return if ENV['STABLE_TEST_MODE']
17
+
16
18
  Stable::Bootstrap.run!
17
19
  Services::SetupRunner.ensure_dependencies!
18
20
  end
@@ -75,6 +77,11 @@ module Stable
75
77
  Commands::Remove.new(name).call
76
78
  end
77
79
 
80
+ desc 'destroy NAME', 'Permanently delete a Rails app and all its files'
81
+ def destroy(name)
82
+ Commands::Destroy.new(name).call
83
+ end
84
+
78
85
  desc 'start NAME', 'Start a Rails app with its correct Ruby version'
79
86
  def start(name)
80
87
  Commands::Start.new(name).call
@@ -123,27 +130,9 @@ module Stable
123
130
  Commands::Doctor.new.call
124
131
  end
125
132
 
126
- desc 'upgrade-ruby NAME VERSION', 'Upgrade Ruby for an app'
133
+ desc 'upgrade-ruby NAME VERSION', 'Change Ruby version for an app (upgrade, downgrade, or switch)'
127
134
  def upgrade_ruby(name, version)
128
- app = Services::AppRegistry.find(name)
129
- unless app
130
- puts "No app named #{name}"
131
- return
132
- end
133
-
134
- if Stable::Services::Ruby.rvm_available?
135
- system("bash -lc 'rvm install #{version}'")
136
- elsif Stable::Services::Ruby.rbenv_available?
137
- system("rbenv install #{version}")
138
- else
139
- puts 'No Ruby version manager found'
140
- return
141
- end
142
-
143
- File.write(File.join(app[:path], '.ruby-version'), version)
144
- Services::AppRegistry.update(name, ruby: version)
145
-
146
- puts "#{name} now uses Ruby #{version}"
135
+ Commands::UpgradeRuby.new(name, version).call
147
136
  end
148
137
 
149
138
  private
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+
5
+ module Stable
6
+ module Commands
7
+ # Destroy command - permanently deletes a Rails application with confirmation
8
+ class Destroy
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+
13
+ def call
14
+ app = Services::AppRegistry.find(@name)
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
102
+ end
103
+ end
104
+ end
105
+ end
@@ -6,47 +6,47 @@ module Stable
6
6
  module Commands
7
7
  # List command - displays all registered applications
8
8
  class List
9
- def call
10
- apps = Services::AppRegistry.all
9
+ def call
10
+ apps = Services::AppRegistry.all
11
11
 
12
- if apps.empty?
13
- puts 'No apps registered.'
14
- return
15
- end
12
+ if apps.empty?
13
+ puts 'No apps registered.'
14
+ return
15
+ end
16
16
 
17
- print_header
17
+ print_header
18
18
 
19
- apps.each do |app|
20
- # Determine status based on whether the app is actually running (port check)
21
- status = app_running?(app) ? 'running' : 'stopped'
22
- puts format_row(app, status)
19
+ apps.each do |app|
20
+ # Determine status based on whether the app is actually running (port check)
21
+ status = app_running?(app) ? 'running' : 'stopped'
22
+ puts format_row(app, status)
23
+ end
23
24
  end
24
- end
25
25
 
26
- private
26
+ private
27
27
 
28
- def app_running?(app)
29
- return false unless app && app[:port]
28
+ def app_running?(app)
29
+ return false unless app && app[:port]
30
30
 
31
- # Check if something is listening on the app's port (cross-platform)
32
- Stable::Utils::Platform.port_in_use?(app[:port])
33
- end
31
+ # Check if something is listening on the app's port (cross-platform)
32
+ Stable::Utils::Platform.port_in_use?(app[:port])
33
+ end
34
34
 
35
- def format_row(app, status)
36
- format(
37
- '%<name>-18s %<domain>-26s %<port>-8s %<ruby>-10s %<status>-10s',
38
- name: app[:name],
39
- domain: app[:domain],
40
- port: app[:port],
41
- ruby: app[:ruby],
42
- status: status
43
- )
44
- end
35
+ def format_row(app, status)
36
+ format(
37
+ '%<name>-18s %<domain>-26s %<port>-8s %<ruby>-10s %<status>-10s',
38
+ name: app[:name],
39
+ domain: app[:domain],
40
+ port: app[:port],
41
+ ruby: app[:ruby],
42
+ status: status
43
+ )
44
+ end
45
45
 
46
- def print_header
47
- puts 'APP DOMAIN PORT RUBY STATUS '
48
- puts '-' * 78
49
- end
46
+ def print_header
47
+ puts 'APP DOMAIN PORT RUBY STATUS '
48
+ puts '-' * 78
49
+ end
50
50
  end
51
51
  end
52
52
  end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module Stable
6
+ module Commands
7
+ # Command for upgrading/downgrading Ruby versions for applications
8
+ class UpgradeRuby
9
+ def initialize(name, version)
10
+ @name = name
11
+ @version = version
12
+ end
13
+
14
+ def call
15
+ app = Services::AppRegistry.find(@name)
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
+ # Clean, simple approach: 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 (like destroy command)
58
+ cleanup_rvm_gemset(app)
59
+
60
+ # 2. Install new Ruby version fresh (like app creator)
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
+ # Follow app_creator.rb pattern exactly
97
+ unless ENV['STABLE_TEST_MODE']
98
+ # Ensure Ruby version & RVM (like app_creator.rb)
99
+ Stable::Services::Ruby.ensure_version(new_version)
100
+ Stable::Services::Ruby.ensure_rvm!
101
+
102
+ # Create gemset (like app_creator.rb)
103
+ Stable::System::Shell.run("bash -lc 'source #{Stable::Services::Ruby.rvm_script} && rvm #{new_version} do rvm gemset create #{@name} || true'")
104
+
105
+ rvm_cmd = Stable::Services::Ruby.rvm_prefix(new_version, @name)
106
+
107
+ # Install Bundler (like app_creator.rb)
108
+ Stable::System::Shell.run("bash -lc '#{rvm_cmd} gem install bundler --no-document'")
109
+
110
+ # Run bundle install (like app_creator.rb)
111
+ Stable::System::Shell.run(rvm_run('bundle install --jobs=4 --retry=3', chdir: app[:path]))
112
+ end
113
+
114
+ # Update app configuration (like app_creator.rb)
115
+ unless ENV['STABLE_TEST_MODE']
116
+ Dir.chdir(app[:path]) do
117
+ File.write('.ruby-version', "#{new_version}\n")
118
+ File.write('.ruby-gemset', "#{@name}\n")
119
+ end
120
+ end
121
+
122
+ # Update registry
123
+ Services::AppRegistry.update(@name, ruby: new_version)
124
+ puts " āœ… New Ruby #{new_version} environment set up with gems"
125
+ end
126
+
127
+ def rvm_run(cmd, chdir: nil)
128
+ cd = chdir ? "cd #{chdir} && " : ''
129
+ "bash -lc '#{cd}source #{Dir.home}/.rvm/scripts/rvm && rvm #{@version}@#{@name} do #{cmd}'"
130
+ end
131
+
132
+ def action(current_version, new_version)
133
+ current_parts = current_version.split('.').map(&:to_i)
134
+ new_parts = new_version.split('.').map(&:to_i)
135
+
136
+ if 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
+ 'Upgrading'
140
+ elsif new_parts[0] < current_parts[0] ||
141
+ (new_parts[0] == current_parts[0] && new_parts[1] < current_parts[1]) ||
142
+ (new_parts[0] == current_parts[0] && new_parts[1] == current_parts[1] && new_parts[2] < current_parts[2])
143
+ 'Downgrading'
144
+ else
145
+ 'Switching'
146
+ end
147
+ end
148
+
149
+ def past_tense_action(action)
150
+ case action
151
+ when 'Upgrading'
152
+ 'upgraded'
153
+ when 'Downgrading'
154
+ 'downgraded'
155
+ when 'Switching'
156
+ 'switched'
157
+ else
158
+ 'updated'
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -66,6 +66,21 @@ module Stable
66
66
  def self.remove_app_config(app_name)
67
67
  config_file = Stable::Paths.app_config_file(app_name)
68
68
  FileUtils.rm_f(config_file)
69
+
70
+ # Also remove from legacy apps.yml file for backward compatibility
71
+ remove_from_legacy_file(app_name)
72
+ end
73
+
74
+ def self.remove_from_legacy_file(app_name)
75
+ legacy_file = Stable::Paths.apps_file
76
+ return unless File.exist?(legacy_file)
77
+
78
+ data = YAML.load_file(legacy_file) || []
79
+ filtered_data = data.reject { |entry| entry.is_a?(Hash) && entry['name'] == app_name }
80
+
81
+ return unless filtered_data != data
82
+
83
+ File.write(legacy_file, filtered_data.to_yaml)
69
84
  end
70
85
 
71
86
  def self.parse_config_file(config_file)
@@ -74,12 +74,12 @@ module Stable
74
74
  data = YAML.load_file(legacy_file) || []
75
75
  idx = data.find_index { |app| app['name'] == name || app[:name] == name }
76
76
 
77
- if idx
78
- # Convert symbols to strings for YAML compatibility
79
- legacy_format = updated_app.transform_keys(&:to_s)
80
- data[idx] = legacy_format
81
- File.write(legacy_file, data.to_yaml)
82
- end
77
+ return unless idx
78
+
79
+ # Convert symbols to strings for YAML compatibility
80
+ legacy_format = updated_app.transform_keys(&:to_s)
81
+ data[idx] = legacy_format
82
+ File.write(legacy_file, data.to_yaml)
83
83
  end
84
84
 
85
85
  def mark_stopped(name)
@@ -100,9 +100,7 @@ module Stable
100
100
  return false unless app
101
101
 
102
102
  # First check if we have PID info and if the process is alive
103
- if app[:pid] && app[:started_at]
104
- return ProcessManager.pid_alive?(app[:pid])
105
- end
103
+ return ProcessManager.pid_alive?(app[:pid]) if app[:pid] && app[:started_at]
106
104
 
107
105
  # Fallback to port checking if no PID info available
108
106
  return false unless app[:port]
@@ -16,7 +16,7 @@ module Stable
16
16
  content = remove_domain_block(content, domain)
17
17
 
18
18
  atomic_write(caddyfile, content)
19
- system("caddy fmt --overwrite #{caddyfile}")
19
+ system("caddy fmt --overwrite #{caddyfile}") unless ENV['STABLE_TEST_MODE']
20
20
 
21
21
  reload_if_running
22
22
  end
@@ -38,12 +38,14 @@ module Stable
38
38
  content << build_block(domain, port, skip_ssl: skip_ssl)
39
39
 
40
40
  atomic_write(caddyfile, content)
41
- system("caddy fmt --overwrite #{caddyfile}")
41
+ system("caddy fmt --overwrite #{caddyfile}") unless ENV['STABLE_TEST_MODE']
42
42
 
43
43
  ensure_running!
44
44
  end
45
45
 
46
46
  def reload
47
+ return if ENV['STABLE_TEST_MODE']
48
+
47
49
  if system('which caddy > /dev/null')
48
50
  pid = Process.spawn("caddy reload --config #{caddyfile}")
49
51
  Process.detach(pid.to_i)
@@ -53,6 +55,8 @@ module Stable
53
55
  end
54
56
 
55
57
  def ensure_running!
58
+ return if ENV['STABLE_TEST_MODE']
59
+
56
60
  if running?
57
61
  reload
58
62
  else
@@ -81,6 +85,8 @@ module Stable
81
85
 
82
86
  return if valid_pem?(cert_path) && valid_pem?(key_path)
83
87
 
88
+ return if ENV['STABLE_TEST_MODE'] # Skip cert generation in tests
89
+
84
90
  raise 'mkcert not installed' unless system('which mkcert > /dev/null')
85
91
 
86
92
  System::Shell.run(
@@ -52,7 +52,11 @@ module Stable
52
52
  if pids.empty?
53
53
  puts "No app running on port #{app[:port]}"
54
54
  else
55
- pids.each { |pid| Process.kill('TERM', pid.to_i) rescue nil }
55
+ pids.each do |pid|
56
+ Process.kill('TERM', pid.to_i)
57
+ rescue StandardError
58
+ nil
59
+ end
56
60
  puts "Stopped #{app[:name]} on port #{app[:port]}"
57
61
  end
58
62
 
@@ -89,9 +93,7 @@ module Stable
89
93
  apps.each do |app|
90
94
  next unless app[:started_at] && app[:pid]
91
95
 
92
- unless pid_alive?(app[:pid])
93
- AppRegistry.update(app[:name], started_at: nil, pid: nil)
94
- end
96
+ AppRegistry.update(app[:name], started_at: nil, pid: nil) unless pid_alive?(app[:pid])
95
97
  end
96
98
  end
97
99
 
@@ -11,6 +11,8 @@ module Stable
11
11
  ensure_apps_registry
12
12
  ensure_caddyfile
13
13
  # start or ensure caddy is running like original CLI
14
+ return if ENV['STABLE_TEST_MODE']
15
+
14
16
  Stable::Services::CaddyManager.ensure_running!
15
17
  puts "Caddy home initialized at #{Stable::Paths.root}"
16
18
  self.class.ensure_dependencies!
@@ -44,6 +46,8 @@ module Stable
44
46
  end
45
47
 
46
48
  def ensure_dependencies!
49
+ return if ENV['STABLE_TEST_MODE']
50
+
47
51
  platform = Stable::Utils::Platform.current
48
52
 
49
53
  unless Stable::Utils::PackageManager.available?
@@ -68,6 +68,7 @@ module Stable
68
68
  # Use lsof to find PIDs listening on the port
69
69
  output = `lsof -i tcp:#{port} -sTCP:LISTEN -t 2>/dev/null`.strip
70
70
  return [] if output.empty?
71
+
71
72
  output.split("\n").map(&:to_i)
72
73
  when :windows
73
74
  # On Windows, this is more complex. For now, return empty array
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.1
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Simfukwe
@@ -37,6 +37,7 @@ files:
37
37
  - lib/stable.rb
38
38
  - lib/stable/bootstrap.rb
39
39
  - lib/stable/cli.rb
40
+ - lib/stable/commands/destroy.rb
40
41
  - lib/stable/commands/doctor.rb
41
42
  - lib/stable/commands/list.rb
42
43
  - lib/stable/commands/new.rb
@@ -45,6 +46,7 @@ files:
45
46
  - lib/stable/commands/setup.rb
46
47
  - lib/stable/commands/start.rb
47
48
  - lib/stable/commands/stop.rb
49
+ - lib/stable/commands/upgrade_ruby.rb
48
50
  - lib/stable/config/paths.rb
49
51
  - lib/stable/db_manager.rb
50
52
  - lib/stable/paths.rb
@@ -91,5 +93,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
93
  requirements: []
92
94
  rubygems_version: 3.6.7
93
95
  specification_version: 4
94
- summary: CLI tool to manage local Rails apps with automatic Caddy and HTTPS setup
96
+ summary: Zero-config CLI tool to manage local Rails apps with automatic Caddy and
97
+ HTTPS setup
95
98
  test_files: []