environment('production')) { return response()->json(['error' => 'Not available in production'], 403); } $base_path = base_path(); $output = []; $return_code = 0; \exec_safe("cd {$base_path} && git status --porcelain 2>&1", $output, $return_code); if ($return_code !== 0) { return response()->json(['error' => 'Git command failed', 'output' => implode("\n", $output)], 500); } $modified = []; $added = []; $conflicts = []; foreach ($output as $line) { if (strlen($line) < 4) continue; $status = substr($line, 0, 2); $file = trim(substr($line, 3)); // Parse git status codes if (str_contains($status, 'U') || str_contains($status, 'A') && str_contains($status, 'A')) { $conflicts[] = $file; } elseif ($status[0] === 'A' || $status[1] === 'A' || $status === '??') { $added[] = $file; } elseif ($status[0] === 'M' || $status[1] === 'M' || $status[0] === 'R') { $modified[] = $file; } } return response()->json([ 'modified' => $modified, 'added' => $added, 'conflicts' => $conflicts, ]); } /** * Resolve various RSX identifiers to their file locations * This endpoint is only available in development mode for IDE integration * * Priority order for resolution: * 1. PHP class lookup (for Ajax_Endpoint routes and controllers) * 2. RSX blade view lookup (for @rsx_extends, @rsx_include, rsx_view(), etc.) * 3. Future: JavaScript class lookup * 4. Future: Bundle/Module lookup */ public function resolve_class(Request $request): JsonResponse { // Double-check we're not in production if (app()->environment('production')) { return response()->json([ 'error' => 'This endpoint is not available in production', ], 403); } $identifier = $request->query('class') ?? $request->query('identifier'); $method_name = $request->query('method'); $type = $request->query('type'); // Optional type hint: 'class', 'view', etc. if (!$identifier) { return response()->json([ 'error' => 'Missing required parameter: class or identifier', 'found' => false, ], 400); } // Priority 1: Try as PHP class (if looks like a class name or type hint suggests it) if (!$type || $type === 'class' || preg_match('/^[A-Z][A-Za-z0-9_]*$/', $identifier)) { try { $file_path = Manifest::php_find_class($identifier); $metadata = Manifest::php_get_metadata_by_class($identifier); // Try to find the line number where the class or method is defined $line_number = 1; $absolute_path = base_path($file_path); if (file_exists($absolute_path)) { $content = file_get_contents($absolute_path); $lines = explode("\n", $content); if ($method_name) { // Check if method metadata exists and has a different file (trait method) $method_file_path = null; if (isset($metadata['public_static_methods'][$method_name])) { $method_metadata = $metadata['public_static_methods'][$method_name]; if (isset($method_metadata['file'])) { // Method is from a trait - use trait file instead $method_file_path = $method_metadata['file']; $file_path = $method_file_path; $absolute_path = base_path($file_path); $content = file_exists($absolute_path) ? file_get_contents($absolute_path) : ''; $lines = explode("\n", $content); } // Use line number from metadata if available if (isset($method_metadata['line'])) { $line_number = $method_metadata['line']; } } // If we don't have metadata or line number, search manually if ($line_number === 1 && !empty($lines)) { // Look for the specific method $in_class_or_trait = false; $brace_count = 0; foreach ($lines as $index => $line) { // Check if we're entering the class or trait if (preg_match('/^\s*(class|trait)\s+\w+(\s|$)/', $line)) { $in_class_or_trait = true; } // Track braces to know when we've left the class/trait if ($in_class_or_trait) { $brace_count += substr_count($line, '{'); $brace_count -= substr_count($line, '}'); // Look for the method definition (public/protected/private static function method_name) if (preg_match('/^\s*(public|protected|private)?\s*(static\s+)?function\s+' . preg_quote($method_name, '/') . '\s*\(/', $line)) { $line_number = $index + 1; break; } // If we've left the class/trait, stop looking if ($brace_count === 0 && strpos($line, '}') !== false) { break; } } } } } else { // Find the line with the class definition foreach ($lines as $index => $line) { if (preg_match('/^\s*class\s+' . preg_quote($identifier, '/') . '(\s|$)/', $line)) { $line_number = $index + 1; break; } } } } $response_data = [ 'found' => true, 'type' => 'class', 'file' => $file_path, 'line' => $line_number, 'metadata' => [ 'namespace' => $metadata['namespace'] ?? null, 'extends' => $metadata['extends'] ?? null, 'fqcn' => $metadata['fqcn'] ?? null, ], ]; if ($method_name) { $response_data['method'] = $method_name; } return response()->json($response_data); } catch (RuntimeException $e) { // Not a PHP class, continue to next priority } } // Priority 2: Try as RSX blade view (if type hint suggests it or doesn't look like a class) if (!$type || $type === 'view' || !preg_match('/Controller$/', $identifier)) { try { $file_path = Manifest::find_view($identifier); // Find the line with @rsx_id definition $line_number = 1; $absolute_path = base_path($file_path); if (file_exists($absolute_path)) { $content = file_get_contents($absolute_path); $lines = explode("\n", $content); // Look for @rsx_id('identifier') or @rsx_id("identifier") foreach ($lines as $index => $line) { if (preg_match('/@rsx_id\s*\(\s*[\'"]' . preg_quote($identifier, '/') . '[\'"]\s*\)/', $line)) { $line_number = $index + 1; break; } } } return response()->json([ 'found' => true, 'type' => 'view', 'file' => $file_path, 'line' => $line_number, 'identifier' => $identifier, ]); } catch (RuntimeException $e) { // Not a view, continue to next priority } } // Priority 3: Try as bundle alias (if type hint suggests it or single lowercase word) if (!$type || $type === 'bundle_alias' || preg_match('/^[a-z0-9]+$/', $identifier)) { try { // Load the rsx config to check bundle_aliases $config_path = base_path('config/rsx.php'); if (file_exists($config_path)) { $config = include $config_path; if (isset($config['bundle_aliases'][$identifier])) { $bundle_class = $config['bundle_aliases'][$identifier]; // Strip namespace and get just the class name $class_parts = explode('\\', $bundle_class); $class_name = end($class_parts); // Now try to find this class in the manifest try { $file_path = Manifest::php_find_class($class_name); // Find the line with the class definition $line_number = 1; $absolute_path = base_path($file_path); if (file_exists($absolute_path)) { $content = file_get_contents($absolute_path); $lines = explode("\n", $content); foreach ($lines as $index => $line) { if (preg_match('/^\s*class\s+' . preg_quote($class_name, '/') . '(\s|$)/', $line)) { $line_number = $index + 1; break; } } } return response()->json([ 'found' => true, 'type' => 'bundle_alias', 'file' => $file_path, 'line' => $line_number, 'identifier' => $identifier, 'resolved_class' => $bundle_class, ]); } catch (RuntimeException $e) { // Class not found in manifest } } } } catch (Exception $e) { // Error reading config } } // Priority 4: jqhtml template files (when type is 'jqhtml_template') if ($type === 'jqhtml_template') { try { // Look for .jqhtml file with matching component name $files = Manifest::get_all(); $component_snake = $this->camel_to_snake($identifier); // Search for the .jqhtml file in manifest foreach ($files as $file_path => $file_data) { if (str_ends_with($file_path, '.jqhtml')) { $basename = basename($file_path, '.jqhtml'); // Check if filename matches (either exact or snake_case version) if ($basename === $component_snake || $this->snake_to_pascal($basename) === $identifier) { // Find line with if present $line_number = 1; $absolute_path = base_path($file_path); if (file_exists($absolute_path)) { $content = file_get_contents($absolute_path); $lines = explode("\n", $content); foreach ($lines as $index => $line) { if (preg_match('/json([ 'found' => true, 'type' => 'jqhtml_template', 'file' => $file_path, 'line' => $line_number, 'identifier' => $identifier, ]); } } } } catch (Exception $e) { // Not found as jqhtml template } } // Priority 5: jqhtml JavaScript classes (when type is 'jqhtml_class') if ($type === 'jqhtml_class') { try { // Look for JS class extending Component $files = Manifest::get_all(); // Search for .js files containing the class foreach ($files as $file_path => $file_data) { if (str_ends_with($file_path, '.js')) { $absolute_path = base_path($file_path); if (file_exists($absolute_path)) { $content = file_get_contents($absolute_path); // Check if this file contains a class with the right name extending Component // Look for patterns like: class ComponentName extends Component if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends\s+[A-Za-z_]*Component/', $content)) { $lines = explode("\n", $content); $line_number = 1; foreach ($lines as $index => $line) { if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends/', $line)) { $line_number = $index + 1; break; } } return response()->json([ 'found' => true, 'type' => 'jqhtml_class', 'file' => $file_path, 'line' => $line_number, 'identifier' => $identifier, ]); } } } } } catch (Exception $e) { // Not found as jqhtml class } } // Priority 6: jqhtml class methods (when type is 'jqhtml_class_method') if ($type === 'jqhtml_class_method' && $method_name) { try { // Special case: if method_name is 'data', look for 'on_load' instead $search_method = ($method_name === 'data') ? 'on_load' : $method_name; // Look for JS class extending Component and find the method $files = Manifest::get_all(); // Search for .js files containing the class foreach ($files as $file_path => $file_data) { if (str_ends_with($file_path, '.js')) { $absolute_path = base_path($file_path); if (file_exists($absolute_path)) { $content = file_get_contents($absolute_path); // Check if this file contains a class with the right name extending Component if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends\s+[A-Za-z_]*Component/', $content)) { $lines = explode("\n", $content); $line_number = 1; $in_class = false; $brace_count = 0; foreach ($lines as $index => $line) { // Check if we're entering the class if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends/', $line)) { $in_class = true; } // Track braces to know when we've left the class if ($in_class) { $brace_count += substr_count($line, '{'); $brace_count -= substr_count($line, '}'); // Look for the method definition // Match patterns like: methodName() { or async methodName() { if (preg_match('/(?:async\s+)?' . preg_quote($search_method, '/') . '\s*\(/', $line)) { $line_number = $index + 1; break; } // If we've left the class, stop looking if ($brace_count === 0 && strpos($line, '}') !== false) { break; } } } return response()->json([ 'found' => true, 'type' => 'jqhtml_class_method', 'file' => $file_path, 'line' => $line_number, 'identifier' => $identifier, 'method' => $method_name, ]); } } } } } catch (Exception $e) { // Not found as jqhtml class method } } // Priority 7: Future - other types // Nothing found return response()->json([ 'found' => false, 'error' => 'Identifier not found in manifest', 'identifier' => $identifier, 'searched_types' => ['class', 'view', 'bundle_alias', 'jqhtml_template', 'jqhtml_class', 'jqhtml_class_method'], ]); } /** * Convert PascalCase or camelCase to snake_case */ private function camel_to_snake(string $input): string { // Insert underscore before uppercase letters $result = preg_replace('/(?environment('production')) { return response()->json([ 'error' => 'This endpoint is not available in production', ], 403); } $class_name = $request->query('class'); if (!$class_name) { return response()->json([ 'error' => 'Missing required parameter: class', ], 400); } $lineage = Manifest::js_get_lineage($class_name); return response()->json([ 'class' => $class_name, 'lineage' => $lineage, ]); } }