Skip to content

Commit f1a3fb9

Browse files
justin808claude
andauthored
Enhance bin/dev error messages with verbose flag suggestion (#2083)
## Summary When `bin/dev` fails with a non-zero exit code, users now see a helpful suggestion to run with the `--verbose` flag for detailed output. This improves the debugging experience by making it clear how to get more information about failures. Additionally, the `--verbose` flag now properly propagates to child processes like pack generation through the `REACT_ON_RAILS_VERBOSE` environment variable. ## Changes - **PackGenerator**: Suggests `--verbose` flag on failure (unless already in verbose mode) - **Verbose propagation**: Verbose mode propagates via `REACT_ON_RAILS_VERBOSE` env var to child processes - **PacksGenerator**: Respects verbose flag to control output verbosity - **Rake task**: `generate_packs` respects `REACT_ON_RAILS_VERBOSE` env var - **Tests**: Added comprehensive tests for verbose flag behavior ## Before ``` 📦 Generating packs... ❌ ❌ Pack generation failed ``` ## After ``` 📦 Generating packs... ❌ ❌ Pack generation failed 💡 Run with --verbose flag for detailed output: bin/dev --verbose ``` ## With --verbose flag ``` 📦 Generating React on Rails packs... 🚀 Starting React on Rails pack generation... 📁 Auto-load bundle: true 📂 Components subdirectory: ror_components 🧹 Cleaning non-generated files... No unexpected files found in /path/to/generated ✨ Pack generation completed in 81.8ms ``` ## Testing All existing tests pass, plus new tests for: - Verbose flag suggestion on failure - No suggestion when already in verbose mode - Environment variable propagation to child processes ## Motivation This addresses the issue where `bin/dev` failures showed terse error messages without guidance on how to get detailed diagnostic output, making debugging difficult for users. --------- Co-authored-by: Claude <[email protected]>
1 parent b2a9aeb commit f1a3fb9

File tree

6 files changed

+192
-62
lines changed

6 files changed

+192
-62
lines changed

lib/react_on_rails/dev/pack_generator.rb

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,40 @@ def generate(verbose: false)
3838

3939
if verbose
4040
puts "📦 Generating React on Rails packs..."
41-
success = run_pack_generation
41+
success = run_pack_generation(silent: false, verbose: true)
4242
else
4343
print "📦 Generating packs... "
44-
success = run_pack_generation(silent: true)
44+
success = run_pack_generation(silent: true, verbose: false)
4545
puts success ? "✅" : "❌"
4646
end
4747

4848
return if success
4949

5050
puts "❌ Pack generation failed"
51+
unless verbose
52+
puts ""
53+
puts "💡 Run with #{Rainbow('--verbose').cyan.bold} flag for detailed output:"
54+
puts " #{Rainbow('bin/dev --verbose').green.bold}"
55+
end
5156
exit 1
5257
end
5358

5459
private
5560

56-
def run_pack_generation(silent: false)
61+
def run_pack_generation(silent: false, verbose: false)
62+
# Set environment variable for child processes to respect verbose mode
63+
ENV["REACT_ON_RAILS_VERBOSE"] = verbose ? "true" : "false"
64+
5765
# If we're already inside a Bundler context AND Rails is available (e.g., called from bin/dev),
5866
# we can directly require and run the task. Otherwise, use bundle exec.
5967
if should_run_directly?
6068
run_rake_task_directly(silent: silent)
6169
else
62-
run_via_bundle_exec(silent: silent)
70+
run_via_bundle_exec(silent: silent, verbose: verbose)
6371
end
72+
ensure
73+
# Clean up environment variable
74+
ENV.delete("REACT_ON_RAILS_VERBOSE")
6475
end
6576

6677
def should_run_directly?
@@ -140,17 +151,22 @@ def handle_rake_error(error, _silent)
140151
# rubocop:enable Style/StderrPuts, Style/GlobalStdStream
141152
end
142153

143-
def run_via_bundle_exec(silent: false)
154+
def run_via_bundle_exec(silent: false, verbose: false)
155+
# Environment variable is already set in run_pack_generation, but we make it explicit here
156+
# for clarity and to ensure it's passed to the subprocess
157+
env = { "REACT_ON_RAILS_VERBOSE" => verbose ? "true" : "false" }
158+
144159
# Need to unbundle to prevent Bundler from intercepting our bundle exec call
145160
# when already running inside a Bundler context (e.g., from bin/dev)
146161
with_unbundled_context do
147162
if silent
148163
system(
164+
env,
149165
"bundle", "exec", "rake", "react_on_rails:generate_packs",
150166
out: File::NULL, err: File::NULL
151167
)
152168
else
153-
system("bundle", "exec", "rake", "react_on_rails:generate_packs")
169+
system(env, "bundle", "exec", "rake", "react_on_rails:generate_packs")
154170
end
155171
end
156172
end

lib/react_on_rails/packs_generator.rb

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,42 @@ def react_on_rails_npm_package
2525
def generate_packs_if_stale
2626
return unless ReactOnRails.configuration.auto_load_bundle
2727

28+
verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"
29+
2830
add_generated_pack_to_server_bundle
2931

3032
# Clean any non-generated files from directories
31-
clean_non_generated_files_with_feedback
33+
clean_non_generated_files_with_feedback(verbose: verbose)
3234

3335
are_generated_files_present_and_up_to_date = Dir.exist?(generated_packs_directory_path) &&
3436
File.exist?(generated_server_bundle_file_path) &&
3537
!stale_or_missing_packs?
3638

3739
if are_generated_files_present_and_up_to_date
38-
puts Rainbow("✅ Generated packs are up to date, no regeneration needed").green
40+
puts Rainbow("✅ Generated packs are up to date, no regeneration needed").green if verbose
3941
return
4042
end
4143

42-
clean_generated_directories_with_feedback
43-
generate_packs
44+
clean_generated_directories_with_feedback(verbose: verbose)
45+
generate_packs(verbose: verbose)
4446
end
4547

4648
private
4749

48-
def generate_packs
49-
common_component_to_path.each_value { |component_path| create_pack(component_path) }
50-
client_component_to_path.each_value { |component_path| create_pack(component_path) }
50+
def generate_packs(verbose: false)
51+
common_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }
52+
client_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }
5153

