Skip to content

Commit 58b5f82

Browse files
justin808claude
andcommitted
Enhance bin/dev error messages with verbose flag suggestion
When bin/dev fails with non-zero exit code, users now see a helpful suggestion to run with --verbose flag for detailed output. This improves the debugging experience by making it clear how to get more information. 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 verbose) - 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 - Added comprehensive tests for verbose flag behavior This addresses the issue where bin/dev failures showed terse error messages without guidance on how to get detailed diagnostic output. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent b2a9aeb commit 58b5f82

File tree

4 files changed

+110
-44
lines changed

4 files changed

+110
-44
lines changed

lib/react_on_rails/dev/pack_generator.rb

Lines changed: 14 additions & 3 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(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
6270
run_via_bundle_exec(silent: silent)
6371
end
72+
ensure
73+
# Clean up environment variable
74+
ENV.delete("REACT_ON_RAILS_VERBOSE")
6475
end
6576

6677
def should_run_directly?

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

spec/react_on_rails/dev/pack_generator_spec.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,43 @@
104104
expect(error_output.join("\n")).to match(/Error generating packs: Task failed/)
105105
end
106106

107+
it "suggests --verbose flag when pack generation fails in quiet mode" do
108+
allow(mock_task).to receive(:invoke).and_raise(StandardError.new("Task failed"))
109+
110+
# Mock STDERR.puts to suppress error output
111+
# rubocop:disable Style/GlobalStdStream
112+
allow(STDERR).to receive(:puts)
113+
# rubocop:enable Style/GlobalStdStream
114+
115+
expect { described_class.generate(verbose: false) }
116+
.to output(/Run with.*--verbose.*flag for detailed output/).to_stdout_from_any_process
117+
.and raise_error(SystemExit)
118+
end
119+
120+
it "does not suggest --verbose flag when already in verbose mode" do
121+
allow(mock_task).to receive(:invoke).and_raise(StandardError.new("Task failed"))
122+
123+
# Mock STDERR.puts to suppress error output
124+
# rubocop:disable Style/GlobalStdStream
125+
allow(STDERR).to receive(:puts)
126+
# rubocop:enable Style/GlobalStdStream
127+
128+
# Capture output to verify --verbose suggestion is not shown
129+
output = StringIO.new
130+
# rubocop:disable RSpec/ExpectOutput
131+
begin
132+
$stdout = output
133+
described_class.generate(verbose: true)
134+
rescue SystemExit
135+
# Expected to exit
136+
ensure
137+
$stdout = STDOUT
138+
end
139+
# rubocop:enable RSpec/ExpectOutput
140+
141+
expect(output.string).not_to match(/Run with.*--verbose/)
142+
end
143+
107144
it "outputs errors to stderr even in silent mode" do
108145
allow(mock_task).to receive(:invoke).and_raise(StandardError.new("Silent mode error"))
109146

0 commit comments

Comments
 (0)