This commit is contained in:
Younes ENNAJI
2025-03-28 03:37:07 +00:00
parent 8dd56001db
commit 424617103f
+64 -256
View File
@@ -107,14 +107,14 @@ final class InstallCommand extends Command
];
/**
* File type icons for visualization.
* File type icons for visualization (ASCII-friendly version).
*/
private array $fileTypeIcons = [
'js' => '📜',
'css' => '🎨',
'json' => '📋',
'php' => '🐘',
'default' => '📄',
'js' => '[JS]',
'css' => '[CSS]',
'json' => '[JSON]',
'php' => '[PHP]',
'default' => '[FILE]',
];
/**
@@ -171,8 +171,8 @@ final class InstallCommand extends Command
$this->detectTerminalDimensions();
// Ensure output is cleared and properly formatted
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGINT, function () {
if (\function_exists('pcntl_signal')) {
pcntl_signal(\SIGINT, function () {
$this->output->writeln('');
$this->output->writeln('<fg=red;options=bold>Installation aborted!</>');
exit(1);
@@ -204,6 +204,7 @@ final class InstallCommand extends Command
$this->task('Preparing installation directory', function () use ($filesystem, $publicDir) {
$filesystem->deleteDirectory($publicDir);
$filesystem->makeDirectory($publicDir, 0755, true);
return true;
});
}
@@ -227,7 +228,7 @@ final class InstallCommand extends Command
$this->debugGroupStart('Plugin Discovery');
$this->debug("Found {$providers->count()} service providers", 'info');
$providers->each(function ($provider, $index) {
$this->debug("Provider #{$index}: " . get_class($provider), 'dim');
$this->debug("Provider #{$index}: ".$provider::class, 'dim');
});
$this->debug("Discovered {$providers->count()} PHPFlasher plugins", 'success');
$this->debugGroupEnd();
@@ -260,7 +261,7 @@ final class InstallCommand extends Command
// Update progress with spinning indicator
if (!$this->minimalMode) {
$spinners = $this->asciiMode ? $this->asciiSpinnerChars : $this->spinnerChars;
$char = $spinners[$index % count($spinners)];
$char = $spinners[$index % \count($spinners)];
$progressBar->setMessage("<fg=blue>{$char}</> <fg=blue;options=bold>Processing:</> <fg=cyan>{$plugin->getAlias()}</>");
}
$progressBar->advance();
@@ -343,6 +344,7 @@ final class InstallCommand extends Command
$this->startTiming('manifest');
$this->task('Creating asset manifest', function () use ($files) {
$this->assetManager->createManifest(array_merge([], ...$files));
return true;
});
$this->stopTiming('manifest');
@@ -352,11 +354,6 @@ final class InstallCommand extends Command
$this->stopTiming('total');
// Show debug performance metrics if requested
if ($this->debugMode) {
$this->displayPerformanceMetrics();
}
return $exitCode;
}
@@ -385,14 +382,14 @@ final class InstallCommand extends Command
*/
private function detectTerminalDimensions(): void
{
if (function_exists('exec')) {
if (\function_exists('exec')) {
@exec('tput cols 2>/dev/null', $columns, $return_var);
if ($return_var === 0 && isset($columns[0])) {
if (0 === $return_var && isset($columns[0])) {
$this->terminalDimensions['width'] = (int) $columns[0];
}
@exec('tput lines 2>/dev/null', $lines, $return_var);
if ($return_var === 0 && isset($lines[0])) {
if (0 === $return_var && isset($lines[0])) {
$this->terminalDimensions['height'] = (int) $lines[0];
}
}
@@ -404,12 +401,12 @@ final class InstallCommand extends Command
private function runningInCI(): bool
{
return (bool) (
getenv('CI') ||
getenv('CONTINUOUS_INTEGRATION') ||
getenv('GITHUB_ACTIONS') ||
getenv('GITLAB_CI') ||
getenv('TRAVIS') ||
getenv('CIRCLECI')
getenv('CI')
|| getenv('CONTINUOUS_INTEGRATION')
|| getenv('GITHUB_ACTIONS')
|| getenv('GITLAB_CI')
|| getenv('TRAVIS')
|| getenv('CIRCLECI')
);
}
@@ -418,8 +415,8 @@ final class InstallCommand extends Command
*/
private function supportsUnicode(): bool
{
return stripos(getenv('LANG') ?: '', 'UTF-8') !== false ||
stripos(getenv('LC_ALL') ?: '', 'UTF-8') !== false;
return false !== stripos(getenv('LANG') ?: '', 'UTF-8')
|| false !== stripos(getenv('LC_ALL') ?: '', 'UTF-8');
}
/**
@@ -490,11 +487,11 @@ final class InstallCommand extends Command
$this->terminalDimensions['width'] - 20 :
60;
$title = 'PHPFLASHER RESOURCE INSTALLER v11';
$padding = max(0, ($titleWidth - strlen(strip_tags($title))) / 2);
$title = 'PHPFLASHER RESOURCE INSTALLER v2';
$padding = max(0, ($titleWidth - \strlen(strip_tags($title))) / 2);
$paddingStr = str_repeat(' ', (int) $padding);
$this->line(' ' . $paddingStr . '<fg=yellow;options=bold>PHPFLASHER RESOURCE INSTALLER</> <fg=blue>v11</fg=blue>');
$this->line(' '.$paddingStr.'<fg=yellow;options=bold>PHPFLASHER RESOURCE INSTALLER</> <fg=blue>v2</fg=blue>');
$this->newLine();
$this->stopTiming('banner');
@@ -540,6 +537,7 @@ final class InstallCommand extends Command
if ($this->debugMode) {
$this->debug("Force flag enabled, cleaning directory without confirmation: {$directory}", 'notice');
}
return true;
}
@@ -556,10 +554,10 @@ final class InstallCommand extends Command
$this->line(' <box>│</box>'.str_repeat(' ', 70).'<box>│</box>');
$message = 'The directory exists and needs to be cleaned before installation:';
$this->line(' <box>│</box> ' . $message . str_repeat(' ', 70 - strlen($message)) . '<box>│</box>');
$this->line(' <box>│</box> '.$message.str_repeat(' ', 70 - \strlen($message)).'<box>│</box>');
$dirLine = " <fg=yellow>{$directory}</>";
$this->line(' <box>│</box>' . $dirLine . str_repeat(' ', 70 - strlen(strip_tags($dirLine))) . '<box>│</box>');
$this->line(' <box>│</box>'.$dirLine.str_repeat(' ', 70 - \strlen(strip_tags($dirLine))).'<box>│</box>');
$this->line(' <box>│</box>'.str_repeat(' ', 70).'<box>│</box>');
$this->line(' <box>╰'.str_repeat('─', 70).'╯</box>');
@@ -576,20 +574,14 @@ final class InstallCommand extends Command
}
/**
* Display installation configuration summary with visual enhancements.
* Display installation configuration summary with simplified styling.
*/
private function displayInstallationConfig(bool $useSymlinks, bool $publishConfig, bool $force): void
{
$this->newLine();
// Box-style header for enhanced visual appeal
if (!$this->asciiMode) {
$this->line(' <box>╭' . str_repeat('─', 70) . '╮</box>');
$this->line(' <box>│</box> <box-title>INSTALLATION CONFIGURATION</box-title>' . str_repeat(' ', 46) . '<box>│</box>');
$this->line(' <box>╰' . str_repeat('─', 70) . '╯</box>');
} else {
// Use bracketed header style for all environments
$this->line(' <fg=blue;options=bold>[ INSTALLATION CONFIGURATION ]</>');
}
$this->newLine();
@@ -700,6 +692,7 @@ final class InstallCommand extends Command
}
$this->stopTiming("task_{$taskName}");
return (bool) $result;
} catch (\Exception $e) {
ob_end_flush();
@@ -718,6 +711,7 @@ final class InstallCommand extends Command
}
$this->stopTiming("task_{$taskName}");
return false;
}
}
@@ -740,6 +734,7 @@ final class InstallCommand extends Command
if ($this->debugMode) {
$this->debug("No assets directory found for {$plugin->getAlias()}: {$originDir}", 'notice');
}
return [];
}
@@ -767,7 +762,7 @@ final class InstallCommand extends Command
// Process files with a mini progress animation for debug mode
foreach ($finder as $file) {
$filesCount++;
++$filesCount;
$relativePath = trim(str_replace($originDir, '', $file->getRealPath()), \DIRECTORY_SEPARATOR);
$targetPath = $publicDir.$relativePath;
$fileSize = $file->getSize();
@@ -805,7 +800,7 @@ final class InstallCommand extends Command
}
// Create a subtle pulsing animation if in debug mode
if ($this->debugMode && !$this->noAnimation && $filesCount % 3 === 0) {
if ($this->debugMode && !$this->noAnimation && 0 === $filesCount % 3) {
echo "\033[s"; // Save cursor position
echo "\033[u"; // Restore cursor position
usleep(5000); // Short delay for subtle animation
@@ -819,18 +814,18 @@ final class InstallCommand extends Command
$this->debug(' ... and '.($filesCount - $maxFilesToShow).' more files', 'dim');
}
if (count($files) > 0) {
$this->debug("Total size: {$this->formatBytes($totalSize)} in " . count($files) . ' files', 'success');
if (\count($files) > 0) {
$this->debug("Total size: {$this->formatBytes($totalSize)} in ".\count($files).' files', 'success');
}
// Add file type breakdown for better visualization
foreach ($filesByType as $type => $typeFiles) {
if (count($typeFiles) > 0) {
if (\count($typeFiles) > 0) {
$totalTypeSize = array_sum(array_column($typeFiles, 'size'));
$icon = $this->asciiMode ?
($this->asciiFileTypeIcons[$type] ?? $this->asciiFileTypeIcons['default']) :
($this->fileTypeIcons[$type] ?? $this->fileTypeIcons['default']);
$this->debug(" {$icon} {$type}: " . count($typeFiles) . " files ({$this->formatBytes($totalTypeSize)})", 'dim');
$this->debug(" {$icon} {$type}: ".\count($typeFiles)." files ({$this->formatBytes($totalTypeSize)})", 'dim');
}
}
}
@@ -853,6 +848,7 @@ final class InstallCommand extends Command
if ($this->debugMode) {
$this->debug("Config file not found for {$plugin->getAlias()}: {$configFile}", 'notice');
}
return false;
}
@@ -863,6 +859,7 @@ final class InstallCommand extends Command
if ($this->debugMode) {
$this->debug("Config already exists for {$plugin->getAlias()}, skipping (use --force to override)", 'notice');
}
return false;
}
@@ -885,43 +882,8 @@ final class InstallCommand extends Command
return true;
}
/**
* Create a symlink with better error handling.
*/
private function createSymlink(string $source, string $target): bool
{
// Make sure the target directory exists
$targetDir = dirname($target);
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
// Remove the target if it exists
if (file_exists($target)) {
@unlink($target);
}
// Create the symlink
$success = false;
if (PHP_OS_FAMILY === 'Windows') {
// Windows needs special handling for symlinks
$isDir = is_dir($source);
$success = @symlink($source, $target);
} else {
$success = @symlink($source, $target);
}
if (!$success) {
throw new \RuntimeException("Failed to create symlink from {$source} to {$target}");
}
return true;
}
/**
* Display a comprehensive summary of the installation process with enhanced visuals.
*
* @param int $exitCode The exit code indicating success or failure
*/
private function displayComprehensiveSummary(int $exitCode): void
{
@@ -947,52 +909,45 @@ final class InstallCommand extends Command
}
$this->newLine();
return;
}
// Enhanced with detailed results and animated success
if ($exitCode === self::SUCCESS) {
if ($this->asciiMode) {
$this->line(' <fg=green;options=bold>[ INSTALLATION COMPLETED SUCCESSFULLY ]</>');
} else {
if (self::SUCCESS === $exitCode) {
// Animate the success message for extra wow effect
if (!$this->noAnimation) {
$chars = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
for ($i = 0; $i < 5; $i++) { // 5 animation cycles
for ($i = 0; $i < 5; ++$i) { // 5 animation cycles
echo "\033[s"; // Save cursor position
$this->line(' <fg=green;options=bold>[ INSTALLATION ' . $chars[$i % count($chars)] . ' ]</>');
$this->line(' <fg=green;options=bold>[ INSTALLATION '.$chars[$i % \count($chars)].' ]</>');
usleep(100000); // 100ms delay
echo "\033[u"; // Restore cursor position
}
}
// Rainbow gradient for success message
// Final success message
$this->line(' <fg=green;options=bold>[ INSTALLATION COMPLETED SUCCESSFULLY ]</>');
// Show a random success message for fun
$randomMessage = $this->successMessages[array_rand($this->successMessages)];
$this->newLine();
$this->line(' <fg=green>'.$randomMessage.'</>');
}
} else {
$this->line(' <fg=red;options=bold>[ INSTALLATION COMPLETED WITH ERRORS ]</>');
}
$this->newLine();
// Create a fancy box for the results table if not in ASCII mode
if (!$this->asciiMode) {
$tableWidth = 70;
$this->line(' <box>╭' . str_repeat('─', $tableWidth) . '╮</box>');
$this->line(' <box>│</box> <box-title>INSTALLATION RESULTS</box-title>' . str_repeat(' ', $tableWidth - 23) . '<box>│</box>');
$this->line(' <box>├' . str_repeat('─', $tableWidth) . '┤</box>');
}
// Results section with bracket-style header
$this->line(' <fg=blue;options=bold>[ INSTALLATION RESULTS ]</>');
$this->newLine();
// Detailed results table with color-coded status and animated icons
// Detailed results table with color-coded status
$headers = ['Plugin', 'Status', 'Assets', 'Config', 'Time (ms)'];
$rows = $this->results->map(function ($result) {
$checkmark = $this->asciiMode ? '√' : '✓';
$xmark = $this->asciiMode ? 'x' : '✗';
$status = $result['status'] === 'success'
$status = 'success' === $result['status']
? '<fg=green;options=bold>'.$checkmark.' Success</>'
: '<fg=red;options=bold>'.$xmark.' Failed</>';
@@ -1007,32 +962,20 @@ final class InstallCommand extends Command
$this->table($headers, $rows);
// Close the box if not in ASCII mode
if (!$this->asciiMode) {
$this->line(' <box>╰' . str_repeat('─', $tableWidth) . '╯</box>');
}
// Statistics section with animated counting if not in no-animation mode
// Statistics section with bracket-style header
$totalTime = round((microtime(true) - $this->startTime) * 1000);
$successCount = $this->results->where('status', 'success')->count();
$failureCount = $this->results->where('status', 'error')->count();
$totalAssets = $this->results->sum('assets');
if (!$this->asciiMode) {
$this->line(' <box>╭' . str_repeat('─', $tableWidth) . '╮</box>');
$this->line(' <box>│</box> <box-title>INSTALLATION STATISTICS</box-title>' . str_repeat(' ', $tableWidth - 25) . '<box>│</box>');
$this->line(' <box>╰' . str_repeat('─', $tableWidth) . '╯</box>');
} else {
$this->line(' <fg=blue;options=bold>[ INSTALLATION STATISTICS ]</>');
}
$this->newLine();
// Animated statistics counter for extra wow effect
if (!$this->noAnimation && !$this->minimalMode) {
// Animate total plugins count
echo ' <fg=cyan;options=bold>Total Plugins</> .................................................................. ';
for ($i = 0; $i <= $this->results->count(); $i++) {
for ($i = 0; $i <= $this->results->count(); ++$i) {
echo "\033[s"; // Save cursor position
echo "<fg=yellow>{$i}</>";
if ($i < $this->results->count()) {
@@ -1040,11 +983,11 @@ final class InstallCommand extends Command
echo "\033[u"; // Restore cursor position
}
}
echo PHP_EOL;
echo \PHP_EOL;
// Animate successful count
echo ' <fg=cyan;options=bold>Successful</> ................................................................... ';
for ($i = 0; $i <= $successCount; $i++) {
for ($i = 0; $i <= $successCount; ++$i) {
echo "\033[s"; // Save cursor position
echo "<fg=green>{$i}</>";
if ($i < $successCount) {
@@ -1052,7 +995,7 @@ final class InstallCommand extends Command
echo "\033[u"; // Restore cursor position
}
}
echo PHP_EOL;
echo \PHP_EOL;
// Other stats without animation
$this->components->twoColumnDetail('<fg=cyan;options=bold>Failed</>', "<fg=red>{$failureCount}</>");
@@ -1067,137 +1010,16 @@ final class InstallCommand extends Command
$this->components->twoColumnDetail('<fg=cyan;options=bold>Total Time</>', "<fg=yellow>{$totalTime}ms</>");
}
// Next steps with animated typing effect
// Documentation section - simplified now that PHPFlasher auto-injects
$this->newLine();
if (!$this->asciiMode) {
$this->line(' <box>╭' . str_repeat('─', $tableWidth) . '╮</box>');
$this->line(' <box>│</box> <box-title>NEXT STEPS</box-title>' . str_repeat(' ', $tableWidth - 13) . '<box>│</box>');
$this->line(' <box>╰' . str_repeat('─', $tableWidth) . '╯</box>');
} else {
$this->line(' <fg=blue;options=bold>[ NEXT STEPS ]</>');
}
$this->line(' <fg=blue;options=bold>[ DOCUMENTATION ]</>');
$this->newLine();
// Animate typing effect for next steps
$nextSteps = [
'<fg=white>• Include PHPFlasher in your layouts using the Blade directive:</> <fg=yellow>@flasher_render</>',
'<fg=white>• For SPA/API usage, include the following in your response:</> <fg=yellow>flasher()->render()</>',
'<fg=white>• Documentation:</> <fg=blue>https://php-flasher.io</>',
];
if (!$this->noAnimation && !$this->minimalMode) {
foreach ($nextSteps as $step) {
$plainStep = strip_tags($step);
echo ' ';
// Typing animation for instruction - fixed to avoid empty string error
for ($i = 1; $i <= strlen($plainStep); $i++) {
// Get the current character to output
$currentChar = $plainStep[$i-1];
echo $currentChar;
usleep(10000); // 10ms delay for character typing
}
echo PHP_EOL;
}
} else {
foreach ($nextSteps as $step) {
$this->line(' ' . $step);
}
}
$this->line(' <fg=white>• PHPFlasher Documentation:</> <fg=blue>https://php-flasher.io</>');
$this->newLine();
$this->stopTiming('summary');
}
/**
* Display performance metrics in debug mode with enhanced visuals.
*/
private function displayPerformanceMetrics(): void
{
// Skip if not in debug mode
if (!$this->debugMode) {
return;
}
$this->newLine();
if (!$this->asciiMode) {
$tableWidth = 70;
$this->line(' <box>╭' . str_repeat('─', $tableWidth) . '╮</box>');
$this->line(' <box>│</box> <box-title>PERFORMANCE METRICS</box-title>' . str_repeat(' ', $tableWidth - 22) . '<box>│</box>');
$this->line(' <box>╰' . str_repeat('─', $tableWidth) . '╯</box>');
} else {
$this->line(' <fg=yellow;options=bold>[ PERFORMANCE METRICS ]</>');
}
$this->newLine();
// Sort timings by duration (descending)
$timings = [];
foreach ($this->metrics as $name => $timing) {
if (isset($timing['duration'])) {
$timings[$name] = $timing['duration'];
}
}
arsort($timings);
// Create a formatted table
$headers = ['Operation', 'Duration (ms)', 'Percentage'];
$rows = [];
$totalTime = $this->metrics['total']['duration'] ?? 1; // Prevent division by zero
foreach ($timings as $name => $duration) {
if ($name === 'total') {
continue; // Skip total, will show it separately
}
$percent = round(($duration / $totalTime) * 100, 1);
$percentDisplay = $this->getColoredPercentage($percent);
// Format operation name nicely
$operation = str_replace(['_', 'task_'], [' ', ''], $name);
$operation = ucwords($operation);
$rows[] = [
$operation,
$duration,
$percentDisplay,
];
}
$this->table($headers, $rows);
// Show total time separately with visual bar
$this->newLine();
$totalDuration = $this->metrics['total']['duration'] ?? round((microtime(true) - $this->startTime) * 1000);
if (!$this->noAnimation && !$this->asciiMode) {
// Animated timer bar
$this->line(' <fg=blue>•</> Total execution time: ');
$barWidth = min(50, $this->terminalDimensions['width'] - 30);
echo ' [';
for ($i = 0; $i < $barWidth; $i++) {
echo "\033[s"; // Save cursor position
echo "\033[32m"; // Green color
for ($j = 0; $j <= $i; $j++) {
echo '■';
}
echo "\033[0m"; // Reset color
echo str_repeat(' ', $barWidth - $i - 1);
echo '] ' . round(($i + 1) / $barWidth * $totalDuration) . 'ms';
if ($i < $barWidth - 1) {
usleep(1000000 / $barWidth); // Distribute over 1 second
echo "\033[u"; // Restore cursor position
}
}
echo PHP_EOL;
} else {
$this->line(" <fg=blue>•</> Total execution time: <fg=yellow;options=bold>{$totalDuration}ms</>");
}
$this->newLine();
}
/**
* Format a file size in bytes to human-readable format with enhanced styling.
*/
@@ -1209,9 +1031,9 @@ final class InstallCommand extends Command
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$pow = floor(log($bytes, 1024));
$pow = min($pow, count($units) - 1);
$pow = min($pow, \count($units) - 1);
$bytes /= pow(1024, $pow);
$bytes /= 1024 ** $pow;
// Color-code based on size
$color = 'green';
@@ -1274,8 +1096,8 @@ final class InstallCommand extends Command
return;
}
$this->debugLineCount++;
$timestamp = '[' . sprintf('%.3f', (microtime(true) - $this->startTime) * 1000) . 'ms]';
++$this->debugLineCount;
$timestamp = '['.\sprintf('%.3f', (microtime(true) - $this->startTime) * 1000).'ms]';
$this->line(" <fg=gray>{$timestamp}</> <{$level}>{$message}</{$level}>");
}
@@ -1304,20 +1126,6 @@ final class InstallCommand extends Command
$this->newLine();
}
/**
* Get a color-coded percentage string based on the value.
*/
private function getColoredPercentage(float $percent): string
{
if ($percent > 30) {
return "<fg=red>{$percent}%</>";
} elseif ($percent > 10) {
return "<fg=yellow>{$percent}%</>";
} else {
return "<fg=green>{$percent}%</>";
}
}
/**
* Get relative path from the application base path.
*/