52-
create_server_pack if ReactOnRails.configuration.server_bundle_js_file.present?
54+
create_server_pack(verbose: verbose) if ReactOnRails.configuration.server_bundle_js_file.present?
5355
end
5456

55-
def create_pack(file_path)
57+
def create_pack(file_path, verbose: false)
5658
output_path = generated_pack_path(file_path)
5759
content = pack_file_contents(file_path)
5860

5961
File.write(output_path, content)
6062

61-
puts(Rainbow("Generated Packs: #{output_path}").yellow)
63+
puts(Rainbow("Generated Packs: #{output_path}").yellow) if verbose
6264
end
6365

6466
def first_js_statement_in_code(content) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
@@ -126,11 +128,11 @@ def pack_file_contents(file_path)
126128
FILE_CONTENT
127129
end
128130

129-
def create_server_pack
131+
def create_server_pack(verbose: false)
130132
File.write(generated_server_bundle_file_path, generated_server_pack_file_content)
131133

132134
add_generated_pack_to_server_bundle
133-
puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange)
135+
puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange) if verbose
134136
end
135137

136138
def build_server_pack_content(component_on_server_imports, server_components, client_components)
@@ -200,17 +202,17 @@ def generated_server_bundle_file_path
200202
"#{generated_nonentrypoints_path}/#{generated_server_bundle_file_name}.js"
201203
end
202204

203-
def clean_non_generated_files_with_feedback
205+
def clean_non_generated_files_with_feedback(verbose: false)
204206
directories_to_clean = [generated_packs_directory_path, generated_server_bundle_directory_path].compact.uniq
205207
expected_files = build_expected_files_set
206208

