environment('production')) { throw new \RuntimeException('FATAL: rsx:debug command is not available in production environment. This is a development-only debugging tool.'); } // Check if --examples was requested if ($this->option('examples')) { $this->showExamples(); return 0; } // Get the URL to debug $url = $this->argument('url'); // If no URL provided, show help if (!$url) { $this->error('No URL provided. Use --examples to see usage examples.'); return 1; } // Check if console_debug is disabled globally and user didn't override $console_debug_enabled = config('rsx.console_debug.enabled', false) || env('CONSOLE_DEBUG_ENABLED') === 'true'; $console_debug_override = $this->option('console-log') || $this->option('console-list') || $this->option('console-debug-all') || $this->option('console-debug-filter') || $this->option('console-debug-benchmark') || env('CONSOLE_DEBUG_FILTER') || env('CONSOLE_DEBUG_BENCHMARK'); $console_debug_disabled = $this->option('console-debug-disable') || env('CONSOLE_DEBUG_ENABLED') === 'false'; // If console_debug is disabled and not overridden, show a single line message if (!$console_debug_enabled && !$console_debug_override && !$console_debug_disabled) { $this->line('console_debug is disabled. Run `php artisan rsx:man console_debug` for more information on its usage.'); // Don't return early - still run the test, just with the message } // Ensure URL starts with / if (!str_starts_with($url, '/')) { $url = '/' . $url; } // Get user ID from options $user_id = $this->option('user-id') ?: $this->option('user'); // Get log flag $show_log = $this->option('log'); // Get no-body flag $no_body = $this->option('no-body'); // Get follow-redirects flag $follow_redirects = $this->option('follow-redirects'); // Get headers flag $headers = $this->option('headers'); // Get console-log flag $console_log = $this->option('console-log'); // Get xhr-dump flag $xhr_dump = $this->option('xhr-dump'); // Get input-elements flag $input_elements = $this->option('input-elements'); // Get POST data $post_data = $this->option('post'); // Get cookies flag $cookies = $this->option('cookies'); // Get wait-for selector $wait_for = $this->option('wait-for'); // Get all-logs flag $all_logs = $this->option('all-logs'); // Get new feature flags $expect_element = $this->option('expect-element'); $dump_element = $this->option('dump-element'); $storage = $this->option('storage'); $xhr_list = $this->option('xhr-list'); $full = $this->option('full'); $eval_code = $this->option('eval'); // Get timeout option and validate $timeout = $this->option('timeout'); if ($timeout !== null) { $timeout = intval($timeout); if ($timeout < 30000) { $this->error('❌ Timeout value is in milliseconds and must be no less than 30000 milliseconds (30 seconds)'); return 1; } } else { $timeout = 30000; // Default 30 seconds } // Get console debug options (with environment variable fallbacks) $console_debug_filter = $this->option('console-debug-filter') ?: env('CONSOLE_DEBUG_FILTER'); $console_debug_benchmark = $this->option('console-debug-benchmark') ?: env('CONSOLE_DEBUG_BENCHMARK', false); $console_debug_all = $this->option('console-debug-all') ?: (env('CONSOLE_DEBUG_FILTER') === 'ALL'); $console_debug_disable = $this->option('console-debug-disable') ?: (env('CONSOLE_DEBUG_ENABLED') === 'false'); // Auto-enable console-log and console_debug when console-debug-filter is set if ($console_debug_filter && !$console_debug_disable) { $console_log = true; // Enable console log output // console_debug is enabled via the filter itself } // Auto-enable console_debug when CONSOLE_DEBUG_ENABLED=true if (env('CONSOLE_DEBUG_ENABLED') === 'true' && !$console_debug_disable) { // Enable console output if not explicitly disabled if (!$console_debug_filter && !$console_debug_all) { $console_debug_all = true; // Show all channels if no specific filter } } $console_list = $this->option('console-list'); // console-list is an alias for console-log if ($console_list) { $console_log = true; } // Rotate logs before test to ensure clean slate Debugger::logrotate(); // Check if Playwright script exists $playwright_script = base_path('bin/route-debug.js'); if (!file_exists($playwright_script)) { $this->error("❌ Playwright script not found: {$playwright_script}"); $this->error('Please create the script or check your installation'); return 1; } // Check if node/npm is available $node_check = new Process(['node', '--version']); $node_check->run(); if (!$node_check->isSuccessful()) { $this->error('❌ Node.js is not installed or not in PATH'); return 1; } // Check if playwright is installed $playwright_check = new Process(['node', '-e', "require('playwright')"], base_path()); $playwright_check->run(); if (!$playwright_check->isSuccessful()) { $this->warn('⚠️ Playwright not installed. Installing now...'); $npm_install = new Process(['npm', 'install', 'playwright'], base_path()); $npm_install->run(function ($type, $buffer) { echo $buffer; }); if (!$npm_install->isSuccessful()) { $this->error('❌ Failed to install Playwright'); return 1; } $this->info('✅ Playwright installed'); $this->info(''); } // Check if chromium browser is installed and up to date $browser_check_script = "const {chromium} = require('playwright'); chromium.launch({headless:true}).then(b => {b.close(); process.exit(0);}).catch(e => {console.error(e.message); process.exit(1);});"; $browser_check = new Process(['node', '-e', $browser_check_script], base_path(), $_ENV, null, 10); $browser_check->run(); if (!$browser_check->isSuccessful()) { $error_output = $browser_check->getErrorOutput() . $browser_check->getOutput(); // Check if it's a browser not installed or out of date error if (str_contains($error_output, "Executable doesn't exist") || str_contains($error_output, "browserType.launch") || str_contains($error_output, "Playwright was just installed or updated")) { $this->info('Installing/updating Chromium browser...'); $browser_install = new Process(['npx', 'playwright', 'install', 'chromium'], base_path()); $browser_install->setTimeout(300); // 5 minute timeout for download $browser_install->run(function ($type, $buffer) { // Silent - downloads can be verbose }); if (!$browser_install->isSuccessful()) { $this->error('❌ Failed to install Chromium browser'); $this->error('Run manually: npx playwright install chromium'); return 1; } $this->info('✅ Chromium browser installed/updated'); $this->info(''); } else { $this->error('❌ Browser check failed: ' . trim($error_output)); return 1; } } // Build command arguments $command_args = ['node', $playwright_script, $url]; if ($user_id) { $command_args[] = "--user-id={$user_id}"; } if ($show_log) { $command_args[] = '--log'; } if ($no_body) { $command_args[] = '--no-body'; } if ($follow_redirects) { $command_args[] = '--follow-redirects'; } if ($headers) { $command_args[] = '--headers'; } if ($console_log) { $command_args[] = '--console-log'; } if ($xhr_dump) { $command_args[] = '--xhr-dump'; } if ($input_elements) { $command_args[] = '--input-elements'; } if ($post_data) { $command_args[] = "--post={$post_data}"; } if ($cookies) { $command_args[] = '--cookies'; } if ($wait_for) { $command_args[] = "--wait-for={$wait_for}"; } if ($all_logs) { $command_args[] = '--all-logs'; } if ($expect_element) { $command_args[] = "--expect-element={$expect_element}"; } if ($dump_element) { $command_args[] = "--dump-element={$dump_element}"; } if ($storage) { $command_args[] = '--storage'; } if ($xhr_list) { $command_args[] = '--xhr-list'; } if ($full) { $command_args[] = '--full'; } if ($eval_code) { // Don't use escapeshellarg here as it adds extra quotes // Just pass the eval code directly since Process class handles escaping $command_args[] = "--eval={$eval_code}"; } if ($timeout) { $command_args[] = "--timeout={$timeout}"; } if ($console_debug_filter) { $command_args[] = "--console-debug-filter={$console_debug_filter}"; } if ($console_debug_benchmark) { $command_args[] = "--console-debug-benchmark"; } if ($console_debug_all) { $command_args[] = "--console-debug-all"; } if ($console_debug_disable) { $command_args[] = "--console-debug-disable"; } // Pass Laravel log path as environment variable $laravel_log_path = storage_path('logs/laravel.log'); $env = array_merge($_ENV, [ 'LARAVEL_LOG_PATH' => $laravel_log_path ]); // Add console debug filter to environment if provided if ($console_debug_filter) { $env['CONSOLE_DEBUG_FILTER'] = $console_debug_filter; $env['CONSOLE_DEBUG_ENABLED'] = 'true'; // Enable console_debug when filter is set } // Convert timeout from milliseconds to seconds for Process timeout // Add 10 seconds buffer to the Process timeout to allow Playwright to timeout first $process_timeout = ($timeout / 1000) + 10; // Release the application lock before running Playwright to prevent lock contention // The artisan command holds a WRITE lock which would block the web request's READ lock \App\RSpade\Core\Bootstrap\RsxBootstrap::temporarily_release_lock(); $process = new Process( $command_args, base_path(), $env, null, $process_timeout ); $process->run(function ($type, $buffer) { // Output directly to console echo $buffer; }); // Rotate logs after test to clean slate for next run Debugger::logrotate(); return $process->isSuccessful() ? 0 : 1; } /** * Show comprehensive usage examples */ protected function showExamples() { $this->info('RSX Debug Command - Comprehensive Usage Examples'); $this->line('================================================='); $this->line(''); $this->comment('BASIC USAGE:'); $this->line(' php artisan rsx:debug /dashboard # Test a URL'); $this->line(' php artisan rsx:debug /api/users --no-body # Headers only'); $this->line(' php artisan rsx:debug /login --full # All information'); $this->line(''); $this->comment('AUTHENTICATION:'); $this->line(' php artisan rsx:debug /admin --user=1 # Test as user ID 1'); $this->line(' php artisan rsx:debug /profile --user-id=5 # Alternative syntax'); $this->line(''); $this->comment('TESTING RSX JAVASCRIPT:'); $this->line(' php artisan rsx:debug /demo --eval="Rsx.Route(\'Demo_Controller\').url()" --no-body'); $this->line(' php artisan rsx:debug /demo --eval="JSON.stringify(Rsx._routes)" --no-body'); $this->line(' php artisan rsx:debug /demo --eval="Rsx.is_dev()" --no-body'); $this->line(''); $this->comment('DEBUGGING OUTPUT:'); $this->line(' php artisan rsx:debug / --console-log # All console output'); $this->line(' php artisan rsx:debug / --console-debug-filter=AUTH # Filter console_debug'); $this->line(' php artisan rsx:debug / --console-debug-benchmark # With timing'); $this->line(' php artisan rsx:debug / --all-logs # Show all log files'); $this->line(''); $this->comment('XHR/AJAX MONITORING:'); $this->line(' php artisan rsx:debug /api --xhr-list # Simple XHR list'); $this->line(' php artisan rsx:debug /api --xhr-dump # Full XHR details'); $this->line(''); $this->comment('DOM INSPECTION:'); $this->line(' php artisan rsx:debug /form --expect-element="#submit" # Verify element exists'); $this->line(' php artisan rsx:debug /page --dump-element=".content" # Extract element HTML'); $this->line(' php artisan rsx:debug /form --input-elements # List form inputs'); $this->line(' php artisan rsx:debug /slow --wait-for=".loaded" # Wait for element'); $this->line(''); $this->comment('HTTP TESTING:'); $this->line(' php artisan rsx:debug /api --post=\'{"key":"value"}\' # POST JSON data'); $this->line(' php artisan rsx:debug /auth --follow-redirects # Track redirects'); $this->line(' php artisan rsx:debug /api --headers # Display all headers'); $this->line(' php artisan rsx:debug /app --cookies # Show all cookies'); $this->line(''); $this->comment('BROWSER STATE:'); $this->line(' php artisan rsx:debug /app --storage # View localStorage/sessionStorage'); $this->line(' php artisan rsx:debug /slow --timeout=60000 # 60 second timeout'); $this->line(''); $this->comment('IMPORTANT NOTES:'); $this->line(' • When using rsx:debug with grep and no output appears, re-run without grep'); $this->line(' to see the full context and any errors that may have occurred'); $this->line(' • Use rsx_dump_die() in your code for temporary debugging output'); $this->line(' • This command is development-only and disabled in production'); $this->line(' • For more details on console_debug: php artisan rsx:man console_debug'); $this->line(' • For config options: php artisan rsx:man config_rsx'); } }