Skip to content

Commit 1969b2d

Browse files
justin808claude
andauthored
Add service dependency checking to bin/dev (#2098)
## Summary This PR introduces a service dependency checking system that validates required external services (like Redis, PostgreSQL, Elasticsearch) are running before `bin/dev` starts the development server. ## Problem Many applications depend on external services that must be running before the dev server starts. Currently, there are two approaches: 1. **Include services in Procfile** (e.g., `redis: redis-server`) - Works but clutters the Procfile 2. **Let Procfile fail** - Results in cryptic errors that confuse new developers Neither approach is ideal. The Procfile is meant for application processes, not infrastructure services. ## Solution A new `.dev-services.yml` configuration file that: - Declaratively defines required services - Checks if they're running before starting Procfile - Provides helpful error messages with start commands - Works cross-platform (macOS and Linux) - Has zero impact if not used ## Key Features ✅ **Declarative configuration** - Simple YAML format ✅ **Self-documenting** - Includes `.dev-services.yml.example` template ✅ **Helpful errors** - Clear instructions on how to start missing services ✅ **Cross-platform** - Works on macOS and Linux ✅ **Zero impact** - If no config exists, bin/dev works exactly as before ✅ **Test coverage** - 11 comprehensive tests, all passing ## Configuration Example ```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" description: "PostgreSQL database" ``` ## User Experience **When all services are running:** ``` 🔍 Checking required services (.dev-services.yml)... ✓ redis - Redis (for caching and background jobs) ✓ postgresql - PostgreSQL database ✅ All services are running [Procfile starts normally] ``` **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 ``` ## Implementation Details ### New Files - `lib/react_on_rails/dev/service_checker.rb` - Core service checking logic - `spec/react_on_rails/dev/service_checker_spec.rb` - Comprehensive test suite - `lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example` - Template for new projects - `spec/dummy/.dev-services.yml.example` - Example for dummy app ### Modified Files - `lib/react_on_rails/dev/server_manager.rb` - Integrated service checks before starting Procfile - `lib/generators/react_on_rails/base_generator.rb` - Added `.dev-services.yml.example` to generated files ### Service Checking Flow 1. Check if `.dev-services.yml` exists (if not, continue normally) 2. Load and parse YAML configuration 3. Run each service's `check_command` 4. Validate output against `expected_output` (if specified) 5. If all pass: continue to start Procfile 6. If any fail: display helpful errors and exit ## Benefits 🎯 **Self-documenting** - New developers see exactly what services are needed 🎯 **Cleaner Procfiles** - Remove infrastructure services, keep only app processes 🎯 **Better onboarding** - Clear instructions instead of cryptic failures 🎯 **Flexible** - Each app can define its own requirements 🎯 **Optional** - Zero impact on existing installations ## Test Plan - [x] All 11 new tests passing - [x] All existing dev module tests still passing (89 total) - [x] RuboCop clean - [x] Pre-commit hooks passing - [x] Manual testing with dummy app ## Breaking Changes None. This is purely additive. If `.dev-services.yml` doesn't exist, `bin/dev` behaves exactly as before. ## Future Enhancements Potential future improvements: - Auto-start services with user confirmation - Integration with Docker Compose - Service health monitoring during development - Template snippets for common services 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Service Dependency Checking for `bin/dev`: An optional `.dev-services.yml` configuration file validates external services (Redis, PostgreSQL, Elasticsearch, etc.) are running before the development server starts. Provides clear error messages with startup and installation guidance when services are missing. Fully backward compatible—disabled by default. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0263541 commit 1969b2d

File tree

10 files changed

+750
-5
lines changed

10 files changed

+750
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th
2323

2424
Changes since the last non-beta release.
2525

26+
#### Added
27+
28+
- **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).
29+
2630
#### Improved
2731

2832
- **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.

Steepfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ target :lib do
3232
check "lib/react_on_rails/dev/pack_generator.rb"
3333
check "lib/react_on_rails/dev/process_manager.rb"
3434
check "lib/react_on_rails/dev/server_manager.rb"
35+
check "lib/react_on_rails/dev/service_checker.rb"
3536
check "lib/react_on_rails/git_utils.rb"
3637
check "lib/react_on_rails/helper.rb"
3738
check "lib/react_on_rails/packer_utils.rb"

docs/building-features/process-managers.md

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ React on Rails includes `bin/dev` which automatically uses Overmind or Foreman:
1616

1717
This script will:
1818

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

2526
### Precompile Hook Integration
2627

@@ -57,6 +58,111 @@ default: &default
5758
5859
See the [i18n documentation](./i18n.md#internationalization) for more details on configuring the precompile hook.
5960
61+
### Service Dependency Checking
62+
63+
`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.
64+
65+
#### Configuration
66+
67+
Create a `.dev-services.yml` file in your project root:
68+
69+
```yaml
70+
services:
71+
redis:
72+
check_command: 'redis-cli ping'
73+
expected_output: 'PONG'
74+
start_command: 'redis-server'
75+
install_hint: 'brew install redis (macOS) or apt-get install redis-server (Linux)'
76+
description: 'Redis (for caching and background jobs)'
77+
78+
postgresql:
79+
check_command: 'pg_isready'
80+
expected_output: 'accepting connections'
81+
start_command: 'pg_ctl -D /usr/local/var/postgres start'
82+
install_hint: 'brew install postgresql (macOS)'
83+
description: 'PostgreSQL database'
84+
```
85+
86+
A `.dev-services.yml.example` file with common service configurations is created when you run the React on Rails generator.
87+
88+
#### Configuration Fields
89+
90+
- **check_command** (required): Shell command to check if the service is running
91+
- **expected_output** (optional): String that must appear in the command output
92+
- **start_command** (optional): Command to start the service (shown in error messages)
93+
- **install_hint** (optional): How to install the service if not found
94+
- **description** (optional): Human-readable description of the service
95+
96+
#### Behavior
97+
98+
If `.dev-services.yml` exists, `bin/dev` will:
99+
100+
1. Check each configured service before starting
101+
2. Show a success message if all services are running
102+
3. Show helpful error messages with start commands if any service is missing
103+
4. Exit before starting the Procfile if services are unavailable
104+
105+
If `.dev-services.yml` doesn't exist, `bin/dev` works exactly as before (zero impact on existing installations).
106+
107+
#### Example Output
108+
109+
**When services are running:**
110+
111+
```
112+
🔍 Checking required services (.dev-services.yml)...
113+
114+
✓ redis - Redis (for caching and background jobs)
115+
✓ postgresql - PostgreSQL database
116+
117+
✅ All services are running
118+
```
119+
120+
**When services are missing:**
121+
122+
```
123+
🔍 Checking required services (.dev-services.yml)...
124+
125+
✗ redis - Redis (for caching and background jobs)
126+
127+
❌ Some services are not running
128+
129+
Please start these services before running bin/dev:
130+
131+
redis
132+
Redis (for caching and background jobs)
133+
134+
To start:
135+
redis-server
136+
137+
Not installed? brew install redis (macOS) or apt-get install redis-server (Linux)
138+
139+
💡 Tips:
140+
• Start services manually, then run bin/dev again
141+
• Or remove service from .dev-services.yml if not needed
142+
• Or add service to Procfile.dev to start automatically
143+
```
144+
145+
#### Security Note
146+
147+
⚠️ **IMPORTANT**: Commands in `.dev-services.yml` are executed during `bin/dev` startup without shell expansion for safety. However, you should still:
148+
149+
- **Only add commands from trusted sources**
150+
- **Avoid shell metacharacters** (&&, ||, ;, |, $, etc.) - they won't work and indicate an anti-pattern
151+
- **Review changes carefully** if .dev-services.yml is committed to version control
152+
- **Consider adding to .gitignore** if it contains machine-specific paths or sensitive information
153+
154+
**Recommended approach:**
155+
156+
- Commit `.dev-services.yml.example` to version control (safe, documentation)
157+
- Add `.dev-services.yml` to `.gitignore` (developers copy from example)
158+
- This prevents accidental execution of untrusted commands from compromised dependencies
159+
160+
**Execution order:**
161+
162+
1. Service dependency checks (`.dev-services.yml`)
163+
2. Precompile hook (if configured in `config/shakapacker.yml`)
164+
3. Process manager starts processes from Procfile
165+
60166
## Installing a Process Manager
61167
62168
### Overmind (Recommended)

lib/generators/react_on_rails/base_generator.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def copy_base_files
4646
Procfile.dev
4747
Procfile.dev-static-assets
4848
Procfile.dev-prod-assets
49+
.dev-services.yml.example
4950
bin/shakapacker-precompile-hook]
5051
base_templates = %w[config/initializers/react_on_rails.rb]
5152
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Service Dependencies Configuration
2+
#
3+
# This file defines external services that must be running before bin/dev starts.
4+
# Copy this file to .dev-services.yml and customize for your application.
5+
#
6+
# bin/dev will check each service before starting the development server.
7+
# If any service is not running, it will display helpful error messages with
8+
# instructions on how to start the service.
9+
#
10+
# ⚠️ SECURITY WARNING:
11+
# Commands in this file are executed during bin/dev startup. Only add commands
12+
# from trusted sources. This file should not be committed if it contains
13+
# sensitive information or custom paths specific to your machine. Consider
14+
# adding .dev-services.yml to .gitignore if it contains machine-specific config.
15+
#
16+
# Security best practices:
17+
# - Commands are executed without shell expansion (shell metacharacters won't work)
18+
# - Use simple, single commands (e.g., "redis-cli ping", "pg_isready")
19+
# - Do NOT use shell features: &&, ||, |, $, ;, backticks, etc. will fail
20+
# - Only include services you trust
21+
# - Keep commands simple and focused on service health checks
22+
# - Consider adding .dev-services.yml to .gitignore (commit .example instead)
23+
#
24+
# Example configuration:
25+
#
26+
# services:
27+
# redis:
28+
# check_command: "redis-cli ping"
29+
# expected_output: "PONG"
30+
# start_command: "redis-server"
31+
# install_hint: "brew install redis (macOS) or apt-get install redis-server (Linux)"
32+
# description: "Redis (for caching and background jobs)"
33+
#
34+
# postgresql:
35+
# check_command: "pg_isready"
36+
# expected_output: "accepting connections"
37+
# start_command: "pg_ctl -D /usr/local/var/postgres start"
38+
# install_hint: "brew install postgresql (macOS) or apt-get install postgresql (Linux)"
39+
# description: "PostgreSQL database"
40+
#
41+
# elasticsearch:
42+
# check_command: "curl -s http://localhost:9200"
43+
# expected_output: "cluster_name"
44+
# start_command: "brew services start elasticsearch-full"
45+
# install_hint: "brew install elasticsearch-full"
46+
# description: "Elasticsearch (for search)"
47+
#
48+
# Field descriptions:
49+
# check_command: Shell command to check if service is running (required)
50+
# expected_output: String that must appear in command output (optional)
51+
# start_command: Command to start the service (shown in error messages)
52+
# install_hint: How to install the service if not found
53+
# description: Human-readable description of the service
54+
#
55+
# To use this file:
56+
# 1. Copy to .dev-services.yml: cp .dev-services.yml.example .dev-services.yml
57+
# 2. Uncomment and configure the services your app needs
58+
# 3. Add .dev-services.yml to .gitignore if it contains sensitive info
59+
# 4. Run bin/dev - it will check services before starting
60+
61+
services:
62+
# Uncomment and configure the services your application requires:
63+
64+
# redis:
65+
# check_command: "redis-cli ping"
66+
# expected_output: "PONG"
67+
# start_command: "redis-server"
68+
# install_hint: "brew install redis (macOS) or apt-get install redis-server (Linux)"
69+
# description: "Redis (for caching and background jobs)"
70+
71+
# postgresql:
72+
# check_command: "pg_isready"
73+
# expected_output: "accepting connections"
74+
# start_command: "pg_ctl -D /usr/local/var/postgres start" # macOS; Linux: sudo service postgresql start
75+
# install_hint: "brew install postgresql (macOS) or apt-get install postgresql (Linux)"
76+
# description: "PostgreSQL database"

lib/react_on_rails/dev/server_manager.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require "open3"
55
require "rainbow"
66
require_relative "../packer_utils"
7+
require_relative "service_checker"
78

89
module ReactOnRails
910
module Dev
@@ -330,6 +331,7 @@ def help_options
330331
end
331332
# rubocop:enable Metrics/AbcSize
332333

334+
# rubocop:disable Metrics/AbcSize
333335
def help_customization
334336
<<~CUSTOMIZATION
335337
#{Rainbow('🔧 CUSTOMIZATION:').cyan.bold}
@@ -340,8 +342,24 @@ def help_customization
340342
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev-prod-assets').green.bold} - Production-optimized assets (port 3001)
341343
342344
#{Rainbow('Edit these files to customize the development environment for your needs.').white}
345+
346+
#{Rainbow('🔍 SERVICE DEPENDENCIES:').cyan.bold}
347+
#{Rainbow('Configure required external services in').white} #{Rainbow('.dev-services.yml').green.bold}#{Rainbow(':').white}
348+
349+
#{Rainbow('•').yellow} #{Rainbow('bin/dev').white} #{Rainbow('checks services before starting (optional)').white}
350+
#{Rainbow('•').yellow} #{Rainbow('Copy from').white} #{Rainbow('.dev-services.yml.example').green.bold} #{Rainbow('to get started').white}
351+
#{Rainbow('•').yellow} #{Rainbow('Supports Redis, PostgreSQL, Elasticsearch, and custom services').white}
352+
#{Rainbow('•').yellow} #{Rainbow('Shows helpful errors with start commands if services are missing').white}
353+
354+
#{Rainbow('Example .dev-services.yml:').white}
355+
#{Rainbow(' services:').cyan}
356+
#{Rainbow(' redis:').cyan}
357+
#{Rainbow(' check_command: "redis-cli ping"').cyan}
358+
#{Rainbow(' expected_output: "PONG"').cyan}
359+
#{Rainbow(' start_command: "redis-server"').cyan}
343360
CUSTOMIZATION
344361
end
362+
# rubocop:enable Metrics/AbcSize
345363

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

394412
print_procfile_info(procfile, route: route)
413+
414+
# Check required services before starting
415+
exit 1 unless ServiceChecker.check_services
416+
395417
print_server_info(
396418
"🏭 Starting production-like development server...",
397419
features,
@@ -514,6 +536,9 @@ def run_production_like(_verbose: false, route: nil, rails_env: nil)
514536
def run_static_development(procfile, verbose: false, route: nil)
515537
print_procfile_info(procfile, route: route)
516538

539+
# Check required services before starting
540+
exit 1 unless ServiceChecker.check_services
541+
517542
features = [
518543
"Using shakapacker --watch (no HMR)",
519544
"CSS extracted to separate files (no FOUC)",
@@ -539,6 +564,10 @@ def run_static_development(procfile, verbose: false, route: nil)
539564

540565
def run_development(procfile, verbose: false, route: nil)
541566
print_procfile_info(procfile, route: route)
567+
568+
# Check required services before starting
569+
exit 1 unless ServiceChecker.check_services
570+
542571
PackGenerator.generate(verbose: verbose)
543572
ProcessManager.ensure_procfile(procfile)
544573
ProcessManager.run_with_process_manager(procfile)

0 commit comments

Comments
 (0)