diff --git a/lib/react_on_rails/dev/process_manager.rb b/lib/react_on_rails/dev/process_manager.rb index f737be16f9..11bf045130 100644 --- a/lib/react_on_rails/dev/process_manager.rb +++ b/lib/react_on_rails/dev/process_manager.rb @@ -36,9 +36,20 @@ def run_with_process_manager(procfile) FileManager.cleanup_stale_files # Try process managers in order of preference - return if run_process_if_available("overmind", ["start", "-f", procfile]) - return if run_process_if_available("foreman", ["start", "-f", procfile]) + # run_process_if_available returns: + # - true/false (exit status) if process was found and executed + # - nil if process was not found + overmind_result = run_process_if_available("overmind", ["start", "-f", procfile]) + return if overmind_result == true # Overmind ran successfully + foreman_result = run_process_if_available("foreman", ["start", "-f", procfile]) + return if foreman_result == true # Foreman ran successfully + + # If either process was found but exited with error, don't show "not found" message + # The process manager itself will have shown its own error message + exit 1 if overmind_result == false || foreman_result == false + + # Neither process manager was found show_process_manager_installation_help exit 1 end @@ -73,7 +84,10 @@ def version_flags_for(process) end # Try to run a process if it's available, with intelligent fallback strategy - # Returns true if process was found and executed, false if not available + # Returns: + # - true if process ran and exited successfully (exit code 0) + # - false if process ran but exited with error (non-zero exit code) + # - nil if process was not found/available def run_process_if_available(process, args) # First attempt: try in current context (works for bundled processes) return system(process, *args) if installed?(process) @@ -82,7 +96,7 @@ def run_process_if_available(process, args) return run_process_outside_bundle(process, args) if process_available_in_system?(process) # Process not available in either context - false + nil end # Run a process outside of bundler context diff --git a/sig/react_on_rails/dev/process_manager.rbs b/sig/react_on_rails/dev/process_manager.rbs index bdcd6dc464..2aec68209e 100644 --- a/sig/react_on_rails/dev/process_manager.rbs +++ b/sig/react_on_rails/dev/process_manager.rbs @@ -11,7 +11,7 @@ module ReactOnRails def self.installed_in_current_context?: (String) -> bool def self.version_flags_for: (String) -> Array[String] - def self.run_process_if_available: (String, Array[String]) -> bool + def self.run_process_if_available: (String, Array[String]) -> (bool | nil) def self.run_process_outside_bundle: (String, Array[String]) -> bool def self.process_available_in_system?: (String) -> bool def self.with_unbundled_context: () { () -> untyped } -> untyped diff --git a/spec/react_on_rails/dev/process_manager_spec.rb b/spec/react_on_rails/dev/process_manager_spec.rb index 581fc59476..3e69cbbe13 100644 --- a/spec/react_on_rails/dev/process_manager_spec.rb +++ b/spec/react_on_rails/dev/process_manager_spec.rb @@ -71,19 +71,63 @@ end it "uses foreman when overmind not available and foreman is available" do + expect(described_class).to receive(:run_process_if_available) + .with("overmind", ["start", "-f", "Procfile.dev"]).and_return(nil) + expect(described_class).to receive(:run_process_if_available) + .with("foreman", ["start", "-f", "Procfile.dev"]).and_return(true) + + described_class.run_with_process_manager("Procfile.dev") + end + + it "exits silently when overmind is installed but exits with error (foreman not found)" do + expect(described_class).to receive(:run_process_if_available) + .with("overmind", ["start", "-f", "Procfile.dev"]).and_return(false) + expect(described_class).to receive(:run_process_if_available) + .with("foreman", ["start", "-f", "Procfile.dev"]).and_return(nil) + expect(described_class).not_to receive(:show_process_manager_installation_help) + expect_any_instance_of(Kernel).to receive(:exit).with(1).and_raise(SystemExit) + + expect { described_class.run_with_process_manager("Procfile.dev") }.to raise_error(SystemExit) + end + + it "exits silently when overmind fails but foreman succeeds" do expect(described_class).to receive(:run_process_if_available) .with("overmind", ["start", "-f", "Procfile.dev"]).and_return(false) expect(described_class).to receive(:run_process_if_available) .with("foreman", ["start", "-f", "Procfile.dev"]).and_return(true) + expect(described_class).not_to receive(:show_process_manager_installation_help) + expect_any_instance_of(Kernel).not_to receive(:exit) described_class.run_with_process_manager("Procfile.dev") end - it "exits with error when no process manager available" do + it "exits silently when overmind fails and foreman also fails" do expect(described_class).to receive(:run_process_if_available) .with("overmind", ["start", "-f", "Procfile.dev"]).and_return(false) expect(described_class).to receive(:run_process_if_available) .with("foreman", ["start", "-f", "Procfile.dev"]).and_return(false) + expect(described_class).not_to receive(:show_process_manager_installation_help) + expect_any_instance_of(Kernel).to receive(:exit).with(1).and_raise(SystemExit) + + expect { described_class.run_with_process_manager("Procfile.dev") }.to raise_error(SystemExit) + end + + it "exits silently when overmind not found but foreman exits with error" do + expect(described_class).to receive(:run_process_if_available) + .with("overmind", ["start", "-f", "Procfile.dev"]).and_return(nil) + expect(described_class).to receive(:run_process_if_available) + .with("foreman", ["start", "-f", "Procfile.dev"]).and_return(false) + expect(described_class).not_to receive(:show_process_manager_installation_help) + expect_any_instance_of(Kernel).to receive(:exit).with(1).and_raise(SystemExit) + + expect { described_class.run_with_process_manager("Procfile.dev") }.to raise_error(SystemExit) + end + + it "exits with error when no process manager available" do + expect(described_class).to receive(:run_process_if_available) + .with("overmind", ["start", "-f", "Procfile.dev"]).and_return(nil) + expect(described_class).to receive(:run_process_if_available) + .with("foreman", ["start", "-f", "Procfile.dev"]).and_return(nil) expect(described_class).to receive(:show_process_manager_installation_help) expect_any_instance_of(Kernel).to receive(:exit).with(1) @@ -117,12 +161,12 @@ expect(result).to be true end - it "returns false when process not available anywhere" do + it "returns nil when process not available anywhere" do allow(described_class).to receive(:installed?).with("nonexistent").and_return(false) allow(described_class).to receive(:process_available_in_system?).with("nonexistent").and_return(false) result = described_class.send(:run_process_if_available, "nonexistent", ["start"]) - expect(result).to be false + expect(result).to be_nil end end