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,304 @@
<?php
/**
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*/
namespace App\RSpade\Commands\Rsx;
use App\RSpade\Core\Manifest\Manifest;
use Exception;
use Illuminate\Console\Command;
/**
* Command to list all RSX routes in table format
*/
class Rsx_Routes_Command extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'rsx:routes
{--type= : Filter by handler type (controller, api, file, custom)}
{--method= : Filter by HTTP method (GET, POST, etc.)}
{--path= : Filter by path pattern (supports wildcards)}
{--class= : Filter by class name}
{--json : Output as JSON}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'List all registered RSX routes in greppable format';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// Get routes from manifest
$routes = Manifest::get_routes();
if (empty($routes)) {
$this->warn('No routes found in manifest.');
return 0;
}
// Collect all routes in a flat array
$all_routes = $this->collect_routes($routes);
// Apply filters
$filtered = $this->filter_routes($all_routes);
if (empty($filtered)) {
$this->warn('No routes match the specified filters.');
return 0;
}
// Output as JSON if requested
if ($this->option('json')) {
$this->line(json_encode($filtered, JSON_PRETTY_PRINT));
return 0;
}
// Display routes in greppable format
foreach ($filtered as $route) {
$this->display_route($route);
}
return 0;
}
/**
* Collect all routes from manifest into flat array
*
* @param array $routes
* @return array
*/
protected function collect_routes($routes)
{
$all_routes = [];
foreach ($routes as $type => $type_routes) {
foreach ($type_routes as $pattern => $methods) {
foreach ($methods as $method => $handlers) {
// Handle new structure where each method can have multiple handlers
if (!isset($handlers[0])) {
// Old structure - single handler
$handlers = [$handlers];
}
foreach ($handlers as $handler) {
$all_routes[] = [
'type' => $type,
'pattern' => $pattern,
'method' => $method,
'class' => $handler['class'] ?? 'N/A',
'action' => $handler['method'] ?? 'N/A',
'middleware' => $this->get_middleware($handler),
'cache' => $this->get_cache_ttl($handler),
'name' => $handler['name'] ?? null,
'attributes' => $handler['attributes'] ?? [],
];
}
}
}
}
// Custom sort: hierarchical path segments with special rules
usort($all_routes, function ($a, $b) {
return $this->compare_routes($a['pattern'], $b['pattern']);
});
return $all_routes;
}
/**
* Filter routes based on command options
*
* @param array $routes
* @return array
*/
protected function filter_routes($routes)
{
$filtered = $routes;
// Filter by type
$type = $this->option('type');
if ($type) {
$filtered = array_filter($filtered, function ($route) use ($type) {
return strcasecmp($route['type'], $type) === 0;
});
}
// Filter by method
$method = $this->option('method');
if ($method) {
$filtered = array_filter($filtered, function ($route) use ($method) {
return strcasecmp($route['method'], $method) === 0;
});
}
// Filter by path pattern
$path = $this->option('path');
if ($path) {
$pattern = str_replace('*', '.*', $path);
$filtered = array_filter($filtered, function ($route) use ($pattern) {
return preg_match('#' . $pattern . '#i', $route['pattern']);
});
}
// Filter by class
$class = $this->option('class');
if ($class) {
$filtered = array_filter($filtered, function ($route) use ($class) {
return stripos($route['class'], $class) !== false;
});
}
return array_values($filtered);
}
/**
* Compare two route patterns for hierarchical sorting
*
* @param string $a First route pattern
* @param string $b Second route pattern
* @return int
*/
protected function compare_routes($a, $b)
{
// Split paths into segments
$segments_a = explode('/', trim($a, '/'));
$segments_b = explode('/', trim($b, '/'));
// Compare segment by segment
$max = max(count($segments_a), count($segments_b));
for ($i = 0; $i < $max; $i++) {
$seg_a = $segments_a[$i] ?? '';
$seg_b = $segments_b[$i] ?? '';
// If one path ends here (empty segment), it comes first
if ($seg_a === '' && $seg_b !== '') {
return -1;
}
if ($seg_b === '' && $seg_a !== '') {
return 1;
}
// Both segments exist - check if they're parameters
$is_param_a = str_starts_with($seg_a, ':');
$is_param_b = str_starts_with($seg_b, ':');
// Non-parameters come before parameters
if (!$is_param_a && $is_param_b) {
return -1;
}
if ($is_param_a && !$is_param_b) {
return 1;
}
// Both same type (both params or both not), compare alphabetically
$cmp = strcmp($seg_a, $seg_b);
if ($cmp !== 0) {
return $cmp;
}
}
return 0;
}
/**
* Display a single route in greppable key-value format
*
* @param array $route
*/
protected function display_route($route)
{
$parts = [];
// Build key-value pairs
$parts[] = 'path=' . $route['pattern'];
$parts[] = 'method=' . $route['method'];
$parts[] = 'controller=' . class_basename($route['class']);
$parts[] = 'action=' . $route['action'];
// Get file path if we can determine it
if ($route['class'] !== 'N/A') {
try {
$file = Manifest::php_find_class(class_basename($route['class']));
if ($file) {
$parts[] = 'file=' . $file;
}
} catch (Exception $e) {
// Class not found in manifest, skip file path
}
}
// Add optional fields if present
if ($route['middleware']) {
$parts[] = 'middleware=' . $route['middleware'];
}
if ($route['cache']) {
$parts[] = 'cache=' . $route['cache'] . 's';
}
if ($route['name']) {
$parts[] = 'name=' . $route['name'];
}
$this->line(implode(' ', $parts));
}
/**
* Get middleware string from handler
*
* @param array $handler
* @return string|null
*/
protected function get_middleware($handler)
{
$attributes = $handler['attributes'] ?? [];
foreach ($attributes as $attr) {
if (isset($attr['class']) && str_ends_with($attr['class'], 'Middleware')) {
$middleware = $attr['args']['middleware'] ?? $attr['args'][0] ?? null;
if (is_array($middleware)) {
return implode(',', $middleware);
}
return $middleware;
}
}
return null;
}
/**
* Get cache TTL from handler
*
* @param array $handler
* @return int|null
*/
protected function get_cache_ttl($handler)
{
$attributes = $handler['attributes'] ?? [];
foreach ($attributes as $attr) {
if (isset($attr['class']) && str_ends_with($attr['class'], 'Cache')) {
return $attr['args']['ttl'] ?? $attr['args'][0] ?? null;
}
}
return null;
}
}