Fix code quality violations and rename select input components
Move small tasks from wishlist to todo, update npm packages Replace #[Auth] attributes with manual auth checks and code quality rule Remove on_jqhtml_ready lifecycle method from framework Complete ACL system with 100-based role indexing and /dev/acl tester WIP: ACL system implementation with debug instrumentation Convert rsx:check JS linting to RPC socket server Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature Reorganize wishlists: priority order, mark sublayouts complete, add email Update model_fetch docs: mark MVP complete, fix enum docs, reorganize Comprehensive documentation overhaul: clarity, compression, and critical rules Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null() Add JS ORM relationship lazy-loading and fetch array handling Add JS ORM relationship fetching and CRUD documentation Fix ORM hydration and add IDE resolution for Base_* model stubs Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework Enhance JS ORM infrastructure and add Json_Tree class name badges 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
352
app/RSpade/CodeQuality/Support/Js_CodeQuality_Rpc.php
Executable file
352
app/RSpade/CodeQuality/Support/Js_CodeQuality_Rpc.php
Executable file
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
namespace App\RSpade\CodeQuality\Support;
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* JavaScript Code Quality RPC Client
|
||||
*
|
||||
* Manages a persistent Node.js RPC server for JavaScript linting and
|
||||
* this-usage analysis. This avoids spawning thousands of Node processes
|
||||
* during code quality checks.
|
||||
*
|
||||
* RPC Methods:
|
||||
* - lint: Check JavaScript syntax using Babel parser
|
||||
* - analyze_this: Analyze 'this' usage patterns using Acorn
|
||||
*/
|
||||
class Js_CodeQuality_Rpc
|
||||
{
|
||||
/**
|
||||
* Node.js RPC server script path
|
||||
*/
|
||||
protected const RPC_SERVER_SCRIPT = 'app/RSpade/CodeQuality/Support/resource/js-code-quality-server.js';
|
||||
|
||||
/**
|
||||
* Unix socket path for RPC server
|
||||
*/
|
||||
protected const RPC_SOCKET = 'storage/rsx-tmp/js-code-quality-server.sock';
|
||||
|
||||
/**
|
||||
* RPC server process
|
||||
*/
|
||||
protected static $rpc_server_process = null;
|
||||
|
||||
/**
|
||||
* Request ID counter
|
||||
*/
|
||||
protected static $request_id = 0;
|
||||
|
||||
/**
|
||||
* Lint a JavaScript file for syntax errors
|
||||
*
|
||||
* @param string $file_path Path to the JavaScript file
|
||||
* @return array|null Error info array or null if no errors
|
||||
*/
|
||||
public static function lint(string $file_path): ?array
|
||||
{
|
||||
// Start RPC server on first use (lazy initialization)
|
||||
if (static::$rpc_server_process === null) {
|
||||
static::start_rpc_server();
|
||||
}
|
||||
|
||||
return static::_lint_via_rpc($file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze a JavaScript file for 'this' usage violations
|
||||
*
|
||||
* @param string $file_path Path to the JavaScript file
|
||||
* @return array Violations array (may be empty)
|
||||
*/
|
||||
public static function analyze_this(string $file_path): array
|
||||
{
|
||||
// Start RPC server on first use (lazy initialization)
|
||||
if (static::$rpc_server_process === null) {
|
||||
static::start_rpc_server();
|
||||
}
|
||||
|
||||
return static::_analyze_this_via_rpc($file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint via RPC server
|
||||
*/
|
||||
protected static function _lint_via_rpc(string $file_path): ?array
|
||||
{
|
||||
$base_path = function_exists('base_path') ? base_path() : '/var/www/html/system';
|
||||
$socket_path = $base_path . '/' . self::RPC_SOCKET;
|
||||
|
||||
try {
|
||||
$sock = @stream_socket_client("unix://{$socket_path}", $errno, $errstr, 0.5);
|
||||
if (!$sock) {
|
||||
throw new \RuntimeException("Failed to connect to RPC server: {$errstr}");
|
||||
}
|
||||
|
||||
// Set blocking mode for reliable reads
|
||||
stream_set_blocking($sock, true);
|
||||
|
||||
// Send lint request
|
||||
$request = [
|
||||
'id' => ++static::$request_id,
|
||||
'method' => 'lint',
|
||||
'files' => [$file_path]
|
||||
];
|
||||
|
||||
fwrite($sock, json_encode($request) . "\n");
|
||||
|
||||
// Read response
|
||||
$response = fgets($sock);
|
||||
fclose($sock);
|
||||
|
||||
if (!$response) {
|
||||
throw new \RuntimeException("No response from RPC server");
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data || !is_array($data)) {
|
||||
throw new \RuntimeException("Invalid JSON response from RPC server");
|
||||
}
|
||||
|
||||
if (isset($data['error'])) {
|
||||
throw new \RuntimeException("RPC error: " . $data['error']);
|
||||
}
|
||||
|
||||
if (!isset($data['results'][$file_path])) {
|
||||
throw new \RuntimeException("No result for file in RPC response");
|
||||
}
|
||||
|
||||
$result = $data['results'][$file_path];
|
||||
|
||||
if ($result['status'] === 'success') {
|
||||
// Return the error info if present, null if no errors
|
||||
return $result['error'];
|
||||
}
|
||||
|
||||
// Handle error response
|
||||
if ($result['status'] === 'error' && isset($result['error'])) {
|
||||
throw new \RuntimeException("Lint error: " . ($result['error']['message'] ?? 'Unknown error'));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException(
|
||||
"JavaScript lint RPC error for {$file_path}: " . $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze this-usage via RPC server
|
||||
*/
|
||||
protected static function _analyze_this_via_rpc(string $file_path): array
|
||||
{
|
||||
$base_path = function_exists('base_path') ? base_path() : '/var/www/html/system';
|
||||
$socket_path = $base_path . '/' . self::RPC_SOCKET;
|
||||
|
||||
try {
|
||||
$sock = @stream_socket_client("unix://{$socket_path}", $errno, $errstr, 0.5);
|
||||
if (!$sock) {
|
||||
throw new \RuntimeException("Failed to connect to RPC server: {$errstr}");
|
||||
}
|
||||
|
||||
// Set blocking mode for reliable reads
|
||||
stream_set_blocking($sock, true);
|
||||
|
||||
// Send analyze_this request
|
||||
$request = [
|
||||
'id' => ++static::$request_id,
|
||||
'method' => 'analyze_this',
|
||||
'files' => [$file_path]
|
||||
];
|
||||
|
||||
fwrite($sock, json_encode($request) . "\n");
|
||||
|
||||
// Read response
|
||||
$response = fgets($sock);
|
||||
fclose($sock);
|
||||
|
||||
if (!$response) {
|
||||
throw new \RuntimeException("No response from RPC server");
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data || !is_array($data)) {
|
||||
throw new \RuntimeException("Invalid JSON response from RPC server");
|
||||
}
|
||||
|
||||
if (isset($data['error'])) {
|
||||
throw new \RuntimeException("RPC error: " . $data['error']);
|
||||
}
|
||||
|
||||
if (!isset($data['results'][$file_path])) {
|
||||
throw new \RuntimeException("No result for file in RPC response");
|
||||
}
|
||||
|
||||
$result = $data['results'][$file_path];
|
||||
|
||||
if ($result['status'] === 'success') {
|
||||
return $result['violations'] ?? [];
|
||||
}
|
||||
|
||||
// Handle error response - return empty violations, don't fail
|
||||
return [];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Log error but don't fail the check
|
||||
if (function_exists('console_debug')) {
|
||||
console_debug('JS_CODE_QUALITY', "RPC error for {$file_path}: " . $e->getMessage());
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the RPC server
|
||||
*/
|
||||
public static function start_rpc_server(): void
|
||||
{
|
||||
$base_path = function_exists('base_path') ? base_path() : '/var/www/html/system';
|
||||
$socket_path = $base_path . '/' . self::RPC_SOCKET;
|
||||
|
||||
if (file_exists($socket_path)) {
|
||||
// Server might be running, force stop it
|
||||
if (function_exists('console_debug')) {
|
||||
console_debug('JS_CODE_QUALITY', 'Found existing socket, forcing shutdown');
|
||||
}
|
||||
static::stop_rpc_server(force: true);
|
||||
}
|
||||
|
||||
// Start new server
|
||||
$server_script = $base_path . '/' . self::RPC_SERVER_SCRIPT;
|
||||
|
||||
if (function_exists('console_debug')) {
|
||||
console_debug('JS_CODE_QUALITY', 'Starting RPC server: ' . $server_script);
|
||||
}
|
||||
|
||||
$process = new Process([
|
||||
'node',
|
||||
$server_script,
|
||||
'--socket=' . $socket_path
|
||||
]);
|
||||
|
||||
$process->start();
|
||||
static::$rpc_server_process = $process;
|
||||
|
||||
// Register shutdown handler
|
||||
register_shutdown_function([self::class, 'stop_rpc_server']);
|
||||
|
||||
// Wait for server to be ready (ping/pong up to 10 seconds)
|
||||
if (function_exists('console_debug')) {
|
||||
console_debug('JS_CODE_QUALITY', 'Waiting for RPC server to be ready...');
|
||||
}
|
||||
|
||||
$max_attempts = 200; // 10 seconds (50ms * 200)
|
||||
$ready = false;
|
||||
|
||||
for ($i = 0; $i < $max_attempts; $i++) {
|
||||
usleep(50000); // 50ms
|
||||
|
||||
if (file_exists($socket_path)) {
|
||||
// Try to ping
|
||||
if (static::ping_rpc_server()) {
|
||||
$ready = true;
|
||||
if (function_exists('console_debug')) {
|
||||
console_debug('JS_CODE_QUALITY', 'RPC server ready after ' . ($i * 50) . 'ms');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ready) {
|
||||
static::stop_rpc_server();
|
||||
throw new \RuntimeException('Failed to start JS Code Quality RPC server - timeout after 10 seconds');
|
||||
}
|
||||
|
||||
if (function_exists('console_debug')) {
|
||||
console_debug('JS_CODE_QUALITY', 'RPC server started successfully');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the RPC server
|
||||
*/
|
||||
protected static function ping_rpc_server(): bool
|
||||
{
|
||||
$base_path = function_exists('base_path') ? base_path() : '/var/www/html/system';
|
||||
$socket_path = $base_path . '/' . self::RPC_SOCKET;
|
||||
|
||||
try {
|
||||
$sock = @stream_socket_client("unix://{$socket_path}", $errno, $errstr, 0.5);
|
||||
if (!$sock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set blocking mode for reliable reads
|
||||
stream_set_blocking($sock, true);
|
||||
|
||||
// Send ping
|
||||
fwrite($sock, json_encode(['id' => ++static::$request_id, 'method' => 'ping']) . "\n");
|
||||
|
||||
// Read response
|
||||
$response = fgets($sock);
|
||||
fclose($sock);
|
||||
|
||||
if (!$response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
return isset($data['result']) && $data['result'] === 'pong';
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the RPC server
|
||||
*/
|
||||
public static function stop_rpc_server(bool $force = false): void
|
||||
{
|
||||
$base_path = function_exists('base_path') ? base_path() : '/var/www/html/system';
|
||||
$socket_path = $base_path . '/' . self::RPC_SOCKET;
|
||||
|
||||
// Try graceful shutdown
|
||||
if (file_exists($socket_path)) {
|
||||
try {
|
||||
$sock = @stream_socket_client("unix://{$socket_path}", $errno, $errstr, 0.5);
|
||||
if ($sock) {
|
||||
fwrite($sock, json_encode(['id' => 0, 'method' => 'shutdown']) . "\n");
|
||||
fclose($sock);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
// Only wait and force kill if $force = true
|
||||
if ($force && static::$rpc_server_process && static::$rpc_server_process->isRunning()) {
|
||||
if (function_exists('console_debug')) {
|
||||
console_debug('JS_CODE_QUALITY', 'Force stopping RPC server');
|
||||
}
|
||||
|
||||
// Wait for graceful shutdown
|
||||
sleep(1);
|
||||
|
||||
// Force kill if still running
|
||||
if (static::$rpc_server_process->isRunning()) {
|
||||
static::$rpc_server_process->stop(3, SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up socket file
|
||||
if (file_exists($socket_path)) {
|
||||
@unlink($socket_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user