react_on_rails 16.1.2 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +85 -0
- data/Gemfile.development_dependencies +8 -7
- data/Gemfile.lock +158 -119
- data/Steepfile +56 -0
- data/lib/generators/react_on_rails/base_generator.rb +43 -120
- data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
- data/lib/generators/react_on_rails/generator_helper.rb +102 -2
- data/lib/generators/react_on_rails/install_generator.rb +36 -156
- data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
- data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
- data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
- data/lib/react_on_rails/configuration.rb +149 -32
- data/lib/react_on_rails/controller.rb +3 -3
- data/lib/react_on_rails/dev/pack_generator.rb +168 -2
- data/lib/react_on_rails/dev/process_manager.rb +136 -14
- data/lib/react_on_rails/dev/server_manager.rb +194 -26
- data/lib/react_on_rails/dev/service_checker.rb +200 -0
- data/lib/react_on_rails/doctor.rb +341 -12
- data/lib/react_on_rails/engine.rb +75 -1
- data/lib/react_on_rails/git_utils.rb +3 -1
- data/lib/react_on_rails/helper.rb +70 -192
- data/lib/react_on_rails/locales/base.rb +17 -5
- data/lib/react_on_rails/packer_utils.rb +79 -2
- data/lib/react_on_rails/packs_generator.rb +57 -39
- data/lib/react_on_rails/prerender_error.rb +74 -17
- data/lib/react_on_rails/pro_helper.rb +64 -0
- data/lib/react_on_rails/react_component/render_options.rb +7 -7
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
- data/lib/react_on_rails/smart_error.rb +326 -0
- data/lib/react_on_rails/system_checker.rb +8 -9
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
- data/lib/react_on_rails/utils.rb +241 -55
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +383 -35
- data/lib/tasks/generate_packs.rake +12 -6
- data/lib/tasks/locale.rake +6 -1
- data/rakelib/docker.rake +26 -0
- data/rakelib/dummy_apps.rake +30 -0
- data/rakelib/example_type.rb +121 -0
- data/rakelib/examples_config.yml +52 -0
- data/rakelib/lint.rake +52 -0
- data/rakelib/node_package.rake +15 -0
- data/rakelib/rbs.rake +70 -0
- data/rakelib/run_rspec.rake +223 -0
- data/rakelib/shakapacker_examples.rake +171 -0
- data/rakelib/task_helpers.rb +134 -0
- data/rakelib/update_changelog.rake +73 -0
- data/react_on_rails.gemspec +4 -3
- data/sig/README.md +52 -0
- data/sig/react_on_rails/configuration.rbs +96 -0
- data/sig/react_on_rails/controller.rbs +15 -0
- data/sig/react_on_rails/dev/file_manager.rbs +15 -0
- data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
- data/sig/react_on_rails/dev/process_manager.rbs +22 -0
- data/sig/react_on_rails/dev/server_manager.rbs +39 -0
- data/sig/react_on_rails/dev/service_checker.rbs +22 -0
- data/sig/react_on_rails/error.rbs +4 -0
- data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
- data/sig/react_on_rails/git_utils.rbs +8 -0
- data/sig/react_on_rails/helper.rbs +65 -0
- data/sig/react_on_rails/json_parse_error.rbs +10 -0
- data/sig/react_on_rails/locales.rbs +46 -0
- data/sig/react_on_rails/packer_utils.rbs +15 -0
- data/sig/react_on_rails/prerender_error.rbs +21 -0
- data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
- data/sig/react_on_rails/smart_error.rbs +28 -0
- data/sig/react_on_rails/test_helper.rbs +11 -0
- data/sig/react_on_rails/utils.rbs +34 -0
- data/sig/react_on_rails/version_checker.rbs +12 -0
- data/sig/react_on_rails.rbs +17 -0
- metadata +49 -32
- data/AI_AGENT_INSTRUCTIONS.md +0 -63
- data/CHANGELOG.md +0 -1836
- data/CLAUDE.md +0 -135
- data/CODING_AGENTS.md +0 -313
- data/CONTRIBUTING.md +0 -668
- data/Dockerfile_tests +0 -12
- data/KUDOS.md +0 -114
- data/LICENSE.md +0 -47
- data/LICENSES/README.md +0 -14
- data/NEWS.md +0 -62
- data/PROJECTS.md +0 -63
- data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
- data/README.md +0 -217
- data/SUMMARY.md +0 -88
- data/TODO.md +0 -135
- data/bin/lefthook/check-trailing-newlines +0 -38
- data/bin/lefthook/get-changed-files +0 -26
- data/bin/lefthook/prettier-format +0 -26
- data/bin/lefthook/ruby-autofix +0 -26
- data/bin/lefthook/ruby-lint +0 -27
- data/docker-compose.yml +0 -11
- data/eslint.config.ts +0 -232
- data/knip.ts +0 -114
- data/lib/react_on_rails/pro/NOTICE +0 -21
- data/lib/react_on_rails/pro/helper.rb +0 -122
- data/lib/react_on_rails/pro/utils.rb +0 -53
- data/tsconfig.eslint.json +0 -6
- data/tsconfig.json +0 -19
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
require "English"
|
|
4
4
|
require "open3"
|
|
5
5
|
require "rainbow"
|
|
6
|
+
require_relative "../packer_utils"
|
|
7
|
+
require_relative "service_checker"
|
|
6
8
|
|
|
7
9
|
module ReactOnRails
|
|
8
10
|
module Dev
|
|
9
11
|
class ServerManager
|
|
12
|
+
HELP_FLAGS = ["-h", "--help"].freeze
|
|
13
|
+
|
|
10
14
|
class << self
|
|
11
15
|
def start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil)
|
|
12
16
|
case mode
|
|
@@ -27,7 +31,7 @@ module ReactOnRails
|
|
|
27
31
|
puts "🔪 Killing all development processes..."
|
|
28
32
|
puts ""
|
|
29
33
|
|
|
30
|
-
killed_any = kill_running_processes || cleanup_socket_files
|
|
34
|
+
killed_any = kill_running_processes || kill_port_processes([3000, 3001]) || cleanup_socket_files
|
|
31
35
|
|
|
32
36
|
print_kill_summary(killed_any)
|
|
33
37
|
end
|
|
@@ -70,11 +74,39 @@ module ReactOnRails
|
|
|
70
74
|
def terminate_processes(pids)
|
|
71
75
|
pids.each do |pid|
|
|
72
76
|
Process.kill("TERM", pid)
|
|
73
|
-
rescue
|
|
77
|
+
rescue Errno::ESRCH, ArgumentError, RangeError
|
|
78
|
+
# Process already stopped, or invalid signal/PID - silently skip
|
|
79
|
+
nil
|
|
80
|
+
rescue Errno::EPERM
|
|
81
|
+
# Permission denied - warn the user
|
|
82
|
+
puts " ⚠️ Process #{pid} - permission denied (process owned by another user)"
|
|
74
83
|
nil
|
|
75
84
|
end
|
|
76
85
|
end
|
|
77
86
|
|
|
87
|
+
def kill_port_processes(ports)
|
|
88
|
+
killed_any = false
|
|
89
|
+
|
|
90
|
+
ports.each do |port|
|
|
91
|
+
pids = find_port_pids(port)
|
|
92
|
+
next unless pids.any?
|
|
93
|
+
|
|
94
|
+
puts " ☠️ Killing process on port #{port} (PIDs: #{pids.join(', ')})"
|
|
95
|
+
terminate_processes(pids)
|
|
96
|
+
killed_any = true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
killed_any
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def find_port_pids(port)
|
|
103
|
+
stdout, _status = Open3.capture2("lsof", "-ti", ":#{port}", err: File::NULL)
|
|
104
|
+
stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid }
|
|
105
|
+
rescue StandardError
|
|
106
|
+
# lsof command not found or other error (permission denied, etc.)
|
|
107
|
+
[]
|
|
108
|
+
end
|
|
109
|
+
|
|
78
110
|
def cleanup_socket_files
|
|
79
111
|
files = [".overmind.sock", "tmp/sockets/overmind.sock", "tmp/pids/server.pid"]
|
|
80
112
|
killed_any = false
|
|
@@ -116,10 +148,18 @@ module ReactOnRails
|
|
|
116
148
|
puts help_troubleshooting
|
|
117
149
|
end
|
|
118
150
|
|
|
151
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
119
152
|
def run_from_command_line(args = ARGV)
|
|
120
153
|
require "optparse"
|
|
121
154
|
|
|
122
|
-
|
|
155
|
+
# Get the command early to check for help/kill before running hooks
|
|
156
|
+
# We need to do this before OptionParser processes flags like -h/--help
|
|
157
|
+
command = args.find { |arg| !arg.start_with?("--") && !arg.start_with?("-") }
|
|
158
|
+
|
|
159
|
+
# Check if help flags are present in args (before OptionParser processes them)
|
|
160
|
+
help_requested = args.any? { |arg| HELP_FLAGS.include?(arg) }
|
|
161
|
+
|
|
162
|
+
options = { route: nil, rails_env: nil, verbose: false }
|
|
123
163
|
|
|
124
164
|
OptionParser.new do |opts|
|
|
125
165
|
opts.banner = "Usage: dev [command] [options]"
|
|
@@ -132,36 +172,125 @@ module ReactOnRails
|
|
|
132
172
|
options[:rails_env] = env
|
|
133
173
|
end
|
|
134
174
|
|
|
175
|
+
opts.on("-v", "--verbose", "Enable verbose output for pack generation") do
|
|
176
|
+
options[:verbose] = true
|
|
177
|
+
end
|
|
178
|
+
|
|
135
179
|
opts.on("-h", "--help", "Prints this help") do
|
|
136
180
|
show_help
|
|
137
181
|
exit
|
|
138
182
|
end
|
|
139
183
|
end.parse!(args)
|
|
140
184
|
|
|
141
|
-
#
|
|
142
|
-
|
|
185
|
+
# Run precompile hook once before starting any mode (except kill/help)
|
|
186
|
+
# Then set environment variable to prevent duplicate execution in spawned processes.
|
|
187
|
+
# Note: We always set SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true (even when no hook is configured)
|
|
188
|
+
# to provide a consistent signal that bin/dev is managing the precompile lifecycle.
|
|
189
|
+
# This allows custom scripts to detect bin/dev's presence and adjust behavior accordingly.
|
|
190
|
+
unless %w[kill help].include?(command) || help_requested
|
|
191
|
+
run_precompile_hook_if_present
|
|
192
|
+
ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] = "true"
|
|
193
|
+
end
|
|
143
194
|
|
|
144
195
|
# Main execution
|
|
145
196
|
case command
|
|
146
197
|
when "production-assets", "prod"
|
|
147
|
-
start(:production_like, nil, verbose:
|
|
198
|
+
start(:production_like, nil, verbose: options[:verbose], route: options[:route],
|
|
199
|
+
rails_env: options[:rails_env])
|
|
148
200
|
when "static"
|
|
149
|
-
start(:static, "Procfile.dev-static-assets", verbose:
|
|
201
|
+
start(:static, "Procfile.dev-static-assets", verbose: options[:verbose], route: options[:route])
|
|
150
202
|
when "kill"
|
|
151
203
|
kill_processes
|
|
152
|
-
when "help"
|
|
204
|
+
when "help"
|
|
153
205
|
show_help
|
|
154
206
|
when "hmr", nil
|
|
155
|
-
start(:development, "Procfile.dev", verbose:
|
|
207
|
+
start(:development, "Procfile.dev", verbose: options[:verbose], route: options[:route])
|
|
156
208
|
else
|
|
157
209
|
puts "Unknown argument: #{command}"
|
|
158
210
|
puts "Run 'dev help' for usage information"
|
|
159
211
|
exit 1
|
|
160
212
|
end
|
|
161
213
|
end
|
|
214
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
162
215
|
|
|
163
216
|
private
|
|
164
217
|
|
|
218
|
+
def run_precompile_hook_if_present
|
|
219
|
+
require "open3"
|
|
220
|
+
require "shellwords"
|
|
221
|
+
|
|
222
|
+
hook_value = PackerUtils.shakapacker_precompile_hook_value
|
|
223
|
+
return unless hook_value
|
|
224
|
+
|
|
225
|
+
# Warn if Shakapacker version doesn't support SHAKAPACKER_SKIP_PRECOMPILE_HOOK
|
|
226
|
+
warn_if_shakapacker_version_too_old
|
|
227
|
+
|
|
228
|
+
puts Rainbow("🔧 Running Shakapacker precompile hook...").cyan
|
|
229
|
+
puts Rainbow(" Command: #{hook_value}").cyan
|
|
230
|
+
puts ""
|
|
231
|
+
|
|
232
|
+
# Capture stdout and stderr for better error reporting
|
|
233
|
+
# Use Shellwords.split for safer command execution (prevents shell metacharacter interpretation)
|
|
234
|
+
command_args = Shellwords.split(hook_value.to_s)
|
|
235
|
+
stdout, stderr, status = Open3.capture3(*command_args)
|
|
236
|
+
|
|
237
|
+
if status.success?
|
|
238
|
+
puts Rainbow("✅ Precompile hook completed successfully").green
|
|
239
|
+
puts ""
|
|
240
|
+
else
|
|
241
|
+
handle_precompile_hook_failure(hook_value, stdout, stderr)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# rubocop:disable Metrics/AbcSize
|
|
246
|
+
def handle_precompile_hook_failure(hook_value, stdout, stderr)
|
|
247
|
+
puts ""
|
|
248
|
+
puts Rainbow("❌ Precompile hook failed!").red.bold
|
|
249
|
+
puts Rainbow(" Command: #{hook_value}").red
|
|
250
|
+
puts ""
|
|
251
|
+
|
|
252
|
+
if stdout && !stdout.strip.empty?
|
|
253
|
+
puts Rainbow(" Output:").yellow
|
|
254
|
+
stdout.strip.split("\n").each { |line| puts Rainbow(" #{line}").yellow }
|
|
255
|
+
puts ""
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
if stderr && !stderr.strip.empty?
|
|
259
|
+
puts Rainbow(" Error:").red
|
|
260
|
+
stderr.strip.split("\n").each { |line| puts Rainbow(" #{line}").red }
|
|
261
|
+
puts ""
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
puts Rainbow("💡 Fix the hook command in config/shakapacker.yml or remove it to continue").yellow
|
|
265
|
+
exit 1
|
|
266
|
+
end
|
|
267
|
+
# rubocop:enable Metrics/AbcSize
|
|
268
|
+
|
|
269
|
+
# rubocop:disable Metrics/AbcSize
|
|
270
|
+
def warn_if_shakapacker_version_too_old
|
|
271
|
+
# Only warn for Shakapacker versions in the range 9.0.0 to 9.3.x
|
|
272
|
+
# Versions below 9.0.0 don't use the precompile_hook feature
|
|
273
|
+
# Versions 9.4.0+ support SHAKAPACKER_SKIP_PRECOMPILE_HOOK environment variable
|
|
274
|
+
has_precompile_hook_support = PackerUtils.shakapacker_version_requirement_met?("9.0.0")
|
|
275
|
+
has_skip_env_var_support = PackerUtils.shakapacker_version_requirement_met?("9.4.0")
|
|
276
|
+
|
|
277
|
+
return unless has_precompile_hook_support
|
|
278
|
+
return if has_skip_env_var_support
|
|
279
|
+
|
|
280
|
+
puts ""
|
|
281
|
+
puts Rainbow("⚠️ Warning: Shakapacker #{PackerUtils.shakapacker_version} detected").yellow.bold
|
|
282
|
+
puts ""
|
|
283
|
+
puts Rainbow(" The SHAKAPACKER_SKIP_PRECOMPILE_HOOK environment variable is not").yellow
|
|
284
|
+
puts Rainbow(" supported in Shakapacker versions below 9.4.0. This may cause the").yellow
|
|
285
|
+
puts Rainbow(" precompile_hook to run multiple times (once by bin/dev, and again").yellow
|
|
286
|
+
puts Rainbow(" by each webpack process).").yellow
|
|
287
|
+
puts ""
|
|
288
|
+
puts Rainbow(" Recommendation: Upgrade to Shakapacker 9.4.0 or later:").cyan
|
|
289
|
+
puts Rainbow(" bundle update shakapacker").cyan.bold
|
|
290
|
+
puts ""
|
|
291
|
+
end
|
|
292
|
+
# rubocop:enable Metrics/AbcSize
|
|
293
|
+
|
|
165
294
|
def help_usage
|
|
166
295
|
Rainbow("📋 Usage: bin/dev [command] [options]").bold
|
|
167
296
|
end
|
|
@@ -202,6 +331,7 @@ module ReactOnRails
|
|
|
202
331
|
end
|
|
203
332
|
# rubocop:enable Metrics/AbcSize
|
|
204
333
|
|
|
334
|
+
# rubocop:disable Metrics/AbcSize
|
|
205
335
|
def help_customization
|
|
206
336
|
<<~CUSTOMIZATION
|
|
207
337
|
#{Rainbow('🔧 CUSTOMIZATION:').cyan.bold}
|
|
@@ -212,15 +342,31 @@ module ReactOnRails
|
|
|
212
342
|
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev-prod-assets').green.bold} - Production-optimized assets (port 3001)
|
|
213
343
|
|
|
214
344
|
#{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}
|
|
215
360
|
CUSTOMIZATION
|
|
216
361
|
end
|
|
362
|
+
# rubocop:enable Metrics/AbcSize
|
|
217
363
|
|
|
218
364
|
# rubocop:disable Metrics/AbcSize
|
|
219
365
|
def help_mode_details
|
|
220
366
|
<<~MODES
|
|
221
367
|
#{Rainbow('🔥 HMR Development mode (default)').cyan.bold} - #{Rainbow('Procfile.dev').green}:
|
|
222
368
|
#{Rainbow('•').yellow} #{Rainbow('Hot Module Replacement (HMR) enabled').white}
|
|
223
|
-
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation
|
|
369
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
|
|
224
370
|
#{Rainbow('•').yellow} #{Rainbow('Webpack dev server for fast recompilation').white}
|
|
225
371
|
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
|
|
226
372
|
#{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
|
|
@@ -229,7 +375,7 @@ module ReactOnRails
|
|
|
229
375
|
|
|
230
376
|
#{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
|
|
231
377
|
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
|
|
232
|
-
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation
|
|
378
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
|
|
233
379
|
#{Rainbow('•').yellow} #{Rainbow('Webpack watch mode for auto-recompilation').white}
|
|
234
380
|
#{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
|
|
235
381
|
#{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
|
|
@@ -237,7 +383,7 @@ module ReactOnRails
|
|
|
237
383
|
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
|
|
238
384
|
|
|
239
385
|
#{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
|
|
240
|
-
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation
|
|
386
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or assets:precompile)').white}
|
|
241
387
|
#{Rainbow('•').yellow} #{Rainbow('Asset precompilation with NODE_ENV=production (webpack optimizations)').white}
|
|
242
388
|
#{Rainbow('•').yellow} #{Rainbow('RAILS_ENV=development by default for assets:precompile (avoids credentials)').white}
|
|
243
389
|
#{Rainbow('•').yellow} #{Rainbow('Use --rails-env=production for assets:precompile only (not server processes)').white}
|
|
@@ -253,16 +399,24 @@ module ReactOnRails
|
|
|
253
399
|
def run_production_like(_verbose: false, route: nil, rails_env: nil)
|
|
254
400
|
procfile = "Procfile.dev-prod-assets"
|
|
255
401
|
|
|
402
|
+
features = [
|
|
403
|
+
"Precompiling assets with production optimizations",
|
|
404
|
+
"Running Rails server on port 3001",
|
|
405
|
+
"No HMR (Hot Module Replacement)",
|
|
406
|
+
"CSS extracted to separate files (no FOUC)"
|
|
407
|
+
]
|
|
408
|
+
|
|
409
|
+
# NOTE: Pack generation happens automatically during assets:precompile
|
|
410
|
+
# either via precompile hook or via the configuration.rb adjust_precompile_task
|
|
411
|
+
|
|
256
412
|
print_procfile_info(procfile, route: route)
|
|
413
|
+
|
|
414
|
+
# Check required services before starting
|
|
415
|
+
exit 1 unless ServiceChecker.check_services
|
|
416
|
+
|
|
257
417
|
print_server_info(
|
|
258
418
|
"🏭 Starting production-like development server...",
|
|
259
|
-
|
|
260
|
-
"Generating React on Rails packs",
|
|
261
|
-
"Precompiling assets with production optimizations",
|
|
262
|
-
"Running Rails server on port 3001",
|
|
263
|
-
"No HMR (Hot Module Replacement)",
|
|
264
|
-
"CSS extracted to separate files (no FOUC)"
|
|
265
|
-
],
|
|
419
|
+
features,
|
|
266
420
|
3001,
|
|
267
421
|
route: route
|
|
268
422
|
)
|
|
@@ -381,15 +535,25 @@ module ReactOnRails
|
|
|
381
535
|
|
|
382
536
|
def run_static_development(procfile, verbose: false, route: nil)
|
|
383
537
|
print_procfile_info(procfile, route: route)
|
|
538
|
+
|
|
539
|
+
# Check required services before starting
|
|
540
|
+
exit 1 unless ServiceChecker.check_services
|
|
541
|
+
|
|
542
|
+
features = [
|
|
543
|
+
"Using shakapacker --watch (no HMR)",
|
|
544
|
+
"CSS extracted to separate files (no FOUC)",
|
|
545
|
+
"Development environment (source maps, faster builds)",
|
|
546
|
+
"Auto-recompiles on file changes"
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
# Add pack generation info if not using precompile hook
|
|
550
|
+
unless ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
|
|
551
|
+
features.unshift("Generating React on Rails packs")
|
|
552
|
+
end
|
|
553
|
+
|
|
384
554
|
print_server_info(
|
|
385
555
|
"⚡ Starting development server with static assets...",
|
|
386
|
-
|
|
387
|
-
"Generating React on Rails packs",
|
|
388
|
-
"Using shakapacker --watch (no HMR)",
|
|
389
|
-
"CSS extracted to separate files (no FOUC)",
|
|
390
|
-
"Development environment (source maps, faster builds)",
|
|
391
|
-
"Auto-recompiles on file changes"
|
|
392
|
-
],
|
|
556
|
+
features,
|
|
393
557
|
route: route
|
|
394
558
|
)
|
|
395
559
|
|
|
@@ -400,6 +564,10 @@ module ReactOnRails
|
|
|
400
564
|
|
|
401
565
|
def run_development(procfile, verbose: false, route: nil)
|
|
402
566
|
print_procfile_info(procfile, route: route)
|
|
567
|
+
|
|
568
|
+
# Check required services before starting
|
|
569
|
+
exit 1 unless ServiceChecker.check_services
|
|
570
|
+
|
|
403
571
|
PackGenerator.generate(verbose: verbose)
|
|
404
572
|
ProcessManager.ensure_procfile(procfile)
|
|
405
573
|
ProcessManager.run_with_process_manager(procfile)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "English"
|
|
5
|
+
require "rainbow"
|
|
6
|
+
|
|
7
|
+
module ReactOnRails
|
|
8
|
+
module Dev
|
|
9
|
+
# ServiceChecker validates that required external services are running
|
|
10
|
+
# before starting the development server.
|
|
11
|
+
#
|
|
12
|
+
# Configuration is read from .dev-services.yml in the app root:
|
|
13
|
+
#
|
|
14
|
+
# services:
|
|
15
|
+
# redis:
|
|
16
|
+
# check_command: "redis-cli ping"
|
|
17
|
+
# expected_output: "PONG"
|
|
18
|
+
# start_command: "redis-server"
|
|
19
|
+
# description: "Redis (for caching and background jobs)"
|
|
20
|
+
# postgresql:
|
|
21
|
+
# check_command: "pg_isready"
|
|
22
|
+
# expected_output: "accepting connections"
|
|
23
|
+
# start_command: "pg_ctl -D /usr/local/var/postgres start"
|
|
24
|
+
# description: "PostgreSQL database"
|
|
25
|
+
#
|
|
26
|
+
class ServiceChecker
|
|
27
|
+
# Configuration file keys
|
|
28
|
+
CONFIG_KEYS = {
|
|
29
|
+
services: "services",
|
|
30
|
+
check_command: "check_command",
|
|
31
|
+
expected_output: "expected_output",
|
|
32
|
+
start_command: "start_command",
|
|
33
|
+
install_hint: "install_hint",
|
|
34
|
+
description: "description"
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
# Check all required services and provide helpful output
|
|
39
|
+
#
|
|
40
|
+
# @param config_path [String] Path to .dev-services.yml (default: ./.dev-services.yml)
|
|
41
|
+
# @return [Boolean] true if all services are running or no config exists
|
|
42
|
+
def check_services(config_path: ".dev-services.yml")
|
|
43
|
+
return true unless File.exist?(config_path)
|
|
44
|
+
|
|
45
|
+
config = load_config(config_path)
|
|
46
|
+
return true unless config_has_services?(config)
|
|
47
|
+
|
|
48
|
+
check_and_report_services(config, config_path)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def config_has_services?(config)
|
|
54
|
+
config &&
|
|
55
|
+
config[CONFIG_KEYS[:services]].is_a?(Hash) &&
|
|
56
|
+
!config[CONFIG_KEYS[:services]].empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def check_and_report_services(config, config_path)
|
|
60
|
+
print_services_header(config_path)
|
|
61
|
+
|
|
62
|
+
failures = collect_service_failures(config[CONFIG_KEYS[:services]])
|
|
63
|
+
|
|
64
|
+
report_results(failures)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def collect_service_failures(services)
|
|
68
|
+
failures = []
|
|
69
|
+
|
|
70
|
+
services.each do |name, service_config|
|
|
71
|
+
if check_service(name, service_config)
|
|
72
|
+
print_service_ok(name, service_config[CONFIG_KEYS[:description]])
|
|
73
|
+
else
|
|
74
|
+
failures << { name: name, config: service_config }
|
|
75
|
+
print_service_failed(name, service_config[CONFIG_KEYS[:description]])
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
failures
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def report_results(failures)
|
|
83
|
+
if failures.empty?
|
|
84
|
+
print_all_services_ok
|
|
85
|
+
true
|
|
86
|
+
else
|
|
87
|
+
print_failures_summary(failures)
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def load_config(config_path)
|
|
93
|
+
YAML.load_file(config_path)
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
puts Rainbow("⚠️ Failed to load #{config_path}: #{e.message}").yellow
|
|
96
|
+
puts Rainbow(" Continuing without service checks...").yellow
|
|
97
|
+
puts ""
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def check_service(_name, config)
|
|
102
|
+
check_command = config[CONFIG_KEYS[:check_command]]
|
|
103
|
+
expected_output = config[CONFIG_KEYS[:expected_output]]
|
|
104
|
+
|
|
105
|
+
return false if check_command.nil?
|
|
106
|
+
|
|
107
|
+
output, status = run_check_command(check_command)
|
|
108
|
+
|
|
109
|
+
return false if status.nil?
|
|
110
|
+
|
|
111
|
+
return status.success? if expected_output.nil?
|
|
112
|
+
|
|
113
|
+
# Safe nil check for output before calling include?
|
|
114
|
+
status.success? && output&.include?(expected_output)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def run_check_command(command)
|
|
118
|
+
require "open3"
|
|
119
|
+
# Execute command as-is. Commands are from local .dev-services.yml config file
|
|
120
|
+
# which should be trusted. Shell metacharacters won't work as expected since
|
|
121
|
+
# Open3.capture3 doesn't invoke a shell by default for simple command strings.
|
|
122
|
+
stdout, stderr, status = Open3.capture3(command, err: %i[child out])
|
|
123
|
+
output = stdout + stderr
|
|
124
|
+
[output, status]
|
|
125
|
+
rescue Errno::ENOENT
|
|
126
|
+
# Command not found - service is not available
|
|
127
|
+
["", nil]
|
|
128
|
+
rescue ArgumentError => e
|
|
129
|
+
# Invalid command format
|
|
130
|
+
warn "Invalid command format: #{e.message}" if ENV["DEBUG"]
|
|
131
|
+
["", nil]
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
# Log unexpected errors for debugging
|
|
134
|
+
warn "Unexpected error checking service: #{e.message}" if ENV["DEBUG"]
|
|
135
|
+
["", nil]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def print_services_header(config_path)
|
|
139
|
+
puts ""
|
|
140
|
+
puts Rainbow("🔍 Checking required services (#{config_path})...").cyan.bold
|
|
141
|
+
puts ""
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def print_service_ok(name, description)
|
|
145
|
+
desc = description ? " - #{description}" : ""
|
|
146
|
+
puts " #{Rainbow('✓').green} #{name}#{desc}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def print_service_failed(name, description)
|
|
150
|
+
desc = description ? " - #{description}" : ""
|
|
151
|
+
puts " #{Rainbow('✗').red} #{name}#{desc}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def print_all_services_ok
|
|
155
|
+
puts ""
|
|
156
|
+
puts Rainbow("✅ All services are running").green.bold
|
|
157
|
+
puts ""
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# rubocop:disable Metrics/AbcSize
|
|
161
|
+
def print_failures_summary(failures)
|
|
162
|
+
puts ""
|
|
163
|
+
puts Rainbow("❌ Some services are not running").red.bold
|
|
164
|
+
puts ""
|
|
165
|
+
puts Rainbow("Please start these services before running bin/dev:").yellow
|
|
166
|
+
puts ""
|
|
167
|
+
|
|
168
|
+
failures.each do |failure|
|
|
169
|
+
name = failure[:name]
|
|
170
|
+
config = failure[:config]
|
|
171
|
+
description = config[CONFIG_KEYS[:description]] || name
|
|
172
|
+
|
|
173
|
+
puts Rainbow(name.to_s).cyan.bold
|
|
174
|
+
puts " #{description}" if config[CONFIG_KEYS[:description]]
|
|
175
|
+
|
|
176
|
+
if config[CONFIG_KEYS[:start_command]]
|
|
177
|
+
puts ""
|
|
178
|
+
puts " #{Rainbow('To start:').yellow}"
|
|
179
|
+
puts " #{Rainbow(config[CONFIG_KEYS[:start_command]]).green}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if config[CONFIG_KEYS[:install_hint]]
|
|
183
|
+
puts ""
|
|
184
|
+
puts " #{Rainbow('Not installed?').yellow} #{config[CONFIG_KEYS[:install_hint]]}"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
puts ""
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
puts Rainbow("💡 Tips:").blue.bold
|
|
191
|
+
puts " • Start services manually, then run bin/dev again"
|
|
192
|
+
puts " • Or remove service from .dev-services.yml if not needed"
|
|
193
|
+
puts " • Or add service to Procfile.dev to start automatically"
|
|
194
|
+
puts ""
|
|
195
|
+
end
|
|
196
|
+
# rubocop:enable Metrics/AbcSize
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|