class ReactOnRails::Dev::ProcessManager
def ensure_procfile(procfile)
def ensure_procfile(procfile) return if File.exist?(procfile) warn <<~MSG ERROR: Please ensure `#{procfile}` exists in your project! MSG exit 1 end
def installed?(process)
Check if a process is available and usable in the current execution context
def installed?(process) installed_in_current_context?(process) end
def installed_in_current_context?(process)
Check if a process is actually usable in the current execution context
def installed_in_current_context?(process) # Try to execute the process with version flags to see if it works # Use system() because that's how we'll actually call it later version_flags_for(process).any? do |flag| # Add timeout to prevent hanging on version checks Timeout.timeout(VERSION_CHECK_TIMEOUT) do system(process, flag, out: File::NULL, err: File::NULL) end end rescue Errno::ENOENT, Timeout::Error false end
def process_available_in_system?(process)
def process_available_in_system?(process) return false unless defined?(Bundler) with_unbundled_context do # Try version flags to check if process exists outside bundler context version_flags_for(process).any? do |flag| # Add timeout to prevent hanging on version checks Timeout.timeout(VERSION_CHECK_TIMEOUT) do system(process, flag, out: File::NULL, err: File::NULL) end end end rescue Errno::ENOENT, Timeout::Error false end
def run_process_if_available(process, args)
Try to run a process if it's available, with intelligent fallback strategy
def run_process_if_available(process, args) # First attempt: try in current context (works for bundled processes) return system(process, *args) if installed?(process) # Second attempt: try in system context (works for system-installed processes) return run_process_outside_bundle(process, args) if process_available_in_system?(process) # Process not available in either context false end
def run_process_outside_bundle(process, args)
Run a process outside of bundler context
def run_process_outside_bundle(process, args) if defined?(Bundler) with_unbundled_context do system(process, *args) end else # Fallback if Bundler is not available system(process, *args) end end
def run_with_process_manager(procfile)
def run_with_process_manager(procfile) # Validate procfile path for security unless valid_procfile_path?(procfile) warn "ERROR: Invalid procfile path: #{procfile}" exit 1 end # Clean up stale files before starting 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]) show_process_manager_installation_help exit 1 end
def show_process_manager_installation_help
def show_process_manager_installation_help warn <<~MSG ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ERROR: Process Manager Not Found ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ To run the React on Rails development server, you need either 'overmind' or 'foreman' installed on your system. RECOMMENDED INSTALLATION: macOS: brew install overmind Linux: gem install foreman IMPORTANT: DO NOT add foreman to your Gemfile. Install it globally on your system. For more information about why foreman should not be in your Gemfile, see: https://github.com/ddollar/foreman/wiki/Don't-Bundle-Foreman After installation, try running this script again. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ MSG end
def valid_procfile_path?(procfile)
def valid_procfile_path?(procfile) # Reject paths with shell metacharacters return false if procfile.match?(/[;&|`$(){}\[\]<>]/) # Ensure it's a readable file File.readable?(procfile) rescue StandardError false end
def version_flags_for(process)
def version_flags_for(process) case process when "overmind" ["--version"] when "foreman" ["--version", "-v"] else ["--version", "-v", "-V"] end end
def with_unbundled_context(&block)
DRY helper method for Bundler context switching with API compatibility
def with_unbundled_context(&block) if Bundler.respond_to?(:with_unbundled_env) Bundler.with_unbundled_env(&block) elsif Bundler.respond_to?(:with_clean_env) Bundler.with_clean_env(&block) else # Fallback if neither method is available (very old Bundler versions) yield end end