Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
<?php
/**
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*/
namespace App\RSpade\Core\Exceptions;
use Illuminate\Http\Request;
use Throwable;
use App\RSpade\Core\Debug\Debugger;
use App\RSpade\Core\Dispatch\Ajax_Endpoint_Controller;
use App\RSpade\Core\Exceptions\Rsx_Exception_Handler_Abstract;
/**
* Ajax_ExceptionHandler - Handle exceptions during AJAX endpoint execution
*
* PRIORITY: 20
*
* This handler processes exceptions that occur during AJAX endpoint execution
* (when using #[Ajax_Endpoint] attribute). It returns JSON-formatted errors with:
* - Error message
* - File and line number
* - Stack trace (last 10 frames)
* - Console debug messages (if any)
*
* This ensures AJAX errors are returned as JSON instead of HTML error pages.
*/
class Ajax_Exception_Handler extends Rsx_Exception_Handler_Abstract
{
/**
* Get priority - AJAX handlers run after CLI but before web
*
* @return int
*/
public static function get_priority(): int
{
return 20;
}
/**
* Handle exception if in AJAX response mode
*
* @param Throwable $e
* @param Request $request
* @return mixed JSON response if in AJAX mode, null otherwise
*/
public function handle(Throwable $e, Request $request)
{
// Only handle if we're in AJAX response mode
if (!Ajax_Endpoint_Controller::is_ajax_response_mode()) {
return null;
}
// Build error response
$error_data = [
'file' => str_replace(base_path() . '/', '', $e->getFile()),
'line' => $e->getLine(),
'error' => $e->getMessage(),
'backtrace' => [],
];
// Get backtrace without args
$trace = $e->getTrace();
foreach ($trace as $index => $frame) {
if ($index >= 10) {
break;
} // Limit to 10 frames
$error_data['backtrace'][] = [
'file' => isset($frame['file']) ? str_replace(base_path() . '/', '', $frame['file']) : 'unknown',
'line' => $frame['line'] ?? 0,
'function' => $frame['function'] ?? 'unknown',
'class' => $frame['class'] ?? null,
'type' => $frame['type'] ?? null,
];
}
// Build response
$response = ['error' => $error_data];
// Include console debug messages if any
$console_messages = Debugger::_get_console_messages();
if (!empty($console_messages)) {
$response['console_debug'] = $console_messages;
}
// Return JSON error response
return response()->json($response, 500);
}
}

View File

