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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th

Changes since the last non-beta release.

#### Added

- **Service Dependency Checking for bin/dev**: Added optional `.dev-services.yml` configuration to validate required external services (Redis, PostgreSQL, Elasticsearch, etc.) are running before `bin/dev` starts the development server. Provides clear error messages with start commands and install hints when services are missing. Zero impact if not configured - backwards compatible with all existing installations. [PR 2098](https://github.com/shakacode/react_on_rails/pull/2098) by [justin808](https://github.com/justin808).

#### Improved

- **Automatic Precompile Hook Coordination in bin/dev**: The `bin/dev` command now automatically runs Shakapacker's `precompile_hook` once before starting development processes and sets `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` to prevent duplicate execution in spawned webpack processes.
Expand Down
1 change: 1 addition & 0 deletions Steepfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ target :lib do
check "lib/react_on_rails/dev/pack_generator.rb"
check "lib/react_on_rails/dev/process_manager.rb"
check "lib/react_on_rails/dev/server_manager.rb"
check "lib/react_on_rails/dev/service_checker.rb"
check "lib/react_on_rails/git_utils.rb"
check "lib/react_on_rails/helper.rb"
check "lib/react_on_rails/packer_utils.rb"
Expand Down
116 changes: 111 additions & 5 deletions docs/building-features/process-managers.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ React on Rails includes `bin/dev` which automatically uses Overmind or Foreman:

This script will:

1. Run Shakapacker's `precompile_hook` once (if configured in `config/shakapacker.yml`)
2. Set `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` to prevent duplicate execution
3. Try to use Overmind (if installed)
4. Fall back to Foreman (if installed)
5. Show installation instructions if neither is found
1. Check required external services (if `.dev-services.yml` exists)
2. Run Shakapacker's `precompile_hook` once (if configured in `config/shakapacker.yml`)
3. Set `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` to prevent duplicate execution
4. Try to use Overmind (if installed)
5. Fall back to Foreman (if installed)
6. Show installation instructions if neither is found

### Precompile Hook Integration

Expand Down Expand Up @@ -57,6 +58,111 @@ default: &default

See the [i18n documentation](./i18n.md#internationalization) for more details on configuring the precompile hook.

### Service Dependency Checking

`bin/dev` can automatically verify that required external services (like Redis, PostgreSQL, Elasticsearch) are running before starting your development server. This prevents cryptic error messages and provides clear instructions on how to start missing services.

#### Configuration

Create a `.dev-services.yml` file in your project root:

```yaml
services:
redis:
check_command: 'redis-cli ping'
expected_output: 'PONG'
start_command: 'redis-server'
install_hint: 'brew install redis (macOS) or apt-get install redis-server (Linux)'
description: 'Redis (for caching and background jobs)'

postgresql:
check_command: 'pg_isready'
expected_output: 'accepting connections'
start_command: 'pg_ctl -D /usr/local/var/postgres start'
install_hint: 'brew install postgresql (macOS)'
description: 'PostgreSQL database'
```

A `.dev-services.yml.example` file with common service configurations is created when you run the React on Rails generator.

#### Configuration Fields

- **check_command** (required): Shell command to check if the service is running
- **expected_output** (optional): String that must appear in the command output
- **start_command** (optional): Command to start the service (shown in error messages)
- **install_hint** (optional): How to install the service if not found
- **description** (optional): Human-readable description of the service

#### Behavior

If `.dev-services.yml` exists, `bin/dev` will:

1. Check each configured service before starting
2. Show a success message if all services are running
3. Show helpful error messages with start commands if any service is missing
4. Exit before starting the Procfile if services are unavailable

If `.dev-services.yml` doesn't exist, `bin/dev` works exactly as before (zero impact on existing installations).

#### Example Output

**When services are running:**

```
🔍 Checking required services (.dev-services.yml)...

✓ redis - Redis (for caching and background jobs)
✓ postgresql - PostgreSQL database

✅ All services are running
```

**When services are missing:**

```
🔍 Checking required services (.dev-services.yml)...

✗ redis - Redis (for caching and background jobs)

❌ Some services are not running

Please start these services before running bin/dev:

redis
Redis (for caching and background jobs)

To start:
redis-server

Not installed? brew install redis (macOS) or apt-get install redis-server (Linux)

💡 Tips:
• Start services manually, then run bin/dev again
• Or remove service from .dev-services.yml if not needed
• Or add service to Procfile.dev to start automatically
```

#### Security Note

⚠️ **IMPORTANT**: Commands in `.dev-services.yml` are executed during `bin/dev` startup without shell expansion for safety. However, you should still:

- **Only add commands from trusted sources**
- **Avoid shell metacharacters** (&&, ||, ;, |, $, etc.) - they won't work and indicate an anti-pattern
- **Review changes carefully** if .dev-services.yml is committed to version control
- **Consider adding to .gitignore** if it contains machine-specific paths or sensitive information

**Recommended approach:**

- Commit `.dev-services.yml.example` to version control (safe, documentation)
- Add `.dev-services.yml` to `.gitignore` (developers copy from example)
- This prevents accidental execution of untrusted commands from compromised dependencies

**Execution order:**

1. Service dependency checks (`.dev-services.yml`)
2. Precompile hook (if configured in `config/shakapacker.yml`)
3. Process manager starts processes from Procfile

## Installing a Process Manager

### Overmind (Recommended)
Expand Down
1 change: 1 addition & 0 deletions lib/generators/react_on_rails/base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def copy_base_files
Procfile.dev
Procfile.dev-static-assets
Procfile.dev-prod-assets
.dev-services.yml.example
bin/shakapacker-precompile-hook]
base_templates = %w[config/initializers/react_on_rails.rb]
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Service Dependencies Configuration
#
# This file defines external services that must be running before bin/dev starts.
# Copy this file to .dev-services.yml and customize for your application.
#
# bin/dev will check each service before starting the development server.
# If any service is not running, it will display helpful error messages with
# instructions on how to start the service.
#
# ⚠️ SECURITY WARNING:
# Commands in this file are executed during bin/dev startup. Only add commands
# from trusted sources. This file should not be committed if it contains
# sensitive information or custom paths specific to your machine. Consider
# adding .dev-services.yml to .gitignore if it contains machine-specific config.
#
# Security best practices:
# - Commands are executed without shell expansion (shell metacharacters won't work)
# - Use simple, single commands (e.g., "redis-cli ping", "pg_isready")
# - Do NOT use shell features: &&, ||, |, $, ;, backticks, etc. will fail
# - Only include services you trust
# - Keep commands simple and focused on service health checks
# - Consider adding .dev-services.yml to .gitignore (commit .example instead)
#
# Example configuration:
#
# services:
# redis:
# check_command: "redis-cli ping"
# expected_output: "PONG"
# start_command: "redis-server"
# install_hint: "brew install redis (macOS) or apt-get install redis-server (Linux)"
# description: "Redis (for caching and background jobs)"
#
# postgresql:
# check_command: "pg_isready"
# expected_output: "accepting connections"
# start_command: "pg_ctl -D /usr/local/var/postgres start"
# install_hint: "brew install postgresql (macOS) or apt-get install postgresql (Linux)"
# description: "PostgreSQL database"
#
# elasticsearch:
# check_command: "curl -s http://localhost:9200"
# expected_output: "cluster_name"
# start_command: "brew services start elasticsearch-full"
# install_hint: "brew install elasticsearch-full"
# description: "Elasticsearch (for search)"
#
# Field descriptions:
# check_command: Shell command to check if service is running (required)
# expected_output: String that must appear in command output (optional)
# start_command: Command to start the service (shown in error messages)
# install_hint: How to install the service if not found
# description: Human-readable description of the service
#
# To use this file:
# 1. Copy to .dev-services.yml: cp .dev-services.yml.example .dev-services.yml
# 2. Uncomment and configure the services your app needs
# 3. Add .dev-services.yml to .gitignore if it contains sensitive info
# 4. Run bin/dev - it will check services before starting

services:
# Uncomment and configure the services your application requires:

# redis:
# check_command: "redis-cli ping"
# expected_output: "PONG"
# start_command: "redis-server"
# install_hint: "brew install redis (macOS) or apt-get install redis-server (Linux)"
# description: "Redis (for caching and background jobs)"

# postgresql:
# check_command: "pg_isready"
# expected_output: "accepting connections"
# start_command: "pg_ctl -D /usr/local/var/postgres start" # macOS; Linux: sudo service postgresql start
# install_hint: "brew install postgresql (macOS) or apt-get install postgresql (Linux)"
# description: "PostgreSQL database"
29 changes: 29 additions & 0 deletions lib/react_on_rails/dev/server_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "open3"
require "rainbow"
require_relative "../packer_utils"
require_relative "service_checker"

module ReactOnRails
module Dev
Expand Down Expand Up @@ -330,6 +331,7 @@ def help_options
end
# rubocop:enable Metrics/AbcSize

# rubocop:disable Metrics/AbcSize
def help_customization
<<~CUSTOMIZATION
#{Rainbow('🔧 CUSTOMIZATION:').cyan.bold}
Expand All @@ -340,8 +342,24 @@ def help_customization
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev-prod-assets').green.bold} - Production-optimized assets (port 3001)

#{Rainbow('Edit these files to customize the development environment for your needs.').white}

#{Rainbow('🔍 SERVICE DEPENDENCIES:').cyan.bold}
#{Rainbow('Configure required external services in').white} #{Rainbow('.dev-services.yml').green.bold}#{Rainbow(':').white}

#{Rainbow('•').yellow} #{Rainbow('bin/dev').white} #{Rainbow('checks services before starting (optional)').white}
#{Rainbow('•').yellow} #{Rainbow('Copy from').white} #{Rainbow('.dev-services.yml.example').green.bold} #{Rainbow('to get started').white}
#{Rainbow('•').yellow} #{Rainbow('Supports Redis, PostgreSQL, Elasticsearch, and custom services').white}
#{Rainbow('•').yellow} #{Rainbow('Shows helpful errors with start commands if services are missing').white}

#{Rainbow('Example .dev-services.yml:').white}
#{Rainbow(' services:').cyan}
#{Rainbow(' redis:').cyan}
#{Rainbow(' check_command: "redis-cli ping"').cyan}
#{Rainbow(' expected_output: "PONG"').cyan}
#{Rainbow(' start_command: "redis-server"').cyan}
CUSTOMIZATION
end
# rubocop:enable Metrics/AbcSize

# rubocop:disable Metrics/AbcSize
def help_mode_details
Expand Down Expand Up @@ -392,6 +410,10 @@ def run_production_like(_verbose: false, route: nil, rails_env: nil)
# either via precompile hook or via the configuration.rb adjust_precompile_task

print_procfile_info(procfile, route: route)

# Check required services before starting
exit 1 unless ServiceChecker.check_services

print_server_info(
"🏭 Starting production-like development server...",
features,
Expand Down Expand Up @@ -514,6 +536,9 @@ def run_production_like(_verbose: false, route: nil, rails_env: nil)
def run_static_development(procfile, verbose: false, route: nil)
print_procfile_info(procfile, route: route)

# Check required services before starting
exit 1 unless ServiceChecker.check_services

features = [
"Using shakapacker --watch (no HMR)",
"CSS extracted to separate files (no FOUC)",
Expand All @@ -539,6 +564,10 @@ def run_static_development(procfile, verbose: false, route: nil)

def run_development(procfile, verbose: false, route: nil)
print_procfile_info(procfile, route: route)

# Check required services before starting
exit 1 unless ServiceChecker.check_services

PackGenerator.generate(verbose: verbose)
ProcessManager.ensure_procfile(procfile)
ProcessManager.run_with_process_manager(procfile)
Expand Down
Loading
Loading