diff --git a/app/models/app_generation/log_entry.rb b/app/models/app_generation/log_entry.rb index 4b47500..92ddac6 100644 --- a/app/models/app_generation/log_entry.rb +++ b/app/models/app_generation/log_entry.rb @@ -29,9 +29,13 @@ class LogEntry < ApplicationRecord self.table_name = "app_generation_log_entries" + def rails_output? + entry_type == "rails_output" + end + after_commit -> { + Rails.logger.debug "%%%%% LogEntry Create callback: #{id}" stream_name = "#{generated_app.to_gid}:app_generation_log_entries" - Turbo::StreamsChannel.broadcast_prepend_to( stream_name, target: "app_generation_log_entries", @@ -41,8 +45,8 @@ class LogEntry < ApplicationRecord }, on: :create after_commit -> { + Rails.logger.debug "%%%%% LogEntry Update callback: #{id}, message length: #{message.length}" stream_name = "#{generated_app.to_gid}:app_generation_log_entries" - Turbo::StreamsChannel.broadcast_replace_to( stream_name, target: "log_entry_#{id}", diff --git a/app/notifiers/app_status_change_notifier.rb b/app/notifiers/app_status_change_notifier.rb index 0c169e3..7c11e41 100644 --- a/app/notifiers/app_status_change_notifier.rb +++ b/app/notifiers/app_status_change_notifier.rb @@ -1,4 +1,6 @@ class AppStatusChangeNotifier < Noticed::Event + include Rails.application.routes.url_helpers + deliver_by :action_cable do |config| config.channel = "Noticed::NotificationChannel" config.stream = -> { recipient } diff --git a/app/services/command_execution_service.rb b/app/services/command_execution_service.rb index e594c49..e36e007 100644 --- a/app/services/command_execution_service.rb +++ b/app/services/command_execution_service.rb @@ -113,10 +113,13 @@ def run_isolated_process env = { "BUNDLE_GEMFILE" => nil, + "BUNDLE_PATH" => File.join(@temp_dir, "vendor/bundle"), + "BUNDLE_APP_CONFIG" => File.join(@temp_dir, ".bundle"), "RAILS_ENV" => "development", "NODE_ENV" => "development", "PATH" => ENV["PATH"] } + log_environment_variables_for_command_execution(env) buffer = Buffer.new(@generated_app) @@ -125,35 +128,17 @@ def run_isolated_process @pid = wait_thr&.pid @logger.info("Rails app generation process started", { pid: @pid }) - # Auto-accept all prompts - stdin_thread = Thread.new do - loop do - stdin.puts "Y" - sleep 0.1 - end - rescue IOError - # Stream closed - end - stdout_thread = Thread.new do stdout.each_line do |line| buffer.append(line.strip) end end - stderr_thread = Thread.new do - stderr.each_line do |line| - buffer.append(line.strip) - end - end - - [stdout_thread, stderr_thread].each(&:join) - stdin_thread.kill # Clean up stdin thread + stdout_thread.join buffer.complete! - exit_status = wait_thr&.value - output = buffer.join("\n").presence || "No output" + output = buffer.message || "No output" if exit_status&.success? @logger.info("Rails app generation process finished successfully", { diff --git a/app/services/command_execution_service/buffer.rb b/app/services/command_execution_service/buffer.rb index 6a17582..de7a02f 100644 --- a/app/services/command_execution_service/buffer.rb +++ b/app/services/command_execution_service/buffer.rb @@ -9,13 +9,15 @@ def initialize(generated_app) @log_entry = create_initial_log_entry @last_flush = Time.current @completed = false + Rails.logger.debug "%%%%% Buffer initialized with log entry #{@log_entry.id}" end def append(message) should_flush = false synchronize do - @output << message.gsub("\n", "
") + Rails.logger.debug "%%%%% Buffer append: Adding message of length #{message.length}" + @output << message should_flush = should_flush? end @@ -23,33 +25,38 @@ def append(message) end def complete! + Rails.logger.debug "%%%%% Buffer complete! called" @completed = true flush end def flush - message = nil - synchronize do - message = @output.join("\n") - message = "No output" if message.blank? - end + return if @output.empty? - if @completed - @log_entry.update!(message: message, entry_type: nil) - else - @log_entry.update!(message: message) - end + Rails.logger.debug "%%%%% Buffer flush: Current entry: #{@log_entry&.id}, buffer size: #{@output.length}" - synchronize do + if @log_entry.nil? + @log_entry = create_initial_log_entry + Rails.logger.debug "%%%%% Created new log entry: #{@log_entry.id}" + end + + new_content = @output.join("\n") + message = @log_entry.message.present? ? "#{@log_entry.message}\n#{new_content}" : new_content + Rails.logger.debug "%%%%% Updating log entry #{@log_entry.id} with message length: #{message.length}" + + @log_entry.update!( + message: message, + phase: @generated_app.status + ) + + @output.clear @last_flush = Time.current end end - def join(separator = "\n") - synchronize do - @output.join(separator) - end + def message + @log_entry&.message end private @@ -70,7 +77,8 @@ def synchronize(&block) end def should_flush? - Time.current - @last_flush >= FLUSH_INTERVAL + # Flush if enough time has passed OR if buffer is getting large + Time.current - @last_flush >= FLUSH_INTERVAL || @output.length >= 20 end end end diff --git a/app/views/app_generation/log_entries/_log_entry.html.erb b/app/views/app_generation/log_entries/_log_entry.html.erb index 64cf226..3795621 100644 --- a/app/views/app_generation/log_entries/_log_entry.html.erb +++ b/app/views/app_generation/log_entries/_log_entry.html.erb @@ -1,9 +1,9 @@ <%= turbo_frame_tag "log_entry_#{log_entry.id}" do %> -
-
"> +
">
diff --git a/test/services/command_execution_service/buffer_test.rb b/test/services/command_execution_service/buffer_test.rb index 9019f90..156a5b3 100644 --- a/test/services/command_execution_service/buffer_test.rb +++ b/test/services/command_execution_service/buffer_test.rb @@ -31,7 +31,13 @@ def setup @buffer.flush log_entry = AppGeneration::LogEntry.last - assert_equal "First message\nSecond message", log_entry.message + expected_message = [ + "Initializing Rails application generation...", + "First message", + "Second message" + ].join("\n") + + assert_equal expected_message, log_entry.message assert_equal "rails_output", log_entry.entry_type end end diff --git a/test/services/command_execution_service_test.rb b/test/services/command_execution_service_test.rb index 8565748..6fc725d 100644 --- a/test/services/command_execution_service_test.rb +++ b/test/services/command_execution_service_test.rb @@ -36,9 +36,7 @@ def teardown error = "Sample error" @valid_commands.each do |command| - # Store the current count to only delete new entries initial_count = @generated_app.log_entries.count - service = CommandExecutionService.new(@generated_app, command) Open3.stub :popen3, mock_popen3(output, error, success: true) do @@ -55,21 +53,17 @@ def teardown "Preparing to execute command", "System environment details", "Environment variables for command execution", - "Sample output" + "Initializing Rails application generation...\nSample output" ] expected_messages.each_with_index do |message, index| assert_equal message, log_entries[index].message, "Log entry #{index} doesn't match for command: #{command}" end - # Verify the final buffer content buffer_entry = log_entries.find { |entry| entry.metadata["stream"] == "stdout" } - assert_equal "Sample output", buffer_entry.message - - # Verify specific log levels + assert_equal "Initializing Rails application generation...\nSample output", buffer_entry.message assert log_entries.all?(&:info?) - # Clean up only the entries from this iteration @generated_app.log_entries.where("id > ?", @generated_app.log_entries.limit(initial_count).pluck(:id).last).destroy_all end end @@ -166,15 +160,14 @@ def teardown assert_equal "Rails app generation process finished successfully", log_entries[0].message assert_equal "Rails app generation process started", log_entries[1].message - assert_equal "Sample output", log_entries[2].message + assert_equal "Initializing Rails application generation...\nSample output", log_entries[2].message assert_equal "Environment variables for command execution", log_entries[3].message assert_equal "System environment details", log_entries[4].message assert_equal "Preparing to execute command", log_entries[5].message assert_equal "Created temporary directory", log_entries[6].message - # Fix: Look for the log entry with stdout stream buffer_entry = log_entries.find { |entry| entry.metadata["stream"] == "stdout" } - assert_equal "Sample output", buffer_entry.message + assert_equal "Initializing Rails application generation...\nSample output", buffer_entry.message end end @@ -189,10 +182,9 @@ def teardown log_entries = @generated_app.log_entries.recent_first - # Verify the sequence of logs expected_messages = [ "Command failed", - "No output", # From Buffer's default message + "Initializing Rails application generation...", "Rails app generation process started", "Environment variables for command execution", "System environment details", @@ -203,14 +195,12 @@ def teardown ] expected_messages.each do |message| - assert log_entries.any? { |entry| entry.message.include?(message) }, - "Expected to find log entry containing '#{message}'" + assert log_entries.any? { |entry| entry.message.include?(message) } end - # Verify error details error_log = log_entries.find { |entry| entry.message == "Command failed" } assert error_log.error? - assert_equal "No output", error_log.metadata["output"] + assert_equal "Initializing Rails application generation...", error_log.metadata["output"] assert error_log.metadata["status"] end end