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,796 @@
<?php
namespace App\RSpade\Commands\Rsx;
use Illuminate\Console\Command;
use App\RSpade\CodeQuality\CodeQualityChecker;
use App\RSpade\Core\Manifest\Manifest;
use Symfony\Component\Finder\Finder;
class Rsx_Check_Command extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'rsx:check
{paths?* : Files or directories to check (defaults to rsx/ and app/)}
{--fix : Attempt to automatically fix violations (not implemented yet)}
{--json : Output results as JSON}
{--silent : Only show output if violations are found}
{--pre-commit-tests : Enable pre-commit checks including rsx/temp validation}
{--convention : Show convention violations}
{--max= : Maximum number of violations to display}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check code for RSpade framework standards compliance';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// Place .placeholder files in all empty directories
$this->place_placeholder_files();
// Clean up any existing code quality remediation report
$remediation_file = base_path('CODE_QUALITY_REMEDIATION.md');
if (file_exists($remediation_file)) {
unlink($remediation_file);
}
$quiet = $this->option('silent');
if (!$quiet) {
$this->info('🔍 RSX Code Quality Checker');
$this->info('==============================');
$this->newLine();
}
// Get pre-commit-tests flag
$pre_commit_tests = $this->option('pre-commit-tests');
// Get paths from argument or use defaults
$paths = $this->argument('paths');
$using_default_paths = empty($paths);
if ($using_default_paths) {
$paths = ['rsx', 'app'];
}
// Tell the checker to exclude manifest-time rules
// They've already run when the manifest was loaded, so running them again is redundant
$config['exclude_manifest_time_rules'] = true;
// Initialize code quality checker with config
$config = [
'pre_commit_tests' => $pre_commit_tests,
'using_default_paths' => $using_default_paths
];
CodeQualityChecker::init($config);
$total_files = 0;
$controller_files = 0;
// Collect all files to check
$files_to_check = [];
// Process each path (file or directory)
foreach ($paths as $path) {
// Handle absolute or relative paths
if (str_starts_with($path, '/')) {
$full_path = $path;
} else {
$full_path = base_path($path);
}
// Check if path exists
if (!file_exists($full_path)) {
$this->error("❌ Error: Path does not exist: {$path}");
return 1;
}
// Check if it's a file
if (is_file($full_path)) {
// Get scan directories from config
$scan_directories = config('rsx.manifest.scan_directories', []);
$relative_path = str_replace(base_path() . '/', '', $full_path);
// Check if file is in allowed directories
$in_valid_path = false;
// Special case: Allow Console Command files
if (str_starts_with($relative_path, 'app/Console/Commands/')) {
$in_valid_path = true;
} else {
foreach ($scan_directories as $scan_path) {
// Skip specific file entries in scan_directories
if (str_contains($scan_path, '.')) {
// This is a specific file, check exact match
if ($relative_path === $scan_path) {
$in_valid_path = true;
break;
}
} else {
// This is a directory, check if file is within it
if (str_starts_with($relative_path, rtrim($scan_path, '/') . '/') ||
rtrim($relative_path, '/') === rtrim($scan_path, '/')) {
$in_valid_path = true;
break;
}
}
}
}
if (!$in_valid_path) {
// Build list of allowed directories (excluding specific files)
$allowed_dirs = [];
foreach ($scan_directories as $scan_path) {
if (!str_contains($scan_path, '.')) {
$allowed_dirs[] = $scan_path;
}
}
$this->error("❌ Error: File '{$path}' is not in an allowed directory");
$this->warn("⚠️ Allowed directories:");
foreach ($allowed_dirs as $dir) {
$this->info(" - {$dir}/");
}
$this->newLine();
$this->warn("📝 For testing code quality rules, place your test file in rsx/temp/");
$this->info(" Example: mv {$path} " . base_path('rsx/temp/') . basename($path));
// Add framework developer message if enabled
if (config('rsx.code_quality.is_framework_developer', false)) {
$this->newLine();
$this->comment("💡 Framework Developer Mode: If testing a rule that only targets app/RSpade/,");
$this->comment(" place the file in app/RSpade/temp/ for testing (create the directory if needed).");
$this->info(" Example: mkdir -p " . base_path('app/RSpade/temp') . " && mv {$path} " . base_path('app/RSpade/temp/') . basename($path));
}
$this->newLine();
return 1;
}
// Check if file is in the manifest (skip for Console Commands)
if (!str_starts_with($relative_path, 'app/Console/Commands/')) {
$manifest = Manifest::get_all();
$found_in_manifest = false;
foreach ($manifest as $manifest_path => $metadata) {
if (realpath($full_path) === realpath(base_path($manifest_path))) {
$found_in_manifest = true;
break;
}
}
if (!$found_in_manifest) {
$this->error("❌ Error: File '{$path}' is not in the manifest");
$this->warn("⚠️ The file must be indexed by the manifest to be checked");
$this->info(" Try running: php artisan rsx:manifest:build");
$this->newLine();
return 1;
}
}
if (!$quiet) {
$this->info("Checking file: {$path}");
}
$total_files++;
$files_to_check[] = $full_path;
// Count controller/model files for stats
if ($this->is_controller_file($full_path) || $this->is_model_file($full_path)) {
$controller_files++;
}
continue;
}
// Check if it's a directory
if (!is_dir($full_path)) {
$this->error("❌ Error: Path is neither a file nor a directory: {$path}");
return 1;
}
// Get scan directories from config for validation
$scan_directories = config('rsx.manifest.scan_directories', []);
$relative_dir = str_replace(base_path() . '/', '', $full_path);
$relative_dir = rtrim($relative_dir, '/');
// Check if directory is in allowed paths
$in_valid_path = false;
foreach ($scan_directories as $scan_path) {
// Skip specific file entries
if (str_contains($scan_path, '.')) {
continue;
}
$scan_dir = rtrim($scan_path, '/');
if ($relative_dir === $scan_dir ||
str_starts_with($relative_dir, $scan_dir . '/') ||
str_starts_with($scan_dir, $relative_dir . '/')) {
$in_valid_path = true;
break;
}
}
// Special handling for 'app' directory to scan app/RSpade subdirectories
if ($relative_dir === 'app') {
$in_valid_path = true;
}
if (!$in_valid_path) {
// Build list of allowed directories (excluding specific files)
$allowed_dirs = [];
foreach ($scan_directories as $scan_path) {
if (!str_contains($scan_path, '.')) {
$allowed_dirs[] = $scan_path;
}
}
$this->error("❌ Error: Directory '{$path}' is not within allowed scan directories");
$this->warn("⚠️ Allowed directories:");
foreach ($allowed_dirs as $dir) {
$this->info(" - {$dir}/");
}
$this->newLine();
return 1;
}
if (!$quiet) {
$this->info("Scanning: {$path}/");
}
$is_rsx_dir = ($path === 'rsx');
// Collect PHP files
$php_finder = new Finder();
$php_finder->files()
->in($full_path)
->name('*.php')
->exclude(['vendor', 'node_modules', 'storage', 'bootstrap/cache']);
foreach ($php_finder as $file) {
$total_files++;
$files_to_check[] = $file->getPathname();
// Count controller/model files for stats
if ($is_rsx_dir) {
if ($this->is_controller_file($file->getPathname()) || $this->is_model_file($file->getPathname())) {
$controller_files++;
}
}
}
// === Additional File Collection ===
// Collect all files for filename case checks (RSX only)
if ($is_rsx_dir) {
$all_files_finder = new Finder();
$all_files_finder->files()
->in($full_path)
->exclude(['vendor', 'node_modules', 'storage', 'bootstrap/cache', 'resource']);
foreach ($all_files_finder as $file) {
$file_path = $file->getPathname();
if (!in_array($file_path, $files_to_check)) {
$files_to_check[] = $file_path;
}
}
}
// Collect JavaScript files for all directories
$js_finder = new Finder();
$js_finder->files()
->in($full_path)
->name('*.js')
->exclude(['vendor', 'node_modules', 'storage', 'public', 'bootstrap/cache']);
foreach ($js_finder as $file) {
$file_path = $file->getPathname();
if (!in_array($file_path, $files_to_check)) {
$files_to_check[] = $file_path;
$total_files++;
}
}
// Collect JSON files for all directories
$json_finder = new Finder();
$json_finder->files()
->in($full_path)
->name('*.json')
->exclude(['vendor', 'node_modules', 'storage', 'bootstrap/cache']);
foreach ($json_finder as $file) {
$file_path = $file->getPathname();
if (!in_array($file_path, $files_to_check)) {
$files_to_check[] = $file_path;
$total_files++;
}
}
}
// If using default paths, also include Console Commands for rules that support them
if ($using_default_paths) {
$commands_dir = base_path('app/Console/Commands');
if (is_dir($commands_dir)) {
if (!$quiet) {
$this->info("Including Console Commands for supporting rules...");
}
$command_files = [];
$command_finder = new Finder();
$command_finder->files()
->in($commands_dir)
->name('*.php')
->exclude(['vendor', 'node_modules']);
foreach ($command_finder as $file) {
$command_files[] = $file->getPathname();
}
// Add Console Command files to the check list
// The checker will determine which rules support them
foreach ($command_files as $file) {
if (!in_array($file, $files_to_check)) {
$files_to_check[] = $file;
$total_files++;
}
}
}
}
// Filter out app/RSpade files if not in framework developer mode
if (!config('rsx.code_quality.is_framework_developer', false)) {
$files_to_check = array_filter($files_to_check, function($file) {
$relative_path = str_replace(base_path() . '/', '', $file);
return !str_starts_with($relative_path, 'app/RSpade/');
});
// Re-index array after filtering
$files_to_check = array_values($files_to_check);
}
// Run checks through the new modular system
CodeQualityChecker::check_files($files_to_check);
// Get violations
$violations = CodeQualityChecker::get_violations();
$convention_count = CodeQualityChecker::get_collector()->get_convention_count();
$show_conventions = $this->option('convention');
// If showing conventions, get convention violations
if ($show_conventions) {
$violations = CodeQualityChecker::get_collector()->get_convention_violations_as_arrays();
}
// In quiet mode, only output if there are violations
if ($quiet && empty($violations) && !$convention_count) {
return 0;
}
// If violations exist or not in quiet mode, show output
if (!empty($violations) || !$quiet) {
// Always show header and counts when there are violations (even in quiet mode)
if (!empty($violations) && $quiet) {
$this->info('🔍 RSX Code Quality Checker');
$this->info('==============================');
$this->newLine();
}
if (!$quiet) {
$this->newLine();
}
$this->info("Files scanned: {$total_files}");
$this->info("Controllers/Models checked: {$controller_files}");
$this->newLine();
}
// Show convention warning if not displaying conventions
if (!$show_conventions && $convention_count > 0) {
$this->warn("⚠️ Warning: There are ({$convention_count}) RSX application convention warnings, call rsx:check --convention to view the warnings");
$this->newLine();
}
if (empty($violations)) {
if ($show_conventions) {
$this->info('✅ No convention violations found!');
} else {
$this->info('✅ No code standard violations found!');
}
return 0;
}
// Apply --max limit if specified
$total_violations = count($violations);
$max_violations = $this->option('max');
$violations_truncated = false;
if ($max_violations && is_numeric($max_violations) && $max_violations > 0) {
$max_violations = (int)$max_violations;
if ($total_violations > $max_violations) {
$violations = array_slice($violations, 0, $max_violations);
$violations_truncated = true;
}
}
// Output violations
if ($this->option('json')) {
$this->output_json($violations, $total_violations, $violations_truncated);
} else {
$this->output_table($violations, $total_violations, $violations_truncated);
}
// Summary
$this->newLine();
if ($show_conventions) {
$this->warn('⚠️ Found ' . $total_violations . ' convention violation(s)');
} else {
$this->error('❌ Found ' . $total_violations . ' code standard violation(s)');
}
// Show truncation message if violations were limited
if ($violations_truncated) {
$shown = count($violations);
$remaining = $total_violations - $shown;
$this->newLine();
$this->comment("📄 Showing first {$shown} violations. {$remaining} additional violations are present.");
} elseif ($max_violations && $total_violations > 0 && $total_violations <= $max_violations) {
$this->newLine();
$this->comment("📄 All {$total_violations} violations shown (within --max={$max_violations} limit).");
}
// Notice about potential validator errors
$this->newLine();
$this->comment(' Notice: While these violations have been detected, please verify each issue carefully.');
$this->comment(' The validators themselves may contain errors. If a violation appears incorrect,');
$this->comment(' thoroughly review both the code and the validator logic. It may be necessary');
$this->comment(' to fix the validator rather than the code.');
// Convention violations don't cause non-zero exit
if ($show_conventions) {
return 0;
}
// Always return non-zero exit code when violations are found
return 1;
}
/**
* Output violations as a table
*/
protected function output_table($violations, $total_violations = null, $violations_truncated = false)
{
$this->warn('Code Standard Violations:');
$this->newLine();
$current_file = null;
foreach ($violations as $violation) {
// Debug output to understand violations
if (!isset($violation['file'])) {
$this->error("Invalid violation structure: " . json_encode($violation));
continue;
}
// Group by file for better readability
$relative_file = str_replace(base_path() . '/', '', $violation['file']);
if ($current_file !== $relative_file) {
if ($current_file !== null) {
$this->newLine();
}
$this->line("<fg=cyan>{$relative_file}</>");
$current_file = $relative_file;
}
$line = str_pad($violation['line'], 6, ' ', STR_PAD_LEFT);
// Use simplified output format for all violations
$type = $violation['type'] ?? 'unknown';
$message = $violation['message'] ?? 'No message provided';
$code = $violation['code'] ?? null;
$resolution = $violation['resolution'] ?? null;
// Show the violation
$this->line(" <fg=yellow>Line {$line}:</> <fg=cyan>[{$type}]</>");
$this->line(" <fg=gray> ↳</> {$message}");
// Show code snippet if available
if ($code) {
$this->line(" <fg=gray> Code:</> <fg=red>{$code}</>");
}
// Show resolution if available
if ($resolution) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$resolution}</>");
}
continue; // Skip the old if-elseif chain
// OLD CODE BELOW - TO BE REMOVED
if (false && $violation['type'] === 'filename_case') {
$filename = $violation['filename'] ?? 'unknown';
$suggested = $violation['suggested'] ?? '';
$this->line(" <fg=yellow>File:</> <fg=red>{$filename}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'defensive_coding') {
$variable = $violation['variable'] ?? 'unknown';
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'mass_assignment_property') {
$property = $violation['property'] ?? 'unknown';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>protected \${$property}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'instance_method') {
$method = $violation['method'] ?? 'unknown';
$class = $violation['class'] ?? 'unknown';
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'instance_pattern') {
$class = $violation['class'] ?? 'unknown';
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'this_usage') {
$class = $violation['class'] ?? 'unknown';
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'this_assignment') {
$class = $violation['class'] ?? 'unknown';
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'var_keyword') {
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'jquery_usage') {
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'document_ready') {
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'fallback_legacy') {
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'function_exists') {
$code = $violation['code'] ?? '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'php_syntax_error') {
$this->line(" <fg=red>⚠ PHP Syntax Error</>");
if ($line > 0) {
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$violation['message']}</>");
} else {
$this->line(" <fg=red>{$violation['message']}</>");
}
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'js_syntax_error') {
$this->line(" <fg=red>⚠ JavaScript Syntax Error</>");
if ($line > 0) {
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$violation['message']}</>");
} else {
$this->line(" <fg=red>{$violation['message']}</>");
}
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'json_syntax_error') {
$this->line(" <fg=red>⚠ JSON Syntax Error</>");
$this->line(" <fg=red>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'enhanced_filename') {
// Enhanced filename violations
$filename = $violation['filename'] ?? 'N/A';
$this->line(" <fg=red>⚠ Filename: {$filename}</>");
$this->line(" <fg=gray> ↳</> <fg=red>{$violation['message']}</>");
if (isset($violation['resolution'])) {
// Split resolution into lines for better readability
$resolution_lines = explode("\n", $violation['resolution']);
foreach ($resolution_lines as $res_line) {
if (trim($res_line)) {
$this->line(" <fg=gray> </> <fg=green>{$res_line}</>");
}
}
}
} elseif ($violation['type'] === 'unauthorized_root_file') {
// Unauthorized files in project root
$filename = $violation['filename'] ?? 'N/A';
$this->line(" <fg=red>⚠ Unauthorized file: {$filename}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'test_file_in_rsx_root') {
// Test files in rsx/ root directory
$filename = $violation['filename'] ?? 'N/A';
$this->line(" <fg=red>⚠ Test file in rsx/: {$filename}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'test_file_in_rsx_temp') {
// Test files in rsx/temp directory
$filename = $violation['filename'] ?? 'N/A';
$this->line(" <fg=red>⚠ File in rsx/temp/: {$filename}</>");
$this->line(" <fg=gray> ↳</> <fg=cyan>{$violation['message']}</>");
if (isset($violation['resolution'])) {
$this->line(" <fg=gray> ↳ Fix:</> <fg=green>{$violation['resolution']}</>");
}
} elseif ($violation['type'] === 'db_table_usage') {
// DB::table() usage violations
$code = $violation['code'] ?? '';
$table = isset($violation['table']) ? " (table: {$violation['table']})" : '';
$this->line(" <fg=yellow>Line {$line}:</> <fg=red>{$code}</>");
$this->line(" <fg=gray> ↳</> <fg=red>{$violation['message']}{$table}</>");
if (isset($violation['resolution'])) {
// Split resolution into lines for better readability
$resolution_lines = explode("\n", $violation['resolution']);
foreach ($resolution_lines as $res_line) {
if (trim($res_line)) {
$this->line(" <fg=gray> </> <fg=green>{$res_line}</>");
}
}
}
}
}
}
/**
* Output violations as JSON
*/
protected function output_json($violations, $total_violations = null, $violations_truncated = false)
{
$output = [
'violations' => $violations,
'summary' => [
'total_violations' => $total_violations ?: count($violations),
'shown_violations' => count($violations),
'violations_truncated' => $violations_truncated,
'files_affected' => count(array_unique(array_column($violations, 'file')))
]
];
$this->line(json_encode($output, JSON_PRETTY_PRINT));
}
/**
* Check if file is a controller
*/
protected function is_controller_file($file_path)
{
// Check if filename matches controller pattern
return preg_match('/_controller\.php$/', basename($file_path));
}
/**
* Check if file is a model
*/
protected function is_model_file($file_path)
{
// Check if file is in Models directory or ends with Model.php
if (strpos($file_path, '/Models/') !== false || strpos($file_path, '/models/') !== false) {
return true;
}
if (preg_match('/Model\.php$/', $file_path)) {
return true;
}
// Quick check for extending Model
$content = file_get_contents($file_path);
return preg_match('/extends\s+.*Model/', $content);
}
/**
* Place .placeholder files in all empty directories
* Remove .placeholder files from non-empty directories
*/
protected function place_placeholder_files()
{
$is_framework_developer = config('rsx.code_quality.is_framework_developer', false);
// Only manage .placeholder files in framework developer mode
// Application developers don't need them
if (!$is_framework_developer) {
return;
}
$base_path = base_path();
// Initialize variables for \exec_safe() call
$output = [];
$return_code = 0;
// Find all empty directories and place .placeholder files (exclude vendor and node_modules)
\exec_safe("find \"$base_path\" -type d -empty -not -path '*/vendor/*' -not -path '*/node_modules/*' -exec touch {}/.placeholder \\; 2>/dev/null", $output, $return_code);
// Find and remove zero-length .placeholder files from non-empty directories
$directory_iterator = new \RecursiveDirectoryIterator($base_path, \RecursiveDirectoryIterator::SKIP_DOTS);
$filter_iterator = new \RecursiveCallbackFilterIterator($directory_iterator, function ($current) {
// Skip vendor and node_modules directories
if ($current->isDir()) {
$dirname = $current->getFilename();
if ($dirname === 'vendor' || $dirname === 'node_modules') {
return false;
}
}
return true;
});
$iterator = new \RecursiveIteratorIterator(
$filter_iterator,
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getFilename() === '.placeholder') {
$placeholder_path = $file->getPathname();
// Check if file is zero length
if ($file->getSize() === 0) {
$dir = dirname($placeholder_path);
// Count other files in the directory (excluding .placeholder)
$files = array_diff(scandir($dir), ['.', '..', '.placeholder']);
// If directory has other files, remove .placeholder
if (count($files) > 0) {
unlink($placeholder_path);
}
}
}
}
// Silently succeed - this is a maintenance operation
}
}