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
28 changes: 22 additions & 6 deletions lib/react_on_rails/dev/pack_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,40 @@ def generate(verbose: false)

if verbose
puts "πŸ“¦ Generating React on Rails packs..."
success = run_pack_generation
success = run_pack_generation(silent: false, verbose: true)
else
print "πŸ“¦ Generating packs... "
success = run_pack_generation(silent: true)
success = run_pack_generation(silent: true, verbose: false)
puts success ? "βœ…" : "❌"
end

return if success

puts "❌ Pack generation failed"
unless verbose
puts ""
puts "πŸ’‘ Run with #{Rainbow('--verbose').cyan.bold} flag for detailed output:"
puts " #{Rainbow('bin/dev --verbose').green.bold}"
end
exit 1
end

private

def run_pack_generation(silent: false)
def run_pack_generation(silent: false, verbose: false)
# Set environment variable for child processes to respect verbose mode
ENV["REACT_ON_RAILS_VERBOSE"] = verbose ? "true" : "false"

# If we're already inside a Bundler context AND Rails is available (e.g., called from bin/dev),
# we can directly require and run the task. Otherwise, use bundle exec.
if should_run_directly?
run_rake_task_directly(silent: silent)
else
run_via_bundle_exec(silent: silent)
run_via_bundle_exec(silent: silent, verbose: verbose)
end
ensure
# Clean up environment variable
ENV.delete("REACT_ON_RAILS_VERBOSE")
end

def should_run_directly?
Expand Down Expand Up @@ -140,17 +151,22 @@ def handle_rake_error(error, _silent)
# rubocop:enable Style/StderrPuts, Style/GlobalStdStream
end

def run_via_bundle_exec(silent: false)
def run_via_bundle_exec(silent: false, verbose: false)
# Environment variable is already set in run_pack_generation, but we make it explicit here
# for clarity and to ensure it's passed to the subprocess
env = { "REACT_ON_RAILS_VERBOSE" => verbose ? "true" : "false" }

# Need to unbundle to prevent Bundler from intercepting our bundle exec call
# when already running inside a Bundler context (e.g., from bin/dev)
with_unbundled_context do
if silent
system(
env,
"bundle", "exec", "rake", "react_on_rails:generate_packs",
out: File::NULL, err: File::NULL
)
else
system("bundle", "exec", "rake", "react_on_rails:generate_packs")
system(env, "bundle", "exec", "rake", "react_on_rails:generate_packs")
end
end
end
Expand Down
82 changes: 47 additions & 35 deletions lib/react_on_rails/packs_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,42 @@ def react_on_rails_npm_package
def generate_packs_if_stale
return unless ReactOnRails.configuration.auto_load_bundle

verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"

add_generated_pack_to_server_bundle

# Clean any non-generated files from directories
clean_non_generated_files_with_feedback
clean_non_generated_files_with_feedback(verbose: verbose)

are_generated_files_present_and_up_to_date = Dir.exist?(generated_packs_directory_path) &&
File.exist?(generated_server_bundle_file_path) &&
!stale_or_missing_packs?

if are_generated_files_present_and_up_to_date
puts Rainbow("βœ… Generated packs are up to date, no regeneration needed").green
puts Rainbow("βœ… Generated packs are up to date, no regeneration needed").green if verbose
return
end

clean_generated_directories_with_feedback
generate_packs
clean_generated_directories_with_feedback(verbose: verbose)
generate_packs(verbose: verbose)
end

private

def generate_packs
common_component_to_path.each_value { |component_path| create_pack(component_path) }
client_component_to_path.each_value { |component_path| create_pack(component_path) }
def generate_packs(verbose: false)
common_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }
client_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }

create_server_pack if ReactOnRails.configuration.server_bundle_js_file.present?
create_server_pack(verbose: verbose) if ReactOnRails.configuration.server_bundle_js_file.present?
end

def create_pack(file_path)
def create_pack(file_path, verbose: false)
output_path = generated_pack_path(file_path)
content = pack_file_contents(file_path)

File.write(output_path, content)

puts(Rainbow("Generated Packs: #{output_path}").yellow)
puts(Rainbow("Generated Packs: #{output_path}").yellow) if verbose
end

def first_js_statement_in_code(content) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
Expand Down Expand Up @@ -126,11 +128,11 @@ def pack_file_contents(file_path)
FILE_CONTENT
end

def create_server_pack
def create_server_pack(verbose: false)
File.write(generated_server_bundle_file_path, generated_server_pack_file_content)

add_generated_pack_to_server_bundle
puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange)
puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange) if verbose
end

def build_server_pack_content(component_on_server_imports, server_components, client_components)
Expand Down Expand Up @@ -200,17 +202,17 @@ def generated_server_bundle_file_path
"#{generated_nonentrypoints_path}/#{generated_server_bundle_file_name}.js"
end

def clean_non_generated_files_with_feedback
def clean_non_generated_files_with_feedback(verbose: false)
directories_to_clean = [generated_packs_directory_path, generated_server_bundle_directory_path].compact.uniq
expected_files = build_expected_files_set

puts Rainbow("🧹 Cleaning non-generated files...").yellow
puts Rainbow("🧹 Cleaning non-generated files...").yellow if verbose

total_deleted = directories_to_clean.sum do |dir_path|
clean_unexpected_files_from_directory(dir_path, expected_files)
clean_unexpected_files_from_directory(dir_path, expected_files, verbose: verbose)
end

display_cleanup_summary(total_deleted)
display_cleanup_summary(total_deleted, verbose: verbose) if verbose
end

def build_expected_files_set
Expand All @@ -225,17 +227,17 @@ def build_expected_files_set
{ pack_files: expected_pack_files, server_bundle: expected_server_bundle }
end