207-
puts Rainbow("🧹 Cleaning non-generated files...").yellow
209+
puts Rainbow("🧹 Cleaning non-generated files...").yellow if verbose
208210

209211
total_deleted = directories_to_clean.sum do |dir_path|
210-
clean_unexpected_files_from_directory(dir_path, expected_files)
212+
clean_unexpected_files_from_directory(dir_path, expected_files, verbose: verbose)
211213
end
212214

213-
display_cleanup_summary(total_deleted)
215+
display_cleanup_summary(total_deleted, verbose: verbose) if verbose
214216
end
215217

216218
def build_expected_files_set
@@ -225,17 +227,17 @@ def build_expected_files_set
225227
{ pack_files: expected_pack_files, server_bundle: expected_server_bundle }
226228
end
227229

228-
def clean_unexpected_files_from_directory(dir_path, expected_files)
230+
def clean_unexpected_files_from_directory(dir_path, expected_files, verbose: false)
229231
return 0 unless Dir.exist?(dir_path)
230232

231233
existing_files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
232234
unexpected_files = find_unexpected_files(existing_files, dir_path, expected_files)
233235

234236
if unexpected_files.any?
235-
delete_unexpected_files(unexpected_files, dir_path)
237+
delete_unexpected_files(unexpected_files, dir_path, verbose: verbose)
236238
unexpected_files.length
237239
else
238-
puts Rainbow(" No unexpected files found in #{dir_path}").cyan
240+
puts Rainbow(" No unexpected files found in #{dir_path}").cyan if verbose
239241
0
240242
end
241243
end
@@ -250,31 +252,39 @@ def find_unexpected_files(existing_files, dir_path, expected_files)
250252
end
251253
end
252254

253-
def delete_unexpected_files(unexpected_files, dir_path)
254-
puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
255-
unexpected_files.each do |file|
256-
puts Rainbow(" - #{File.basename(file)}").blue
257-
File.delete(file)
255+
def delete_unexpected_files(unexpected_files, dir_path, verbose: false)
256+
if verbose
257+
puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
258+
unexpected_files.each do |file|
259+
puts Rainbow(" - #{File.basename(file)}").blue
260+
File.delete(file)
261+
end
262+
else
263+
unexpected_files.each { |file| File.delete(file) }
258264
end
259265
end
260266

261-
def display_cleanup_summary(total_deleted)
267+
def display_cleanup_summary(total_deleted, verbose: false)
268+
return unless verbose
269+
262270
if total_deleted.positive?
263271
puts Rainbow("🗑️ Deleted #{total_deleted} unexpected files total").red
264272
else
265273
puts Rainbow("✨ No unexpected files to delete").green
266274
end
267275
end
268276

269-
def clean_generated_directories_with_feedback
277+
def clean_generated_directories_with_feedback(verbose: false)
270278
directories_to_clean = [
271279
generated_packs_directory_path,
272280
generated_server_bundle_directory_path
273281
].compact.uniq
274282

275-
puts Rainbow("🧹 Cleaning generated directories...").yellow
283+
puts Rainbow("🧹 Cleaning generated directories...").yellow if verbose
284+
285+
total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path, verbose: verbose) }
276286

277-
total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path) }
287+
return unless verbose
278288

279289
if total_deleted.positive?
280290
puts Rainbow("🗑️ Deleted #{total_deleted} generated files total").red
@@ -283,27 +293,29 @@ def clean_generated_directories_with_feedback
283293
end
284294
end
285295

286-
def clean_directory_with_feedback(dir_path)
287-
return create_directory_with_feedback(dir_path) unless Dir.exist?(dir_path)
296+
def clean_directory_with_feedback(dir_path, verbose: false)
297+
return create_directory_with_feedback(dir_path, verbose: verbose) unless Dir.exist?(dir_path)
288298

289299
files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
290300

