refactor: reorganize project structure for better maintainability
- Move 45 debug/test files from root to organized scripts/ directories - Secure public/ directory by removing debug files (security improvement) - Create structured scripts organization: • scripts/debug/ (20 files) - Framework debugging tools • scripts/test/ (18 files) - Test and validation scripts • scripts/maintenance/ (5 files) - Maintenance utilities • scripts/dev/ (2 files) - Development tools Security improvements: - Removed all debug/test files from public/ directory - Only production files remain: index.php, health.php Root directory cleanup: - Reduced from 47 to 2 PHP files in root - Only essential production files: console.php, worker.php This improves: ✅ Security (no debug code in public/) ✅ Organization (clear separation of concerns) ✅ Maintainability (easy to find and manage scripts) ✅ Professional structure (clean root directory)
This commit is contained in:
153
scripts/dev/hot-reload-minimal.php
Normal file
153
scripts/dev/hot-reload-minimal.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Minimal Hot Reload Endpoint
|
||||
* Simple SSE implementation without complex framework dependencies
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Disable deprecation warnings
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
// Simple .env parsing
|
||||
function loadEnvVar(string $key, string $envFile = null): ?string {
|
||||
$envFile ??= __DIR__ . '/../.env';
|
||||
if (!file_exists($envFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = file_get_contents($envFile);
|
||||
if (preg_match("/^{$key}\s*=\s*(.*)$/m", $content, $matches)) {
|
||||
return trim($matches[1], " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only allow in development
|
||||
$appDebug = loadEnvVar('APP_DEBUG');
|
||||
if ($appDebug !== 'true') {
|
||||
http_response_code(403);
|
||||
exit('Hot Reload is only available in development mode');
|
||||
}
|
||||
|
||||
// Set SSE headers
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('Connection: keep-alive');
|
||||
header('X-Accel-Buffering: no'); // Disable nginx buffering
|
||||
|
||||
// Get watched directories
|
||||
$watchedDirs = [
|
||||
__DIR__ . '/../src',
|
||||
__DIR__ . '/../resources/views',
|
||||
__DIR__ . '/../config',
|
||||
];
|
||||
|
||||
// Store file modification times
|
||||
$fileCache = [];
|
||||
|
||||
// Function to scan directories for PHP files
|
||||
function scanDirectory(string $dir, array &$fileCache): array {
|
||||
$changes = [];
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
return $changes;
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if (!$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $file->getPathname();
|
||||
|
||||
// Only watch specific file types
|
||||
if (!preg_match('/\.(php|view\.php|css|js|ts)$/', $path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mtime = $file->getMTime();
|
||||
|
||||
if (!isset($fileCache[$path])) {
|
||||
$fileCache[$path] = $mtime;
|
||||
} elseif ($fileCache[$path] < $mtime) {
|
||||
$changes[] = [
|
||||
'path' => $path,
|
||||
'type' => 'modified',
|
||||
'time' => $mtime
|
||||
];
|
||||
$fileCache[$path] = $mtime;
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
// Send initial connection event
|
||||
echo "event: connected\n";
|
||||
echo "data: " . json_encode([
|
||||
'status' => 'connected',
|
||||
'timestamp' => date('c'),
|
||||
'message' => 'Hot Reload server started'
|
||||
]) . "\n\n";
|
||||
flush();
|
||||
|
||||
// Initialize file cache
|
||||
foreach ($watchedDirs as $dir) {
|
||||
scanDirectory($dir, $fileCache);
|
||||
}
|
||||
|
||||
$lastHeartbeat = time();
|
||||
|
||||
// Keep connection alive and watch for changes
|
||||
while (connection_aborted() === 0) {
|
||||
$hasChanges = false;
|
||||
|
||||
// Check each watched directory
|
||||
foreach ($watchedDirs as $dir) {
|
||||
$changes = scanDirectory($dir, $fileCache);
|
||||
|
||||
foreach ($changes as $change) {
|
||||
$reloadType = 'full';
|
||||
if (str_ends_with($change['path'], '.css')) {
|
||||
$reloadType = 'css';
|
||||
} elseif (str_ends_with($change['path'], '.js') || str_ends_with($change['path'], '.ts')) {
|
||||
$reloadType = 'hmr';
|
||||
}
|
||||
|
||||
echo "event: reload\n";
|
||||
echo "data: " . json_encode([
|
||||
'type' => $reloadType,
|
||||
'file' => basename($change['path']),
|
||||
'path' => $change['path'],
|
||||
'timestamp' => date('c'),
|
||||
'message' => 'File changed: ' . basename($change['path'])
|
||||
]) . "\n\n";
|
||||
flush();
|
||||
|
||||
$hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Send heartbeat every 30 seconds
|
||||
if (time() - $lastHeartbeat >= 30) {
|
||||
echo "event: heartbeat\n";
|
||||
echo "data: " . json_encode([
|
||||
'timestamp' => date('c'),
|
||||
'message' => 'Connection alive'
|
||||
]) . "\n\n";
|
||||
flush();
|
||||
$lastHeartbeat = time();
|
||||
}
|
||||
|
||||
// Small delay to prevent high CPU usage
|
||||
usleep(500000); // 500ms
|
||||
}
|
||||
145
scripts/dev/hot-reload-server.php
Normal file
145
scripts/dev/hot-reload-server.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Development Hot Reload Endpoint
|
||||
* Bypasses full framework bootstrap to avoid deprecation warnings
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Disable deprecation warnings for this development endpoint
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
|
||||
// Set error reporting for debugging
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
|
||||
// Set up minimal autoloading
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
try {
|
||||
// Minimal framework classes needed
|
||||
use App\Framework\Filesystem\FilePath;
|
||||
use App\Framework\Filesystem\FileWatcher;
|
||||
use App\Framework\DateTime\SystemTimer;
|
||||
|
||||
// Simple .env parsing for development check
|
||||
function loadEnvVar(string $key, string $envFile = null): ?string {
|
||||
$envFile ??= __DIR__ . '/../.env';
|
||||
if (!file_exists($envFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = file_get_contents($envFile);
|
||||
if (preg_match("/^{$key}\s*=\s*(.*)$/m", $content, $matches)) {
|
||||
return trim($matches[1], " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only allow in development
|
||||
$appDebug = loadEnvVar('APP_DEBUG');
|
||||
if ($appDebug !== 'true') {
|
||||
http_response_code(403);
|
||||
exit('Hot Reload is only available in development mode');
|
||||
}
|
||||
|
||||
// Set SSE headers
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('Connection: keep-alive');
|
||||
header('X-Accel-Buffering: no'); // Disable nginx buffering
|
||||
|
||||
// Create file watcher
|
||||
$fileWatcher = new FileWatcher(
|
||||
FilePath::create(__DIR__ . '/..'),
|
||||
new SystemTimer()
|
||||
);
|
||||
|
||||
// Watch patterns
|
||||
$watchPatterns = [
|
||||
'src/**/*.php',
|
||||
'resources/views/**/*.php',
|
||||
'resources/views/**/*.view.php',
|
||||
'config/**/*.php',
|
||||
];
|
||||
|
||||
$ignorePatterns = [
|
||||
'vendor/**',
|
||||
'var/**',
|
||||
'storage/**',
|
||||
'tests/**',
|
||||
'public/**',
|
||||
];
|
||||
|
||||
// Send initial connection event
|
||||
echo "event: connected\n";
|
||||
echo "data: " . json_encode([
|
||||
'status' => 'connected',
|
||||
'timestamp' => date('c'),
|
||||
'message' => 'Hot Reload server started'
|
||||
]) . "\n\n";
|
||||
flush();
|
||||
|
||||
// Keep connection alive and watch for changes
|
||||
$lastCheck = time();
|
||||
|
||||
while (connection_aborted() === 0) {
|
||||
// Check for file changes every 500ms
|
||||
$changes = $fileWatcher->watchOnce($watchPatterns, $ignorePatterns);
|
||||
|
||||
foreach ($changes as $change) {
|
||||
$reloadType = determineReloadType($change);
|
||||
|
||||
echo "event: reload\n";
|
||||
echo "data: " . json_encode([
|
||||
'type' => $reloadType->value,
|
||||
'file' => $change->getPath(),
|
||||
'timestamp' => $change->getTimestamp()->format('c'),
|
||||
'message' => 'File changed: ' . basename($change->getPath())
|
||||
]) . "\n\n";
|
||||
flush();
|
||||
}
|
||||
|
||||
// Send heartbeat every 30 seconds
|
||||
if (time() - $lastCheck >= 30) {
|
||||
echo "event: heartbeat\n";
|
||||
echo "data: " . json_encode([
|
||||
'timestamp' => date('c'),
|
||||
'message' => 'Connection alive'
|
||||
]) . "\n\n";
|
||||
flush();
|
||||
$lastCheck = time();
|
||||
}
|
||||
|
||||
// Small delay to prevent high CPU usage
|
||||
usleep(500000); // 500ms
|
||||
}
|
||||
|
||||
function determineReloadType(FileChangeEvent $event): ReloadType
|
||||
{
|
||||
$path = $event->getPath();
|
||||
|
||||
// PHP files need full page reload
|
||||
if (str_ends_with($path, '.php')) {
|
||||
return ReloadType::FULL;
|
||||
}
|
||||
|
||||
// CSS files can use hot replacement
|
||||
if (str_ends_with($path, '.css')) {
|
||||
return ReloadType::CSS;
|
||||
}
|
||||
|
||||
// JS modules can use HMR if supported
|
||||
if (str_ends_with($path, '.js') || str_ends_with($path, '.ts')) {
|
||||
return ReloadType::HMR;
|
||||
}
|
||||
|
||||
// Templates need full reload
|
||||
if (str_ends_with($path, '.view.php')) {
|
||||
return ReloadType::FULL;
|
||||
}
|
||||
|
||||
return ReloadType::FULL;
|
||||
}
|
||||
Reference in New Issue
Block a user