Class: ReactOnRails::Dev::ServerManager
- Inherits:
-
Object
- Object
- ReactOnRails::Dev::ServerManager
- Defined in:
- lib/react_on_rails/dev/server_manager.rb
Constant Summary collapse
- HELP_FLAGS =
["-h", "--help"].freeze
- TEST_WATCH_MODES =
%w[auto full client-only].freeze
- OPEN_BROWSER_WAIT_TIMEOUT =
60- OPEN_BROWSER_POLL_INTERVAL =
0.5- FLAGS_WITH_VALUES =
Flags that take a value as the next argument (not using = syntax)
%w[--route --rails-env --test-watch-mode].freeze
Class Method Summary collapse
- .cleanup_socket_files ⇒ Object
- .configured_renderer_port_for_kill ⇒ Object
- .default_killable_ports ⇒ Object
- .development_processes ⇒ Object
- .find_port_pids(port) ⇒ Object
- .find_process_pids(pattern) ⇒ Object
- .kill_port_processes(ports) ⇒ Object
- .kill_processes ⇒ Object
- .kill_running_processes ⇒ Object
-
.killable_ports ⇒ Object
Fallback port list for the port-scan kill path.
- .local_renderer_url_port_for_kill ⇒ Object
- .print_kill_summary(killed_any) ⇒ Object
- .remote_renderer_url_configured? ⇒ Boolean
-
.run_from_command_line(args = ARGV) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity.
- .show_help ⇒ Object
- .start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil, skip_database_check: false, open_browser: false, open_browser_once: false) ⇒ Object
- .terminate_processes(pids) ⇒ Object
Class Method Details
.cleanup_socket_files ⇒ Object
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 227 def cleanup_socket_files # Mirrors FileManager#cleanup_overmind_sockets so renamed/copied # variants like overmind-4100.sock are removed during `bin/dev kill`, # not just at startup. overmind_sockets = Dir.glob("tmp/sockets/overmind*.sock") files = [".overmind.sock", *overmind_sockets, "tmp/pids/server.pid"].uniq killed_any = false files.each do |file| next unless File.exist?(file) puts " 🧹 Removing #{file}" File.delete(file) killed_any = true rescue StandardError nil end killed_any end |
.configured_renderer_port_for_kill ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 117 def configured_renderer_port_for_kill raw_port = ENV.fetch("RENDERER_PORT", nil) return raw_port.strip.to_i if valid_port_string?(raw_port) local_url_port = local_renderer_url_port_for_kill return local_url_port if local_url_port return nil if remote_renderer_url_configured? # Only fall back to the default renderer port when the user has set # at least one renderer env var. Without that signal (Pro gem loaded # but no renderer ever started), `bin/dev kill` would otherwise # target an unrelated process bound to 3800 in OSS+Pro-gem apps. renderer_env_signal? ? 3800 : nil end |
.default_killable_ports ⇒ Object
108 109 110 111 112 113 114 115 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 108 def default_killable_ports ports = [3000, 3001] if pro_renderer_active? renderer_port = configured_renderer_port_for_kill ports << renderer_port if renderer_port end ports end |
.development_processes ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 156 def development_processes { "rails" => "Rails server", "node.*react[-_]on[-_]rails" => "React on Rails Node processes", "overmind" => "Overmind process manager", "foreman" => "Foreman process manager", "ruby.*puma" => "Puma server", "webpack-dev-server" => "Webpack dev server", "bin/shakapacker-dev-server" => "Shakapacker dev server" } end |
.find_port_pids(port) ⇒ Object
219 220 221 222 223 224 225 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 219 def find_port_pids(port) stdout, _status = Open3.capture2("lsof", "-ti", ":#{port}", err: File::NULL) stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid } rescue StandardError # lsof command not found or other error (permission denied, etc.) [] end |
.find_process_pids(pattern) ⇒ Object
183 184 185 186 187 188 189 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 183 def find_process_pids(pattern) stdout, _status = Open3.capture2("pgrep", "-f", pattern, err: File::NULL) stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid } rescue Errno::ENOENT # pgrep command not found [] end |
.kill_port_processes(ports) ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 204 def kill_port_processes(ports) killed_any = false ports.each do |port| pids = find_port_pids(port) next unless pids.any? puts " ☠️ Killing process on port #{port} (PIDs: #{pids.join(', ')})" terminate_processes(pids) killed_any = true end killed_any end |
.kill_processes ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 53 def kill_processes puts "🔪 Killing all development processes..." puts "" # Run every cleanup step unconditionally so a successful first step # (e.g. pattern-based kill) doesn't leave stale port-bound processes # or socket/pid files behind. `.any?` still gives us the # "anything actually got killed?" signal for the summary message. killed_any = [ kill_running_processes, kill_port_processes(killable_ports), cleanup_socket_files ].any? print_kill_summary(killed_any) end |
.kill_running_processes ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 168 def kill_running_processes killed_any = false development_processes.each do |pattern, description| pids = find_process_pids(pattern) next unless pids.any? puts " ☠️ Killing #{description} (PIDs: #{pids.join(', ')})" terminate_processes(pids) killed_any = true end killed_any end |
.killable_ports ⇒ Object
Fallback port list for the port-scan kill path. Uses the base-port derived ports when REACT_ON_RAILS_BASE_PORT / CONDUCTOR_PORT is set, so ‘bin/dev kill` in a worktree on ports 5000/5001/5002 targets the right ports instead of the 3000/3001 default. Falls back to
- 3000, 3001
-
when no base port is configured, plus the renderer port
when Pro renderer support is active. Uses PortSelector’s pure #base_port_hash so no “Base port detected” banner prints during a kill.
In base-port mode we include base whenever the Pro gem is loaded, even if the current shell has no renderer env vars set. The user has explicitly claimed this port range, and ‘bin/dev kill` is usually invoked from a fresh shell where RENDERER_PORT / *_URL aren’t carried over from the dev session — so requiring env-var presence would let a stale renderer survive. Pattern-based killing (development_processes / node.*react[-]on[-]rails) does NOT catch the Pro renderer because it runs as ‘node renderer/node-renderer.js` with no “react_on_rails” substring in the command line. Port-based killing is the only reliable path. The default-port branch keeps the tighter renderer_env_signal? guard via configured_renderer_port_for_kill because 3800 is a shared default that could belong to an unrelated process.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 90 def killable_ports base = PortSelector.base_port_hash return default_killable_ports unless base ports = [base[:rails], base[:webpack]] if pro_renderer_active? # When the Pro gem is loaded but no renderer env var is set, the # user may not realize base+2 is being scanned. Surface it so an # unrelated process killed on that port isn't a silent surprise. unless renderer_env_signal? puts " ℹ️ Including renderer port #{base[:renderer]} (base+2): " \ "react_on_rails_pro is loaded but no renderer env var is set." end ports << base[:renderer] end ports end |
.local_renderer_url_port_for_kill ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 132 def local_renderer_url_port_for_kill %w[REACT_RENDERER_URL RENDERER_URL].each do |var| url = ENV.fetch(var, nil) next if url.nil? || url.strip.empty? parsed = URI.parse(url) next unless localhost_hostname?(parsed.hostname) next unless url.match?(URL_WITH_EXPLICIT_PORT_RE) return parsed.port rescue URI::InvalidURIError next end nil end |
.print_kill_summary(killed_any) ⇒ Object
248 249 250 251 252 253 254 255 256 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 248 def print_kill_summary(killed_any) if killed_any puts "" puts "✅ All processes terminated and sockets cleaned" puts "💡 You can now run 'bin/dev' for a clean start" else puts " ℹ️ No development processes found running" end end |
.remote_renderer_url_configured? ⇒ Boolean
149 150 151 152 153 154 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 149 def remote_renderer_url_configured? %w[REACT_RENDERER_URL RENDERER_URL].any? do |var| url = ENV.fetch(var, nil) !url.nil? && !url.strip.empty? && !localhost_renderer_url?(url) end end |
.run_from_command_line(args = ARGV) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 276 def run_from_command_line(args = ARGV) # Get the command early to check for help/kill before running hooks # We need to do this before OptionParser processes flags like -h/--help # Skip arguments that are values for flags (e.g., "hello_world" after "--route") command = extract_command_from_args(args) # Check if help flags are present in args (before OptionParser processes them) help_requested = args.any? { |arg| HELP_FLAGS.include?(arg) } = (args) # Run precompile hook once before starting any mode (except kill/help) # Then set environment variable to prevent duplicate execution in spawned processes. # Note: We always set SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true (even when no hook is configured) # to provide a consistent signal that bin/dev is managing the precompile lifecycle. # This allows custom scripts to detect bin/dev's presence and adjust behavior accordingly. unless %w[kill help].include?(command) || help_requested run_precompile_hook_if_present ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] = "true" end # Main execution case command when "production-assets", "prod" start(:production_like, nil, verbose: [:verbose], route: [:route], rails_env: [:rails_env], skip_database_check: [:skip_database_check], open_browser: [:open_browser], open_browser_once: [:open_browser_once]) when "static" start(:static, "Procfile.dev-static-assets", verbose: [:verbose], route: [:route], skip_database_check: [:skip_database_check], open_browser: [:open_browser], open_browser_once: [:open_browser_once]) when "kill" kill_processes when "help" show_help when "test-watch" run_test_watch(test_watch_mode: [:test_watch_mode]) when "hmr", nil start(:development, "Procfile.dev", verbose: [:verbose], route: [:route], skip_database_check: [:skip_database_check], open_browser: [:open_browser], open_browser_once: [:open_browser_once]) else puts "Unknown argument: #{command}" puts "Run 'dev help' for usage information" exit 1 end end |
.show_help ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 258 def show_help puts help_usage puts "" puts help_commands puts "" puts puts "" puts help_customization puts "" puts help_mode_details puts "" puts help_troubleshooting end |
.start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil, skip_database_check: false, open_browser: false, open_browser_once: false) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 28 def start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil, skip_database_check: false, open_browser: false, open_browser_once: false) case mode when :production_like run_production_like(_verbose: verbose, route: route, rails_env: rails_env, skip_database_check: skip_database_check, open_browser: open_browser, open_browser_once: open_browser_once) when :static procfile ||= "Procfile.dev-static-assets" run_static_development(procfile, verbose: verbose, route: route, skip_database_check: skip_database_check, open_browser: open_browser, open_browser_once: open_browser_once) when :development, :hmr procfile ||= "Procfile.dev" run_development(procfile, verbose: verbose, route: route, skip_database_check: skip_database_check, open_browser: open_browser, open_browser_once: open_browser_once) else raise ArgumentError, "Unknown mode: #{mode}" end end |
.terminate_processes(pids) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 191 def terminate_processes(pids) pids.each do |pid| Process.kill("TERM", pid) rescue Errno::ESRCH, ArgumentError, RangeError # Process already stopped, or invalid signal/PID - silently skip nil rescue Errno::EPERM # Permission denied - warn the user puts " ⚠️ Process #{pid} - permission denied (process owned by another user)" nil end end |