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>
305 lines
8.7 KiB
PHP
Executable File
305 lines
8.7 KiB
PHP
Executable File
<?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;
|
|
}
|
|
}
|