Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions lib/react_on_rails/dev/process_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion sig/react_on_rails/dev/process_manager.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 47 additions & 3 deletions spec/react_on_rails/dev/process_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down
Loading