@@ -0,0 +1,177 @@
<?php
/**
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*/
namespace App\RSpade\Core\Exceptions;
use Illuminate\Http\Request;
use Throwable;
use App\RSpade\Core\Exceptions\Rsx_Exception_Handler_Abstract;
/**
* Cli_ExceptionHandler - Handle exceptions in CLI mode with formatted output
*
* PRIORITY: 10 (high priority - runs first)
*
* This handler processes exceptions when running in CLI mode (artisan commands).
* It provides formatted, colored output optimized for terminal display with:
* - Exception type and location
* - Wrapped error message
* - Stack trace (last 10 calls)
* - Console debug messages (if any)
*
* When this handler handles an exception, it outputs to STDERR and exits with code 1.
*/
class Cli_Exception_Handler extends Rsx_Exception_Handler_Abstract
{
/**
* Get priority - CLI handlers run first
*
* @return int
*/
public static function get_priority(): int
{
return 10;
}
/**
* Handle exception if in CLI mode
*
* @param Throwable $e
* @param Request $request
* @return mixed Response if handled (exits), null if not CLI mode
*/
public function handle(Throwable $e, Request $request)
{
// Only handle in CLI mode
if (!app()->runningInConsole()) {
return null;
}
// ANSI color codes
$reset = "\033[0m";
$bold = "\033[1m";
$bold_orange = "\033[1;38;5;208m";
$amber = "\033[33m";
$white = "\033[37m";
$bold_white = "\033[1;37m";
// Get exception class name (without namespace)
$exception_type = (new \ReflectionClass($e))->getShortName();
// Format file path (remove base path for readability)
$file = str_replace(base_path() . '/', '', $e->getFile());
// Build formatted error output
$error_output = "\n";
$error_output .= "Fatal {$bold_orange}{$exception_type}{$reset} on {$amber}{$file}{$white}:{$amber}{$e->getLine()}{$reset}\n";
$error_output .= "\n";
// Format error message with word wrapping if terminal width detected
$error_message = $e->getMessage();
$terminal_width = 0;
try {
// Try to get terminal width
$terminal = new \Symfony\Component\Console\Terminal();
$terminal_width = $terminal->getWidth();
} catch (\Exception $ex) {
// Ignore, use default formatting
}
if ($terminal_width > 50) { // Valid width detected
// Word wrap with 2-space indent
$max_width = $terminal_width - 2; // Account for indent
$wrapped_lines = [];
$words = explode(' ', $error_message);
$current_line = '';
foreach ($words as $word) {
$test_line = $current_line ? $current_line . ' ' . $word : $word;
if (strlen($test_line) > $max_width) {
if ($current_line) {
$wrapped_lines[] = $current_line;
$current_line = $word;
} else {
// Single word longer than max width, force break
$wrapped_lines[] = $word;
$current_line = '';
}
} else {
$current_line = $test_line;
}
}
if ($current_line) {
$wrapped_lines[] = $current_line;
}
// Add each line with indent and color
foreach ($wrapped_lines as $line) {
$error_output .= " {$bold_white}{$line}{$reset}\n";
}
} else {
// Default: no wrapping, no indent
$error_output .= "{$bold_white}{$error_message}{$reset}\n";
}
$error_output .= "\n";
$error_output .= "Stack Trace (last 10 calls):\n";
// Get stack trace
$trace = $e->getTrace();
$count = 0;
foreach ($trace as $frame) {
if ($count >= 10) {
break;
}
$file = $frame['file'] ?? 'unknown';
$line = $frame['line'] ?? 0;
$function = $frame['function'] ?? 'unknown';
$class = $frame['class'] ?? '';
$type = $frame['type'] ?? '';
if ($class) {
$function = $class . $type . $function;
}
$error_output .= sprintf(
" #%d %s:%d %s()\n",
$count,
$file,
$line,
$function
);
$count++;
}
// Output console debug messages if enabled and any exist
if (!app()->environment('production')) {
$console_messages = \App\RSpade\Core\Debug\Debugger::_get_console_messages();
if (!empty($console_messages)) {
$error_output .= "\nConsole Debug Messages:\n";
foreach ($console_messages as $message) {
// Messages are structured arrays with channel and arguments
if (is_array($message) && count($message) >= 2) {
$channel_line = $message[0];
$arguments = $message[1];
$error_output .= ' ' . $channel_line;
foreach ($arguments as $arg) {
if (is_scalar($arg) || is_null($arg)) {
$output = is_bool($arg) ? ($arg ? 'true' : 'false') :
(is_null($arg) ? 'null' : (string)$arg);
$error_output .= ' ' . $output;
}
}
$error_output .= "\n";
}
}
}
}
// Output to STDERR and exit with error code
fwrite(STDERR, $error_output);
exit(1);
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*
*/
namespace App\RSpade\Core\Exceptions;
use Illuminate\Foundation\Exceptions\Handler;
use Illuminate\Http\Request;
use RuntimeException;
use Throwable;
use App\RSpade\Core\Exceptions\Rsx_Exception_Handler_Abstract;
/**
* Rsx_ExceptionHandler - Main exception handler for RSX framework
*
* FILE-SUBCLASS-01*: Extends Laravel's Handler but uses compound suffix ExceptionHandler
* This is intentional for consistency with other *_ExceptionHandler classes
*
* HOW THIS IS CALLED:
* File: bootstrap/app.php
* Mechanism: Laravel's exception handler registration
* Code: $app->singleton(ExceptionHandler::class, Rsx_ExceptionHandler::class)
*
* WHAT IT DOES:
* Implements a chain-of-responsibility pattern for exception handling:
* 1. Loads exception handler classes from config/rsx.php
* 2. Sorts handlers by priority (lower number = higher priority)
* 3. Calls each handler's handle() method in order
* 4. Returns first non-null response
* 5. Falls back to Laravel's default handling if no handler responds
*
* HANDLER EXECUTION ORDER:
* - Priority 10: CLI exceptions (formatted output for terminal)
* - Priority 20: AJAX exceptions (JSON error responses)
* - Priority 30: Playwright test exceptions (plain text output)
* - Priority 1000: RSX dispatch bootstrapper (404 RSX routing)
*
* EXTENSIBILITY:
* Users can add/remove/reorder handlers by modifying config/rsx.php:
* 'exception_handlers' => [
* \App\MyApp\My_Custom_ExceptionHandler::class,
* \App\RSpade\Core\Exceptions\Cli_ExceptionHandler::class,
* // ... etc
* ]
*/
#[Instantiatable]
class Rsx_Exception_Handler extends Handler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
*/
protected $dontReport = [];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
// Default reportable - can be extended
});
// Main renderable - executes the handler chain
$this->renderable(function (Throwable $e, Request $request) {
// Get handler classes from config
$handler_classes = config('rsx.exception_handlers', []);
if (empty($handler_classes)) {
// No handlers configured, fall back to default Laravel handling
return null;
}
// Validate and collect handlers with their priorities
$handlers_with_priority = [];
foreach ($handler_classes as $handler_class) {
// Validate handler class exists
if (!class_exists($handler_class)) {
throw new RuntimeException(
"Exception handler class not found: {$handler_class}. " .
"Check your config/rsx.php 'exception_handlers' array."
);
}
// Validate handler extends abstract base
if (!is_subclass_of($handler_class, Rsx_Exception_Handler_Abstract::class)) {
throw new RuntimeException(
"Exception handler {$handler_class} must extend Rsx_Exception_Handler_Abstract. " .
"Check your config/rsx.php 'exception_handlers' array."
);
}
$handlers_with_priority[] = [
'class' => $handler_class,
'priority' => $handler_class::get_priority(),
];
}
// Sort by priority (lower number = higher priority)
usort($handlers_with_priority, function ($a, $b) {
return $a['priority'] <=> $b['priority'];
});
// Execute handlers in priority order
foreach ($handlers_with_priority as $handler_info) {
$handler_class = $handler_info['class'];
$handler = new $handler_class();
$response = $handler->handle($e, $request);
// If handler returned a response, use it
if ($response !== null) {
return $response;
}
}
// No handler handled the exception, continue to Laravel's default handling
return null;
});
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*/
namespace App\RSpade\Core\Exceptions;
use Illuminate\Http\Request;
use Throwable;
/**
* Rsx_Exception_Handler_Abstract - Base class for RSX exception handlers
*
* This abstract class defines the contract for exception handlers in the RSX framework.
* Handlers are executed in priority order (lower number = higher priority) and can
* choose to handle an exception by returning a response, or pass it to the next handler
* by returning null.
*
* HANDLER CHAIN:
* - Handlers are registered in config/rsx.php under 'exception_handlers'
* - Main Rsx_ExceptionHandler loads and sorts handlers by priority
* - Each handler's handle() method is called in order
* - First non-null response is returned
* - If all handlers return null, Laravel's default handling is used
*
* CREATING A HANDLER:
* 1. Extend this abstract class
* 2. Implement handle() method
* 3. Optionally override get_priority() (default is 100)
* 4. Add to config/rsx.php 'exception_handlers' array
*
* EXAMPLE:
* class My_Custom_ExceptionHandler extends Rsx_Exception_Handler_Abstract
* {
* public static function get_priority(): int { return 50; }
*
* public function handle(Throwable $e, Request $request)
* {
* if (!($e instanceof MyException)) {
* return null; // Not my concern
* }
*
* return response('Custom error', 500);
* }
* }
*/
#[Instantiatable]
abstract class Rsx_Exception_Handler_Abstract
{
/**
* Try to handle the exception
*
* @param Throwable $e The exception to handle
* @param Request $request The current request
* @return mixed Response if this handler handles the exception, null to pass to next handler
*/
abstract public function handle(Throwable $e, Request $request);
/**
* Get priority for this handler
*
* Lower numbers have higher priority and run first.
* Default is 100 for normal handlers.
*
* Suggested priority ranges:
* - 1-50: Critical/environment-specific handlers (CLI, AJAX)
* - 51-100: Standard handlers
* - 101-500: Low priority handlers
* - 501+: Fallback* or catch-all handlers
*
* @return int Priority value (lower = higher priority)
*/
public static function get_priority(): int
{
return 100;
}
}