291301
if files.any?
292-
puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
293-
files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
302+
if verbose
303+
puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
304+
files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
305+
end
294306
FileUtils.rm_rf(dir_path)
295307
FileUtils.mkdir_p(dir_path)
296308
files.length
297309
else
298-
puts Rainbow(" Directory #{dir_path} is already empty").cyan
310+
puts Rainbow(" Directory #{dir_path} is already empty").cyan if verbose
299311
FileUtils.rm_rf(dir_path)
300312
FileUtils.mkdir_p(dir_path)
301313
0
302314
end
303315
end
304316

305-
def create_directory_with_feedback(dir_path)
306-
puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan
317+
def create_directory_with_feedback(dir_path, verbose: false)
318+
puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan if verbose
307319
FileUtils.mkdir_p(dir_path)
308320
0
309321
end

lib/tasks/generate_packs.rake

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,24 @@ namespace :react_on_rails do
1717
DESC
1818

1919
task generate_packs: :environment do
20-
puts Rainbow("🚀 Starting React on Rails pack generation...").bold
21-
puts Rainbow("📁 Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
22-
puts Rainbow("📂 Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
23-
puts ""
20+
verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"
21+
22+
if verbose
23+
puts Rainbow("🚀 Starting React on Rails pack generation...").bold
24+
puts Rainbow("📁 Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
25+
puts Rainbow("📂 Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
26+
puts ""
27+
end
2428

2529
begin
2630
start_time = Time.now
2731
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
2832
end_time = Time.now
2933

30-
puts ""
31-
puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
34+
if verbose
35+
puts ""
36+
puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
37+
end
3238
rescue ReactOnRails::Error => e
3339
handle_react_on_rails_error(e)
3440
exit 1

sig/react_on_rails/dev/pack_generator.rbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ module ReactOnRails
55

66
private
77

8-
def self.run_pack_generation: (?silent: bool) -> bool
8+
def self.run_pack_generation: (?silent: bool, ?verbose: bool) -> bool
99
def self.should_run_directly?: () -> bool
1010
def self.rails_available?: () -> bool
1111
def self.run_rake_task_directly: (?silent: bool) -> bool
1212
def self.load_rake_tasks: () -> void
1313
def self.prepare_rake_task: () -> untyped
1414
def self.capture_output: (bool) { () -> bool } -> bool
1515
def self.handle_rake_error: (Exception, bool) -> void
16-
def self.run_via_bundle_exec: (?silent: bool) -> (bool | nil)
16+
def self.run_via_bundle_exec: (?silent: bool, ?verbose: bool) -> (bool | nil)
1717
end
1818
end
1919
end

spec/dummy/spec/packs_generator_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,19 +413,25 @@ def self.rsc_support_enabled?
413413
it "generate packs if a new component is added" do
414414
create_new_component("NewComponent")
415415

416+
# Set verbose mode to see pack generation output
417+
ENV["REACT_ON_RAILS_VERBOSE"] = "true"
416418
expect do
417419
described_class.instance.generate_packs_if_stale
418420
end.to output(GENERATED_PACKS_CONSOLE_OUTPUT_REGEX).to_stdout
421+
ENV.delete("REACT_ON_RAILS_VERBOSE")
419422
FileUtils.rm "#{packer_source_path}/components/ComponentWithCommonOnly/ror_components/NewComponent.jsx"
420423
end
421424

422425
it "generate packs if an old component is updated" do
423426
FileUtils.rm component_pack
424427
create_new_component(component_name)
425428

429+
# Set verbose mode to see pack generation output
430+
ENV["REACT_ON_RAILS_VERBOSE"] = "true"
426431
expect do
427432
described_class.instance.generate_packs_if_stale
428433
end.to output(GENERATED_PACKS_CONSOLE_OUTPUT_REGEX).to_stdout
434+
ENV.delete("REACT_ON_RAILS_VERBOSE")
429435
end
430436

431437
def create_new_component(name)

0 commit comments

Comments
 (0)