def clean_unexpected_files_from_directory(dir_path, expected_files)
def clean_unexpected_files_from_directory(dir_path, expected_files, verbose: false)
return 0 unless Dir.exist?(dir_path)

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

if unexpected_files.any?
delete_unexpected_files(unexpected_files, dir_path)
delete_unexpected_files(unexpected_files, dir_path, verbose: verbose)
unexpected_files.length
else
puts Rainbow(" No unexpected files found in #{dir_path}").cyan
puts Rainbow(" No unexpected files found in #{dir_path}").cyan if verbose
0
end
end
Expand All @@ -250,31 +252,39 @@ def find_unexpected_files(existing_files, dir_path, expected_files)
end
end

def delete_unexpected_files(unexpected_files, dir_path)
puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
unexpected_files.each do |file|
puts Rainbow(" - #{File.basename(file)}").blue
File.delete(file)
def delete_unexpected_files(unexpected_files, dir_path, verbose: false)
if verbose
puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
unexpected_files.each do |file|
puts Rainbow(" - #{File.basename(file)}").blue
File.delete(file)
end
else
unexpected_files.each { |file| File.delete(file) }
end
end

def display_cleanup_summary(total_deleted)
def display_cleanup_summary(total_deleted, verbose: false)
return unless verbose

if total_deleted.positive?
puts Rainbow("πŸ—‘οΈ Deleted #{total_deleted} unexpected files total").red
else
puts Rainbow("✨ No unexpected files to delete").green
end
end

def clean_generated_directories_with_feedback
def clean_generated_directories_with_feedback(verbose: false)
directories_to_clean = [
generated_packs_directory_path,
generated_server_bundle_directory_path
].compact.uniq

puts Rainbow("🧹 Cleaning generated directories...").yellow
puts Rainbow("🧹 Cleaning generated directories...").yellow if verbose

total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path, verbose: verbose) }

total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path) }
return unless verbose

if total_deleted.positive?
puts Rainbow("πŸ—‘οΈ Deleted #{total_deleted} generated files total").red
Expand All @@ -283,27 +293,29 @@ def clean_generated_directories_with_feedback
end
end

def clean_directory_with_feedback(dir_path)
return create_directory_with_feedback(dir_path) unless Dir.exist?(dir_path)
def clean_directory_with_feedback(dir_path, verbose: false)
return create_directory_with_feedback(dir_path, verbose: verbose) unless Dir.exist?(dir_path)

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

if files.any?
puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
if verbose
puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
end
FileUtils.rm_rf(dir_path)
FileUtils.mkdir_p(dir_path)
files.length
else
puts Rainbow(" Directory #{dir_path} is already empty").cyan
puts Rainbow(" Directory #{dir_path} is already empty").cyan if verbose
FileUtils.rm_rf(dir_path)
FileUtils.mkdir_p(dir_path)
0
end
end

def create_directory_with_feedback(dir_path)
puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan
def create_directory_with_feedback(dir_path, verbose: false)
puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan if verbose
FileUtils.mkdir_p(dir_path)
0
end
Expand Down
18 changes: 12 additions & 6 deletions lib/tasks/generate_packs.rake
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ namespace :react_on_rails do
DESC

task generate_packs: :environment do
puts Rainbow("πŸš€ Starting React on Rails pack generation...").bold
puts Rainbow("πŸ“ Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
puts Rainbow("πŸ“‚ Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
puts ""
verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"

if verbose
puts Rainbow("πŸš€ Starting React on Rails pack generation...").bold
puts Rainbow("πŸ“ Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
puts Rainbow("πŸ“‚ Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
puts ""
end

begin
start_time = Time.now
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
end_time = Time.now

puts ""
puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
if verbose
puts ""
puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
end
rescue ReactOnRails::Error => e
handle_react_on_rails_error(e)
exit 1
Expand Down
4 changes: 2 additions & 2 deletions sig/react_on_rails/dev/pack_generator.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ module ReactOnRails

private

def self.run_pack_generation: (?silent: bool) -> bool
def self.run_pack_generation: (?silent: bool, ?verbose: bool) -> bool
def self.should_run_directly?: () -> bool
def self.rails_available?: () -> bool
def self.run_rake_task_directly: (?silent: bool) -> bool
def self.load_rake_tasks: () -> void
def self.prepare_rake_task: () -> untyped
def self.capture_output: (bool) { () -> bool } -> bool
def self.handle_rake_error: (Exception, bool) -> void
def self.run_via_bundle_exec: (?silent: bool) -> (bool | nil)
def self.run_via_bundle_exec: (?silent: bool, ?verbose: bool) -> (bool | nil)
end
end
end
6 changes: 6 additions & 0 deletions spec/dummy/spec/packs_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -413,19 +413,25 @@ def self.rsc_support_enabled?
it "generate packs if a new component is added" do
create_new_component("NewComponent")

# Set verbose mode to see pack generation output
ENV["REACT_ON_RAILS_VERBOSE"] = "true"
expect do
described_class.instance.generate_packs_if_stale
end.to output(GENERATED_PACKS_CONSOLE_OUTPUT_REGEX).to_stdout
ENV.delete("REACT_ON_RAILS_VERBOSE")
FileUtils.rm "#{packer_source_path}/components/ComponentWithCommonOnly/ror_components/NewComponent.jsx"
end

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

# Set verbose mode to see pack generation output
ENV["REACT_ON_RAILS_VERBOSE"] = "true"
expect do
described_class.instance.generate_packs_if_stale
end.to output(GENERATED_PACKS_CONSOLE_OUTPUT_REGEX).to_stdout
ENV.delete("REACT_ON_RAILS_VERBOSE")
end

def create_new_component(name)
Expand Down
Loading
Loading