Class: ReactOnRails::Dev::ServerManager
- Inherits:
-
Object
- Object
- ReactOnRails::Dev::ServerManager
- Extended by:
- ShakapackerConfigHelpers
- Defined in:
- lib/react_on_rails/dev/server_manager.rb,
sig/react_on_rails/dev/server_manager.rbs
Constant Summary collapse
- HELP_FLAGS =
["-h", "--help"].freeze
- TEST_WATCH_MODES =
%w[auto full client-only].freeze
- CLEAN_SHAKAPACKER_ENVIRONMENTS =
%w[development test production].freeze
- OPEN_BROWSER_WAIT_TIMEOUT =
60- OPEN_BROWSER_POLL_INTERVAL =
0.5- DOCS_BASE_URL =
"https://reactonrails.com/docs"- DEV_SERVER_AND_TESTING_DOCS_URL =
"#{DOCS_BASE_URL}/building-features/dev-server-and-testing/".freeze
- TESTING_CONFIGURATION_DOCS_URL =
"#{DOCS_BASE_URL}/building-features/testing-configuration/".freeze
- FLAGS_WITH_VALUES =
Flags that take a value as the next argument (not using = syntax)
%w[--route --rails-env --test-watch-mode].freeze
Constants included from ShakapackerConfigHelpers
ShakapackerConfigHelpers::DEFAULT_SHAKAPACKER_CONFIG_PATH, ShakapackerConfigHelpers::SHAKAPACKER_ASSETS_BUNDLER_ENV, ShakapackerConfigHelpers::SUPPORTED_ASSETS_BUNDLERS
Class Method Summary collapse
- .clean_generated_assets_and_caches ⇒ Object
- .cleanup_socket_files ⇒ Boolean
- .configured_renderer_port_for_kill ⇒ Object
- .default_killable_ports ⇒ Object
- .development_processes ⇒ Hash[String, String]
- .find_port_pids(port) ⇒ Array[Integer]
- .find_process_pids(pattern) ⇒ Array[Integer]
- .kill_port_processes(ports) ⇒ Boolean
- .kill_processes ⇒ void
- .kill_running_processes ⇒ Boolean
-
.killable_ports ⇒ Object
Fallback port list for the port-scan kill path.
- .local_renderer_url_port_for_kill ⇒ Object
- .print_kill_summary(killed_any) ⇒ void
- .remote_renderer_url_configured? ⇒ Boolean
-
.run_from_command_line(args = ARGV) ⇒ void
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity.
- .show_help ⇒ void
- .start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil, skip_database_check: false, open_browser: false, open_browser_once: false) ⇒ void
- .terminate_processes(pids) ⇒ void
Class Method Details
.clean_generated_assets_and_caches ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 81 def clean_generated_assets_and_caches puts "🧹 Cleaning generated bundles and caches..." puts "" kill_processes puts "" print_shakapacker_config_status puts "" clean_finished_without_warnings = remove_clean_targets(clean_targets) puts "" if clean_finished_without_warnings puts "✅ Generated bundles and caches cleaned" else puts "⚠️ Cleanup completed with warnings" end end |
.cleanup_socket_files ⇒ Boolean
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 256 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
146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 146 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
137 138 139 140 141 142 143 144 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 137 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 ⇒ Hash[String, String]
185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 185 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) ⇒ Array[Integer]
248 249 250 251 252 253 254 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 248 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) ⇒ Array[Integer]
212 213 214 215 216 217 218 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 212 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) ⇒ Boolean
233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 233 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 ⇒ void
This method returns an undefined value.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 64 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 ⇒ Boolean
197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 197 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.*reactonrails) 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.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 119 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
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 161 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) ⇒ void
This method returns an undefined value.
277 278 279 280 281 282 283 284 285 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 277 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
178 179 180 181 182 183 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 178 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) ⇒ void
This method returns an undefined value.
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 307 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/clean/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 clean 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 "clean" clean_generated_assets_and_caches 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 ⇒ void
This method returns an undefined value.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 287 def show_help default_mode = default_dev_server_mode puts help_usage puts "" puts help_commands(default_mode) puts "" puts puts "" puts help_customization(default_mode) puts "" puts help_mode_details(default_mode) puts "" puts help_troubleshooting(default_mode) end |
.start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil, skip_database_check: false, open_browser: false, open_browser_once: false) ⇒ void
This method returns an undefined value.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 39 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:, rails_env:, skip_database_check:, open_browser:, open_browser_once:) when :static procfile ||= "Procfile.dev-static-assets" run_static_development(procfile, verbose:, route:, skip_database_check:, open_browser:, open_browser_once:) when :development, :hmr procfile ||= "Procfile.dev" run_development(procfile, verbose:, route:, skip_database_check:, open_browser:, open_browser_once:) else raise ArgumentError, "Unknown mode: #{mode}" end end |
.terminate_processes(pids) ⇒ void
This method returns an undefined value.
220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/react_on_rails/dev/server_manager.rb', line 220 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 |