From fb4407fdb91fbbaba344a82c4c462f903a576378 Mon Sep 17 00:00:00 2001 From: Raphael Owino Date: Tue, 24 Feb 2026 11:14:00 +0300 Subject: [PATCH 1/2] feat: Add show command to track active sessions --- .claude/hooks/session-start.php | 22 +- .claude/scripts/statusline.php | 45 +- .laracode/settings.json | 25 +- app/Commands/BuildCommand.php | 230 +- app/Commands/ShowCommand.php | 397 + app/Providers/AppServiceProvider.php | 4 + app/Services/TaskSelector.php | 8 +- app/Tui/Components/HeaderBar.php | 24 + app/Tui/Components/KeyHelp.php | 41 + app/Tui/Components/ProgressBar.php | 33 + app/Tui/Components/SessionList.php | 164 + app/Tui/Components/StatusBar.php | 29 + app/Tui/Components/TaskDetail.php | 60 + app/Tui/Components/TaskList.php | 99 + app/Tui/DashboardRenderer.php | 80 + app/Tui/DashboardState.php | 121 + app/Tui/SessionRegistry.php | 152 + composer.json | 3 +- composer.lock | 8318 ++++++++--------- tests/Feature/BuildCommandModeTest.php | 17 +- tests/Feature/BuildCommandTest.php | 72 +- tests/Feature/ShowCommandTest.php | 26 + tests/Unit/Tui/Components/HeaderBarTest.php | 60 + tests/Unit/Tui/Components/KeyHelpTest.php | 54 + tests/Unit/Tui/Components/ProgressBarTest.php | 97 + tests/Unit/Tui/Components/SessionListTest.php | 457 + tests/Unit/Tui/Components/StatusBarTest.php | 52 + tests/Unit/Tui/Components/TaskDetailTest.php | 129 + tests/Unit/Tui/Components/TaskListTest.php | 119 + tests/Unit/Tui/DashboardStateTest.php | 297 + tests/Unit/Tui/SessionRegistryTest.php | 199 + 31 files changed, 7057 insertions(+), 4377 deletions(-) create mode 100644 app/Commands/ShowCommand.php create mode 100644 app/Tui/Components/HeaderBar.php create mode 100644 app/Tui/Components/KeyHelp.php create mode 100644 app/Tui/Components/ProgressBar.php create mode 100644 app/Tui/Components/SessionList.php create mode 100644 app/Tui/Components/StatusBar.php create mode 100644 app/Tui/Components/TaskDetail.php create mode 100644 app/Tui/Components/TaskList.php create mode 100644 app/Tui/DashboardRenderer.php create mode 100644 app/Tui/DashboardState.php create mode 100644 app/Tui/SessionRegistry.php create mode 100644 tests/Feature/ShowCommandTest.php create mode 100644 tests/Unit/Tui/Components/HeaderBarTest.php create mode 100644 tests/Unit/Tui/Components/KeyHelpTest.php create mode 100644 tests/Unit/Tui/Components/ProgressBarTest.php create mode 100644 tests/Unit/Tui/Components/SessionListTest.php create mode 100644 tests/Unit/Tui/Components/StatusBarTest.php create mode 100644 tests/Unit/Tui/Components/TaskDetailTest.php create mode 100644 tests/Unit/Tui/Components/TaskListTest.php create mode 100644 tests/Unit/Tui/DashboardStateTest.php create mode 100644 tests/Unit/Tui/SessionRegistryTest.php diff --git a/.claude/hooks/session-start.php b/.claude/hooks/session-start.php index ebb61e5..d18a21b 100644 --- a/.claude/hooks/session-start.php +++ b/.claude/hooks/session-start.php @@ -3,12 +3,12 @@ declare(strict_types=1); - - - - - - +/** + * SessionStart Hook + * + * Updates the lock file with Claude's session_id for statusline matching. + * Reads session_id from stdin JSON and writes to lock file specified in LARACODE_LOCK_FILE env var. + */ $input = file_get_contents('php://stdin'); $data = $input ? json_decode($input, true) : []; @@ -16,23 +16,23 @@ $lockFile = getenv('LARACODE_LOCK_FILE'); if (! $sessionId || ! $lockFile || ! file_exists($lockFile)) { -exit(0); + exit(0); } $lockContent = file_get_contents($lockFile); if ($lockContent === false) { -exit(0); + exit(0); } $lockData = json_decode($lockContent, true); if (! is_array($lockData)) { -exit(0); + exit(0); } $lockData['session_id'] = $sessionId; $result = file_put_contents($lockFile, json_encode($lockData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); if ($result === false) { -fwrite(STDERR, "Failed to write session_id to lock file: {$lockFile}\n"); -exit(1); + fwrite(STDERR, "Failed to write session_id to lock file: {$lockFile}\n"); + exit(1); } diff --git a/.claude/scripts/statusline.php b/.claude/scripts/statusline.php index 9bf0bb1..6ec731d 100644 --- a/.claude/scripts/statusline.php +++ b/.claude/scripts/statusline.php @@ -29,6 +29,9 @@ $status = $input ? json_decode($input, true) : []; +// Extract session_id for lock file matching +$sessionId = $status['session_id'] ?? null; + // Extract model - handle both nested format and simple string $model = 'Unknown'; if (isset($status['model'])) { @@ -52,8 +55,11 @@ } } -// Find active build lock file -$lockFile = findActiveLock(); +// Get current git branch +$gitBranch = getCurrentBranch(); + +// Find active build lock file (matching session_id if available) +$lockFile = findActiveLock($sessionId); $taskInfo = ''; if ($lockFile && file_exists($lockFile)) { @@ -78,6 +84,10 @@ // Build the status line with ANSI colors $statusLine = ''; +if ($gitBranch) { + $statusLine .= "\033[35m[{$gitBranch}]\033[0m "; // Magenta for branch +} + if ($taskInfo) { $statusLine .= "\033[36m{$taskInfo}\033[0m"; // Cyan for task info } @@ -91,9 +101,19 @@ echo $statusLine; /** - * Find the active lock file in .laracode/specs/ + * Get current git branch name + */ +function getCurrentBranch(): ?string +{ + $result = exec('git rev-parse --abbrev-ref HEAD 2>/dev/null', $output, $returnCode); + + return $returnCode === 0 && $result ? trim($result) : null; +} + +/** + * Find the active lock file in .laracode/specs/ matching the session_id */ -function findActiveLock(): ?string +function findActiveLock(?string $sessionId): ?string { $cwd = getcwd(); if ($cwd === false) { @@ -110,6 +130,23 @@ function findActiveLock(): ?string return null; } + // First pass: try to match by session_id + if ($sessionId !== null) { + foreach ($dirs as $dir) { + $lockPath = $dir.'/index.lock'; + if (file_exists($lockPath)) { + $content = file_get_contents($lockPath); + if ($content !== false) { + $data = json_decode($content, true); + if (isset($data['session_id']) && $data['session_id'] === $sessionId) { + return $lockPath; + } + } + } + } + } + + // Fallback: return first lock file (backward compatibility) foreach ($dirs as $dir) { $lockPath = $dir.'/index.lock'; if (file_exists($lockPath)) { diff --git a/.laracode/settings.json b/.laracode/settings.json index 9848f90..e1429ec 100644 --- a/.laracode/settings.json +++ b/.laracode/settings.json @@ -1,9 +1,10 @@ { "watch": { "paths": [ - "app/", - "routes/", - "resources/" + "app", + "config", + "resources", + "tests" ], "searchWord": "@claude", "stopWord": "claude!", @@ -19,5 +20,21 @@ "**/*.log", "**/.DS_Store" ] - } + }, + "testing": { + "commands": [ + "composer test", + "composer checks" + ] + }, + "linting": { + "commands": [ + "composer lint", + "composer phpstan" + ] + }, + "worktrees": { + "defaultSourceBranch": "master" + }, + "defaultMode": "yolo" } diff --git a/app/Commands/BuildCommand.php b/app/Commands/BuildCommand.php index a7f1401..6f75690 100644 --- a/app/Commands/BuildCommand.php +++ b/app/Commands/BuildCommand.php @@ -7,7 +7,14 @@ use App\Enums\BuildMode; use App\Services\AgentRunner; use App\Services\Settings\SettingsService; +use App\Services\TaskSelector; +use App\Tui\DashboardRenderer; +use App\Tui\DashboardState; +use App\Tui\SessionRegistry; use LaravelZero\Framework\Commands\Command; +use Symfony\Component\Console\Output\BufferedOutput; + +use function Termwind\renderUsing; class BuildCommand extends Command { @@ -21,29 +28,31 @@ class BuildCommand extends Command public function __construct( private AgentRunner $agentRunner, - private SettingsService $settingsService + private SettingsService $settingsService, + private DashboardRenderer $renderer, + private TaskSelector $taskSelector, + private SessionRegistry $registry, ) { parent::__construct(); } public function handle(): int { + $startTime = time(); + /** @var string $tasksPath */ $tasksPath = $this->argument('path'); $maxIterations = (int) $this->option('iterations'); $delay = (int) $this->option('delay'); - // Validate tasks file exists first to get project path if (! file_exists($tasksPath)) { $this->error("Tasks file not found: {$tasksPath}"); return self::FAILURE; } - // Determine project path early for settings resolution $realTasksPath = realpath($tasksPath); $projectPath = $realTasksPath ? dirname($realTasksPath) : dirname($tasksPath); - // Try to find project root by looking for .claude or .laracode directory while ($projectPath !== '/' && ! is_dir($projectPath.'/.claude') && ! is_dir($projectPath.'/.laracode')) { $projectPath = dirname($projectPath); } @@ -62,7 +71,6 @@ public function handle(): int return self::FAILURE; } - // Parse and validate JSON $content = file_get_contents($tasksPath); if ($content === false) { $this->error("Cannot read tasks file: {$tasksPath}"); @@ -84,48 +92,51 @@ public function handle(): int return self::FAILURE; } - // Compute lock path next to tasks.json $lockPath = dirname($realTasksPath ?: $tasksPath).'/index.lock'; + $canonicalTasksPath = $realTasksPath ?: $tasksPath; + + $this->registry->register($canonicalTasksPath, (int) getmypid(), $mode->value, $projectPath); - $this->info("Project path: {$projectPath}"); - $this->displayStats($tasks); + $this->renderDashboard($tasks, 0, $maxIterations, $startTime, null, $mode); - $this->registerSignalHandlers($lockPath); + $this->registerSignalHandlers($lockPath, $canonicalTasksPath); $iteration = 0; while ($iteration < $maxIterations) { - // Reload tasks on each iteration (agent may have updated them) $content = file_get_contents($tasksPath); if ($content === false) { + $iteration++; + continue; } - /** @var array{title: string, tasks: array}>} $tasks */ - $tasks = json_decode($content, true); + $decoded = json_decode($content, true); - // Find pending tasks - $pending = array_filter($tasks['tasks'], fn ($t) => $t['status'] === 'pending'); + if (! is_array($decoded) || ! isset($decoded['tasks'])) { + $iteration++; - if (empty($pending)) { - $this->newLine(); - $this->info('✓ All tasks completed!'); - $this->displayFinalStats($tasks); + continue; + } + + /** @var array{title: string, tasks: array}>} $decoded */ + $tasks = $decoded; + + $nextTask = $this->taskSelector->selectNextTask($tasks['tasks']); + + if ($nextTask === null) { + $this->registry->deregister($canonicalTasksPath); + $this->renderDashboard($tasks, $iteration, $maxIterations, $startTime, null, $mode, 'All tasks completed!'); + $this->captureTermwindOutput(fn () => $this->renderer->renderFinalStats($tasks)); return self::SUCCESS; } $iteration++; - $nextTask = reset($pending); - - $this->newLine(); - $this->line('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - $this->info("Iteration {$iteration}/{$maxIterations}"); $taskLabel = $nextTask['title'] ?? $nextTask['description'] ?? 'Untitled'; - $this->line("Next Task: #{$nextTask['id']} - {$taskLabel}"); + $this->renderDashboard($tasks, $iteration, $maxIterations, $startTime, $nextTask['id'], $mode, "Task #{$nextTask['id']}: {$taskLabel}"); $this->runAgent($projectPath, $mode, $tasksPath, $lockPath, $nextTask); - // Read completion signal and update stats $completedPath = dirname($realTasksPath ?: $tasksPath).'/completed.json'; if (file_exists($completedPath)) { $completionContent = file_get_contents($completedPath); @@ -136,8 +147,8 @@ public function handle(): int $this->updateTaskStats( $tasksPath, (int) $completion['taskId'], - $completion['startedAt'], - $completion['completedAt'], + (string) $completion['startedAt'], + (string) $completion['completedAt'], $gitStats ); } @@ -145,45 +156,34 @@ public function handle(): int @unlink($completedPath); } - // Reload and display stats $content = file_get_contents($tasksPath); if ($content !== false) { /** @var array{title: string, tasks: array}>} $tasks */ $tasks = json_decode($content, true); - $this->displayStats($tasks); + $this->renderDashboard($tasks, $iteration, $maxIterations, $startTime, null, $mode); } - // Check if there are still pending tasks $pending = array_filter($tasks['tasks'], fn ($t) => $t['status'] === 'pending'); if (! empty($pending) && $iteration < $maxIterations) { - $this->info("Sleeping {$delay}s before next task..."); sleep($delay); } } - $this->newLine(); - $this->warn("Reached max iterations ({$maxIterations})"); - $this->displayStats($tasks); + $this->registry->deregister($canonicalTasksPath); + $this->renderDashboard($tasks, $iteration, $maxIterations, $startTime, null, $mode, "Reached max iterations ({$maxIterations})"); return self::SUCCESS; } - /** - * Resolves the mode option with precedence: CLI flag > settings > fallback. - * Reads defaultMode from SettingsService when --mode flag is not explicitly provided. - * Falls back to 'interactive' if no settings found. - */ private function resolveModeOption(string $projectPath): string { /** @var string|null $cliMode */ $cliMode = $this->option('mode'); - // CLI flag takes precedence if ($cliMode !== null && $cliMode !== '') { return $cliMode; } - // Read from settings $this->settingsService->setProjectPath($projectPath); /** @var string|null $defaultMode */ $defaultMode = $this->settingsService->get('defaultMode'); @@ -196,11 +196,7 @@ private function resolveModeOption(string $projectPath): string */ private function runAgent(string $projectPath, BuildMode $mode, string $tasksPath, string $lockPath, array $currentTask): void { - $this->line("Mode: {$mode->description()}"); - $prompt = "/build-next $tasksPath"; - $this->line("Running agent with prompt: {$prompt}"); - $this->newLine(); $process = $this->agentRunner->run( $mode, @@ -224,8 +220,6 @@ private function runAgent(string $projectPath, BuildMode $mode, string $tasksPat } $this->agentRunner->monitor($process, $lockPath, function (int $pid) use ($process): void { - $this->newLine(); - $this->line('Lock file removed, terminating agent...'); $this->agentRunner->terminate($process, $pid); }); @@ -236,22 +230,23 @@ private function runAgent(string $projectPath, BuildMode $mode, string $tasksPat private function restoreTerminal(): void { if (defined('STDOUT') && function_exists('posix_isatty') && posix_isatty(STDOUT)) { - echo "\e[?25h"; // Show cursor - echo "\e[?1004l"; // Disable focus reporting + echo "\e[?25h"; + echo "\e[?1004l"; system('stty sane 2>/dev/null'); } } - private function registerSignalHandlers(string $lockPath): void + private function registerSignalHandlers(string $lockPath, string $tasksPath): void { if (! function_exists('pcntl_signal')) { return; } - $cleanup = function () use ($lockPath): void { + $cleanup = function () use ($lockPath, $tasksPath): void { + $this->registry->deregister($tasksPath); @unlink($lockPath); $this->restoreTerminal(); - exit(130); // Standard exit code for SIGINT + exit(130); }; pcntl_signal(SIGINT, $cleanup); @@ -262,100 +257,66 @@ private function registerSignalHandlers(string $lockPath): void /** * @param array $tasks */ - private function displayStats(array $tasks): void - { - $total = count($tasks['tasks']); - $completed = count(array_filter($tasks['tasks'], fn ($t) => $t['status'] === 'completed')); - $pending = count(array_filter($tasks['tasks'], fn ($t) => $t['status'] === 'pending')); - $blocked = $this->countBlockedTasks($tasks['tasks']); - $percentage = $total > 0 ? (int) round(($completed / $total) * 100) : 0; - - $barLength = 20; - $filled = (int) round($barLength * $percentage / 100); - $bar = str_repeat('█', $filled).str_repeat('░', $barLength - $filled); - - $featureName = $tasks['title'] ?? $tasks['feature'] ?? 'Unknown'; - - $this->newLine(); - $this->line("Feature: {$featureName}"); - - if (! empty($tasks['branch'])) { - $this->line("Branch: {$tasks['branch']}"); - } - - if (! empty($tasks['created'])) { - $createdDate = date('Y-m-d H:i', strtotime($tasks['created'])); - $this->line("Created: {$createdDate}"); + private function renderDashboard( + array $tasks, + int $iteration, + int $maxIterations, + int $startTime, + ?int $activeTaskId, + BuildMode $mode, + string $statusMessage = '', + ): void { + if ($statusMessage === '') { + $statusMessage = $this->buildStatusMessage($tasks['tasks'] ?? []); } - $this->line("Progress: [{$bar}] {$percentage}%"); + $state = DashboardState::fromTasksArray( + $tasks, + $iteration, + $maxIterations, + time() - $startTime, + $activeTaskId, + $mode->value, + $statusMessage, + ); - $statsLine = "Tasks: {$completed}/{$total} completed | {$pending} pending"; - if ($blocked > 0) { - $statsLine .= " | {$blocked} blocked"; - } - $this->line($statsLine); + $this->captureTermwindOutput(fn () => $this->renderer->render($state)); } - /** - * @param array}> $taskList - */ - private function countBlockedTasks(array $taskList): int + private function captureTermwindOutput(callable $callback): void { - $completedIds = []; - foreach ($taskList as $task) { - if ($task['status'] === 'completed') { - $completedIds[$task['id']] = true; + $buffer = new BufferedOutput; + renderUsing($buffer); + $callback(); + $captured = $buffer->fetch(); + renderUsing(null); + + foreach (explode("\n", $captured) as $line) { + if ($line !== '') { + $this->line($line); } } - - $blocked = 0; - foreach ($taskList as $task) { - if ($task['status'] !== 'pending') { - continue; - } - - $dependencies = $task['dependencies'] ?? []; - if (empty($dependencies)) { - continue; - } - - foreach ($dependencies as $depId) { - if (! isset($completedIds[$depId])) { - $blocked++; - break; - } - } - } - - return $blocked; } /** - * @param array{tasks: array, stats?: array{filesChanged?: int, linesAdded?: int, linesRemoved?: int}} $tasks + * @param array}> $taskList */ - private function displayFinalStats(array $tasks): void + private function buildStatusMessage(array $taskList): string { - $totalSeconds = 0; - foreach ($tasks['tasks'] as $task) { - $totalSeconds += $task['stats']['durationSeconds'] ?? 0; + $total = count($taskList); + $completed = count(array_filter($taskList, fn ($t) => $t['status'] === 'completed')); + $pending = count(array_filter($taskList, fn ($t) => $t['status'] === 'pending')); + $blocked = $this->taskSelector->countBlockedTasks($taskList); + + $parts = ["{$completed}/{$total} completed"]; + if ($pending > 0) { + $parts[] = "{$pending} pending"; + } + if ($blocked > 0) { + $parts[] = "{$blocked} blocked"; } - $minutes = (int) floor($totalSeconds / 60); - $seconds = $totalSeconds % 60; - $durationStr = $minutes > 0 ? "{$minutes}m {$seconds}s" : "{$seconds}s"; - - $filesChanged = $tasks['stats']['filesChanged'] ?? 0; - $linesAdded = $tasks['stats']['linesAdded'] ?? 0; - $linesRemoved = $tasks['stats']['linesRemoved'] ?? 0; - - $this->newLine(); - $this->line('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - $this->line('Build Statistics'); - $this->line("Total Duration: {$durationStr}"); - $this->line("Files Changed: {$filesChanged}"); - $this->line("Lines Added: +{$linesAdded}"); - $this->line("Lines Removed: -{$linesRemoved}"); + return implode(' | ', $parts); } /** @@ -379,7 +340,6 @@ private function getGitStats(string $projectPath): array fclose($pipes[2]); proc_close($process); - // Parse summary line: "X files changed, Y insertions(+), Z deletions(-)" preg_match('/(\d+) files? changed/', $output ?: '', $files); preg_match('/(\d+) insertions?\(\+\)/', $output ?: '', $added); preg_match('/(\d+) deletions?\(-\)/', $output ?: '', $removed); @@ -411,9 +371,10 @@ private function updateTaskStats( return; } - $durationSeconds = strtotime($completedAt) - strtotime($startedAt); + $start = strtotime($startedAt); + $end = strtotime($completedAt); + $durationSeconds = ($start !== false && $end !== false) ? max(0, $end - $start) : 0; - // Update specific task's stats foreach ($tasks['tasks'] as &$task) { if ($task['id'] === $taskId) { $task['stats'] = [ @@ -429,7 +390,6 @@ private function updateTaskStats( } unset($task); - // Update root-level stats (cumulative) $tasks['stats'] = $tasks['stats'] ?? []; $tasks['stats']['filesChanged'] = ($tasks['stats']['filesChanged'] ?? 0) + $gitStats['filesChanged']; $tasks['stats']['linesAdded'] = ($tasks['stats']['linesAdded'] ?? 0) + $gitStats['linesAdded']; diff --git a/app/Commands/ShowCommand.php b/app/Commands/ShowCommand.php new file mode 100644 index 0000000..ff83642 --- /dev/null +++ b/app/Commands/ShowCommand.php @@ -0,0 +1,397 @@ + */ + private array $sessions = []; + + private bool $running = true; + + private int $exitCode = self::SUCCESS; + + public function __construct( + private SessionRegistry $registry, + private SessionList $sessionList, + private KeyHelp $keyHelp, + private TaskList $taskList, + private TaskDetail $taskDetail, + private ProgressBar $progressBar, + private StatusBar $statusBar, + ) { + parent::__construct(); + } + + public function handle(): int + { + $this->sessions = $this->registry->getActiveSessions(); + + $this->setupTerminal(); + $this->registerSignalHandlers(); + + $lastRefresh = 0; + $dirty = true; + + try { + while ($this->running) { + if (function_exists('pcntl_signal_dispatch')) { + pcntl_signal_dispatch(); + } + + $key = $this->readKey(); + if ($key !== null) { + $dirty = $this->handleKey($key) || $dirty; + } + + $now = time(); + if ($now - $lastRefresh >= 2) { + $this->sessions = $this->registry->getActiveSessions(); + $sessionCount = count($this->sessions); + if ($this->selectedIndex >= $sessionCount) { + $this->selectedIndex = max(0, $sessionCount - 1); + } + $lastRefresh = $now; + $dirty = true; + } + + if ($dirty) { + $this->renderCurrentView(); + $dirty = false; + } + + usleep(50000); + } + } finally { + $this->restoreTerminal(); + } + + return $this->exitCode; + } + + private function setupTerminal(): void + { + echo "\e[?1049h"; + system('stty -icanon -echo 2>/dev/null'); + stream_set_blocking(STDIN, false); + echo "\e[?25l"; + } + + private function restoreTerminal(): void + { + stream_set_blocking(STDIN, true); + if (defined('STDOUT') && function_exists('posix_isatty') && posix_isatty(STDOUT)) { + echo "\e[?25h"; + system('stty sane 2>/dev/null'); + echo "\e[?1049l"; + } + } + + private function registerSignalHandlers(): void + { + if (! function_exists('pcntl_signal')) { + return; + } + + $cleanup = function (int $signal): void { + $this->running = false; + $this->exitCode = 128 + $signal; + }; + + pcntl_signal(SIGINT, $cleanup); + pcntl_signal(SIGTERM, $cleanup); + } + + private function readKey(): ?string + { + $char = fread(STDIN, 1); + if ($char === false || $char === '') { + return null; + } + + if ($char === "\e") { + usleep(1000); + $bracket = fread(STDIN, 1); + if ($bracket === '[') { + usleep(1000); + $code = fread(STDIN, 1); + + return match ($code) { + 'A' => 'up', + 'B' => 'down', + default => null, + }; + } + + if ($bracket === false || $bracket === '') { + return 'esc'; + } + + return null; + } + + return match ($char) { + 'q' => 'quit', + 'j' => 'down', + 'k' => 'up', + "\n", "\r" => 'enter', + "\x7f" => 'backspace', + default => null, + }; + } + + private function handleKey(string $key): bool + { + if ($key === 'quit') { + $this->running = false; + + return false; + } + + if ($this->view === 'list') { + return $this->handleListKey($key); + } + + return $this->handleDetailKey($key); + } + + private function handleListKey(string $key): bool + { + return match ($key) { + 'up' => $this->moveSelection(-1), + 'down' => $this->moveSelection(1), + 'enter' => $this->enterDetailView(), + default => false, + }; + } + + private function handleDetailKey(string $key): bool + { + return match ($key) { + 'esc', 'backspace' => $this->backToList(), + default => false, + }; + } + + private function moveSelection(int $direction): bool + { + $count = count($this->sessions); + if ($count === 0) { + return false; + } + + $newIndex = $this->selectedIndex + $direction; + if ($newIndex < 0 || $newIndex >= $count) { + return false; + } + + $this->selectedIndex = $newIndex; + + return true; + } + + private function enterDetailView(): bool + { + if ($this->sessions === []) { + return false; + } + + $this->view = 'detail'; + + return true; + } + + private function backToList(): bool + { + $this->view = 'list'; + + return true; + } + + private function renderCurrentView(): void + { + ob_start(); + echo "\033[2J\033[H"; + + if ($this->view === 'detail') { + $this->renderDetailView(); + } else { + $this->renderListView(); + } + + echo "\033[J"; + $frame = ob_get_clean(); + echo $frame; + } + + private function renderListView(): void + { + $count = count($this->sessions); + $countLabel = $count === 1 ? '1 session' : "{$count} sessions"; + + $header = << + laracode [Sessions] + {$countLabel} + + HTML; + + $sessionListHtml = $this->sessionList->render($this->sessions, $this->selectedIndex); + $keyHelpHtml = $this->keyHelp->render('list'); + + $this->renderHtml("
{$header}{$sessionListHtml}{$keyHelpHtml}
"); + } + + private function renderDetailView(): void + { + if (! isset($this->sessions[$this->selectedIndex])) { + $this->view = 'list'; + $this->renderListView(); + + return; + } + + $session = $this->sessions[$this->selectedIndex]; + $state = $this->buildDashboardState($session); + + if ($state === null) { + $this->view = 'list'; + $this->renderListView(); + + return; + } + + $featureTitle = htmlspecialchars($state->featureTitle); + $elapsed = $this->formatElapsedSeconds($state->elapsedSeconds); + $projectPath = htmlspecialchars($this->shortenPath($session['projectPath'])); + + $header = << +
+ laracode [Viewing] {$featureTitle} +
+
+ {$projectPath} + {$elapsed} +
+ + HTML; + + $html = '
' + .implode("\n", [ + $header, + $this->taskList->render($state), + $this->progressBar->render($state), + '
', + $this->taskDetail->render($state), + $this->statusBar->render($state), + $this->keyHelp->render('detail'), + ]) + .'
'; + + $this->renderHtml($html); + } + + /** + * @param array{tasksPath: string, pid: int, startedAt: string, mode: string, projectPath: string} $session + */ + private function buildDashboardState(array $session): ?DashboardState + { + $tasksPath = $session['tasksPath']; + if (! file_exists($tasksPath)) { + return null; + } + + $content = file_get_contents($tasksPath); + if ($content === false) { + return null; + } + + /** @var array{title?: string, branch?: string, tasks?: array}|null $data */ + $data = json_decode($content, true); + if (! is_array($data)) { + return null; + } + + $startedAt = strtotime($session['startedAt']); + $elapsed = $startedAt !== false ? time() - $startedAt : 0; + + $activeTaskId = null; + foreach ($data['tasks'] ?? [] as $task) { + if ($task['status'] === 'in_progress') { + $activeTaskId = $task['id']; + break; + } + } + + $tasks = $data['tasks'] ?? []; + $total = count($tasks); + $completed = count(array_filter($tasks, fn (array $t) => $t['status'] === 'completed')); + + return DashboardState::fromTasksArray( + $data, + $completed, + $total, + $elapsed, + $activeTaskId, + $session['mode'], + "{$completed}/{$total} completed", + ); + } + + private function formatElapsedSeconds(int $seconds): string + { + $minutes = intdiv($seconds, 60); + $secs = $seconds % 60; + + return $minutes > 0 ? "{$minutes}m{$secs}s" : "{$secs}s"; + } + + private function shortenPath(string $path): string + { + $home = getenv('HOME') ?: (getenv('USERPROFILE') ?: ''); + if ($home !== '' && str_starts_with($path, $home)) { + return '~'.substr($path, strlen($home)); + } + + return $path; + } + + private function renderHtml(string $html): void + { + $buffer = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + renderUsing($buffer); + render($html); + $captured = $buffer->fetch(); + renderUsing(null); + + echo $captured; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6ae354f..cc6d92a 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -15,6 +15,8 @@ use App\Services\Settings\SettingsLoader; use App\Services\Settings\SettingsService; use App\Services\Settings\SettingsWriter; +use App\Tui\DashboardRenderer; +use App\Tui\SessionRegistry; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -49,5 +51,7 @@ public function register(): void }); $this->app->singleton(AgentRunner::class); + $this->app->singleton(DashboardRenderer::class); + $this->app->singleton(SessionRegistry::class); } } diff --git a/app/Services/TaskSelector.php b/app/Services/TaskSelector.php index 761ff76..8fd7fdd 100644 --- a/app/Services/TaskSelector.php +++ b/app/Services/TaskSelector.php @@ -7,8 +7,8 @@ class TaskSelector { /** - * @param array, priority?: int}> $tasks - * @return array{id: int, status: string, dependencies?: array, priority?: int}|null + * @param array, priority?: int}> $tasks + * @return array{id: int, status: string, title?: string, description?: string, dependencies?: array, priority?: int}|null */ public function selectNextTask(array $tasks): ?array { @@ -50,9 +50,9 @@ public function getCompletedTaskIds(array $tasks): array } /** - * @param array, priority?: int}> $tasks + * @param array, priority?: int}> $tasks * @param array $completedIds - * @return array, priority?: int}> + * @return array, priority?: int}> */ public function getAvailableTasks(array $tasks, array $completedIds): array { diff --git a/app/Tui/Components/HeaderBar.php b/app/Tui/Components/HeaderBar.php new file mode 100644 index 0000000..3cc21a1 --- /dev/null +++ b/app/Tui/Components/HeaderBar.php @@ -0,0 +1,24 @@ +elapsedSeconds, 60); + $seconds = $state->elapsedSeconds % 60; + $time = $minutes > 0 ? "{$minutes}m{$seconds}s" : "{$seconds}s"; + + return << + laracode [Running] + Iteration: {$state->currentIteration}/{$state->maxIterations} Time: {$time} + + HTML; + } +} diff --git a/app/Tui/Components/KeyHelp.php b/app/Tui/Components/KeyHelp.php new file mode 100644 index 0000000..3039f13 --- /dev/null +++ b/app/Tui/Components/KeyHelp.php @@ -0,0 +1,41 @@ + $this->detailHints(), + default => $this->listHints(), + }; + + return << + {$hints} + + HTML; + } + + private function listHints(): string + { + return '↑↓ Navigate' + .' ' + .'Enter Details' + .' ' + .'q Quit'; + } + + private function detailHints(): string + { + return 'Esc Back' + .' ' + .'q Quit'; + } +} diff --git a/app/Tui/Components/ProgressBar.php b/app/Tui/Components/ProgressBar.php new file mode 100644 index 0000000..d0c6c98 --- /dev/null +++ b/app/Tui/Components/ProgressBar.php @@ -0,0 +1,33 @@ +progressPercent(); + $completed = $state->completedCount(); + $total = $state->totalCount(); + + $filledWidth = $total > 0 + ? (int) round(($completed / $total) * self::BAR_WIDTH) + : 0; + $emptyWidth = self::BAR_WIDTH - $filledWidth; + + $filled = str_repeat('█', $filledWidth); + $empty = str_repeat('░', $emptyWidth); + + return << + {$filled}{$empty} {$percent}% {$completed}/{$total} + + HTML; + } +} diff --git a/app/Tui/Components/SessionList.php b/app/Tui/Components/SessionList.php new file mode 100644 index 0000000..7715f00 --- /dev/null +++ b/app/Tui/Components/SessionList.php @@ -0,0 +1,164 @@ + $sessions + */ + public function render(array $sessions, int $selectedIndex): string + { + if ($sessions === []) { + return <<<'HTML' +
No active sessions
+ HTML; + } + + $rows = ''; + foreach ($sessions as $index => $session) { + $rows .= $this->renderRow($session, $index === $selectedIndex); + } + + return << + {$rows} + + HTML; + } + + /** + * @param array{tasksPath: string, pid: int, startedAt: string, mode: string, projectPath: string} $session + */ + private function renderRow(array $session, bool $selected): string + { + $taskData = $this->readTasksFile($session['tasksPath']); + + $selector = $selected ? '' : ' '; + $rawTitle = htmlspecialchars($taskData['title'] ?? 'Untitled'); + $title = $selected ? "{$rawTitle}" : $rawTitle; + + $tasks = $taskData['tasks'] ?? []; + $total = count($tasks); + $completed = count(array_filter($tasks, fn (array $t) => $t['status'] === 'completed')); + + $elapsed = $this->formatElapsed($session['startedAt']); + + $path = ''.htmlspecialchars($this->truncateLeft($session['projectPath'], 50)).''; + + $branch = $taskData['branch'] ?? ''; + $branchHtml = ($branch !== '' && $branch !== 'unknown') + ? ' ('.htmlspecialchars($branch).')' + : ''; + + $activeTaskName = $this->activeTaskName($tasks); + $statusLabel = $this->sessionStatus($tasks); + + $selectedClass = $selected ? 'text-white' : ''; + + $line1 = "{$selector} {$title}"; + $line2 = " {$path}{$branchHtml}"; + $tasksColor = ($total > 0 && $completed === $total) ? 'text-green-400' : 'text-yellow-400'; + $line3 = " Tasks: {$completed}/{$total}Status: {$statusLabel}Elapsed: {$elapsed}"; + + $html = "
{$line1}
" + ."
{$line2}
"; + + if ($activeTaskName !== null) { + $activeLabel = ''.htmlspecialchars($activeTaskName).''; + $line4 = " {$activeLabel}"; + + $html .= "
{$line3}
" + ."
{$line4}
"; + } else { + $html .= "
{$line3}
"; + } + + return $html; + } + + private function truncateLeft(string $text, int $max): string + { + if (mb_strlen($text) <= $max) { + return $text; + } + + return '…'.mb_substr($text, -($max - 1)); + } + + private function formatElapsed(string $startedAt): string + { + $start = strtotime($startedAt); + if ($start === false) { + return '--'; + } + + $seconds = time() - $start; + if ($seconds < 0) { + $seconds = 0; + } + + $minutes = intdiv($seconds, 60); + $secs = $seconds % 60; + + $formatted = $minutes > 0 ? "{$minutes}m{$secs}s" : "{$secs}s"; + + return "{$formatted}"; + } + + /** + * @param array $tasks + */ + private function activeTaskName(array $tasks): ?string + { + foreach ($tasks as $task) { + if ($task['status'] === 'in_progress') { + return $task['title'] ?? "Task #{$task['id']}"; + } + } + + return null; + } + + /** + * @param array $tasks + */ + private function sessionStatus(array $tasks): string + { + foreach ($tasks as $task) { + if ($task['status'] === 'in_progress') { + return 'active'; + } + } + + if ($tasks !== [] && count(array_filter($tasks, fn (array $t) => $t['status'] === 'completed')) === count($tasks)) { + return 'complete'; + } + + return 'running'; + } + + /** + * @return array{title?: string, branch?: string, tasks?: array} + */ + private function readTasksFile(string $tasksPath): array + { + if (! file_exists($tasksPath)) { + return []; + } + + $contents = file_get_contents($tasksPath); + if ($contents === false || $contents === '') { + return []; + } + + $data = json_decode($contents, true); + + return is_array($data) ? $data : []; + } +} diff --git a/app/Tui/Components/StatusBar.php b/app/Tui/Components/StatusBar.php new file mode 100644 index 0000000..b4dd93d --- /dev/null +++ b/app/Tui/Components/StatusBar.php @@ -0,0 +1,29 @@ +statusMessage); + $mode = htmlspecialchars($state->mode); + $branch = htmlspecialchars($state->branch); + + $showBranch = $branch !== '' && $branch !== 'unknown'; + $branchHtml = $showBranch + ? " · {$branch}" + : ''; + + return << + {$statusMessage} + {$mode}{$branchHtml} + + HTML; + } +} diff --git a/app/Tui/Components/TaskDetail.php b/app/Tui/Components/TaskDetail.php new file mode 100644 index 0000000..2273bc0 --- /dev/null +++ b/app/Tui/Components/TaskDetail.php @@ -0,0 +1,60 @@ +activeTask(); + + if ($activeTask === null) { + return '
No task in progress
'; + } + + $title = htmlspecialchars($activeTask['title'] ?? $activeTask['description'] ?? 'Untitled'); + $status = $activeTask['status']; + $priority = $activeTask['priority'] ?? 3; + $description = htmlspecialchars($activeTask['description'] ?? ''); + + $statusBadge = match ($status) { + 'completed' => 'completed', + 'in_progress' => 'in_progress', + 'pending' => 'pending', + default => ''.$status.'', + }; + + $acceptanceHtml = ''; + $acceptanceCriteria = $activeTask['acceptance'] ?? []; + if (! empty($acceptanceCriteria)) { + $items = ''; + foreach ($acceptanceCriteria as $criterion) { + $escapedCriterion = htmlspecialchars((string) $criterion); + $items .= "
  • {$escapedCriterion}
  • "; + } + $acceptanceHtml = << + Acceptance: +
      {$items}
    + + HTML; + } + + $descriptionHtml = $description !== '' + ? "
    {$description}
    " + : ''; + + return << +
    {$title}
    +
    {$statusBadge} Priority: {$priority}
    + {$descriptionHtml} + {$acceptanceHtml} + + HTML; + } +} diff --git a/app/Tui/Components/TaskList.php b/app/Tui/Components/TaskList.php new file mode 100644 index 0000000..323435e --- /dev/null +++ b/app/Tui/Components/TaskList.php @@ -0,0 +1,99 @@ +tasks as $task) { + if ($task['status'] === 'completed') { + $completedIds[$task['id']] = true; + } + } + + $rows = ''; + foreach ($state->tasks as $task) { + $isActive = $task['id'] === $state->activeTaskId; + $icon = $this->statusIcon($task, $completedIds); + $idLabel = 'US-'.$task['id'].''; + $titleText = htmlspecialchars($task['title'] ?? $task['description'] ?? 'Untitled'); + $titleClass = $this->titleColorClass($task, $completedIds); + $titleSpan = "{$titleText}"; + $label = "{$idLabel} {$titleSpan}"; + + if ($isActive) { + $rows .= "
    {$icon} {$label}
    "; + } else { + $rows .= "
    {$icon} {$label}
    "; + } + } + + return << + {$rows} + + HTML; + } + + /** + * @param array{id: int, status: string, dependencies?: array} $task + * @param array $completedIds + */ + private function statusIcon(array $task, array $completedIds): string + { + if ($task['id'] === 0) { + // won't happen, but satisfies static analysis + return ''; + } + + return match ($task['status']) { + 'completed' => '', + 'in_progress' => '', + 'pending' => $this->isBlocked($task, $completedIds) + ? '' + : '', + default => '', + }; + } + + /** + * @param array{id: int, status: string, dependencies?: array} $task + * @param array $completedIds + */ + private function titleColorClass(array $task, array $completedIds): string + { + return match ($task['status']) { + 'completed' => 'text-green-400', + 'in_progress' => 'text-cyan-400 font-bold', + 'pending' => $this->isBlocked($task, $completedIds) ? 'text-red-400' : '', + default => '', + }; + } + + /** + * @param array{id: int, status: string, dependencies?: array} $task + * @param array $completedIds + */ + private function isBlocked(array $task, array $completedIds): bool + { + $dependencies = $task['dependencies'] ?? []; + + if (empty($dependencies)) { + return false; + } + + foreach ($dependencies as $depId) { + if (! isset($completedIds[$depId])) { + return true; + } + } + + return false; + } +} diff --git a/app/Tui/DashboardRenderer.php b/app/Tui/DashboardRenderer.php new file mode 100644 index 0000000..3ffcee3 --- /dev/null +++ b/app/Tui/DashboardRenderer.php @@ -0,0 +1,80 @@ +featureTitle); + + $html = '
    ' + .implode("\n", [ + $this->headerBar->render($state), + "
    {$featureTitle}
    ", + $this->taskList->render($state), + $this->progressBar->render($state), + '
    ', + $this->taskDetail->render($state), + $this->statusBar->render($state), + ]) + .'
    '; + + render($html); + } + + /** + * @param array{tasks: array, stats?: array{filesChanged?: int, linesAdded?: int, linesRemoved?: int}} $tasks + */ + public function renderFinalStats(array $tasks): void + { + $totalSeconds = 0; + foreach ($tasks['tasks'] as $task) { + $totalSeconds += $task['stats']['durationSeconds'] ?? 0; + } + + $minutes = intdiv($totalSeconds, 60); + $seconds = $totalSeconds % 60; + $duration = $minutes > 0 ? "{$minutes}m {$seconds}s" : "{$seconds}s"; + + $filesChanged = $tasks['stats']['filesChanged'] ?? 0; + $linesAdded = $tasks['stats']['linesAdded'] ?? 0; + $linesRemoved = $tasks['stats']['linesRemoved'] ?? 0; + + $html = << +
    Build Complete
    +
    +
    Duration: {$duration}
    +
    Files Changed: {$filesChanged}
    +
    Lines Added: +{$linesAdded}
    +
    Lines Removed: -{$linesRemoved}
    +
    + + HTML; + + render($html); + } +} diff --git a/app/Tui/DashboardState.php b/app/Tui/DashboardState.php new file mode 100644 index 0000000..e8fae3e --- /dev/null +++ b/app/Tui/DashboardState.php @@ -0,0 +1,121 @@ +, priority?: int, acceptance?: array, steps?: array}> $tasks + */ + public function __construct( + public string $featureTitle, + public string $branch, + public array $tasks, + public int $currentIteration, + public int $maxIterations, + public int $elapsedSeconds, + public ?int $activeTaskId, + public string $mode, + public string $statusMessage, + ) {} + + /** + * @param array{title?: string, branch?: string, tasks?: array, priority?: int, acceptance?: array, steps?: array}>} $data + */ + public static function fromTasksArray( + array $data, + int $iteration, + int $maxIterations, + int $elapsed, + ?int $activeTaskId, + string $mode, + string $statusMessage, + ): self { + return new self( + featureTitle: $data['title'] ?? 'Untitled', + branch: $data['branch'] ?? 'unknown', + tasks: $data['tasks'] ?? [], + currentIteration: $iteration, + maxIterations: $maxIterations, + elapsedSeconds: $elapsed, + activeTaskId: $activeTaskId, + mode: $mode, + statusMessage: $statusMessage, + ); + } + + public function completedCount(): int + { + return count(array_filter($this->tasks, fn (array $task) => $task['status'] === 'completed')); + } + + public function pendingCount(): int + { + return count(array_filter($this->tasks, fn (array $task) => $task['status'] === 'pending')); + } + + public function blockedCount(): int + { + $completedIds = []; + foreach ($this->tasks as $task) { + if ($task['status'] === 'completed') { + $completedIds[$task['id']] = true; + } + } + + $blocked = 0; + foreach ($this->tasks as $task) { + if ($task['status'] !== 'pending') { + continue; + } + + $dependencies = $task['dependencies'] ?? []; + if (empty($dependencies)) { + continue; + } + + foreach ($dependencies as $depId) { + if (! isset($completedIds[$depId])) { + $blocked++; + break; + } + } + } + + return $blocked; + } + + public function totalCount(): int + { + return count($this->tasks); + } + + public function progressPercent(): float + { + if ($this->totalCount() === 0) { + return 0.0; + } + + return round(($this->completedCount() / $this->totalCount()) * 100, 1); + } + + /** + * @return array{id: int, status: string, title?: string, description?: string, dependencies?: array, priority?: int, acceptance?: array, steps?: array}|null + */ + public function activeTask(): ?array + { + if ($this->activeTaskId === null) { + return null; + } + + foreach ($this->tasks as $task) { + if ($task['id'] === $this->activeTaskId) { + return $task; + } + } + + return null; + } +} diff --git a/app/Tui/SessionRegistry.php b/app/Tui/SessionRegistry.php new file mode 100644 index 0000000..ca0969e --- /dev/null +++ b/app/Tui/SessionRegistry.php @@ -0,0 +1,152 @@ +registryPath = $registryPath ?? $dir.'/sessions.json'; + } + + public function register(string $tasksPath, int $pid, string $mode, string $projectPath): void + { + $this->withLock(function (array &$data) use ($tasksPath, $pid, $mode, $projectPath) { + $data['sessions'] = array_values(array_filter( + $data['sessions'] ?? [], + fn (array $session) => $session['tasksPath'] !== $tasksPath + )); + + $data['sessions'][] = [ + 'tasksPath' => $tasksPath, + 'pid' => $pid, + 'startedAt' => date('c'), + 'mode' => $mode, + 'projectPath' => $projectPath, + ]; + }); + } + + public function deregister(string $tasksPath): void + { + $this->withLock(function (array &$data) use ($tasksPath) { + $data['sessions'] = array_values(array_filter( + $data['sessions'] ?? [], + fn (array $session) => $session['tasksPath'] !== $tasksPath + )); + }); + } + + /** + * @return array + */ + public function getActiveSessions(): array + { + $data = $this->readRegistry(); + + return array_values(array_filter( + $data['sessions'] ?? [], + fn (array $session) => $this->isProcessAlive((int) $session['pid']) + )); + } + + public function cleanup(): void + { + $this->withLock(function (array &$data) { + $data['sessions'] = array_values(array_filter( + $data['sessions'] ?? [], + fn (array $session) => $this->isProcessAlive((int) $session['pid']) + )); + }); + } + + public function getRegistryPath(): string + { + return $this->registryPath; + } + + /** + * @return array{sessions?: array} + */ + private function readRegistry(): array + { + if (! file_exists($this->registryPath)) { + return ['sessions' => []]; + } + + $contents = file_get_contents($this->registryPath); + if ($contents === false || $contents === '') { + return ['sessions' => []]; + } + + $data = json_decode($contents, true); + if (! is_array($data)) { + return ['sessions' => []]; + } + + return $data; + } + + /** + * @param callable(array&): void $callback + */ + private function withLock(callable $callback): void + { + $handle = fopen($this->registryPath, 'c+'); + if ($handle === false) { + return; + } + + try { + if (! flock($handle, LOCK_EX)) { + return; + } + + $contents = stream_get_contents($handle); + $data = ($contents !== false && $contents !== '') + ? (json_decode($contents, true) ?? ['sessions' => []]) + : ['sessions' => []]; + + if (! is_array($data)) { + $data = ['sessions' => []]; + } + + $callback($data); + + ftruncate($handle, 0); + rewind($handle); + fwrite($handle, (string) json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + fflush($handle); + flock($handle, LOCK_UN); + } finally { + fclose($handle); + } + } + + private function isProcessAlive(int $pid): bool + { + if ($pid <= 0) { + return false; + } + + if (! function_exists('posix_kill')) { + return file_exists("/proc/$pid"); + } + + return posix_kill($pid, 0); + } +} diff --git a/composer.json b/composer.json index dde54e5..77a18e9 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ ], "require": { "php": "^8.2", - "laravel-zero/phar-updater": "^1.4" + "laravel-zero/phar-updater": "^1.4", + "nunomaduro/termwind": "^2.4" }, "require-dev": { "larastan/larastan": "^3.8", diff --git a/composer.lock b/composer.lock index 4e1a542..be0ce2a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1dafc4097e16cd7d8abaf4737ad2e2ea", + "content-hash": "59d32778574ece01f62637fb13359023", "packages": [ { "name": "laravel-zero/phar-updater", @@ -66,60 +66,53 @@ "source": "https://github.com/laravel-zero/phar-updater/tree/v1.4.2" }, "time": "2025-04-07T12:28:11+00:00" - } - ], - "packages-dev": [ + }, { - "name": "brianium/paratest", - "version": "v7.8.5", + "name": "nunomaduro/termwind", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/paratestphp/paratest.git", - "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5" + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", - "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-simplexml": "*", - "fidry/cpu-core-counter": "^1.3.0", - "jean85/pretty-package-versions": "^2.1.1", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.5.46", - "sebastian/environment": "^7.2.1", - "symfony/console": "^6.4.22 || ^7.3.4 || ^8.0.3", - "symfony/process": "^6.4.20 || ^7.3.4 || ^8.0.3" + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" }, "require-dev": { - "doctrine/coding-standard": "^12.0.0", - "ext-pcov": "*", - "ext-posix": "*", - "phpstan/phpstan": "^2.1.33", - "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpstan/phpstan-phpunit": "^2.0.11", - "phpstan/phpstan-strict-rules": "^2.0.7", - "squizlabs/php_codesniffer": "^3.13.5", - "symfony/filesystem": "^6.4.13 || ^7.3.2 || ^8.0.1" + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, - "bin": [ - "bin/paratest", - "bin/paratest_for_phpstorm" - ], "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, "autoload": { + "files": [ + "src/Functions.php" + ], "psr-4": { - "ParaTest\\": [ - "src/" - ] + "Termwind\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -128,130 +121,144 @@ ], "authors": [ { - "name": "Brian Scaturro", - "email": "scaturrob@gmail.com", - "role": "Developer" - }, - { - "name": "Filippo Tessarotto", - "email": "zoeslam@gmail.com", - "role": "Developer" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "Parallel testing for PHP", - "homepage": "https://github.com/paratestphp/paratest", + "description": "It's like Tailwind CSS, but for the console.", "keywords": [ - "concurrent", - "parallel", - "phpunit", - "testing" + "cli", + "console", + "css", + "package", + "php", + "style" ], "support": { - "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.8.5" + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" }, "funding": [ { - "url": "https://github.com/sponsors/Slamdunk", + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", "type": "github" }, { - "url": "https://paypal.me/filippotessarotto", - "type": "paypal" + "url": "https://github.com/xiCO2k", + "type": "github" } ], - "time": "2026-01-08T08:02:38+00:00" + "time": "2026-02-16T23:10:27+00:00" }, { - "name": "brick/math", - "version": "0.14.7", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50", - "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^8.2" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpstan/phpstan": "2.1.22", - "phpunit/phpunit": "^11.5" + "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { - "Brick\\Math\\": "src/" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Arbitrary-precision arithmetic library", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "Arbitrary-precision", - "BigInteger", - "BigRational", - "arithmetic", - "bigdecimal", - "bignum", - "bignumber", - "brick", - "decimal", - "integer", - "math", - "mathematics", - "rational" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "support": { - "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.7" + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "funding": [ - { - "url": "https://github.com/BenMorel", - "type": "github" - } - ], - "time": "2026-02-07T10:57:35+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "carbonphp/carbon-doctrine-types", - "version": "3.2.0", + "name": "symfony/console", + "version": "v7.4.4", "source": { "type": "git", - "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + "url": "https://github.com/symfony/console.git", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { - "php": "^8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" }, "conflict": { - "doctrine/dbal": "<4.0.0 || >=5.0.0" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "doctrine/dbal": "^4.0.0", - "nesbot/carbon": "^2.71.0 || ^3.0.0", - "phpunit/phpunit": "^10.3" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" - } + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -259,114 +266,148 @@ ], "authors": [ { - "name": "KyleKatarn", - "email": "kylekatarnls@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Types to use Carbon in Doctrine", + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", "keywords": [ - "carbon", - "date", - "datetime", - "doctrine", - "time" + "cli", + "command-line", + "console", + "terminal" ], "support": { - "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { - "url": "https://github.com/kylekatarnls", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://opencollective.com/Carbon", - "type": "open_collective" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-02-09T16:56:22+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { - "name": "doctrine/deprecations", - "version": "1.1.6", + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", - "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "phpunit/phpunit": "<=7.5 || >=14" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^14", - "phpstan/phpstan": "1.4.10 || 2.1.30", - "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", - "psr/log": "^1 || ^2 || ^3" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + "php": ">=8.1" }, "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "src" + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, + "autoload": { + "files": [ + "function.php" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, - "time": "2026-02-07T07:09:04+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "doctrine/inflector", - "version": "2.1.0", + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", - "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=7.2" }, - "require-dev": { - "doctrine/coding-standard": "^12.0 || ^13.0", - "phpstan/phpstan": "^1.12 || ^2.0", - "phpstan/phpstan-phpunit": "^1.4 || ^2.0", - "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", - "phpunit/phpunit": "^8.5 || ^12.2" + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Doctrine\\Inflector\\": "src" + "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -375,94 +416,78 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", - "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", "keywords": [ - "inflection", - "inflector", - "lowercase", - "manipulation", - "php", - "plural", - "singular", - "strings", - "uppercase", - "words" + "compatibility", + "ctype", + "polyfill", + "portable" ], "support": { - "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.1.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-10T19:31:58+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "dragonmantank/cron-expression", - "version": "v3.6.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", - "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": "^8.2|^8.3|^8.4|^8.5" - }, - "replace": { - "mtdowling/cron-expression": "^1.0" + "php": ">=7.2" }, - "require-dev": { - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.32|^2.1.31", - "phpunit/phpunit": "^8.5.48|^9.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Cron\\": "src/Cron/" + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -471,61 +496,84 @@ ], "authors": [ { - "name": "Chris Tankersley", - "email": "chris@ctankersley.com", - "homepage": "https://github.com/dragonmantank" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", "keywords": [ - "cron", - "schedule" + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { - "url": "https://github.com/dragonmantank", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-10-31T18:51:33+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { - "name": "fidry/cpu-core-counter", - "version": "1.3.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=7.2" }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-deprecation-rules": "^2.0.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -533,63 +581,84 @@ ], "authors": [ { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Tiny utility to get the number of CPU cores.", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", "keywords": [ - "CPU", - "core" + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { - "url": "https://github.com/theofidry", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-08-14T07:29:31+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "filp/whoops", - "version": "2.18.4", + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", - "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^4.0 || ^5.0" + "provide": { + "ext-mbstring": "*" }, "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.7-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Whoops\\": "src/Whoops/" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -598,66 +667,85 @@ ], "authors": [ { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.4" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { - "url": "https://github.com/denis-sokolov", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-08-08T12:00:00+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { - "name": "fruitcake/php-cors", - "version": "v1.4.0", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/fruitcake/php-cors.git", - "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", - "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": "^8.1", - "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, - "require-dev": { - "phpstan/phpstan": "^2", - "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^4" + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "1.3-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { - "Fruitcake\\Cors\\": "src/" - } + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -665,63 +753,90 @@ ], "authors": [ { - "name": "Fruitcake", - "homepage": "https://fruitcake.nl" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Barryvdh", - "email": "barryvdh@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", - "homepage": "https://github.com/fruitcake/php-cors", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", "keywords": [ - "cors", - "laravel", - "symfony" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { - "url": "https://fruitcake.nl", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://github.com/barryvdh", + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-12-03T09:33:47+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { - "name": "graham-campbell/result-type", - "version": "v1.1.4", + "name": "symfony/string", + "version": "v7.4.4", "source": { "type": "git", - "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + "url": "https://github.com/symfony/string.git", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.5" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { - "GrahamCampbell\\ResultType\\": "src/" - } + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -729,86 +844,100 @@ ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "An Implementation Of The Result Type", - "keywords": [ - "Graham Campbell", - "GrahamCampbell", - "Result Type", - "Result-Type", - "result" + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" ], "support": { - "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-12-27T19:43:20+00:00" - }, + "time": "2026-01-12T10:54:30+00:00" + } + ], + "packages-dev": [ { - "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "name": "brianium/paratest", + "version": "v7.8.5", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "url": "https://github.com/paratestphp/paratest.git", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", "shasum": "" }, "require": { - "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.3.0", + "jean85/pretty-package-versions": "^2.1.1", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-timer": "^7.0.1", + "phpunit/phpunit": "^11.5.46", + "sebastian/environment": "^7.2.1", + "symfony/console": "^6.4.22 || ^7.3.4 || ^8.0.3", + "symfony/process": "^6.4.20 || ^7.3.4 || ^8.0.3" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", - "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^2.1.33", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.11", + "phpstan/phpstan-strict-rules": "^2.0.7", + "squizlabs/php_codesniffer": "^3.13.5", + "symfony/filesystem": "^6.4.13 || ^7.3.2 || ^8.0.1" }, + "bin": [ + "bin/paratest", + "bin/paratest_for_phpstorm" + ], "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { - "GuzzleHttp\\": "src/" + "ParaTest\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -817,198 +946,129 @@ ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" }, { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" } ], - "description": "Guzzle is a PHP HTTP client library", + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" + "concurrent", + "parallel", + "phpunit", + "testing" ], "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.8.5" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", + "url": "https://github.com/sponsors/Slamdunk", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2026-01-08T08:02:38+00:00" }, { - "name": "guzzlehttp/promises", - "version": "2.3.0", + "name": "brick/math", + "version": "0.14.7", "source": { "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" + "url": "https://github.com/brick/math.git", + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50", + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^8.2" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "autoload": { "psr-4": { - "GuzzleHttp\\Promise\\": "src/" + "Brick\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", + "description": "Arbitrary-precision arithmetic library", "keywords": [ - "promise" + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.14.7" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", + "url": "https://github.com/BenMorel", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" } ], - "time": "2025-08-22T14:34:08+00:00" + "time": "2026-02-07T10:57:35+00:00" }, { - "name": "guzzlehttp/psr7", - "version": "2.8.0", + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "php": "^8.1" }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" }, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "autoload": { "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1017,105 +1077,114 @@ ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" } ], - "description": "PSR-7 message implementation that also provides common utility methods", + "description": "Types to use Carbon in Doctrine", "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" + "carbon", + "date", + "datetime", + "doctrine", + "time" ], "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", + "url": "https://github.com/kylekatarnls", "type": "github" }, { - "url": "https://github.com/Nyholm", - "type": "github" + "url": "https://opencollective.com/Carbon", + "type": "open_collective" }, { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", "type": "tidelift" } ], - "time": "2025-08-23T21:21:41+00:00" + "time": "2024-02-09T16:56:22+00:00" }, { - "name": "guzzlehttp/uri-template", - "version": "v1.0.5", + "name": "doctrine/deprecations", + "version": "1.1.6", "source": { "type": "git", - "url": "https://github.com/guzzle/uri-template.git", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "symfony/polyfill-php80": "^1.24" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25", - "uri-template/tests": "1.0.0" + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" }, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", "autoload": { "psr-4": { - "GuzzleHttp\\UriTemplate\\": "src" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1124,124 +1193,156 @@ ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" }, { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "Roman Borschel", + "email": "roman@code-factory.org" }, { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" }, { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" } ], - "description": "A polyfill class for uri_template of PHP", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", "keywords": [ - "guzzlehttp", - "uri-template" + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" ], "support": { - "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", - "type": "github" + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" }, { - "url": "https://github.com/Nyholm", - "type": "github" + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", "type": "tidelift" } ], - "time": "2025-08-22T14:27:06+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { - "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", "shasum": "" }, "require": { - "php": "^7.4|^8.0" + "php": "^8.2|^8.3|^8.4|^8.5" }, "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" + "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "type": "library", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "hamcrest" - ] + "psr-4": { + "Cron\\": "src/Cron/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "This is the PHP port of Hamcrest Matchers", + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", "keywords": [ - "test" + "cron", + "schedule" ], "support": { - "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" }, - "time": "2025-04-30T06:54:44+00:00" + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2025-10-31T18:51:33+00:00" }, { - "name": "iamcal/sql-parser", - "version": "v0.7", + "name": "fidry/cpu-core-counter", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/iamcal/SQLParser.git", - "reference": "610392f38de49a44dab08dc1659960a29874c4b8" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/610392f38de49a44dab08dc1659960a29874c4b8", - "reference": "610392f38de49a44dab08dc1659960a29874c4b8", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, + "require": { + "php": "^7.2 || ^8.0" + }, "require-dev": { - "php-coveralls/php-coveralls": "^1.0", - "phpunit/phpunit": "^5|^6|^7|^8|^9" + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" }, "type": "library", "autoload": { "psr-4": { - "iamcal\\": "src" + "Fidry\\CpuCoreCounter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1250,50 +1351,63 @@ ], "authors": [ { - "name": "Cal Henderson", - "email": "cal@iamcal.com" + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" } ], - "description": "MySQL schema parser", + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], "support": { - "issues": "https://github.com/iamcal/SQLParser/issues", - "source": "https://github.com/iamcal/SQLParser/tree/v0.7" + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, - "time": "2026-01-28T22:20:33+00:00" + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" }, { - "name": "illuminate/bus", - "version": "v12.50.0", + "name": "filp/whoops", + "version": "2.18.4", "source": { "type": "git", - "url": "https://github.com/illuminate/bus.git", - "reference": "e2100faf26fce579a954699d927ca77e3b5c2ad7" + "url": "https://github.com/filp/whoops.git", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/bus/zipball/e2100faf26fce579a954699d927ca77e3b5c2ad7", - "reference": "e2100faf26fce579a954699d927ca77e3b5c2ad7", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", "shasum": "" }, "require": { - "illuminate/collections": "^12.0", - "illuminate/contracts": "^12.0", - "illuminate/pipeline": "^12.0", - "illuminate/support": "^12.0", - "php": "^8.2" + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { - "illuminate/queue": "Required to use closures when chaining jobs (^12.0)." + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "12.x-dev" + "dev-master": "2.7-dev" } }, "autoload": { "psr-4": { - "Illuminate\\Bus\\": "" + "Whoops\\": "src/Whoops/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1302,60 +1416,65 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" } ], - "description": "The Illuminate Bus package.", - "homepage": "https://laravel.com", + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.4" }, - "time": "2026-01-28T15:18:32+00:00" + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-08-08T12:00:00+00:00" }, { - "name": "illuminate/cache", - "version": "v12.50.0", + "name": "fruitcake/php-cors", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/illuminate/cache.git", - "reference": "b92e7a214db00b80eef1b2a920f4995acdb6279f" + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/cache/zipball/b92e7a214db00b80eef1b2a920f4995acdb6279f", - "reference": "b92e7a214db00b80eef1b2a920f4995acdb6279f", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", "shasum": "" }, "require": { - "illuminate/collections": "^12.0", - "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0", - "illuminate/support": "^12.0", - "php": "^8.2" - }, - "provide": { - "psr/simple-cache-implementation": "1.0|2.0|3.0" + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" }, - "suggest": { - "ext-apcu": "Required to use the APC cache driver.", - "ext-filter": "Required to use the DynamoDb cache driver.", - "ext-memcached": "Required to use the memcache cache driver.", - "illuminate/database": "Required to use the database cache driver (^12.0).", - "illuminate/filesystem": "Required to use the file cache driver (^12.0).", - "illuminate/redis": "Required to use the redis cache driver (^12.0).", - "symfony/cache": "Required to use PSR-6 cache bridge (^7.2)." + "require-dev": { + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "12.x-dev" + "dev-master": "1.3-dev" } }, "autoload": { "psr-4": { - "Illuminate\\Cache\\": "" + "Fruitcake\\Cors\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1364,58 +1483,62 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" } ], - "description": "The Illuminate Cache package.", - "homepage": "https://laravel.com", + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" }, - "time": "2026-02-04T15:20:21+00:00" + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" }, { - "name": "illuminate/collections", - "version": "v12.50.0", + "name": "graham-campbell/result-type", + "version": "v1.1.4", "source": { "type": "git", - "url": "https://github.com/illuminate/collections.git", - "reference": "b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38" + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38", - "reference": "b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { - "illuminate/conditionable": "^12.0", - "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0", - "php": "^8.2", - "symfony/polyfill-php83": "^1.33", - "symfony/polyfill-php84": "^1.33", - "symfony/polyfill-php85": "^1.33" + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" }, - "suggest": { - "illuminate/http": "Required to convert collections to API resources (^12.0).", - "symfony/var-dumper": "Required to use the dump method (^7.2)." + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "12.x-dev" - } - }, "autoload": { - "files": [ - "functions.php", - "helpers.php" - ], "psr-4": { - "Illuminate\\Support\\": "" + "GrahamCampbell\\ResultType\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1424,44 +1547,86 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], - "description": "The Illuminate Collections package.", - "homepage": "https://laravel.com", + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" }, - "time": "2026-02-01T16:38:26+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" }, { - "name": "illuminate/conditionable", - "version": "v12.50.0", + "name": "guzzlehttp/guzzle", + "version": "7.10.0", "source": { "type": "git", - "url": "https://github.com/illuminate/conditionable.git", - "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/conditionable/zipball/ec677967c1f2faf90b8428919124d2184a4c9b49", - "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { - "php": "^8.2" + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "12.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Illuminate\\Support\\": "" + "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1470,46 +1635,104 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "The Illuminate Conditionable package.", - "homepage": "https://laravel.com", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, - "time": "2025-05-13T15:08:45+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" }, { - "name": "illuminate/config", - "version": "v12.50.0", + "name": "guzzlehttp/promises", + "version": "2.3.0", "source": { "type": "git", - "url": "https://github.com/illuminate/config.git", - "reference": "7adbf5cc27081d4613e1fa2f4f53e2a4fc91ad53" + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/config/zipball/7adbf5cc27081d4613e1fa2f4f53e2a4fc91ad53", - "reference": "7adbf5cc27081d4613e1fa2f4f53e2a4fc91ad53", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { - "illuminate/collections": "^12.0", - "illuminate/contracts": "^12.0", - "php": "^8.2" + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "12.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "Illuminate\\Config\\": "" + "GuzzleHttp\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1518,64 +1741,92 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "The Illuminate Config package.", - "homepage": "https://laravel.com", + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, - "time": "2025-10-26T17:11:19+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" }, { - "name": "illuminate/console", - "version": "v12.50.0", + "name": "guzzlehttp/psr7", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/illuminate/console.git", - "reference": "fa1b3774e951bd7eb102bf5099a03a0a4ea35468" + "url": "https://github.com/guzzle/psr7.git", + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/console/zipball/fa1b3774e951bd7eb102bf5099a03a0a4ea35468", - "reference": "fa1b3774e951bd7eb102bf5099a03a0a4ea35468", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { - "ext-mbstring": "*", - "illuminate/collections": "^12.0", - "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0", - "illuminate/support": "^12.0", - "illuminate/view": "^12.0", - "laravel/prompts": "^0.3.0", - "nunomaduro/termwind": "^2.0", - "php": "^8.2", - "symfony/console": "^7.2.0", - "symfony/polyfill-php83": "^1.33", - "symfony/process": "^7.2.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { - "dragonmantank/cron-expression": "Required to use scheduler (^3.3.2).", - "ext-pcntl": "Required to use signal trapping.", - "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.8).", - "illuminate/bus": "Required to use the scheduled job dispatcher (^12.0).", - "illuminate/container": "Required to use the scheduler (^12.0).", - "illuminate/filesystem": "Required to use the generator command (^12.0).", - "illuminate/queue": "Required to use closures for scheduled jobs (^12.0)." + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "12.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "Illuminate\\Console\\": "" + "GuzzleHttp\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1584,60 +1835,105 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], - "description": "The Illuminate Console package.", - "homepage": "https://laravel.com", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, - "time": "2026-02-01T02:54:43+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-08-23T21:21:41+00:00" }, { - "name": "illuminate/container", - "version": "v12.50.0", + "name": "guzzlehttp/uri-template", + "version": "v1.0.5", "source": { "type": "git", - "url": "https://github.com/illuminate/container.git", - "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f" + "url": "https://github.com/guzzle/uri-template.git", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/a605f25d0e014b6e521bcb142a4eba578966a24f", - "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f", - "shasum": "" - }, - "require": { - "illuminate/contracts": "^12.0", - "illuminate/reflection": "^12.0", - "php": "^8.2", - "psr/container": "^1.1.1|^2.0.1", - "symfony/polyfill-php84": "^1.33", - "symfony/polyfill-php85": "^1.33" + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "shasum": "" }, - "provide": { - "psr/container-implementation": "1.1|2.0" + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" }, - "suggest": { - "illuminate/auth": "Required to use the Auth attribute", - "illuminate/cache": "Required to use the Cache attribute", - "illuminate/config": "Required to use the Config attribute", - "illuminate/database": "Required to use the DB attribute", - "illuminate/filesystem": "Required to use the Storage attribute", - "illuminate/log": "Required to use the Log or Context attributes" + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "uri-template/tests": "1.0.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "12.x-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "Illuminate\\Container\\": "" + "GuzzleHttp\\UriTemplate\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1646,112 +1942,124 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" } ], - "description": "The Illuminate Container package.", - "homepage": "https://laravel.com", + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" }, - "time": "2026-01-29T03:13:58+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:27:06+00:00" }, { - "name": "illuminate/contracts", - "version": "v12.50.0", + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", "source": { "type": "git", - "url": "https://github.com/illuminate/contracts.git", - "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a" + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/3d4eeab332c04a9eaea90968c19a66f78745e47a", - "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^8.2", - "psr/container": "^1.1.1|^2.0.1", - "psr/simple-cache": "^1.0|^2.0|^3.0" + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "12.x-dev" + "dev-master": "2.1-dev" } }, "autoload": { - "psr-4": { - "Illuminate\\Contracts\\": "" - } + "classmap": [ + "hamcrest" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" ], - "description": "The Illuminate Contracts package.", - "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2026-01-28T15:26:27+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { - "name": "illuminate/database", - "version": "v12.50.0", + "name": "iamcal/sql-parser", + "version": "v0.7", "source": { "type": "git", - "url": "https://github.com/illuminate/database.git", - "reference": "a20d2278cca2b9052381fbfab301d9bc7b5c47d1" + "url": "https://github.com/iamcal/SQLParser.git", + "reference": "610392f38de49a44dab08dc1659960a29874c4b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/a20d2278cca2b9052381fbfab301d9bc7b5c47d1", - "reference": "a20d2278cca2b9052381fbfab301d9bc7b5c47d1", + "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/610392f38de49a44dab08dc1659960a29874c4b8", + "reference": "610392f38de49a44dab08dc1659960a29874c4b8", "shasum": "" }, - "require": { - "brick/math": "^0.11|^0.12|^0.13|^0.14", - "ext-pdo": "*", - "illuminate/collections": "^12.0", - "illuminate/container": "^12.0", - "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0", - "illuminate/support": "^12.0", - "laravel/serializable-closure": "^1.3|^2.0", - "php": "^8.2", - "symfony/polyfill-php83": "^1.33", - "symfony/polyfill-php85": "^1.33" - }, - "suggest": { - "ext-filter": "Required to use the Postgres database driver.", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.24).", - "illuminate/console": "Required to use the database commands (^12.0).", - "illuminate/events": "Required to use the observers with Eloquent (^12.0).", - "illuminate/filesystem": "Required to use the migrations (^12.0).", - "illuminate/http": "Required to convert Eloquent models to API resources (^12.0).", - "illuminate/pagination": "Required to paginate the result set (^12.0).", - "symfony/finder": "Required to use Eloquent model factories (^7.2)." + "require-dev": { + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^5|^6|^7|^8|^9" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "12.x-dev" - } - }, "autoload": { "psr-4": { - "Illuminate\\Database\\": "" + "iamcal\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1760,47 +2068,41 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Cal Henderson", + "email": "cal@iamcal.com" } ], - "description": "The Illuminate Database package.", - "homepage": "https://laravel.com", - "keywords": [ - "database", - "laravel", - "orm", - "sql" - ], + "description": "MySQL schema parser", "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" + "issues": "https://github.com/iamcal/SQLParser/issues", + "source": "https://github.com/iamcal/SQLParser/tree/v0.7" }, - "time": "2026-02-04T15:21:01+00:00" + "time": "2026-01-28T22:20:33+00:00" }, { - "name": "illuminate/events", + "name": "illuminate/bus", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/events.git", - "reference": "8666a48c949109581c9deb5a1503098959c2acef" + "url": "https://github.com/illuminate/bus.git", + "reference": "e2100faf26fce579a954699d927ca77e3b5c2ad7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/events/zipball/8666a48c949109581c9deb5a1503098959c2acef", - "reference": "8666a48c949109581c9deb5a1503098959c2acef", + "url": "https://api.github.com/repos/illuminate/bus/zipball/e2100faf26fce579a954699d927ca77e3b5c2ad7", + "reference": "e2100faf26fce579a954699d927ca77e3b5c2ad7", "shasum": "" }, "require": { - "illuminate/bus": "^12.0", "illuminate/collections": "^12.0", - "illuminate/container": "^12.0", "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0", + "illuminate/pipeline": "^12.0", "illuminate/support": "^12.0", "php": "^8.2" }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^12.0)." + }, "type": "library", "extra": { "branch-alias": { @@ -1808,11 +2110,8 @@ } }, "autoload": { - "files": [ - "functions.php" - ], "psr-4": { - "Illuminate\\Events\\": "" + "Illuminate\\Bus\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -1825,7 +2124,7 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Events package.", + "description": "The Illuminate Bus package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", @@ -1834,17 +2133,17 @@ "time": "2026-01-28T15:18:32+00:00" }, { - "name": "illuminate/filesystem", + "name": "illuminate/cache", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/filesystem.git", - "reference": "8cad07cf6004a7cd0a876c6ad2136a1511c2bb58" + "url": "https://github.com/illuminate/cache.git", + "reference": "b92e7a214db00b80eef1b2a920f4995acdb6279f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/8cad07cf6004a7cd0a876c6ad2136a1511c2bb58", - "reference": "8cad07cf6004a7cd0a876c6ad2136a1511c2bb58", + "url": "https://api.github.com/repos/illuminate/cache/zipball/b92e7a214db00b80eef1b2a920f4995acdb6279f", + "reference": "b92e7a214db00b80eef1b2a920f4995acdb6279f", "shasum": "" }, "require": { @@ -1852,21 +2151,19 @@ "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", "illuminate/support": "^12.0", - "php": "^8.2", - "symfony/finder": "^7.2.0" + "php": "^8.2" + }, + "provide": { + "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "suggest": { - "ext-fileinfo": "Required to use the Filesystem class.", - "ext-ftp": "Required to use the Flysystem FTP driver.", - "ext-hash": "Required to use the Filesystem class.", - "illuminate/http": "Required for handling uploaded files (^12.0).", - "league/flysystem": "Required to use the Flysystem local driver (^3.25.1).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", - "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", - "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", - "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", - "symfony/mime": "Required to enable support for guessing extensions (^7.2)." + "ext-apcu": "Required to use the APC cache driver.", + "ext-filter": "Required to use the DynamoDb cache driver.", + "ext-memcached": "Required to use the memcache cache driver.", + "illuminate/database": "Required to use the database cache driver (^12.0).", + "illuminate/filesystem": "Required to use the file cache driver (^12.0).", + "illuminate/redis": "Required to use the redis cache driver (^12.0).", + "symfony/cache": "Required to use PSR-6 cache bridge (^7.2)." }, "type": "library", "extra": { @@ -1875,11 +2172,8 @@ } }, "autoload": { - "files": [ - "functions.php" - ], "psr-4": { - "Illuminate\\Filesystem\\": "" + "Illuminate\\Cache\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -1892,46 +2186,40 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Filesystem package.", + "description": "The Illuminate Cache package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-19T15:15:34+00:00" + "time": "2026-02-04T15:20:21+00:00" }, { - "name": "illuminate/http", + "name": "illuminate/collections", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/http.git", - "reference": "03a49ac6a09bdb5c0420758dec50216abbffcb70" + "url": "https://github.com/illuminate/collections.git", + "reference": "b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/http/zipball/03a49ac6a09bdb5c0420758dec50216abbffcb70", - "reference": "03a49ac6a09bdb5c0420758dec50216abbffcb70", + "url": "https://api.github.com/repos/illuminate/collections/zipball/b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38", + "reference": "b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38", "shasum": "" }, "require": { - "ext-filter": "*", - "fruitcake/php-cors": "^1.3", - "guzzlehttp/guzzle": "^7.8.2", - "guzzlehttp/uri-template": "^1.0", - "illuminate/collections": "^12.0", + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", - "illuminate/session": "^12.0", - "illuminate/support": "^12.0", "php": "^8.2", - "symfony/http-foundation": "^7.2.0", - "symfony/http-kernel": "^7.2.0", - "symfony/mime": "^7.2.0", "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", "symfony/polyfill-php85": "^1.33" }, "suggest": { - "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()." + "illuminate/http": "Required to convert collections to API resources (^12.0).", + "symfony/var-dumper": "Required to use the dump method (^7.2)." }, "type": "library", "extra": { @@ -1940,8 +2228,12 @@ } }, "autoload": { + "files": [ + "functions.php", + "helpers.php" + ], "psr-4": { - "Illuminate\\Http\\": "" + "Illuminate\\Support\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -1954,26 +2246,26 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Http package.", + "description": "The Illuminate Collections package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-04T15:08:54+00:00" + "time": "2026-02-01T16:38:26+00:00" }, { - "name": "illuminate/macroable", + "name": "illuminate/conditionable", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/macroable.git", - "reference": "e862e5648ee34004fa56046b746f490dfa86c613" + "url": "https://github.com/illuminate/conditionable.git", + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/macroable/zipball/e862e5648ee34004fa56046b746f490dfa86c613", - "reference": "e862e5648ee34004fa56046b746f490dfa86c613", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/ec677967c1f2faf90b8428919124d2184a4c9b49", + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49", "shasum": "" }, "require": { @@ -2000,37 +2292,33 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Macroable package.", + "description": "The Illuminate Conditionable package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-07-23T16:31:01+00:00" + "time": "2025-05-13T15:08:45+00:00" }, { - "name": "illuminate/pipeline", + "name": "illuminate/config", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/pipeline.git", - "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee" + "url": "https://github.com/illuminate/config.git", + "reference": "7adbf5cc27081d4613e1fa2f4f53e2a4fc91ad53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/pipeline/zipball/b6a14c20d69a44bf0a6fba664a00d23ca71770ee", - "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee", + "url": "https://api.github.com/repos/illuminate/config/zipball/7adbf5cc27081d4613e1fa2f4f53e2a4fc91ad53", + "reference": "7adbf5cc27081d4613e1fa2f4f53e2a4fc91ad53", "shasum": "" }, "require": { + "illuminate/collections": "^12.0", "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0", - "illuminate/support": "^12.0", "php": "^8.2" }, - "suggest": { - "illuminate/database": "Required to use database transactions (^12.0)." - }, "type": "library", "extra": { "branch-alias": { @@ -2039,7 +2327,7 @@ }, "autoload": { "psr-4": { - "Illuminate\\Pipeline\\": "" + "Illuminate\\Config\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2052,36 +2340,51 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Pipeline package.", + "description": "The Illuminate Config package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-08-20T13:36:50+00:00" + "time": "2025-10-26T17:11:19+00:00" }, { - "name": "illuminate/process", + "name": "illuminate/console", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/process.git", - "reference": "b3c34ed7d090fc995addf8c928b700bf34a93956" + "url": "https://github.com/illuminate/console.git", + "reference": "fa1b3774e951bd7eb102bf5099a03a0a4ea35468" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/process/zipball/b3c34ed7d090fc995addf8c928b700bf34a93956", - "reference": "b3c34ed7d090fc995addf8c928b700bf34a93956", + "url": "https://api.github.com/repos/illuminate/console/zipball/fa1b3774e951bd7eb102bf5099a03a0a4ea35468", + "reference": "fa1b3774e951bd7eb102bf5099a03a0a4ea35468", "shasum": "" }, "require": { + "ext-mbstring": "*", "illuminate/collections": "^12.0", "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", "illuminate/support": "^12.0", + "illuminate/view": "^12.0", + "laravel/prompts": "^0.3.0", + "nunomaduro/termwind": "^2.0", "php": "^8.2", + "symfony/console": "^7.2.0", + "symfony/polyfill-php83": "^1.33", "symfony/process": "^7.2.0" }, + "suggest": { + "dragonmantank/cron-expression": "Required to use scheduler (^3.3.2).", + "ext-pcntl": "Required to use signal trapping.", + "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^7.8).", + "illuminate/bus": "Required to use the scheduled job dispatcher (^12.0).", + "illuminate/container": "Required to use the scheduler (^12.0).", + "illuminate/filesystem": "Required to use the generator command (^12.0).", + "illuminate/queue": "Required to use closures for scheduled jobs (^12.0)." + }, "type": "library", "extra": { "branch-alias": { @@ -2090,7 +2393,7 @@ }, "autoload": { "psr-4": { - "Illuminate\\Process\\": "" + "Illuminate\\Console\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2103,32 +2406,46 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Process package.", + "description": "The Illuminate Console package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-28T00:44:54+00:00" + "time": "2026-02-01T02:54:43+00:00" }, { - "name": "illuminate/reflection", + "name": "illuminate/container", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/reflection.git", - "reference": "6188e97a587371b9951c2a7e337cd760308c17d7" + "url": "https://github.com/illuminate/container.git", + "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/reflection/zipball/6188e97a587371b9951c2a7e337cd760308c17d7", - "reference": "6188e97a587371b9951c2a7e337cd760308c17d7", + "url": "https://api.github.com/repos/illuminate/container/zipball/a605f25d0e014b6e521bcb142a4eba578966a24f", + "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f", "shasum": "" }, "require": { - "illuminate/collections": "^12.0", "illuminate/contracts": "^12.0", - "php": "^8.2" + "illuminate/reflection": "^12.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "suggest": { + "illuminate/auth": "Required to use the Auth attribute", + "illuminate/cache": "Required to use the Cache attribute", + "illuminate/config": "Required to use the Config attribute", + "illuminate/database": "Required to use the DB attribute", + "illuminate/filesystem": "Required to use the Storage attribute", + "illuminate/log": "Required to use the Log or Context attributes" }, "type": "library", "extra": { @@ -2137,11 +2454,8 @@ } }, "autoload": { - "files": [ - "helpers.php" - ], "psr-4": { - "Illuminate\\Support\\": "" + "Illuminate\\Container\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2154,41 +2468,32 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Reflection package.", + "description": "The Illuminate Container package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-04T15:21:22+00:00" + "time": "2026-01-29T03:13:58+00:00" }, { - "name": "illuminate/session", + "name": "illuminate/contracts", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/session.git", - "reference": "ddd0fce2f49889eede824db81c283b4e82aa77d0" + "url": "https://github.com/illuminate/contracts.git", + "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/session/zipball/ddd0fce2f49889eede824db81c283b4e82aa77d0", - "reference": "ddd0fce2f49889eede824db81c283b4e82aa77d0", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/3d4eeab332c04a9eaea90968c19a66f78745e47a", + "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-session": "*", - "illuminate/collections": "^12.0", - "illuminate/contracts": "^12.0", - "illuminate/filesystem": "^12.0", - "illuminate/support": "^12.0", "php": "^8.2", - "symfony/finder": "^7.2.0", - "symfony/http-foundation": "^7.2.0" - }, - "suggest": { - "illuminate/console": "Required to use the session:table command (^12.0)." + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" }, "type": "library", "extra": { @@ -2198,7 +2503,7 @@ }, "autoload": { "psr-4": { - "Illuminate\\Session\\": "" + "Illuminate\\Contracts\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2211,60 +2516,50 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Session package.", + "description": "The Illuminate Contracts package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-22T17:27:05+00:00" + "time": "2026-01-28T15:26:27+00:00" }, { - "name": "illuminate/support", + "name": "illuminate/database", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/support.git", - "reference": "411a11401406e7d542aa67a4b400feed6bedef0c" + "url": "https://github.com/illuminate/database.git", + "reference": "a20d2278cca2b9052381fbfab301d9bc7b5c47d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/411a11401406e7d542aa67a4b400feed6bedef0c", - "reference": "411a11401406e7d542aa67a4b400feed6bedef0c", + "url": "https://api.github.com/repos/illuminate/database/zipball/a20d2278cca2b9052381fbfab301d9bc7b5c47d1", + "reference": "a20d2278cca2b9052381fbfab301d9bc7b5c47d1", "shasum": "" }, "require": { - "doctrine/inflector": "^2.0", - "ext-ctype": "*", - "ext-filter": "*", - "ext-mbstring": "*", + "brick/math": "^0.11|^0.12|^0.13|^0.14", + "ext-pdo": "*", "illuminate/collections": "^12.0", - "illuminate/conditionable": "^12.0", + "illuminate/container": "^12.0", "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", - "illuminate/reflection": "^12.0", - "nesbot/carbon": "^3.8.4", + "illuminate/support": "^12.0", + "laravel/serializable-closure": "^1.3|^2.0", "php": "^8.2", "symfony/polyfill-php83": "^1.33", - "symfony/polyfill-php85": "^1.33", - "voku/portable-ascii": "^2.0.2" - }, - "conflict": { - "tightenco/collect": "<5.5.33" - }, - "replace": { - "spatie/once": "*" + "symfony/polyfill-php85": "^1.33" }, "suggest": { - "illuminate/filesystem": "Required to use the Composer class (^12.0).", - "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", - "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).", - "league/uri": "Required to use the Uri class (^7.5.1).", - "ramsey/uuid": "Required to use Str::uuid() (^4.7).", - "symfony/process": "Required to use the Composer class (^7.2).", - "symfony/uid": "Required to use Str::ulid() (^7.2).", - "symfony/var-dumper": "Required to use the dd function (^7.2).", - "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)." + "ext-filter": "Required to use the Postgres database driver.", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.24).", + "illuminate/console": "Required to use the database commands (^12.0).", + "illuminate/events": "Required to use the observers with Eloquent (^12.0).", + "illuminate/filesystem": "Required to use the migrations (^12.0).", + "illuminate/http": "Required to convert Eloquent models to API resources (^12.0).", + "illuminate/pagination": "Required to paginate the result set (^12.0).", + "symfony/finder": "Required to use Eloquent model factories (^7.2)." }, "type": "library", "extra": { @@ -2273,12 +2568,8 @@ } }, "autoload": { - "files": [ - "functions.php", - "helpers.php" - ], "psr-4": { - "Illuminate\\Support\\": "" + "Illuminate\\Database\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2291,44 +2582,42 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Support package.", + "description": "The Illuminate Database package.", "homepage": "https://laravel.com", + "keywords": [ + "database", + "laravel", + "orm", + "sql" + ], "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-04T15:14:59+00:00" + "time": "2026-02-04T15:21:01+00:00" }, { - "name": "illuminate/testing", + "name": "illuminate/events", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/testing.git", - "reference": "00a464a3319d8e54ff0075ca801bb08dd121a2e5" + "url": "https://github.com/illuminate/events.git", + "reference": "8666a48c949109581c9deb5a1503098959c2acef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/testing/zipball/00a464a3319d8e54ff0075ca801bb08dd121a2e5", - "reference": "00a464a3319d8e54ff0075ca801bb08dd121a2e5", + "url": "https://api.github.com/repos/illuminate/events/zipball/8666a48c949109581c9deb5a1503098959c2acef", + "reference": "8666a48c949109581c9deb5a1503098959c2acef", "shasum": "" }, "require": { - "ext-mbstring": "*", + "illuminate/bus": "^12.0", "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0", - "illuminate/support": "^12.0", - "php": "^8.2", - "symfony/polyfill-php83": "^1.33" - }, - "suggest": { - "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", - "illuminate/console": "Required to assert console commands (^12.0).", - "illuminate/database": "Required to assert databases (^12.0).", - "illuminate/http": "Required to assert responses (^12.0).", - "mockery/mockery": "Required to use mocking (^1.6).", - "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1)." + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" }, "type": "library", "extra": { @@ -2337,8 +2626,11 @@ } }, "autoload": { + "files": [ + "functions.php" + ], "psr-4": { - "Illuminate\\Testing\\": "" + "Illuminate\\Events\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2351,38 +2643,48 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate Testing package.", + "description": "The Illuminate Events package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-20T17:18:54+00:00" + "time": "2026-01-28T15:18:32+00:00" }, { - "name": "illuminate/view", + "name": "illuminate/filesystem", "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/view.git", - "reference": "5b3d7ae07665b98cca46846074edc008ed5e3864" + "url": "https://github.com/illuminate/filesystem.git", + "reference": "8cad07cf6004a7cd0a876c6ad2136a1511c2bb58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/view/zipball/5b3d7ae07665b98cca46846074edc008ed5e3864", - "reference": "5b3d7ae07665b98cca46846074edc008ed5e3864", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/8cad07cf6004a7cd0a876c6ad2136a1511c2bb58", + "reference": "8cad07cf6004a7cd0a876c6ad2136a1511c2bb58", "shasum": "" }, "require": { - "ext-tokenizer": "*", "illuminate/collections": "^12.0", - "illuminate/container": "^12.0", "illuminate/contracts": "^12.0", - "illuminate/events": "^12.0", - "illuminate/filesystem": "^12.0", "illuminate/macroable": "^12.0", "illuminate/support": "^12.0", - "php": "^8.2" + "php": "^8.2", + "symfony/finder": "^7.2.0" + }, + "suggest": { + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-hash": "Required to use the Filesystem class.", + "illuminate/http": "Required for handling uploaded files (^12.0).", + "league/flysystem": "Required to use the Flysystem local driver (^3.25.1).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/mime": "Required to enable support for guessing extensions (^7.2)." }, "type": "library", "extra": { @@ -2391,8 +2693,11 @@ } }, "autoload": { + "files": [ + "functions.php" + ], "psr-4": { - "Illuminate\\View\\": "" + "Illuminate\\Filesystem\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2405,49 +2710,56 @@ "email": "taylor@laravel.com" } ], - "description": "The Illuminate View package.", + "description": "The Illuminate Filesystem package.", "homepage": "https://laravel.com", "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-20T15:07:29+00:00" + "time": "2026-01-19T15:15:34+00:00" }, { - "name": "jean85/pretty-package-versions", - "version": "2.1.1", + "name": "illuminate/http", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + "url": "https://github.com/illuminate/http.git", + "reference": "03a49ac6a09bdb5c0420758dec50216abbffcb70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "url": "https://api.github.com/repos/illuminate/http/zipball/03a49ac6a09bdb5c0420758dec50216abbffcb70", + "reference": "03a49ac6a09bdb5c0420758dec50216abbffcb70", "shasum": "" }, "require": { - "composer-runtime-api": "^2.1.0", - "php": "^7.4|^8.0" + "ext-filter": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/uri-template": "^1.0", + "illuminate/collections": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/session": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php85": "^1.33" }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^7.5|^8.5|^9.6", - "rector/rector": "^2.0", - "vimeo/psalm": "^4.3 || ^5.0" + "suggest": { + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { "psr-4": { - "Jean85\\": "src/" + "Illuminate\\Http\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2456,58 +2768,44 @@ ], "authors": [ { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" } ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], + "description": "The Illuminate Http package.", + "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "time": "2025-03-19T14:43:43+00:00" + "time": "2026-02-04T15:08:54+00:00" }, { - "name": "jolicode/jolinotif", - "version": "v2.7.3", + "name": "illuminate/macroable", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/jolicode/JoliNotif.git", - "reference": "3c3e1c410b107dd2603b732508fd95830f0e0196" + "url": "https://github.com/illuminate/macroable.git", + "reference": "e862e5648ee34004fa56046b746f490dfa86c613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jolicode/JoliNotif/zipball/3c3e1c410b107dd2603b732508fd95830f0e0196", - "reference": "3c3e1c410b107dd2603b732508fd95830f0e0196", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e862e5648ee34004fa56046b746f490dfa86c613", + "reference": "e862e5648ee34004fa56046b746f490dfa86c613", "shasum": "" }, "require": { - "jolicode/php-os-helper": "^0.1.0", - "php": ">=8.1", - "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/deprecation-contracts": "^3", - "symfony/process": "^5.4 || ^6.0 || ^7.0" - }, - "require-dev": { - "symfony/finder": "^5.4 || ^6.0 || ^7.0", - "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0" - }, - "suggest": { - "ext-ffi": "Needed to send notifications via libnotify on Linux" + "php": "^8.2" }, - "bin": [ - "jolinotif" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, "autoload": { "psr-4": { - "Joli\\JoliNotif\\": "src/" + "Illuminate\\Support\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2516,54 +2814,50 @@ ], "authors": [ { - "name": "Loïck Piera", - "email": "pyrech@gmail.com" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" } ], - "description": "Send desktop notifications on Windows, Linux, MacOS.", - "keywords": [ - "MAC", - "growl", - "linux", - "notification", - "windows" - ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/jolicode/JoliNotif/issues", - "source": "https://github.com/jolicode/JoliNotif/tree/v2.7.3" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/jolicode/jolinotif", - "type": "tidelift" - } - ], - "time": "2024-09-30T13:34:54+00:00" + "time": "2024-07-23T16:31:01+00:00" }, { - "name": "jolicode/php-os-helper", - "version": "v0.1.0", + "name": "illuminate/pipeline", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/jolicode/php-os-helper.git", - "reference": "1622ad8bbcab98e62b5c041397e8519f10d90e29" + "url": "https://github.com/illuminate/pipeline.git", + "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jolicode/php-os-helper/zipball/1622ad8bbcab98e62b5c041397e8519f10d90e29", - "reference": "1622ad8bbcab98e62b5c041397e8519f10d90e29", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/b6a14c20d69a44bf0a6fba664a00d23ca71770ee", + "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee", "shasum": "" }, "require": { - "php": ">=8.1" + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" }, - "require-dev": { - "symfony/phpunit-bridge": "^6.3.1" + "suggest": { + "illuminate/database": "Required to use database transactions (^12.0)." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, "autoload": { "psr-4": { - "JoliCode\\PhpOsHelper\\": "src/" + "Illuminate\\Pipeline\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2572,79 +2866,49 @@ ], "authors": [ { - "name": "Loïck Piera", - "email": "pyrech@gmail.com" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" } ], - "description": "Helpers to detect the OS of the machine where PHP is running.", - "keywords": [ - "linux", - "os", - "osx", - "php", - "windows" - ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/jolicode/php-os-helper/issues", - "source": "https://github.com/jolicode/php-os-helper/tree/v0.1.0" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "time": "2023-12-03T12:46:03+00:00" + "time": "2025-08-20T13:36:50+00:00" }, { - "name": "larastan/larastan", - "version": "v3.9.2", - "source": { - "type": "git", - "url": "https://github.com/larastan/larastan.git", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", - "shasum": "" - }, - "require": { - "ext-json": "*", - "iamcal/sql-parser": "^0.7.0", - "illuminate/console": "^11.44.2 || ^12.4.1", - "illuminate/container": "^11.44.2 || ^12.4.1", - "illuminate/contracts": "^11.44.2 || ^12.4.1", - "illuminate/database": "^11.44.2 || ^12.4.1", - "illuminate/http": "^11.44.2 || ^12.4.1", - "illuminate/pipeline": "^11.44.2 || ^12.4.1", - "illuminate/support": "^11.44.2 || ^12.4.1", - "php": "^8.2", - "phpstan/phpstan": "^2.1.32" - }, - "require-dev": { - "doctrine/coding-standard": "^13", - "laravel/framework": "^11.44.2 || ^12.7.2", - "mockery/mockery": "^1.6.12", - "nikic/php-parser": "^5.4", - "orchestra/canvas": "^v9.2.2 || ^10.0.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", - "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15" + "name": "illuminate/process", + "version": "v12.50.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/process.git", + "reference": "b3c34ed7d090fc995addf8c928b700bf34a93956" }, - "suggest": { - "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", - "phpmyadmin/sql-parser": "Install to enable Larastan's optional phpMyAdmin-based SQL parser automatically" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/process/zipball/b3c34ed7d090fc995addf8c928b700bf34a93956", + "reference": "b3c34ed7d090fc995addf8c928b700bf34a93956", + "shasum": "" }, - "type": "phpstan-extension", + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2", + "symfony/process": "^7.2.0" + }, + "type": "library", "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "12.x-dev" } }, "autoload": { "psr-4": { - "Larastan\\Larastan\\": "src/" + "Illuminate\\Process\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2653,156 +2917,96 @@ ], "authors": [ { - "name": "Can Vural", - "email": "can9119@gmail.com" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" } ], - "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel", - "keywords": [ - "PHPStan", - "code analyse", - "code analysis", - "larastan", - "laravel", - "package", - "php", - "static analysis" - ], + "description": "The Illuminate Process package.", + "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.2" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "funding": [ - { - "url": "https://github.com/canvural", - "type": "github" - } - ], - "time": "2026-01-30T15:16:32+00:00" + "time": "2025-12-28T00:44:54+00:00" }, { - "name": "laravel-zero/foundation", - "version": "v12.48.1", + "name": "illuminate/reflection", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/laravel-zero/foundation.git", - "reference": "93b553b994ea63a1a69f396dc02add38012d1b5d" + "url": "https://github.com/illuminate/reflection.git", + "reference": "6188e97a587371b9951c2a7e337cd760308c17d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel-zero/foundation/zipball/93b553b994ea63a1a69f396dc02add38012d1b5d", - "reference": "93b553b994ea63a1a69f396dc02add38012d1b5d", + "url": "https://api.github.com/repos/illuminate/reflection/zipball/6188e97a587371b9951c2a7e337cd760308c17d7", + "reference": "6188e97a587371b9951c2a7e337cd760308c17d7", "shasum": "" }, "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", "php": "^8.2" }, - "require-dev": { - "laravel/framework": "^12.40" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "12.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { "files": [ - "src/Illuminate/Foundation/helpers.php" + "helpers.php" ], "psr-4": { - "Illuminate\\": "src/Illuminate/" + "Illuminate\\Support\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "This is a mirror from illuminate/foundation.", - "keywords": [ - "framework", - "laravel" - ], - "support": { - "source": "https://github.com/laravel-zero/foundation/tree/v12.48.1" - }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, + "authors": [ { - "url": "https://github.com/nunomaduro", - "type": "github" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" } ], - "time": "2026-01-26T09:29:33+00:00" + "description": "The Illuminate Reflection package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-02-04T15:21:22+00:00" }, { - "name": "laravel-zero/framework", - "version": "v12.0.5", + "name": "illuminate/session", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/laravel-zero/framework.git", - "reference": "206d53048c1b58b1577ab491390d704768532a01" + "url": "https://github.com/illuminate/session.git", + "reference": "ddd0fce2f49889eede824db81c283b4e82aa77d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel-zero/framework/zipball/206d53048c1b58b1577ab491390d704768532a01", - "reference": "206d53048c1b58b1577ab491390d704768532a01", + "url": "https://api.github.com/repos/illuminate/session/zipball/ddd0fce2f49889eede824db81c283b4e82aa77d0", + "reference": "ddd0fce2f49889eede824db81c283b4e82aa77d0", "shasum": "" }, "require": { - "dragonmantank/cron-expression": "^3.6", - "ext-json": "*", - "guzzlehttp/guzzle": "^7.10.0", - "illuminate/cache": "^12.40.0", - "illuminate/collections": "^12.40.0", - "illuminate/config": "^12.40.0", - "illuminate/console": "^12.40.0", - "illuminate/container": "^12.40.0", - "illuminate/contracts": "^12.40.0", - "illuminate/events": "^12.40.0", - "illuminate/filesystem": "^12.40.0", - "illuminate/process": "^12.40.0", - "illuminate/support": "^12.40.0", - "illuminate/testing": "^12.40.0", - "laravel-zero/foundation": "^12.40.0", - "laravel/prompts": "^0.3.8", - "league/flysystem": "^3.30.2", - "nunomaduro/collision": "^8.8.3", - "nunomaduro/laravel-console-summary": "^1.13.0", - "nunomaduro/laravel-console-task": "^1.10", - "nunomaduro/laravel-desktop-notifier": "^2.9.0", - "nunomaduro/termwind": "^2.3.3", + "ext-ctype": "*", + "ext-session": "*", + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2", - "psr/log": "^3.0.2", - "ramsey/uuid": "^4.9.1", - "symfony/console": "^7.3.6", - "symfony/error-handler": "^7.3.6", - "symfony/event-dispatcher": "^7.3.3", - "symfony/finder": "^7.3.5", - "symfony/process": "^7.3.4", - "symfony/var-dumper": "^7.3.5", - "vlucas/phpdotenv": "^5.6.2" - }, - "require-dev": { - "illuminate/bus": "^12.40.0", - "illuminate/database": "^12.40.0", - "illuminate/http": "^12.40.0", - "illuminate/log": "^12.40.0", - "illuminate/queue": "^12.40.0", - "illuminate/redis": "^12.40.0", - "illuminate/view": "^12.40.0", - "laravel-zero/phar-updater": "^1.4.2", - "laravel/pint": "^1.25.1", - "nunomaduro/laravel-console-dusk": "^1.14", - "nunomaduro/laravel-console-menu": "^3.6", - "pestphp/pest": "^3.8.4|^4.1.5", - "phpstan/phpstan": "^2.1.32" + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0" }, "suggest": { - "ext-pcntl": "Required to ensure that data is cleared when cancelling the build process." + "illuminate/console": "Required to use the session:table command (^12.0)." }, "type": "library", "extra": { @@ -2812,7 +3016,7 @@ }, "autoload": { "psr-4": { - "LaravelZero\\Framework\\": "src" + "Illuminate\\Session\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2821,78 +3025,78 @@ ], "authors": [ { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - }, - { - "name": "Owen Voke", - "email": "development@voke.dev" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" } ], - "description": "The Laravel Zero Framework.", - "homepage": "https://laravel-zero.com", - "keywords": [ - "Laravel Zero", - "cli", - "console", - "framework", - "laravel" - ], + "description": "The Illuminate Session package.", + "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/laravel-zero/laravel-zero/issues", - "source": "https://github.com/laravel-zero/laravel-zero" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - } - ], - "time": "2026-01-24T09:18:21+00:00" + "time": "2026-01-22T17:27:05+00:00" }, { - "name": "laravel/pint", - "version": "v1.27.0", + "name": "illuminate/support", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/laravel/pint.git", - "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90" + "url": "https://github.com/illuminate/support.git", + "reference": "411a11401406e7d542aa67a4b400feed6bedef0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90", - "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90", + "url": "https://api.github.com/repos/illuminate/support/zipball/411a11401406e7d542aa67a4b400feed6bedef0c", + "reference": "411a11401406e7d542aa67a4b400feed6bedef0c", "shasum": "" }, "require": { - "ext-json": "*", + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", "ext-mbstring": "*", - "ext-tokenizer": "*", - "ext-xml": "*", - "php": "^8.2.0" + "illuminate/collections": "^12.0", + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/reflection": "^12.0", + "nesbot/carbon": "^3.8.4", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php85": "^1.33", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "replace": { + "spatie/once": "*" + }, + "suggest": { + "illuminate/filesystem": "Required to use the Composer class (^12.0).", + "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).", + "league/uri": "Required to use the Uri class (^7.5.1).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the Composer class (^7.2).", + "symfony/uid": "Required to use Str::ulid() (^7.2).", + "symfony/var-dumper": "Required to use the dd function (^7.2).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)." }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.92.4", - "illuminate/view": "^12.44.0", - "larastan/larastan": "^3.8.1", - "laravel-zero/framework": "^12.0.4", - "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3.3", - "pestphp/pest": "^3.8.4" + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } }, - "bin": [ - "builds/pint" - ], - "type": "project", "autoload": { + "files": [ + "functions.php", + "helpers.php" + ], "psr-4": { - "App\\": "app/", - "Database\\Seeders\\": "database/seeders/", - "Database\\Factories\\": "database/factories/" + "Illuminate\\Support\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2901,118 +3105,112 @@ ], "authors": [ { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" } ], - "description": "An opinionated code formatter for PHP.", + "description": "The Illuminate Support package.", "homepage": "https://laravel.com", - "keywords": [ - "dev", - "format", - "formatter", - "lint", - "linter", - "php" - ], "support": { - "issues": "https://github.com/laravel/pint/issues", - "source": "https://github.com/laravel/pint" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "time": "2026-01-05T16:49:17+00:00" + "time": "2026-02-04T15:14:59+00:00" }, { - "name": "laravel/prompts", - "version": "v0.3.12", + "name": "illuminate/testing", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/laravel/prompts.git", - "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8" + "url": "https://github.com/illuminate/testing.git", + "reference": "00a464a3319d8e54ff0075ca801bb08dd121a2e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/4861ded9003b7f8a158176a0b7666f74ee761be8", - "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8", + "url": "https://api.github.com/repos/illuminate/testing/zipball/00a464a3319d8e54ff0075ca801bb08dd121a2e5", + "reference": "00a464a3319d8e54ff0075ca801bb08dd121a2e5", "shasum": "" }, "require": { - "composer-runtime-api": "^2.2", "ext-mbstring": "*", - "php": "^8.1", - "symfony/console": "^6.2|^7.0|^8.0" - }, - "conflict": { - "illuminate/console": ">=10.17.0 <10.25.0", - "laravel/framework": ">=10.17.0 <10.25.0" - }, - "require-dev": { - "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", - "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3|^3.4|^4.0", - "phpstan/phpstan": "^1.12.28", - "phpstan/phpstan-mockery": "^1.1.3" + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33" }, "suggest": { - "ext-pcntl": "Required for the spinner to be animated." + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", + "illuminate/console": "Required to assert console commands (^12.0).", + "illuminate/database": "Required to assert databases (^12.0).", + "illuminate/http": "Required to assert responses (^12.0).", + "mockery/mockery": "Required to use mocking (^1.6).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1)." }, "type": "library", "extra": { "branch-alias": { - "dev-main": "0.3.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { - "files": [ - "src/helpers.php" - ], "psr-4": { - "Laravel\\Prompts\\": "src/" + "Illuminate\\Testing\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Add beautiful and user-friendly forms to your command-line applications.", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Testing package.", + "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.12" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "time": "2026-02-03T06:57:26+00:00" + "time": "2026-01-20T17:18:54+00:00" }, { - "name": "laravel/serializable-closure", - "version": "v2.0.9", + "name": "illuminate/view", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" + "url": "https://github.com/illuminate/view.git", + "reference": "5b3d7ae07665b98cca46846074edc008ed5e3864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", + "url": "https://api.github.com/repos/illuminate/view/zipball/5b3d7ae07665b98cca46846074edc008ed5e3864", + "reference": "5b3d7ae07665b98cca46846074edc008ed5e3864", "shasum": "" }, "require": { - "php": "^8.1" - }, - "require-dev": { - "illuminate/support": "^10.0|^11.0|^12.0|^13.0", - "nesbot/carbon": "^2.67|^3.0", - "pestphp/pest": "^2.36|^3.0|^4.0", - "phpstan/phpstan": "^2.0", - "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + "ext-tokenizer": "*", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/events": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { "psr-4": { - "Laravel\\SerializableClosure\\": "src/" + "Illuminate\\View\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3023,75 +3221,51 @@ { "name": "Taylor Otwell", "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "nuno@laravel.com" } ], - "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": [ - "closure", - "laravel", - "serializable" - ], + "description": "The Illuminate View package.", + "homepage": "https://laravel.com", "support": { - "issues": "https://github.com/laravel/serializable-closure/issues", - "source": "https://github.com/laravel/serializable-closure" + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" }, - "time": "2026-02-03T06:55:34+00:00" + "time": "2026-01-20T15:07:29+00:00" }, { - "name": "league/flysystem", - "version": "3.31.0", + "name": "jean85/pretty-package-versions", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", "shasum": "" }, "require": { - "league/flysystem-local": "^3.0.0", - "league/mime-type-detection": "^1.0.0", - "php": "^8.0.2" - }, - "conflict": { - "async-aws/core": "<1.19.0", - "async-aws/s3": "<1.14.0", - "aws/aws-sdk-php": "3.209.31 || 3.210.0", - "guzzlehttp/guzzle": "<7.0", - "guzzlehttp/ringphp": "<1.1.1", - "phpseclib/phpseclib": "3.0.15", - "symfony/http-client": "<5.2" + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" }, "require-dev": { - "async-aws/s3": "^1.5 || ^2.0", - "async-aws/simple-s3": "^1.1 || ^2.0", - "aws/aws-sdk-php": "^3.295.10", - "composer/semver": "^3.0", - "ext-fileinfo": "*", - "ext-ftp": "*", - "ext-mongodb": "^1.3|^2", - "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.5", - "google/cloud-storage": "^1.23", - "guzzlehttp/psr7": "^2.6", - "microsoft/azure-storage-blob": "^1.1", - "mongodb/mongodb": "^1.2|^2", - "phpseclib/phpseclib": "^3.0.36", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.11|^10.0", - "sabre/dav": "^4.6.0" + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, "autoload": { "psr-4": { - "League\\Flysystem\\": "src" + "Jean85\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3100,54 +3274,58 @@ ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" } ], - "description": "File storage abstraction for PHP", + "description": "A library to get pretty versions strings of installed dependencies", "keywords": [ - "WebDAV", - "aws", - "cloud", - "file", - "files", - "filesystem", - "filesystems", - "ftp", - "s3", - "sftp", - "storage" + "composer", + "package", + "release", + "versions" ], "support": { - "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" }, - "time": "2026-01-23T15:38:47+00:00" + "time": "2025-03-19T14:43:43+00:00" }, { - "name": "league/flysystem-local", - "version": "3.31.0", + "name": "jolicode/jolinotif", + "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + "url": "https://github.com/jolicode/JoliNotif.git", + "reference": "3c3e1c410b107dd2603b732508fd95830f0e0196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "url": "https://api.github.com/repos/jolicode/JoliNotif/zipball/3c3e1c410b107dd2603b732508fd95830f0e0196", + "reference": "3c3e1c410b107dd2603b732508fd95830f0e0196", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "league/flysystem": "^3.0.0", - "league/mime-type-detection": "^1.0.0", - "php": "^8.0.2" + "jolicode/php-os-helper": "^0.1.0", + "php": ">=8.1", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^3", + "symfony/process": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-ffi": "Needed to send notifications via libnotify on Linux" }, + "bin": [ + "jolinotif" + ], "type": "library", "autoload": { "psr-4": { - "League\\Flysystem\\Local\\": "" + "Joli\\JoliNotif\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3156,50 +3334,54 @@ ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" + "name": "Loïck Piera", + "email": "pyrech@gmail.com" } ], - "description": "Local filesystem adapter for Flysystem.", + "description": "Send desktop notifications on Windows, Linux, MacOS.", "keywords": [ - "Flysystem", - "file", - "files", - "filesystem", - "local" + "MAC", + "growl", + "linux", + "notification", + "windows" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + "issues": "https://github.com/jolicode/JoliNotif/issues", + "source": "https://github.com/jolicode/JoliNotif/tree/v2.7.3" }, - "time": "2026-01-23T15:30:45+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/jolicode/jolinotif", + "type": "tidelift" + } + ], + "time": "2024-09-30T13:34:54+00:00" }, { - "name": "league/mime-type-detection", - "version": "1.16.0", + "name": "jolicode/php-os-helper", + "version": "v0.1.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "url": "https://github.com/jolicode/php-os-helper.git", + "reference": "1622ad8bbcab98e62b5c041397e8519f10d90e29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/jolicode/php-os-helper/zipball/1622ad8bbcab98e62b5c041397e8519f10d90e29", + "reference": "1622ad8bbcab98e62b5c041397e8519f10d90e29", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "php": "^7.4 || ^8.0" + "php": ">=8.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "symfony/phpunit-bridge": "^6.3.1" }, "type": "library", "autoload": { "psr-4": { - "League\\MimeTypeDetection\\": "src" + "JoliCode\\PhpOsHelper\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3208,230 +3390,247 @@ ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" + "name": "Loïck Piera", + "email": "pyrech@gmail.com" } ], - "description": "Mime-type detection for Flysystem", + "description": "Helpers to detect the OS of the machine where PHP is running.", + "keywords": [ + "linux", + "os", + "osx", + "php", + "windows" + ], "support": { - "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "issues": "https://github.com/jolicode/php-os-helper/issues", + "source": "https://github.com/jolicode/php-os-helper/tree/v0.1.0" }, - "funding": [ - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } - ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2023-12-03T12:46:03+00:00" }, { - "name": "mockery/mockery", - "version": "1.6.12", + "name": "larastan/larastan", + "version": "v3.9.2", "source": { "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "url": "https://github.com/larastan/larastan.git", + "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/larastan/larastan/zipball/2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", + "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", - "php": ">=7.3" - }, - "conflict": { - "phpunit/phpunit": "<8.0" + "ext-json": "*", + "iamcal/sql-parser": "^0.7.0", + "illuminate/console": "^11.44.2 || ^12.4.1", + "illuminate/container": "^11.44.2 || ^12.4.1", + "illuminate/contracts": "^11.44.2 || ^12.4.1", + "illuminate/database": "^11.44.2 || ^12.4.1", + "illuminate/http": "^11.44.2 || ^12.4.1", + "illuminate/pipeline": "^11.44.2 || ^12.4.1", + "illuminate/support": "^11.44.2 || ^12.4.1", + "php": "^8.2", + "phpstan/phpstan": "^2.1.32" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "doctrine/coding-standard": "^13", + "laravel/framework": "^11.44.2 || ^12.7.2", + "mockery/mockery": "^1.6.12", + "nikic/php-parser": "^5.4", + "orchestra/canvas": "^v9.2.2 || ^10.0.1", + "orchestra/testbench-core": "^9.12.0 || ^10.1", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpunit/phpunit": "^10.5.35 || ^11.5.15" + }, + "suggest": { + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", + "phpmyadmin/sql-parser": "Install to enable Larastan's optional phpMyAdmin-based SQL parser automatically" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } }, - "type": "library", "autoload": { - "files": [ - "library/helpers.php", - "library/Mockery.php" - ], "psr-4": { - "Mockery\\": "library/Mockery" + "Larastan\\Larastan\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "https://github.com/padraic", - "role": "Author" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "https://davedevelopment.co.uk", - "role": "Developer" - }, - { - "name": "Nathanael Esayeas", - "email": "nathanael.esayeas@protonmail.com", - "homepage": "https://github.com/ghostwriter", - "role": "Lead Developer" + "name": "Can Vural", + "email": "can9119@gmail.com" } ], - "description": "Mockery is a simple yet flexible PHP mock object framework", - "homepage": "https://github.com/mockery/mockery", + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel", "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" ], "support": { - "docs": "https://docs.mockery.io/", - "issues": "https://github.com/mockery/mockery/issues", - "rss": "https://github.com/mockery/mockery/releases.atom", - "security": "https://github.com/mockery/mockery/security/advisories", - "source": "https://github.com/mockery/mockery" + "issues": "https://github.com/larastan/larastan/issues", + "source": "https://github.com/larastan/larastan/tree/v3.9.2" }, - "time": "2024-05-16T03:13:13+00:00" + "funding": [ + { + "url": "https://github.com/canvural", + "type": "github" + } + ], + "time": "2026-01-30T15:16:32+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.13.4", + "name": "laravel-zero/foundation", + "version": "v12.48.1", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "url": "https://github.com/laravel-zero/foundation.git", + "reference": "93b553b994ea63a1a69f396dc02add38012d1b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/laravel-zero/foundation/zipball/93b553b994ea63a1a69f396dc02add38012d1b5d", + "reference": "93b553b994ea63a1a69f396dc02add38012d1b5d", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "php": "^8.2" }, "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + "laravel/framework": "^12.40" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.x-dev" + } + }, "autoload": { "files": [ - "src/DeepCopy/deep_copy.php" + "src/Illuminate/Foundation/helpers.php" ], "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "Illuminate\\": "src/Illuminate/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Create deep copies (clones) of your objects", + "description": "This is a mirror from illuminate/foundation.", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "framework", + "laravel" ], "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/laravel-zero/foundation/tree/v12.48.1" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-01-26T09:29:33+00:00" }, { - "name": "nesbot/carbon", - "version": "3.11.1", + "name": "laravel-zero/framework", + "version": "v12.0.5", "source": { "type": "git", - "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "f438fcc98f92babee98381d399c65336f3a3827f" + "url": "https://github.com/laravel-zero/framework.git", + "reference": "206d53048c1b58b1577ab491390d704768532a01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", - "reference": "f438fcc98f92babee98381d399c65336f3a3827f", + "url": "https://api.github.com/repos/laravel-zero/framework/zipball/206d53048c1b58b1577ab491390d704768532a01", + "reference": "206d53048c1b58b1577ab491390d704768532a01", "shasum": "" }, "require": { - "carbonphp/carbon-doctrine-types": "<100.0", + "dragonmantank/cron-expression": "^3.6", "ext-json": "*", - "php": "^8.1", - "psr/clock": "^1.0", - "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" - }, - "provide": { - "psr/clock-implementation": "1.0" + "guzzlehttp/guzzle": "^7.10.0", + "illuminate/cache": "^12.40.0", + "illuminate/collections": "^12.40.0", + "illuminate/config": "^12.40.0", + "illuminate/console": "^12.40.0", + "illuminate/container": "^12.40.0", + "illuminate/contracts": "^12.40.0", + "illuminate/events": "^12.40.0", + "illuminate/filesystem": "^12.40.0", + "illuminate/process": "^12.40.0", + "illuminate/support": "^12.40.0", + "illuminate/testing": "^12.40.0", + "laravel-zero/foundation": "^12.40.0", + "laravel/prompts": "^0.3.8", + "league/flysystem": "^3.30.2", + "nunomaduro/collision": "^8.8.3", + "nunomaduro/laravel-console-summary": "^1.13.0", + "nunomaduro/laravel-console-task": "^1.10", + "nunomaduro/laravel-desktop-notifier": "^2.9.0", + "nunomaduro/termwind": "^2.3.3", + "php": "^8.2", + "psr/log": "^3.0.2", + "ramsey/uuid": "^4.9.1", + "symfony/console": "^7.3.6", + "symfony/error-handler": "^7.3.6", + "symfony/event-dispatcher": "^7.3.3", + "symfony/finder": "^7.3.5", + "symfony/process": "^7.3.4", + "symfony/var-dumper": "^7.3.5", + "vlucas/phpdotenv": "^5.6.2" }, "require-dev": { - "doctrine/dbal": "^3.6.3 || ^4.0", - "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^v3.87.1", - "kylekatarnls/multi-tester": "^2.5.3", - "phpmd/phpmd": "^2.15.0", - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.22", - "phpunit/phpunit": "^10.5.53", - "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" + "illuminate/bus": "^12.40.0", + "illuminate/database": "^12.40.0", + "illuminate/http": "^12.40.0", + "illuminate/log": "^12.40.0", + "illuminate/queue": "^12.40.0", + "illuminate/redis": "^12.40.0", + "illuminate/view": "^12.40.0", + "laravel-zero/phar-updater": "^1.4.2", + "laravel/pint": "^1.25.1", + "nunomaduro/laravel-console-dusk": "^1.14", + "nunomaduro/laravel-console-menu": "^3.6", + "pestphp/pest": "^3.8.4|^4.1.5", + "phpstan/phpstan": "^2.1.32" + }, + "suggest": { + "ext-pcntl": "Required to ensure that data is cleared when cancelling the build process." }, - "bin": [ - "bin/carbon" - ], "type": "library", "extra": { - "laravel": { - "providers": [ - "Carbon\\Laravel\\ServiceProvider" - ] - }, - "phpstan": { - "includes": [ - "extension.neon" - ] - }, "branch-alias": { - "dev-2.x": "2.x-dev", - "dev-master": "3.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { "psr-4": { - "Carbon\\": "src/Carbon/" + "LaravelZero\\Framework\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3440,233 +3639,198 @@ ], "authors": [ { - "name": "Brian Nesbitt", - "email": "brian@nesbot.com", - "homepage": "https://markido.com" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" }, { - "name": "kylekatarnls", - "homepage": "https://github.com/kylekatarnls" + "name": "Owen Voke", + "email": "development@voke.dev" } ], - "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbonphp.github.io/carbon/", + "description": "The Laravel Zero Framework.", + "homepage": "https://laravel-zero.com", "keywords": [ - "date", - "datetime", - "time" + "Laravel Zero", + "cli", + "console", + "framework", + "laravel" ], "support": { - "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", - "issues": "https://github.com/CarbonPHP/carbon/issues", - "source": "https://github.com/CarbonPHP/carbon" + "issues": "https://github.com/laravel-zero/laravel-zero/issues", + "source": "https://github.com/laravel-zero/laravel-zero" }, "funding": [ { - "url": "https://github.com/sponsors/kylekatarnls", - "type": "github" - }, - { - "url": "https://opencollective.com/Carbon#sponsor", - "type": "opencollective" + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" }, { - "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", - "type": "tidelift" + "url": "https://github.com/nunomaduro", + "type": "github" } ], - "time": "2026-01-29T09:26:29+00:00" + "time": "2026-01-24T09:18:21+00:00" }, { - "name": "nikic/php-parser", - "version": "v5.7.0", + "name": "laravel/pint", + "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "url": "https://github.com/laravel/pint.git", + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90", + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", + "ext-mbstring": "*", "ext-tokenizer": "*", - "php": ">=7.4" + "ext-xml": "*", + "php": "^8.2.0" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "friendsofphp/php-cs-fixer": "^3.92.4", + "illuminate/view": "^12.44.0", + "larastan/larastan": "^3.8.1", + "laravel-zero/framework": "^12.0.4", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest": "^3.8.4" }, "bin": [ - "bin/php-parse" + "builds/pint" ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, + "type": "project", "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "A PHP parser written in PHP", + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", "keywords": [ - "parser", + "dev", + "format", + "formatter", + "lint", + "linter", "php" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-01-05T16:49:17+00:00" }, { - "name": "nunomaduro/collision", - "version": "v8.8.3", + "name": "laravel/prompts", + "version": "v0.3.12", "source": { "type": "git", - "url": "https://github.com/nunomaduro/collision.git", - "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" + "url": "https://github.com/laravel/prompts.git", + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", - "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "url": "https://api.github.com/repos/laravel/prompts/zipball/4861ded9003b7f8a158176a0b7666f74ee761be8", + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8", "shasum": "" }, "require": { - "filp/whoops": "^2.18.1", - "nunomaduro/termwind": "^2.3.1", - "php": "^8.2.0", - "symfony/console": "^7.3.0" + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0|^8.0" }, "conflict": { - "laravel/framework": "<11.44.2 || >=13.0.0", - "phpunit/phpunit": "<11.5.15 || >=13.0.0" + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "brianium/paratest": "^7.8.3", - "larastan/larastan": "^3.4.2", - "laravel/framework": "^11.44.2 || ^12.18", - "laravel/pint": "^1.22.1", - "laravel/sail": "^1.43.1", - "laravel/sanctum": "^4.1.1", - "laravel/tinker": "^2.10.1", - "orchestra/testbench-core": "^9.12.0 || ^10.4", - "pestphp/pest": "^3.8.2 || ^4.0.0", - "sebastian/environment": "^7.2.1 || ^8.0" + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" - ] - }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { "branch-alias": { - "dev-8.x": "8.x-dev" + "dev-main": "0.3.x-dev" } }, "autoload": { "files": [ - "./src/Adapters/Phpunit/Autoload.php" + "src/helpers.php" ], "psr-4": { - "NunoMaduro\\Collision\\": "src/" + "Laravel\\Prompts\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "Cli error handling for console/command-line PHP applications.", - "keywords": [ - "artisan", - "cli", - "command-line", - "console", - "dev", - "error", - "handling", - "laravel", - "laravel-zero", - "php", - "symfony" - ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { - "issues": "https://github.com/nunomaduro/collision/issues", - "source": "https://github.com/nunomaduro/collision" + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.12" }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - }, - { - "url": "https://www.patreon.com/nunomaduro", - "type": "patreon" - } - ], - "time": "2025-11-20T02:55:25+00:00" + "time": "2026-02-03T06:57:26+00:00" }, { - "name": "nunomaduro/laravel-console-summary", - "version": "v1.13.0", + "name": "laravel/serializable-closure", + "version": "v2.0.9", "source": { "type": "git", - "url": "https://github.com/nunomaduro/laravel-console-summary.git", - "reference": "8fe07f5ecbedca8544edc54f397538dc0b49d7f9" + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/laravel-console-summary/zipball/8fe07f5ecbedca8544edc54f397538dc0b49d7f9", - "reference": "8fe07f5ecbedca8544edc54f397538dc0b49d7f9", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", "shasum": "" }, "require": { - "illuminate/console": "^11.4|^12.0", - "illuminate/support": "^11.4|^12.0", - "php": "^8.2" + "php": "^8.1" }, "require-dev": { - "laravel/pint": "^1.21" + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" }, "type": "library", "extra": { - "laravel": { - "providers": [ - "NunoMaduro\\LaravelConsoleSummary\\LaravelConsoleSummaryServiceProvider" - ] + "branch-alias": { + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "NunoMaduro\\LaravelConsoleSummary\\": "src/" + "Laravel\\SerializableClosure\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3674,61 +3838,78 @@ "MIT" ], "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, { "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" + "email": "nuno@laravel.com" } ], - "description": "A Beautiful Laravel Console Summary for your Laravel/Laravel Zero commands.", + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", "keywords": [ - "artisan", - "cli", - "command-line", - "console", + "closure", "laravel", - "laravel-zero", - "php", - "symfony" + "serializable" ], "support": { - "issues": "https://github.com/nunomaduro/laravel-console-summary/issues", - "source": "https://github.com/nunomaduro/laravel-console-summary" + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-02-19T11:10:44+00:00" + "time": "2026-02-03T06:55:34+00:00" }, { - "name": "nunomaduro/laravel-console-task", - "version": "v1.10.0", + "name": "league/flysystem", + "version": "3.31.0", "source": { "type": "git", - "url": "https://github.com/nunomaduro/laravel-console-task.git", - "reference": "9d11073ad8b0215c63a962250e2bf071611f975d" + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/laravel-console-task/zipball/9d11073ad8b0215c63a962250e2bf071611f975d", - "reference": "9d11073ad8b0215c63a962250e2bf071611f975d", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", "shasum": "" }, "require": { - "illuminate/console": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", - "php": "^8.2" + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" }, "require-dev": { - "pestphp/pest": "^3.7" + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" }, "type": "library", - "extra": { - "laravel": { - "providers": [ - "NunoMaduro\\LaravelConsoleTask\\LaravelConsoleTaskServiceProvider" - ] - } - }, "autoload": { "psr-4": { - "NunoMaduro\\LaravelConsoleTask\\": "src/" + "League\\Flysystem\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3737,65 +3918,54 @@ ], "authors": [ { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } ], - "description": "Laravel Console Task is a output method for your Laravel/Laravel Zero commands.", + "description": "File storage abstraction for PHP", "keywords": [ - "artisan", - "cli", - "command-line", - "console", - "laravel", - "laravel-zero", - "php", - "symfony" + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" ], "support": { - "issues": "https://github.com/nunomaduro/laravel-console-task/issues", - "source": "https://github.com/nunomaduro/laravel-console-task" + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" }, - "time": "2025-02-19T11:02:37+00:00" + "time": "2026-01-23T15:38:47+00:00" }, { - "name": "nunomaduro/laravel-desktop-notifier", - "version": "v2.9.0", + "name": "league/flysystem-local", + "version": "3.31.0", "source": { "type": "git", - "url": "https://github.com/nunomaduro/laravel-desktop-notifier.git", - "reference": "4871ee90fff38fbe25a2b8f81b5daeedf98a3ed7" + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/laravel-desktop-notifier/zipball/4871ee90fff38fbe25a2b8f81b5daeedf98a3ed7", - "reference": "4871ee90fff38fbe25a2b8f81b5daeedf98a3ed7", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", "shasum": "" }, "require": { - "illuminate/console": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", - "jolicode/jolinotif": "^2.5", - "php": "^8.2" - }, - "require-dev": { - "graham-campbell/testbench": "^5.7|^6.2", - "pestphp/pest": "^3.7" + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" }, "type": "library", - "extra": { - "laravel": { - "aliases": { - "Notifier": "NunoMaduro\\LaravelDesktopNotifier\\Facaces\\Notifier" - }, - "providers": [ - "NunoMaduro\\LaravelDesktopNotifier\\LaravelDesktopNotifierServiceProvider" - ] - } - }, "autoload": { "psr-4": { - "NunoMaduro\\LaravelDesktopNotifier\\": "src/" + "League\\Flysystem\\Local\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3804,76 +3974,50 @@ ], "authors": [ { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } ], - "description": "Send notifications to your desktop from your Laravel commands. An JoliNotif wrapper for Laravel 5.", + "description": "Local filesystem adapter for Flysystem.", "keywords": [ - "JoliNotif", - "Nuno Maduro", - "NunoMaduro", - "artisan", - "console", - "framework", - "laravel", - "notification", - "notifier", - "php", - "wrapper" + "Flysystem", + "file", + "files", + "filesystem", + "local" ], "support": { - "issues": "https://github.com/nunomaduro/laravel-desktop-notifier/issues", - "source": "https://github.com/nunomaduro/laravel-desktop-notifier/tree/v2.9.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" }, - "time": "2025-02-19T11:22:09+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { - "name": "nunomaduro/termwind", - "version": "v2.3.3", + "name": "league/mime-type-detection", + "version": "1.16.0", "source": { "type": "git", - "url": "https://github.com/nunomaduro/termwind.git", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": "^8.2", - "symfony/console": "^7.3.6" + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" }, "require-dev": { - "illuminate/console": "^11.46.1", - "laravel/pint": "^1.25.1", - "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", - "phpstan/phpstan": "^1.12.32", - "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.5", - "thecodingmachine/phpstan-strict-rules": "^1.0.0" + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", - "extra": { - "laravel": { - "providers": [ - "Termwind\\Laravel\\TermwindServiceProvider" - ] - }, - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, "autoload": { - "files": [ - "src/Functions.php" - ], "psr-4": { - "Termwind\\": "src/" + "League\\MimeTypeDetection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3882,507 +4026,527 @@ ], "authors": [ { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } ], - "description": "Its like Tailwind CSS, but for the console.", - "keywords": [ - "cli", - "console", - "css", - "package", - "php", - "style" - ], + "description": "Mime-type detection for Flysystem", "support": { - "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", + "url": "https://github.com/frankdejonge", "type": "github" }, { - "url": "https://github.com/xiCO2k", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" } ], - "time": "2025-11-20T02:34:59+00:00" + "time": "2024-09-21T08:32:55+00:00" }, { - "name": "pestphp/pest", - "version": "v3.8.5", + "name": "mockery/mockery", + "version": "1.6.12", "source": { "type": "git", - "url": "https://github.com/pestphp/pest.git", - "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4" + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/7796630eafcfd1c02660cecdde3bc6984fbf01f4", - "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", "shasum": "" }, "require": { - "brianium/paratest": "^7.8.5", - "nunomaduro/collision": "^8.8.3", - "nunomaduro/termwind": "^2.3.3", - "pestphp/pest-plugin": "^3.0.0", - "pestphp/pest-plugin-arch": "^3.1.1", - "pestphp/pest-plugin-mutate": "^3.0.5", - "php": "^8.2.0", - "phpunit/phpunit": "^11.5.50" + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" }, "conflict": { - "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.50", - "sebastian/exporter": "<6.0.0", - "webmozart/assert": "<1.11.0" + "phpunit/phpunit": "<8.0" }, "require-dev": { - "pestphp/pest-dev-tools": "^3.4.0", - "pestphp/pest-plugin-type-coverage": "^3.6.1", - "symfony/process": "^7.4.4" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, - "bin": [ - "bin/pest" - ], "type": "library", - "extra": { - "pest": { - "plugins": [ - "Pest\\Mutate\\Plugins\\Mutate", - "Pest\\Plugins\\Configuration", - "Pest\\Plugins\\Bail", - "Pest\\Plugins\\Cache", - "Pest\\Plugins\\Coverage", - "Pest\\Plugins\\Init", - "Pest\\Plugins\\Environment", - "Pest\\Plugins\\Help", - "Pest\\Plugins\\Memory", - "Pest\\Plugins\\Only", - "Pest\\Plugins\\Printer", - "Pest\\Plugins\\ProcessIsolation", - "Pest\\Plugins\\Profile", - "Pest\\Plugins\\Retry", - "Pest\\Plugins\\Snapshot", - "Pest\\Plugins\\Verbose", - "Pest\\Plugins\\Version", - "Pest\\Plugins\\Parallel" - ] - }, - "phpstan": { - "includes": [ - "extension.neon" - ] - } - }, "autoload": { "files": [ - "src/Functions.php", - "src/Pest.php" + "library/helpers.php", + "library/Mockery.php" ], "psr-4": { - "Pest\\": "src/" + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], - "description": "The elegant PHP Testing Framework.", + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", "keywords": [ - "framework", - "pest", - "php", + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", "test", - "testing", - "unit" + "test double", + "testing" ], "support": { - "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.8.5" + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - } - ], - "time": "2026-01-28T01:33:45+00:00" + "time": "2024-05-16T03:13:13+00:00" }, { - "name": "pestphp/pest-plugin", - "version": "v3.0.0", + "name": "myclabs/deep-copy", + "version": "1.13.4", "source": { "type": "git", - "url": "https://github.com/pestphp/pest-plugin.git", - "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e79b26c65bc11c41093b10150c1341cc5cdbea83", - "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { - "composer-plugin-api": "^2.0.0", - "composer-runtime-api": "^2.2.2", - "php": "^8.2" + "php": "^7.1 || ^8.0" }, "conflict": { - "pestphp/pest": "<3.0.0" + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "composer/composer": "^2.7.9", - "pestphp/pest": "^3.0.0", - "pestphp/pest-dev-tools": "^3.0.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Pest\\Plugin\\Manager" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, + "type": "library", "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { - "Pest\\Plugin\\": "src/" + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "The Pest plugin manager", + "description": "Create deep copies (clones) of your objects", "keywords": [ - "framework", - "manager", - "pest", - "php", - "plugin", - "test", - "testing", - "unit" + "clone", + "copy", + "duplicate", + "object", + "object graph" ], "support": { - "source": "https://github.com/pestphp/pest-plugin/tree/v3.0.0" + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { - "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - }, - { - "url": "https://www.patreon.com/nunomaduro", - "type": "patreon" + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" } ], - "time": "2024-09-08T23:21:41+00:00" + "time": "2025-08-01T08:46:24+00:00" }, - { - "name": "pestphp/pest-plugin-arch", - "version": "v3.1.1", + { + "name": "nesbot/carbon", + "version": "3.11.1", "source": { "type": "git", - "url": "https://github.com/pestphp/pest-plugin-arch.git", - "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "f438fcc98f92babee98381d399c65336f3a3827f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/db7bd9cb1612b223e16618d85475c6f63b9c8daa", - "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", + "reference": "f438fcc98f92babee98381d399c65336f3a3827f", "shasum": "" }, "require": { - "pestphp/pest-plugin": "^3.0.0", - "php": "^8.2", - "ta-tikoma/phpunit-architecture-test": "^0.8.4" + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { - "pestphp/pest": "^3.8.1", - "pestphp/pest-dev-tools": "^3.4.0" + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, + "bin": [ + "bin/carbon" + ], "type": "library", "extra": { - "pest": { - "plugins": [ - "Pest\\Arch\\Plugin" + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { - "files": [ - "src/Autoload.php" - ], "psr-4": { - "Pest\\Arch\\": "src/" + "Carbon\\": "src/Carbon/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "The Arch plugin for Pest PHP.", + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ - "arch", - "architecture", - "framework", - "pest", - "php", - "plugin", - "test", - "testing", - "unit" + "date", + "datetime", + "time" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.1.1" + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" }, "funding": [ { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" }, { - "url": "https://github.com/nunomaduro", - "type": "github" + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" } ], - "time": "2025-04-16T22:59:48+00:00" + "time": "2026-01-29T09:26:29+00:00" }, { - "name": "pestphp/pest-plugin-mutate", - "version": "v3.0.5", + "name": "nikic/php-parser", + "version": "v5.7.0", "source": { "type": "git", - "url": "https://github.com/pestphp/pest-plugin-mutate.git", - "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/e10dbdc98c9e2f3890095b4fe2144f63a5717e08", - "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { - "nikic/php-parser": "^5.2.0", - "pestphp/pest-plugin": "^3.0.0", - "php": "^8.2", - "psr/simple-cache": "^3.0.0" + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" }, "require-dev": { - "pestphp/pest": "^3.0.8", - "pestphp/pest-dev-tools": "^3.0.0", - "pestphp/pest-plugin-type-coverage": "^3.0.0" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, "autoload": { "psr-4": { - "Pest\\Mutate\\": "src/" + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Sandro Gehri", - "email": "sandrogehri@gmail.com" + "name": "Nikita Popov" } ], - "description": "Mutates your code to find untested cases", + "description": "A PHP parser written in PHP", "keywords": [ - "framework", - "mutate", - "mutation", - "pest", - "php", - "plugin", - "test", - "testing", - "unit" + "parser", + "php" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v3.0.5" + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/gehrisandro", - "type": "github" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - } - ], - "time": "2024-09-22T07:54:40+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "nunomaduro/collision", + "version": "v8.8.3", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/nunomaduro/collision.git", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "filp/whoops": "^2.18.1", + "nunomaduro/termwind": "^2.3.1", + "php": "^8.2.0", + "symfony/console": "^7.3.0" + }, + "conflict": { + "laravel/framework": "<11.44.2 || >=13.0.0", + "phpunit/phpunit": "<11.5.15 || >=13.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.3", + "larastan/larastan": "^3.4.2", + "laravel/framework": "^11.44.2 || ^12.18", + "laravel/pint": "^1.22.1", + "laravel/sail": "^1.43.1", + "laravel/sanctum": "^4.1.1", + "laravel/tinker": "^2.10.1", + "orchestra/testbench-core": "^9.12.0 || ^10.4", + "pestphp/pest": "^3.8.2 || ^4.0.0", + "sebastian/environment": "^7.2.1 || ^8.0" }, "type": "library", "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-8.x": "8.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "dev", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" }, "funding": [ { - "url": "https://github.com/theseer", + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" } ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2025-11-20T02:55:25+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "nunomaduro/laravel-console-summary", + "version": "v1.13.0", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/nunomaduro/laravel-console-summary.git", + "reference": "8fe07f5ecbedca8544edc54f397538dc0b49d7f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/nunomaduro/laravel-console-summary/zipball/8fe07f5ecbedca8544edc54f397538dc0b49d7f9", + "reference": "8fe07f5ecbedca8544edc54f397538dc0b49d7f9", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "illuminate/console": "^11.4|^12.0", + "illuminate/support": "^11.4|^12.0", + "php": "^8.2" + }, + "require-dev": { + "laravel/pint": "^1.21" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\LaravelConsoleSummary\\LaravelConsoleSummaryServiceProvider" + ] + } + }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "NunoMaduro\\LaravelConsoleSummary\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "Library for handling version information and constraints", + "description": "A Beautiful Laravel Console Summary for your Laravel/Laravel Zero commands.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "laravel", + "laravel-zero", + "php", + "symfony" + ], "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "issues": "https://github.com/nunomaduro/laravel-console-summary/issues", + "source": "https://github.com/nunomaduro/laravel-console-summary" }, - "time": "2022-02-21T01:04:05+00:00" + "time": "2025-02-19T11:10:44+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", + "name": "nunomaduro/laravel-console-task", + "version": "v1.10.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + "url": "https://github.com/nunomaduro/laravel-console-task.git", + "reference": "9d11073ad8b0215c63a962250e2bf071611f975d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "url": "https://api.github.com/repos/nunomaduro/laravel-console-task/zipball/9d11073ad8b0215c63a962250e2bf071611f975d", + "reference": "9d11073ad8b0215c63a962250e2bf071611f975d", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "php": "^8.2" + }, + "require-dev": { + "pestphp/pest": "^3.7" }, "type": "library", "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" + "laravel": { + "providers": [ + "NunoMaduro\\LaravelConsoleTask\\LaravelConsoleTaskServiceProvider" + ] } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src/" + "NunoMaduro\\LaravelConsoleTask\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4391,66 +4555,65 @@ ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "Laravel Console Task is a output method for your Laravel/Laravel Zero commands.", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "artisan", + "cli", + "command-line", + "console", + "laravel", + "laravel-zero", + "php", + "symfony" ], "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + "issues": "https://github.com/nunomaduro/laravel-console-task/issues", + "source": "https://github.com/nunomaduro/laravel-console-task" }, - "time": "2020-06-27T09:03:43+00:00" + "time": "2025-02-19T11:02:37+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "5.6.6", + "name": "nunomaduro/laravel-desktop-notifier", + "version": "v2.9.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" + "url": "https://github.com/nunomaduro/laravel-desktop-notifier.git", + "reference": "4871ee90fff38fbe25a2b8f81b5daeedf98a3ed7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", - "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "url": "https://api.github.com/repos/nunomaduro/laravel-desktop-notifier/zipball/4871ee90fff38fbe25a2b8f81b5daeedf98a3ed7", + "reference": "4871ee90fff38fbe25a2b8f81b5daeedf98a3ed7", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.1", - "ext-filter": "*", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1 || ^2" + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "jolicode/jolinotif": "^2.5", + "php": "^8.2" }, "require-dev": { - "mockery/mockery": "~1.3.5 || ~1.6.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-webmozart-assert": "^1.2", - "phpunit/phpunit": "^9.5", - "psalm/phar": "^5.26" + "graham-campbell/testbench": "^5.7|^6.2", + "pestphp/pest": "^3.7" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.x-dev" + "laravel": { + "aliases": { + "Notifier": "NunoMaduro\\LaravelDesktopNotifier\\Facaces\\Notifier" + }, + "providers": [ + "NunoMaduro\\LaravelDesktopNotifier\\LaravelDesktopNotifierServiceProvider" + ] } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src" + "NunoMaduro\\LaravelDesktopNotifier\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4459,60 +4622,105 @@ ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "description": "Send notifications to your desktop from your Laravel commands. An JoliNotif wrapper for Laravel 5.", + "keywords": [ + "JoliNotif", + "Nuno Maduro", + "NunoMaduro", + "artisan", + "console", + "framework", + "laravel", + "notification", + "notifier", + "php", + "wrapper" + ], "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" + "issues": "https://github.com/nunomaduro/laravel-desktop-notifier/issues", + "source": "https://github.com/nunomaduro/laravel-desktop-notifier/tree/v2.9.0" }, - "time": "2025-12-22T21:13:58+00:00" + "time": "2025-02-19T11:22:09+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.12.0", + "name": "pestphp/pest", + "version": "v3.8.5", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + "url": "https://github.com/pestphp/pest.git", + "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", - "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "url": "https://api.github.com/repos/pestphp/pest/zipball/7796630eafcfd1c02660cecdde3bc6984fbf01f4", + "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.3 || ^8.0", - "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.18|^2.0" + "brianium/paratest": "^7.8.5", + "nunomaduro/collision": "^8.8.3", + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest-plugin": "^3.0.0", + "pestphp/pest-plugin-arch": "^3.1.1", + "pestphp/pest-plugin-mutate": "^3.0.5", + "php": "^8.2.0", + "phpunit/phpunit": "^11.5.50" + }, + "conflict": { + "filp/whoops": "<2.16.0", + "phpunit/phpunit": ">11.5.50", + "sebastian/exporter": "<6.0.0", + "webmozart/assert": "<1.11.0" }, "require-dev": { - "ext-tokenizer": "*", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" + "pestphp/pest-dev-tools": "^3.4.0", + "pestphp/pest-plugin-type-coverage": "^3.6.1", + "symfony/process": "^7.4.4" }, + "bin": [ + "bin/pest" + ], "type": "library", "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" + "pest": { + "plugins": [ + "Pest\\Mutate\\Plugins\\Mutate", + "Pest\\Plugins\\Configuration", + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], "psr-4": { - "phpDocumentor\\Reflection\\": "src" + "Pest\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4521,232 +4729,273 @@ ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v3.8.5" }, - "time": "2025-11-21T15:09:14+00:00" + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2026-01-28T01:33:45+00:00" }, { - "name": "phpoption/phpoption", - "version": "1.9.5", + "name": "pestphp/pest-plugin", + "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e79b26c65bc11c41093b10150c1341cc5cdbea83", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.2" + }, + "conflict": { + "pestphp/pest": "<3.0.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + "composer/composer": "^2.7.9", + "pestphp/pest": "^3.0.0", + "pestphp/pest-dev-tools": "^3.0.0" }, - "type": "library", + "type": "composer-plugin", "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "1.9-dev" - } + "class": "Pest\\Plugin\\Manager" }, "autoload": { "psr-4": { - "PhpOption\\": "src/PhpOption/" + "Pest\\Plugin\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh" - }, - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - } + "MIT" ], - "description": "Option Type for PHP", + "description": "The Pest plugin manager", "keywords": [ - "language", - "option", + "framework", + "manager", + "pest", "php", - "type" + "plugin", + "test", + "testing", + "unit" ], "support": { - "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + "source": "https://github.com/pestphp/pest-plugin/tree/v3.0.0" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", - "type": "tidelift" + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" } ], - "time": "2025-12-27T19:41:33+00:00" + "time": "2024-09-08T23:21:41+00:00" }, { - "name": "phpstan/phpdoc-parser", - "version": "2.3.2", + "name": "pestphp/pest-plugin-arch", + "version": "v3.1.1", "source": { "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/db7bd9cb1612b223e16618d85475c6f63b9c8daa", + "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "ta-tikoma/phpunit-architecture-test": "^0.8.4" }, "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" + "pestphp/pest": "^3.8.1", + "pestphp/pest-dev-tools": "^3.4.0" }, "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, "autoload": { + "files": [ + "src/Autoload.php" + ], "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] + "Pest\\Arch\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.1.1" }, - "time": "2026-01-25T14:56:51+00:00" + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-04-16T22:59:48+00:00" }, { - "name": "phpstan/phpstan", - "version": "2.1.38", + "name": "pestphp/pest-plugin-mutate", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mutate.git", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08" + }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", - "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08", "shasum": "" }, "require": { - "php": "^7.4|^8.0" + "nikic/php-parser": "^5.2.0", + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "psr/simple-cache": "^3.0.0" }, - "conflict": { - "phpstan/phpstan-shim": "*" + "require-dev": { + "pestphp/pest": "^3.0.8", + "pestphp/pest-dev-tools": "^3.0.0", + "pestphp/pest-plugin-type-coverage": "^3.0.0" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ] + "psr-4": { + "Pest\\Mutate\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPStan - PHP Static Analysis Tool", + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "Mutates your code to find untested cases", "keywords": [ - "dev", - "static analysis" + "framework", + "mutate", + "mutation", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" ], "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" + "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v3.0.5" }, "funding": [ { - "url": "https://github.com/ondrejmirtes", + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", "type": "github" }, { - "url": "https://github.com/phpstan", + "url": "https://github.com/nunomaduro", "type": "github" } ], - "time": "2026-01-30T17:12:46+00:00" + "time": "2024-09-22T07:54:40+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "11.0.12", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", + "ext-phar": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.7.0", - "php": ">=8.2", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-text-template": "^4.0.1", - "sebastian/code-unit-reverse-lookup": "^4.0.1", - "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.1", - "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.3.1" - }, - "require-dev": { - "phpunit/phpunit": "^11.5.46" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -4759,359 +5008,479 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", - "role": "lead" + "role": "Developer" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/theseer", "type": "github" - }, + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" }, { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", - "type": "tidelift" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "time": "2025-12-24T07:01:01+00:00" + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "5.1.1", + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.3" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", "keywords": [ - "filesystem", - "iterator" + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.6", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" }, { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", - "type": "tidelift" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "time": "2026-02-02T13:52:54+00:00" + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" + }, + "time": "2025-12-22T21:13:58+00:00" }, { - "name": "phpunit/php-invoker", - "version": "5.0.1", + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { - "php": ">=8.2" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^11.0" - }, - "suggest": { - "ext-pcntl": "*" + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:07:44+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { - "name": "phpunit/php-text-template", - "version": "4.0.1", + "name": "phpoption/phpoption", + "version": "1.9.5", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { - "php": ">=8.2" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "1.9-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Option Type for PHP", "keywords": [ - "template" + "language", + "option", + "php", + "type" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/GrahamCampbell", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" } ], - "time": "2024-07-03T05:08:43+00:00" + "time": "2025-12-27T19:41:33+00:00" }, { - "name": "phpunit/php-timer", - "version": "7.0.1", + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { - "php": ">=8.2" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.38", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", + "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", "autoload": { - "classmap": [ - "src/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } + "MIT" ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "timer" + "dev", + "static analysis" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", "type": "github" } ], - "time": "2024-07-03T05:09:35+00:00" + "time": "2026-01-30T17:12:46+00:00" }, { - "name": "phpunit/phpunit", - "version": "11.5.50", + "name": "phpunit/php-code-coverage", + "version": "11.0.12", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { "ext-dom": "*", - "ext-json": "*", "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", + "nikic/php-parser": "^5.7.0", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.12", "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", - "phpunit/php-timer": "^7.0.1", - "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.3", - "sebastian/diff": "^6.0.2", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.2", - "sebastian/global-state": "^7.0.2", - "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.3", + "sebastian/lines-of-code": "^3.0.1", "sebastian/version": "^5.0.2", - "staabm/side-effects-detector": "^1.0.5" + "theseer/tokenizer": "^1.3.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.46" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-main": "11.5-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], "classmap": [ "src/" ] @@ -5127,23 +5496,19 @@ "role": "lead" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "phpunit", + "coverage", "testing", "xunit" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, { "url": "https://github.com/sebastianbergmann", "type": "github" @@ -5157,399 +5522,399 @@ "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", "type": "tidelift" } ], - "time": "2026-01-27T05:59:18+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { - "name": "psr/clock", - "version": "1.0.0", + "name": "phpunit/php-file-iterator", + "version": "5.1.1", "source": { "type": "git", - "url": "https://github.com/php-fig/clock.git", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Clock\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for reading the clock.", - "homepage": "https://github.com/php-fig/clock", - "keywords": [ - "clock", - "now", - "psr", - "psr-20", - "time" - ], - "support": { - "issues": "https://github.com/php-fig/clock/issues", - "source": "https://github.com/php-fig/clock/tree/1.0.0" - }, - "time": "2022-11-25T14:36:26+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" + "php": ">=8.2" }, - "require": { - "php": ">=7.4.0" + "require-dev": { + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "5.1-dev" } }, "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "filesystem", + "iterator" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { - "name": "psr/http-client", - "version": "1.0.3", + "name": "phpunit/php-invoker", + "version": "5.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "http", - "http-client", - "psr", - "psr-18" + "process" ], "support": { - "source": "https://github.com/php-fig/http-client" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, - "time": "2023-09-23T14:17:50+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" }, { - "name": "psr/http-factory", - "version": "1.1.0", + "name": "phpunit/php-text-template", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" + "template" ], "support": { - "source": "https://github.com/php-fig/http-factory" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, - "time": "2024-04-15T12:06:14+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" }, { - "name": "psr/http-message", - "version": "2.0", + "name": "phpunit/php-timer", + "version": "7.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "7.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" + "timer" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, - "time": "2023-04-04T09:54:51+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" }, { - "name": "psr/log", - "version": "3.0.2", + "name": "phpunit/phpunit", + "version": "11.5.50", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", "shasum": "" }, "require": { - "php": ">=8.0.0" + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-main": "11.5-dev" } }, "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "log", - "psr", - "psr-3" + "phpunit", + "testing", + "xunit" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" }, - "time": "2024-09-11T13:17:53+00:00" + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:59:18+00:00" }, { - "name": "psr/simple-cache", - "version": "3.0.0", + "name": "psr/clock", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": "^7.0 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, "autoload": { "psr-4": { - "Psr\\SimpleCache\\": "src/" + "Psr\\Clock\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5562,45 +5927,48 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for simple caching", + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", "keywords": [ - "cache", - "caching", + "clock", + "now", "psr", - "psr-16", - "simple-cache" + "psr-20", + "time" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" }, - "time": "2021-10-29T13:26:27+00:00" + "time": "2022-11-25T14:36:26+00:00" }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", + { + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "shasum": "" }, "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" + "php": ">=7.2.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { - "files": [ - "src/getallheaders.php" - ] + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5608,65 +5976,49 @@ ], "authors": [ { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "A polyfill for getallheaders.", + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" }, - "time": "2019-03-08T08:55:37+00:00" + "time": "2019-01-08T18:20:26+00:00" }, { - "name": "ramsey/collection", - "version": "2.1.1", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "php": "^8.1" - }, - "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.45", - "fakerphp/faker": "^1.24", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^2.1", - "mockery/mockery": "^1.6", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpspec/prophecy-phpunit": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", - "ramsey/coding-standard": "^2.3", - "ramsey/conventional-commits": "^1.6", - "roave/security-advisories": "dev-latest" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" + "branch-alias": { + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Ramsey\\Collection\\": "src/" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5675,518 +6027,454 @@ ], "authors": [ { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "A PHP library for representing and manipulating collections.", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" + "http", + "http-client", + "psr", + "psr-18" ], "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.1" + "source": "https://github.com/php-fig/http-client" }, - "time": "2025-03-22T05:38:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { - "name": "ramsey/uuid", - "version": "4.9.2", + "name": "psr/http-factory", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "8429c78ca35a09f27565311b98101e2826affde0" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", - "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", - "php": "^8.0", - "ramsey/collection": "^1.2 || ^2.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "captainhook/captainhook": "^5.25", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "ergebnis/composer-normalize": "^2.47", - "mockery/mockery": "^1.6", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.6", - "php-mock/php-mock-mockery": "^1.5", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpbench/phpbench": "^1.2.14", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "slevomat/coding-standard": "^8.18", - "squizlabs/php_codesniffer": "^3.13" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { - "captainhook": { - "force-install": true + "branch-alias": { + "dev-master": "1.0.x-dev" } }, "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { - "Ramsey\\Uuid\\": "src/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "guid", - "identifier", - "uuid" + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" ], "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2025-12-14T04:43:48+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { - "name": "sebastian/cli-parser", - "version": "3.0.2", + "name": "psr/http-message", + "version": "2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:41:36+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { - "name": "sebastian/code-unit", - "version": "3.0.3", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", - "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.5" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2025-03-19T07:56:08+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "4.0.1", + "name": "psr/simple-cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:45:54+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { - "name": "sebastian/comparator", - "version": "6.3.3", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", - "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/diff": "^6.0", - "sebastian/exporter": "^6.0" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "^11.4" - }, - "suggest": { - "ext-bcmath": "For comparing BcMath\\Number objects" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.3-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "src/getallheaders.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], + "description": "A polyfill for getallheaders.", "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-01-24T09:26:40+00:00" + "time": "2019-03-08T08:55:37+00:00" }, { - "name": "sebastian/complexity", - "version": "4.0.1", + "name": "ramsey/collection", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.2" + "php": "^8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "4.0-dev" + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Ramsey\\Collection\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" ], - "time": "2024-07-03T04:49:50+00:00" + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" }, { - "name": "sebastian/diff", - "version": "6.0.2", + "name": "ramsey/uuid", + "version": "4.9.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "php": ">=8.2" + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" }, "require-dev": { - "phpunit/phpunit": "^11.0", - "symfony/process": "^4.2 || ^5" + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "6.0-dev" + "captainhook": { + "force-install": true } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } + "MIT" ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "guid", + "identifier", + "uuid" ], "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T04:53:05+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { - "name": "sebastian/environment", - "version": "7.2.1", + "name": "sebastian/cli-parser", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", - "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.3" - }, - "suggest": { - "ext-posix": "*" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.2-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6201,67 +6489,49 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", - "type": "tidelift" } ], - "time": "2025-05-21T11:55:47+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { - "name": "sebastian/exporter", - "version": "6.3.2", + "name": "sebastian/code-unit", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/recursion-context": "^6.0" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.3-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6276,83 +6546,49 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" } ], - "time": "2025-09-24T06:12:51+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { - "name": "sebastian/global-state", - "version": "7.0.2", + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" + "php": ">=8.2" }, "require-dev": { - "ext-dom": "*", "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -6370,15 +6606,12 @@ "email": "sebastian@phpunit.de" } ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -6386,33 +6619,39 @@ "type": "github" } ], - "time": "2024-07-03T04:57:36+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "3.0.1", + "name": "sebastian/comparator", + "version": "6.3.3", "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.2" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -6427,43 +6666,70 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2024-07-03T04:58:38+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "6.0.1", + "name": "sebastian/complexity", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { "phpunit/phpunit": "^11.0" @@ -6471,7 +6737,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -6486,15 +6752,16 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -6502,32 +6769,33 @@ "type": "github" } ], - "time": "2024-07-03T05:00:13+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { - "name": "sebastian/object-reflector", - "version": "4.0.1", + "name": "sebastian/diff", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6543,14 +6811,24 @@ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -6558,20 +6836,20 @@ "type": "github" } ], - "time": "2024-07-03T05:01:32+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { - "name": "sebastian/recursion-context", - "version": "6.0.3", + "name": "sebastian/environment", + "version": "7.2.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", - "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { @@ -6580,10 +6858,13 @@ "require-dev": { "phpunit/phpunit": "^11.3" }, + "suggest": { + "ext-posix": "*" + }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -6599,22 +6880,19 @@ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { @@ -6630,28 +6908,30 @@ "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", "type": "tidelift" } ], - "time": "2025-08-13T04:42:22+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { - "name": "sebastian/type", - "version": "5.1.3", + "name": "sebastian/exporter", + "version": "6.3.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", - "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { - "php": ">=8.2" + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "phpunit/phpunit": "^11.3" @@ -6659,7 +6939,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -6674,16 +6954,35 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { @@ -6699,33 +6998,39 @@ "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", "type": "tidelift" } ], - "time": "2025-08-09T06:55:48+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { - "name": "sebastian/version", - "version": "5.0.2", + "name": "sebastian/global-state", + "version": "7.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -6740,16 +7045,18 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -6757,570 +7064,460 @@ "type": "github" } ], - "time": "2024-10-09T05:16:32+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { - "name": "staabm/side-effects-detector", - "version": "1.0.5", + "name": "sebastian/lines-of-code", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/staabm/side-effects-detector.git", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.6", - "phpunit/phpunit": "^9.6.21", - "symfony/var-dumper": "^5.4.43", - "tomasvotruba/type-coverage": "1.0.0", - "tomasvotruba/unused-public": "1.0.0" + "phpunit/phpunit": "^11.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, "autoload": { "classmap": [ - "lib/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "A static analysis tool to detect side effects in PHP code", - "keywords": [ - "static analysis" + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { - "issues": "https://github.com/staabm/side-effects-detector/issues", - "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { - "url": "https://github.com/staabm", + "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2024-10-20T05:08:20+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { - "name": "symfony/clock", - "version": "v7.4.0", + "name": "sebastian/object-enumerator", + "version": "6.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/clock.git", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { "php": ">=8.2", - "psr/clock": "^1.0", - "symfony/polyfill-php83": "^1.28" + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, - "provide": { - "psr/clock-implementation": "1.0" + "require-dev": { + "phpunit/phpunit": "^11.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, "autoload": { - "files": [ - "Resources/now.php" - ], - "psr-4": { - "Symfony\\Component\\Clock\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Decouples applications from the system clock", - "homepage": "https://symfony.com", - "keywords": [ - "clock", - "psr20", - "time" - ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.0" + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { - "name": "symfony/console", - "version": "v7.4.4", + "name": "sebastian/object-reflector", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "php": ">=8.2" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "phpunit/phpunit": "^11.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { - "source": "https://github.com/symfony/console/tree/v7.4.4" + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "name": "sebastian/recursion-context", + "version": "6.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "files": [ - "function.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { - "name": "symfony/error-handler", - "version": "v7.4.4", + "name": "sebastian/type", + "version": "5.1.3", "source": { "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/polyfill-php85": "^1.32", - "symfony/var-dumper": "^6.4|^7.0|^8.0" - }, - "conflict": { - "symfony/deprecation-contracts": "<2.5", - "symfony/http-kernel": "<6.4" + "php": ">=8.2" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", - "symfony/webpack-encore-bundle": "^1.0|^2.0" + "phpunit/phpunit": "^11.3" }, - "bin": [ - "Resources/bin/patch-type-declarations" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides tools to manage errors and ease debugging PHP code", - "homepage": "https://symfony.com", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.4" + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", "type": "tidelift" } ], - "time": "2026-01-20T16:42:42+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v7.4.4", + "name": "sebastian/version", + "version": "5.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "php": ">=8.2" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2026-01-05T11:45:34+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, + "type": "library", "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } + "classmap": [ + "lib/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "MIT" ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", + "description": "A static analysis tool to detect side effects in PHP code", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "static analysis" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/staabm", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { - "name": "symfony/finder", - "version": "v7.4.5", + "name": "symfony/clock", + "version": "v7.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "url": "https://github.com/symfony/clock.git", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "provide": { + "psr/clock-implementation": "1.0" }, "type": "library", "autoload": { + "files": [ + "Resources/now.php" + ], "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\Clock\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7332,18 +7529,23 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Decouples applications from the system clock", "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/clock/tree/v7.4.0" }, "funding": [ { @@ -7363,46 +7565,46 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { - "name": "symfony/http-foundation", - "version": "v7.4.5", + "name": "symfony/error-handler", + "version": "v7.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" + "url": "https://github.com/symfony/error-handler.git", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "^1.1" + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "conflict": { - "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "doctrine/dbal": "^3.6|^4", - "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0" + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" + "Symfony\\Component\\ErrorHandler\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7422,10 +7624,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Defines an object-oriented layer for the HTTP specification", + "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" + "source": "https://github.com/symfony/error-handler/tree/v7.4.4" }, "funding": [ { @@ -7445,83 +7647,49 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-01-20T16:42:42+00:00" }, { - "name": "symfony/http-kernel", - "version": "v7.4.5", + "name": "symfony/event-dispatcher", + "version": "v7.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746", "shasum": "" }, "require": { "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^7.3|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/polyfill-ctype": "^1.8" + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/browser-kit": "<6.4", - "symfony/cache": "<6.4", - "symfony/config": "<6.4", - "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<6.4", - "symfony/flex": "<2.10", - "symfony/form": "<6.4", - "symfony/http-client": "<6.4", - "symfony/http-client-contracts": "<2.5", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", - "symfony/translation": "<6.4", - "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.4", - "symfony/validator": "<6.4", - "symfony/var-dumper": "<6.4", - "twig/twig": "<3.12" + "symfony/service-contracts": "<2.5" }, "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", + "psr/log": "^1|^2|^3", "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/css-selector": "^6.4|^7.0|^8.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^7.1|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/serializer": "^7.1|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" + "Symfony\\Component\\EventDispatcher\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7541,10 +7709,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a structured process for converting a Request into a Response", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" }, "funding": [ { @@ -7564,53 +7732,40 @@ "type": "tidelift" } ], - "time": "2026-01-28T10:33:42+00:00" + "time": "2026-01-05T11:45:34+00:00" }, { - "name": "symfony/mime", - "version": "v7.4.5", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=6", - "phpdocumentor/type-resolver": "<1.5.1", - "symfony/mailer": "<6.4", - "symfony/serializer": "<6.4.3|>7.0,<7.0.3" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1|^4", - "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7618,22 +7773,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Allows manipulating MIME messages", + "description": "Generic abstractions related to dispatching event", "homepage": "https://symfony.com", "keywords": [ - "mime", - "mime-type" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.5" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -7644,54 +7803,41 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "name": "symfony/finder", + "version": "v7.4.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "url": "https://github.com/symfony/finder.git", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" + "php": ">=8.2" }, - "suggest": { - "ext-ctype": "For best performance" + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7699,24 +7845,18 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -7736,42 +7876,50 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "name": "symfony/http-foundation", + "version": "v7.4.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" }, - "suggest": { - "ext-intl": "For best performance" + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7779,26 +7927,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" }, "funding": [ { @@ -7818,43 +7958,87 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", + "name": "symfony/http-kernel", + "version": "v7.4.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", "shasum": "" }, "require": { - "php": ">=7.2", - "symfony/polyfill-intl-normalizer": "^1.10" + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" }, - "suggest": { - "ext-intl": "For best performance" + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7862,30 +8046,18 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" }, "funding": [ { @@ -7905,44 +8077,52 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-01-28T10:33:42+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "name": "symfony/mime", + "version": "v7.4.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "url": "https://github.com/symfony/mime.git", + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, - "suggest": { - "ext-intl": "For best performance" + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Component\\Mime\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -7951,26 +8131,22 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Allows manipulating MIME messages", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" + "mime", + "mime-type" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/mime/tree/v7.4.5" }, "funding": [ { @@ -7990,31 +8166,28 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-01-27T08:59:58+00:00" }, { - "name": "symfony/polyfill-mbstring", + "name": "symfony/polyfill-intl-idn", "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { @@ -8028,7 +8201,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Intl\\Idn\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -8037,25 +8210,30 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "idn", + "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -8075,7 +8253,7 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-php80", @@ -8466,184 +8644,6 @@ ], "time": "2026-01-26T15:07:59+00:00" }, - { - "name": "symfony/service-contracts", - "version": "v3.6.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-07-15T11:30:57+00:00" - }, - { - "name": "symfony/string", - "version": "v7.4.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.5" - }, - "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v7.4.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-01-12T10:54:30+00:00" - }, { "name": "symfony/translation", "version": "v7.4.4", diff --git a/tests/Feature/BuildCommandModeTest.php b/tests/Feature/BuildCommandModeTest.php index 7bdaade..94e10eb 100644 --- a/tests/Feature/BuildCommandModeTest.php +++ b/tests/Feature/BuildCommandModeTest.php @@ -53,7 +53,7 @@ function mockAgentRunnerForModeTest(): void '--iterations' => 1, '--delay' => 0, ]) - ->expectsOutputToContain('Mode: Yolo'); + ->expectsOutputToContain('yolo'); }); it('respects explicit --mode flag over settings', function () { @@ -72,7 +72,7 @@ function mockAgentRunnerForModeTest(): void '--iterations' => 1, '--delay' => 0, ]) - ->expectsOutputToContain('Mode: Yolo'); + ->expectsOutputToContain('yolo'); }); it('falls back to interactive when no settings file exists', function () { @@ -124,7 +124,7 @@ function mockAgentRunnerForModeTest(): void '--iterations' => 1, '--delay' => 0, ]) - ->expectsOutputToContain('Mode: Plan'); + ->expectsOutputToContain('plan'); // Restore user settings if ($backupPath !== null) { @@ -169,18 +169,13 @@ function mockAgentRunnerForModeTest(): void '--iterations' => 1, '--delay' => 0, ]) - ->expectsOutputToContain('Mode: Accept'); + ->expectsOutputToContain('accept'); }); it('uses resolveModeOption method for mode resolution', function () { - // Verify resolveModeOption method exists and is properly documented $reflection = new ReflectionClass(BuildCommand::class); $method = $reflection->getMethod('resolveModeOption'); - expect($method->isPrivate())->toBeTrue(); - - $docComment = $method->getDocComment(); - expect($docComment)->toContain('precedence') - ->and($docComment)->toContain('CLI flag') - ->and($docComment)->toContain('settings'); + expect($method->isPrivate())->toBeTrue() + ->and($method->getNumberOfParameters())->toBe(1); }); diff --git a/tests/Feature/BuildCommandTest.php b/tests/Feature/BuildCommandTest.php index fc80eb8..284d1db 100644 --- a/tests/Feature/BuildCommandTest.php +++ b/tests/Feature/BuildCommandTest.php @@ -57,7 +57,7 @@ function mockAgentRunner(): void file_put_contents($tasksPath, json_encode([ 'title' => 'Test Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'completed'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'completed'], ], ])); @@ -71,8 +71,8 @@ function mockAgentRunner(): void file_put_contents($tasksPath, json_encode([ 'title' => 'Test Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'completed'], - ['id' => 2, 'description' => 'Task 2', 'status' => 'completed'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'completed'], + ['id' => 2, 'title' => 'Task 2', 'status' => 'completed'], ], ])); @@ -80,7 +80,7 @@ function mockAgentRunner(): void ->assertSuccessful() ->expectsOutputToContain('Test Feature') ->expectsOutputToContain('100%') - ->expectsOutputToContain('2/2 completed'); + ->expectsOutputToContain('2/2'); }); it('respects max iterations option', function () { @@ -88,9 +88,9 @@ function mockAgentRunner(): void file_put_contents($tasksPath, json_encode([ 'title' => 'Test Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'pending'], - ['id' => 2, 'description' => 'Task 2', 'status' => 'pending'], - ['id' => 3, 'description' => 'Task 3', 'status' => 'pending'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'pending'], + ['id' => 2, 'title' => 'Task 2', 'status' => 'pending'], + ['id' => 3, 'title' => 'Task 3', 'status' => 'pending'], ], ])); @@ -106,46 +106,37 @@ function mockAgentRunner(): void }); it('invokes claude subprocess for pending tasks', function () { - // Note: This test verifies the command display output since the actual - // subprocess uses proc_open for TTY passthrough, which can't be mocked. - // The command output shows the full claude invocation including tasks path. $tasksPath = $this->testPath.'/.laracode/specs/test-feature/tasks.json'; file_put_contents($tasksPath, json_encode([ 'title' => 'Test Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'pending'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'pending'], ], ])); - // The command displays what it's running, verify tasks path is included $this->artisan('build', [ 'path' => $tasksPath, '--iterations' => 1, '--delay' => 0, ]) - ->expectsOutputToContain('Running: claude /build-next '.$tasksPath) - ->expectsOutputToContain('Next Task: #1 - Task 1'); + ->expectsOutputToContain('Task #1: Task 1'); })->skip('Subprocess test requires real claude CLI - covered by integration tests'); it('handles claude subprocess errors gracefully', function () { - // Note: Error handling test requires real subprocess execution. - // The current implementation uses proc_open for TTY passthrough - // and subprocess errors are displayed directly to the terminal. $tasksPath = $this->testPath.'/.laracode/specs/test-feature/tasks.json'; file_put_contents($tasksPath, json_encode([ 'title' => 'Test Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'pending'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'pending'], ], ])); - // Verify command structure is correct $this->artisan('build', [ 'path' => $tasksPath, '--iterations' => 1, '--delay' => 0, ]) - ->expectsOutputToContain('Running: claude /build-next '.$tasksPath); + ->expectsOutputToContain('Task #1: Task 1'); })->skip('Subprocess error test requires real claude CLI - covered by integration tests'); it('displays branch name when present in tasks.json', function () { @@ -154,13 +145,13 @@ function mockAgentRunner(): void 'title' => 'Test Feature', 'branch' => 'feature/test-branch', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'completed'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'completed'], ], ])); $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful() - ->expectsOutputToContain('Branch: feature/test-branch'); + ->expectsOutputToContain('feature/test-branch'); }); it('displays created date when present in tasks.json', function () { @@ -169,13 +160,13 @@ function mockAgentRunner(): void 'title' => 'Test Feature', 'created' => '2026-01-12T10:30:00Z', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'completed'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'completed'], ], ])); $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful() - ->expectsOutputToContain('Created: 2026-01-12'); + ->expectsOutputToContain('Test Feature'); }); it('displays blocked task count when tasks have unsatisfied dependencies', function () { @@ -189,7 +180,6 @@ function mockAgentRunner(): void ], ])); - // Task 2 is blocked because task 3 is not completed $this->artisan('build', ['path' => $tasksPath, '--iterations' => 0]) ->expectsOutputToContain('1 blocked'); }); @@ -206,7 +196,6 @@ function mockAgentRunner(): void ], ])); - // Tasks 2, 3, 4 are blocked (task 1 not completed) $this->artisan('build', ['path' => $tasksPath, '--iterations' => 0]) ->expectsOutputToContain('3 blocked'); }); @@ -221,7 +210,6 @@ function mockAgentRunner(): void ], ])); - // Task 2 is not blocked because task 1 is completed $this->artisan('build', ['path' => $tasksPath, '--iterations' => 0]) ->doesntExpectOutputToContain('blocked'); }); @@ -250,7 +238,6 @@ function mockAgentRunner(): void ->assertSuccessful() ->expectsOutputToContain('New Schema Feature') ->expectsOutputToContain('feature/new-schema') - ->expectsOutputToContain('Created: 2026-01-12') ->expectsOutputToContain('100%'); }); @@ -269,8 +256,6 @@ function mockAgentRunner(): void ], ])); - // The command displays "Next Task: #id - title" - // Since proc_open can't be mocked, we test the full schema support $this->artisan('build', ['path' => $tasksPath, '--iterations' => 0]) ->expectsOutputToContain('Test Feature'); }); @@ -281,7 +266,7 @@ function mockAgentRunner(): void file_put_contents($tasksPath, json_encode([ 'title' => 'Test Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'pending'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'pending'], ], ])); @@ -302,7 +287,7 @@ function mockAgentRunner(): void file_put_contents($tasksPath, json_encode([ 'title' => 'Test Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'pending'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'pending'], ], ])); @@ -318,7 +303,6 @@ function mockAgentRunner(): void }); it('computes lock path correctly next to tasks.json', function () { - // Create tasks in a nested directory structure $nestedPath = $this->testPath.'/.laracode/specs/deeply/nested/feature'; mkdir($nestedPath, 0755, true); $tasksPath = $nestedPath.'/tasks.json'; @@ -327,16 +311,15 @@ function mockAgentRunner(): void file_put_contents($tasksPath, json_encode([ 'title' => 'Nested Feature', 'tasks' => [ - ['id' => 1, 'description' => 'Task 1', 'status' => 'completed'], + ['id' => 1, 'title' => 'Task 1', 'status' => 'completed'], ], ])); $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful(); - // Verify lock file would be created in the correct location (dirname of tasks.json) - expect(dirname($tasksPath))->toEqual($nestedPath); - expect($expectedLockPath)->toEqual($nestedPath.'/index.lock'); + expect(dirname($tasksPath))->toEqual($nestedPath) + ->and($expectedLockPath)->toEqual($nestedPath.'/index.lock'); }); it('displays final stats when all tasks are completed with stats data', function () { @@ -375,7 +358,7 @@ function mockAgentRunner(): void $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful() ->expectsOutputToContain('All tasks completed') - ->expectsOutputToContain('Build Statistics') + ->expectsOutputToContain('Build Complete') ->expectsOutputToContain('7m 0s') ->expectsOutputToContain('5') ->expectsOutputToContain('+120') @@ -393,7 +376,7 @@ function mockAgentRunner(): void $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful() - ->expectsOutputToContain('Build Statistics') + ->expectsOutputToContain('Build Complete') ->expectsOutputToContain('0s') ->expectsOutputToContain('+0') ->expectsOutputToContain('-0'); @@ -416,16 +399,13 @@ function mockAgentRunner(): void $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful() ->expectsOutputToContain('45s') - ->doesntExpectOutputToContain('0m'); + ->doesntExpectOutputToContain('0m 45s'); }); it('cleans up stale completion signal files on startup', function () { - // This tests that completion signal files are properly cleaned up - // The actual stats update integration requires Claude CLI to run $tasksPath = $this->testPath.'/.laracode/specs/test-feature/tasks.json'; $completedPath = $this->testPath.'/.laracode/specs/test-feature/completed.json'; - // Create tasks file with all tasks completed file_put_contents($tasksPath, json_encode([ 'title' => 'Stats Integration Test', 'tasks' => [ @@ -450,20 +430,16 @@ function mockAgentRunner(): void ], ])); - // Create a stale completion signal file (from a previous interrupted run) file_put_contents($completedPath, json_encode([ 'taskId' => 1, 'startedAt' => '2026-01-14T10:00:00+00:00', 'completedAt' => '2026-01-14T10:05:00+00:00', ])); - // Run build - exits immediately since all tasks complete $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful() ->expectsOutputToContain('All tasks completed'); - // Stale completion file should still exist since we never entered the loop - // (This is expected behavior - cleanup only happens when processing iterations) expect(file_exists($completedPath))->toBeTrue(); })->skip('Completion signal processing requires active build loop - covered by integration tests'); @@ -532,7 +508,7 @@ function mockAgentRunner(): void $this->artisan('build', ['path' => $tasksPath]) ->assertSuccessful() - ->expectsOutputToContain('Build Statistics') + ->expectsOutputToContain('Build Complete') ->expectsOutputToContain('4') ->expectsOutputToContain('+100') ->expectsOutputToContain('-25'); diff --git a/tests/Feature/ShowCommandTest.php b/tests/Feature/ShowCommandTest.php new file mode 100644 index 0000000..e4bfd9a --- /dev/null +++ b/tests/Feature/ShowCommandTest.php @@ -0,0 +1,26 @@ +artisan('show --help') + ->assertSuccessful() + ->expectsOutputToContain('Monitor all active build sessions'); +}); + +it('can be resolved from the container', function () { + $command = app(\App\Commands\ShowCommand::class); + + expect($command)->toBeInstanceOf(\App\Commands\ShowCommand::class); +}); + +it('receives SessionRegistry as dependency', function () { + $command = app(\App\Commands\ShowCommand::class); + + $property = new ReflectionProperty($command, 'registry'); + $registry = $property->getValue($command); + + expect($registry)->toBeInstanceOf(SessionRegistry::class); +}); diff --git a/tests/Unit/Tui/Components/HeaderBarTest.php b/tests/Unit/Tui/Components/HeaderBarTest.php new file mode 100644 index 0000000..3da235e --- /dev/null +++ b/tests/Unit/Tui/Components/HeaderBarTest.php @@ -0,0 +1,60 @@ + 'Test', 'branch' => 'main', 'tasks' => []], + iteration: $extra['iteration'] ?? 1, + maxIterations: $extra['maxIterations'] ?? 10, + elapsed: $extra['elapsed'] ?? 0, + activeTaskId: null, + mode: 'normal', + statusMessage: '', + ); +} + +describe('HeaderBar', function () { + it('renders laracode title with running badge', function () { + $html = (new HeaderBar)->render(makeHeaderState()); + + expect($html)->toContain('laracode') + ->and($html)->toContain('[Running]'); + }); + + it('renders iteration counter', function () { + $html = (new HeaderBar)->render(makeHeaderState([ + 'iteration' => 3, + 'maxIterations' => 8, + ])); + + expect($html)->toContain('text-cyan-400') + ->and($html)->toContain('3/8'); + }); + + it('renders time in seconds when under one minute', function () { + $html = (new HeaderBar)->render(makeHeaderState(['elapsed' => 45])); + + expect($html)->toContain('text-yellow-400') + ->and($html)->toContain('45s') + ->and($html)->not->toContain('m45s'); + }); + + it('renders time with minutes and seconds', function () { + $html = (new HeaderBar)->render(makeHeaderState(['elapsed' => 125])); + + expect($html)->toContain('text-yellow-400') + ->and($html)->toContain('2m5s'); + }); + + it('renders zero elapsed as 0s', function () { + $html = (new HeaderBar)->render(makeHeaderState(['elapsed' => 0])); + + expect($html)->toContain('text-yellow-400') + ->and($html)->toContain('0s'); + }); +}); diff --git a/tests/Unit/Tui/Components/KeyHelpTest.php b/tests/Unit/Tui/Components/KeyHelpTest.php new file mode 100644 index 0000000..17a7456 --- /dev/null +++ b/tests/Unit/Tui/Components/KeyHelpTest.php @@ -0,0 +1,54 @@ +render('list'); + + expect($html)->toContain('Navigate') + ->and($html)->toContain('Details') + ->and($html)->toContain('Quit') + ->and($html)->toContain('↑↓') + ->and($html)->toContain('Enter') + ->and($html)->toContain('q'); + }); + + it('renders detail view hints with back and quit', function () { + $html = (new KeyHelp)->render('detail'); + + expect($html)->toContain('Back') + ->and($html)->toContain('Quit') + ->and($html)->toContain('Esc') + ->and($html)->toContain('q'); + }); + + it('renders key names with text-cyan-400 highlight', function () { + $html = (new KeyHelp)->render('list'); + + expect($html)->toContain('text-cyan-400'); + }); + + it('renders with text-gray bottom bar styling and no background', function () { + $html = (new KeyHelp)->render('list'); + + expect($html)->toContain('text-gray') + ->and($html)->not->toContain('bg-'); + }); + + it('defaults to list hints for unknown view', function () { + $html = (new KeyHelp)->render('something-else'); + + expect($html)->toContain('Navigate') + ->and($html)->toContain('Details'); + }); + + it('detail view does not contain list-specific hints', function () { + $html = (new KeyHelp)->render('detail'); + + expect($html)->not->toContain('Navigate') + ->and($html)->not->toContain('Details'); + }); +}); diff --git a/tests/Unit/Tui/Components/ProgressBarTest.php b/tests/Unit/Tui/Components/ProgressBarTest.php new file mode 100644 index 0000000..9d4ce66 --- /dev/null +++ b/tests/Unit/Tui/Components/ProgressBarTest.php @@ -0,0 +1,97 @@ + array_merge([ + 'id' => 1, + 'status' => 'pending', + 'title' => 'Task', + 'dependencies' => [], + ], $t), $tasks); + + return DashboardState::fromTasksArray( + data: ['title' => 'Test', 'branch' => 'main', 'tasks' => $fullTasks], + iteration: 1, + maxIterations: 10, + elapsed: 0, + activeTaskId: null, + mode: 'normal', + statusMessage: '', + ); +} + +describe('ProgressBar', function () { + it('renders 0% for no completed tasks', function () { + $state = makeProgressState([ + ['id' => 1, 'status' => 'pending'], + ['id' => 2, 'status' => 'pending'], + ]); + + $html = (new ProgressBar)->render($state); + + expect($html)->toContain('0%') + ->and($html)->toContain('0/2') + ->and($html)->toContain('░'); + }); + + it('renders 100% for all completed tasks', function () { + $state = makeProgressState([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'completed'], + ]); + + $html = (new ProgressBar)->render($state); + + expect($html)->toContain('100%') + ->and($html)->toContain('2/2') + ->and($html)->toContain('█'); + }); + + it('renders partial progress', function () { + $state = makeProgressState([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'pending'], + ]); + + $html = (new ProgressBar)->render($state); + + expect($html)->toContain('50%') + ->and($html)->toContain('1/2') + ->and($html)->toContain('█') + ->and($html)->toContain('░'); + }); + + it('renders empty bar for zero tasks', function () { + $state = makeProgressState([]); + + $html = (new ProgressBar)->render($state); + + expect($html)->toContain('0%') + ->and($html)->toContain('0/0'); + }); + + it('uses green color for filled portion', function () { + $state = makeProgressState([ + ['id' => 1, 'status' => 'completed'], + ]); + + $html = (new ProgressBar)->render($state); + + expect($html)->toContain('text-green-400'); + }); + + it('uses gray color for empty portion', function () { + $state = makeProgressState([ + ['id' => 1, 'status' => 'pending'], + ]); + + $html = (new ProgressBar)->render($state); + + expect($html)->toContain('text-gray'); + }); +}); diff --git a/tests/Unit/Tui/Components/SessionListTest.php b/tests/Unit/Tui/Components/SessionListTest.php new file mode 100644 index 0000000..a2f4a4c --- /dev/null +++ b/tests/Unit/Tui/Components/SessionListTest.php @@ -0,0 +1,457 @@ +tempDir = sys_get_temp_dir().'/laracode-sessionlist-test-'.uniqid(); + mkdir($this->tempDir, 0755, true); + $this->component = new SessionList; +}); + +afterEach(function () { + $files = glob($this->tempDir.'/*'); + foreach ($files as $file) { + unlink($file); + } + if (is_dir($this->tempDir)) { + rmdir($this->tempDir); + } +}); + +describe('empty sessions', function () { + it('renders no active sessions message when list is empty', function () { + $html = $this->component->render([], 0); + + expect($html)->toContain('No active sessions') + ->and($html)->toContain('text-gray'); + }); +}); + +describe('session rendering', function () { + it('renders 4 div lines when active task present', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Working'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + // 1 wrapper div + 4 line divs + expect(substr_count($html, '
    tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + // 1 wrapper div + 3 line divs (no line 4 when idle) + expect(substr_count($html, '
    tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'My Feature Implementation', + 'branch' => 'main', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('▸') + ->and($html)->toContain('My Feature Implementation'); + }); + + it('renders full untruncated title even when very long', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + $longTitle = str_repeat('A', 80); + file_put_contents($tasksPath, json_encode([ + 'title' => $longTitle, + 'branch' => 'main', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain($longTitle); + }); + + it('renders left-truncated path with branch on line 2', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'feature/cool', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/home/user/projects/myproject'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('/home/user/projects/myproject') + ->and($html)->toContain('(feature/cool)'); + }); + + it('left-truncates long paths with ellipsis prefix', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [], + ])); + + $longPath = '/home/user/very/deeply/nested/directory/structure/that/goes/on/and/on/project'; + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => $longPath], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('…') + ->and($html)->not->toContain($longPath) + ->and($html)->toContain('project'); + }); + + it('renders progress and elapsed time on line 3', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'completed', 'title' => 'Done'], + ['id' => 2, 'status' => 'completed', 'title' => 'Done too'], + ['id' => 3, 'status' => 'pending', 'title' => 'Not yet'], + ['id' => 4, 'status' => 'pending', 'title' => 'Also not yet'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('Tasks:') + ->and($html)->toMatch('/text-yellow-400[^>]*>2\/4/') + ->and($html)->toContain('Status:') + ->and($html)->toContain('Elapsed:') + ->and($html)->toMatch('/text-yellow-400[^>]*>\d+stempDir.'/tasks.json'; + $longTask = str_repeat('B', 60); + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'in_progress', 'title' => $longTask], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain($longTask) + ->and($html)->toContain('text-cyan-400'); + }); + + it('renders status label active when task in_progress', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Working'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toMatch('/text-cyan-400[^>]*>activetempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'completed', 'title' => 'Done'], + ['id' => 2, 'status' => 'completed', 'title' => 'Also done'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toMatch('/text-green-400[^>]*>completetempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'completed', 'title' => 'Done'], + ['id' => 2, 'status' => 'pending', 'title' => 'Waiting'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toMatch('/text-cyan-400[^>]*>runningtempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Selected', + 'branch' => 'main', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('▸') + ->and($html)->toMatch('/font-bold[^>]*>Selected/') + ->and(substr_count($html, 'text-white'))->toBe(3) + ->and($html)->not->toContain('bg-'); + }); + + it('does not highlight non-selected rows', function () { + $tasksPath1 = $this->tempDir.'/tasks1.json'; + $tasksPath2 = $this->tempDir.'/tasks2.json'; + file_put_contents($tasksPath1, json_encode(['title' => 'First', 'branch' => 'main', 'tasks' => []])); + file_put_contents($tasksPath2, json_encode(['title' => 'Second', 'branch' => 'dev', 'tasks' => []])); + + $sessions = [ + ['tasksPath' => $tasksPath1, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/a'], + ['tasksPath' => $tasksPath2, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/b'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('First') + ->and($html)->toContain('Second') + ->and(substr_count($html, 'text-white'))->toBe(3); + }); + + it('hides unknown branch', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'unknown', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->not->toContain('unknown'); + }); + + it('hides empty branch', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => '', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->not->toMatch('/text-cyan-400[^>]*>\(/'); + }); + + it('renders branch in parentheses in cyan on line 2', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'feature/xyz', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toMatch('/text-cyan-400[^>]*>\(feature\/xyz\)/'); + }); + + it('adds mb-1 spacing on last line of each card', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('mb-1') + ->and($html)->not->toContain('mb-2'); + }); + + it('does not render percentage label', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'completed', 'title' => 'Done'], + ['id' => 2, 'status' => 'pending', 'title' => 'Not yet'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->not->toContain('50%') + ->and($html)->toContain('1/2'); + }); +}); + +describe('edge cases', function () { + it('handles missing tasks file gracefully', function () { + $sessions = [ + ['tasksPath' => '/nonexistent/tasks.json', 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('Untitled') + ->and($html)->not->toContain('unknown'); + }); + + it('handles corrupt JSON in tasks file', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, 'not valid json'); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('Untitled'); + }); + + it('handles empty tasks file', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, ''); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('Untitled'); + }); + + it('renders 0/0 progress when tasks array is empty', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Empty', + 'branch' => 'main', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('Tasks:') + ->and($html)->toContain('0/0'); + }); + + it('renders all completed progress', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Done', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'completed', 'title' => 'A'], + ['id' => 2, 'status' => 'completed', 'title' => 'B'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('Tasks:') + ->and($html)->toMatch('/text-green-400[^>]*>2\/2/'); + }); +}); diff --git a/tests/Unit/Tui/Components/StatusBarTest.php b/tests/Unit/Tui/Components/StatusBarTest.php new file mode 100644 index 0000000..fac0992 --- /dev/null +++ b/tests/Unit/Tui/Components/StatusBarTest.php @@ -0,0 +1,52 @@ + 'Test', 'branch' => $branch, 'tasks' => []], + iteration: 1, + maxIterations: 10, + elapsed: 0, + activeTaskId: null, + mode: $mode, + statusMessage: $statusMessage, + ); +} + +describe('StatusBar', function () { + it('renders status message', function () { + $html = (new StatusBar)->render(makeStatusBarState('Building task 3...')); + + expect($html)->toContain('Building task 3...'); + }); + + it('renders mode and branch', function () { + $html = (new StatusBar)->render(makeStatusBarState(mode: 'yolo', branch: 'feature/tui')); + + expect($html)->toContain('text-yellow-400') + ->and($html)->toContain('yolo') + ->and($html)->toContain('text-cyan-400') + ->and($html)->toContain('feature/tui'); + }); + + it('separates mode and branch with dot separator', function () { + $html = (new StatusBar)->render(makeStatusBarState(mode: 'accept', branch: 'main')); + + expect($html)->toContain('text-yellow-400') + ->and($html)->toContain('accept') + ->and($html)->toContain('·') + ->and($html)->toContain('text-cyan-400') + ->and($html)->toContain('main'); + }); + + it('renders empty status message', function () { + $html = (new StatusBar)->render(makeStatusBarState('')); + + expect($html)->toContain(''); + }); +}); diff --git a/tests/Unit/Tui/Components/TaskDetailTest.php b/tests/Unit/Tui/Components/TaskDetailTest.php new file mode 100644 index 0000000..d10da97 --- /dev/null +++ b/tests/Unit/Tui/Components/TaskDetailTest.php @@ -0,0 +1,129 @@ + 'Test', 'branch' => 'main', 'tasks' => $tasks], + iteration: 1, + maxIterations: 10, + elapsed: 0, + activeTaskId: $activeTaskId, + mode: 'normal', + statusMessage: '', + ); +} + +describe('TaskDetail', function () { + it('shows fallback when no active task', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'pending', 'title' => 'Task'], + ]); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('No task in progress'); + }); + + it('renders active task title in bold cyan', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Build the widget', 'description' => 'desc', 'priority' => 2], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('font-bold text-cyan-400') + ->and($html)->toContain('Build the widget'); + }); + + it('renders in_progress status badge', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Task', 'description' => '', 'priority' => 3], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('text-cyan-400 font-bold') + ->and($html)->toContain('in_progress') + ->and($html)->not->toContain('bg-'); + }); + + it('renders completed status badge', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'completed', 'title' => 'Task', 'description' => '', 'priority' => 3], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('text-green-400 font-bold') + ->and($html)->toContain('completed') + ->and($html)->not->toContain('bg-'); + }); + + it('renders priority value in yellow', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Task', 'description' => '', 'priority' => 1], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('Priority:') + ->and($html)->toMatch('/1<\/span>/'); + }); + + it('renders description text in white', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Task', 'description' => 'Some detailed desc', 'priority' => 3], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('Some detailed desc') + ->and($html)->toContain('text-white'); + }); + + it('renders acceptance criteria with green checkmarks', function () { + $state = makeDetailState([ + [ + 'id' => 1, + 'status' => 'in_progress', + 'title' => 'Task', + 'description' => '', + 'priority' => 3, + 'acceptance' => ['Tests pass', 'No regressions'], + ], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('Acceptance:') + ->and($html)->toContain('Tests pass') + ->and($html)->toContain('No regressions') + ->and($html)->toContain('text-green-400') + ->and($html)->toContain('✓'); + }); + + it('omits acceptance section when empty', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Task', 'description' => '', 'priority' => 3], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->not->toContain('Acceptance:'); + }); + + it('falls back to description when title missing', function () { + $state = makeDetailState([ + ['id' => 1, 'status' => 'in_progress', 'description' => 'Fallback desc', 'priority' => 3], + ], activeTaskId: 1); + + $html = (new TaskDetail)->render($state); + + expect($html)->toContain('Fallback desc'); + }); +}); diff --git a/tests/Unit/Tui/Components/TaskListTest.php b/tests/Unit/Tui/Components/TaskListTest.php new file mode 100644 index 0000000..41f977a --- /dev/null +++ b/tests/Unit/Tui/Components/TaskListTest.php @@ -0,0 +1,119 @@ + array_merge([ + 'id' => 1, + 'status' => 'pending', + 'title' => 'Task', + 'dependencies' => [], + ], $t), $tasks); + + return DashboardState::fromTasksArray( + data: ['title' => 'Test', 'branch' => 'main', 'tasks' => $fullTasks], + iteration: 1, + maxIterations: 10, + elapsed: 0, + activeTaskId: $activeTaskId, + mode: 'normal', + statusMessage: '', + ); +} + +describe('TaskList', function () { + it('renders completed task with green filled icon and green title', function () { + $state = makeTaskListState([ + ['id' => 1, 'status' => 'completed', 'title' => 'Done task'], + ]); + + $html = (new TaskList)->render($state); + + expect($html)->toContain('text-green-400') + ->and($html)->toContain('●') + ->and($html)->toContain('US-1') + ->and($html)->toContain('Done task') + ->and($html)->toMatch('/Done task<\/span>/'); + }); + + it('renders in_progress task with cyan filled icon and cyan bold title', function () { + $state = makeTaskListState([ + ['id' => 2, 'status' => 'in_progress', 'title' => 'Active task'], + ]); + + $html = (new TaskList)->render($state); + + expect($html)->toContain('text-cyan-400') + ->and($html)->toContain('●') + ->and($html)->toMatch('/Active task<\/span>/'); + }); + + it('renders pending task with gray open icon', function () { + $state = makeTaskListState([ + ['id' => 1, 'status' => 'pending', 'title' => 'Waiting', 'dependencies' => []], + ]); + + $html = (new TaskList)->render($state); + + expect($html)->toContain('text-gray') + ->and($html)->toContain('○'); + }); + + it('renders blocked task with red open icon and red title', function () { + $state = makeTaskListState([ + ['id' => 1, 'status' => 'pending', 'title' => 'Unblocked', 'dependencies' => []], + ['id' => 2, 'status' => 'pending', 'title' => 'Blocked', 'dependencies' => [1]], + ]); + + $html = (new TaskList)->render($state); + + expect($html)->toContain('text-red-400') + ->and($html)->toContain('Blocked') + ->and($html)->toMatch('/Blocked<\/span>/'); + }); + + it('highlights active task row with text-white', function () { + $state = makeTaskListState([ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Active'], + ['id' => 2, 'status' => 'pending', 'title' => 'Other'], + ], activeTaskId: 1); + + $html = (new TaskList)->render($state); + + expect($html)->toContain('text-white') + ->and($html)->not->toContain('bg-'); + }); + + it('renders task id prefix as gray US-N', function () { + $state = makeTaskListState([ + ['id' => 42, 'status' => 'pending', 'title' => 'My task'], + ]); + + $html = (new TaskList)->render($state); + + expect($html)->toContain('US-42') + ->and($html)->toMatch('/US-42<\/span>/'); + }); + + it('falls back to description when title is missing', function () { + $state = makeTaskListState([ + ['id' => 1, 'status' => 'pending', 'description' => 'Fallback desc'], + ]); + + // Remove 'title' key — the factory sets it, so we build manually + $tasks = [['id' => 1, 'status' => 'pending', 'description' => 'Fallback desc', 'dependencies' => []]]; + $manualState = DashboardState::fromTasksArray( + data: ['title' => 'Test', 'branch' => 'main', 'tasks' => $tasks], + iteration: 1, maxIterations: 10, elapsed: 0, + activeTaskId: null, mode: 'normal', statusMessage: '', + ); + + $html = (new TaskList)->render($manualState); + + expect($html)->toContain('Fallback desc'); + }); +}); diff --git a/tests/Unit/Tui/DashboardStateTest.php b/tests/Unit/Tui/DashboardStateTest.php new file mode 100644 index 0000000..f938268 --- /dev/null +++ b/tests/Unit/Tui/DashboardStateTest.php @@ -0,0 +1,297 @@ + array_merge([ + 'id' => 1, + 'status' => 'pending', + 'title' => 'Task', + 'description' => 'Description', + 'dependencies' => [], + 'priority' => 3, + ], $task), $overrides); +} + +function makeState(array $tasks = [], array $extra = []): DashboardState +{ + $data = array_merge([ + 'title' => 'Test Feature', + 'branch' => 'feature/test', + 'tasks' => $tasks, + ], $extra); + + return DashboardState::fromTasksArray( + data: $data, + iteration: $extra['iteration'] ?? 1, + maxIterations: $extra['maxIterations'] ?? 10, + elapsed: $extra['elapsed'] ?? 60, + activeTaskId: $extra['activeTaskId'] ?? null, + mode: $extra['mode'] ?? 'normal', + statusMessage: $extra['statusMessage'] ?? 'Running', + ); +} + +describe('fromTasksArray', function () { + it('extracts featureTitle and branch from data', function () { + $state = makeState(); + + expect($state->featureTitle)->toBe('Test Feature') + ->and($state->branch)->toBe('feature/test'); + }); + + it('defaults featureTitle to Untitled when missing', function () { + $state = DashboardState::fromTasksArray( + data: ['branch' => 'main'], + iteration: 1, + maxIterations: 5, + elapsed: 0, + activeTaskId: null, + mode: 'normal', + statusMessage: '', + ); + + expect($state->featureTitle)->toBe('Untitled'); + }); + + it('defaults branch to unknown when missing', function () { + $state = DashboardState::fromTasksArray( + data: ['title' => 'Foo'], + iteration: 1, + maxIterations: 5, + elapsed: 0, + activeTaskId: null, + mode: 'normal', + statusMessage: '', + ); + + expect($state->branch)->toBe('unknown'); + }); + + it('defaults tasks to empty array when missing', function () { + $state = DashboardState::fromTasksArray( + data: [], + iteration: 1, + maxIterations: 5, + elapsed: 0, + activeTaskId: null, + mode: 'normal', + statusMessage: '', + ); + + expect($state->tasks)->toBe([]); + }); + + it('passes through iteration, timing, mode, and status', function () { + $state = makeState([], [ + 'iteration' => 3, + 'maxIterations' => 8, + 'elapsed' => 120, + 'activeTaskId' => 5, + 'mode' => 'yolo', + 'statusMessage' => 'Building...', + ]); + + expect($state->currentIteration)->toBe(3) + ->and($state->maxIterations)->toBe(8) + ->and($state->elapsedSeconds)->toBe(120) + ->and($state->activeTaskId)->toBe(5) + ->and($state->mode)->toBe('yolo') + ->and($state->statusMessage)->toBe('Building...'); + }); +}); + +describe('completedCount', function () { + it('counts completed tasks', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'completed'], + ['id' => 3, 'status' => 'pending'], + ['id' => 4, 'status' => 'in_progress'], + ]); + + expect(makeState($tasks)->completedCount())->toBe(2); + }); + + it('returns zero when none completed', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'pending'], + ['id' => 2, 'status' => 'in_progress'], + ]); + + expect(makeState($tasks)->completedCount())->toBe(0); + }); +}); + +describe('pendingCount', function () { + it('counts pending tasks', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'pending'], + ['id' => 3, 'status' => 'pending'], + ['id' => 4, 'status' => 'in_progress'], + ]); + + expect(makeState($tasks)->pendingCount())->toBe(2); + }); + + it('returns zero when none pending', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'in_progress'], + ]); + + expect(makeState($tasks)->pendingCount())->toBe(0); + }); +}); + +describe('blockedCount', function () { + it('counts tasks blocked by unsatisfied dependencies', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'pending', 'dependencies' => []], + ['id' => 2, 'status' => 'pending', 'dependencies' => [1]], + ['id' => 3, 'status' => 'pending', 'dependencies' => [1]], + ]); + + expect(makeState($tasks)->blockedCount())->toBe(2); + }); + + it('returns zero when all pending tasks have satisfied deps', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed', 'dependencies' => []], + ['id' => 2, 'status' => 'pending', 'dependencies' => [1]], + ['id' => 3, 'status' => 'pending', 'dependencies' => []], + ]); + + expect(makeState($tasks)->blockedCount())->toBe(0); + }); + + it('does not count completed or in_progress tasks as blocked', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed', 'dependencies' => [99]], + ['id' => 2, 'status' => 'in_progress', 'dependencies' => [99]], + ]); + + expect(makeState($tasks)->blockedCount())->toBe(0); + }); + + it('counts task blocked when any dependency unsatisfied', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed', 'dependencies' => []], + ['id' => 2, 'status' => 'pending', 'dependencies' => []], + ['id' => 3, 'status' => 'pending', 'dependencies' => [1, 2]], + ]); + + expect(makeState($tasks)->blockedCount())->toBe(1); + }); + + it('treats tasks without dependencies as not blocked', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'pending'], + ['id' => 2, 'status' => 'pending'], + ]); + + expect(makeState($tasks)->blockedCount())->toBe(0); + }); +}); + +describe('totalCount', function () { + it('returns total number of tasks', function () { + $tasks = makeTasks([ + ['id' => 1], + ['id' => 2], + ['id' => 3], + ]); + + expect(makeState($tasks)->totalCount())->toBe(3); + }); + + it('returns zero for empty task list', function () { + expect(makeState([])->totalCount())->toBe(0); + }); +}); + +describe('progressPercent', function () { + it('calculates percentage of completed tasks', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'completed'], + ['id' => 3, 'status' => 'pending'], + ['id' => 4, 'status' => 'pending'], + ]); + + expect(makeState($tasks)->progressPercent())->toBe(50.0); + }); + + it('returns 0 when no tasks exist', function () { + expect(makeState([])->progressPercent())->toBe(0.0); + }); + + it('returns 100 when all tasks completed', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'completed'], + ]); + + expect(makeState($tasks)->progressPercent())->toBe(100.0); + }); + + it('returns 0 when no tasks completed', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'pending'], + ['id' => 2, 'status' => 'pending'], + ]); + + expect(makeState($tasks)->progressPercent())->toBe(0.0); + }); + + it('rounds to one decimal', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed'], + ['id' => 2, 'status' => 'pending'], + ['id' => 3, 'status' => 'pending'], + ]); + + expect(makeState($tasks)->progressPercent())->toBe(33.3); + }); +}); + +describe('activeTask', function () { + it('returns the active task by id', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'completed', 'title' => 'First'], + ['id' => 2, 'status' => 'in_progress', 'title' => 'Second'], + ['id' => 3, 'status' => 'pending', 'title' => 'Third'], + ]); + + $state = makeState($tasks, ['activeTaskId' => 2]); + + expect($state->activeTask())->not->toBeNull() + ->and($state->activeTask()['id'])->toBe(2) + ->and($state->activeTask()['title'])->toBe('Second'); + }); + + it('returns null when activeTaskId is null', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'pending'], + ]); + + $state = makeState($tasks, ['activeTaskId' => null]); + + expect($state->activeTask())->toBeNull(); + }); + + it('returns null when activeTaskId does not match any task', function () { + $tasks = makeTasks([ + ['id' => 1, 'status' => 'pending'], + ['id' => 2, 'status' => 'pending'], + ]); + + $state = makeState($tasks, ['activeTaskId' => 99]); + + expect($state->activeTask())->toBeNull(); + }); +}); diff --git a/tests/Unit/Tui/SessionRegistryTest.php b/tests/Unit/Tui/SessionRegistryTest.php new file mode 100644 index 0000000..79d1e65 --- /dev/null +++ b/tests/Unit/Tui/SessionRegistryTest.php @@ -0,0 +1,199 @@ +tempDir = sys_get_temp_dir().'/laracode-test-'.uniqid(); + mkdir($this->tempDir, 0755, true); + $this->registryPath = $this->tempDir.'/sessions.json'; + $this->registry = new SessionRegistry($this->registryPath); +}); + +afterEach(function () { + if (file_exists($this->registryPath)) { + unlink($this->registryPath); + } + if (is_dir($this->tempDir)) { + rmdir($this->tempDir); + } +}); + +describe('register', function () { + it('creates registry file and adds session entry', function () { + $this->registry->register('/path/to/tasks.json', getmypid(), 'normal', '/project'); + + expect(file_exists($this->registryPath))->toBeTrue(); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1) + ->and($data['sessions'][0]['tasksPath'])->toBe('/path/to/tasks.json') + ->and($data['sessions'][0]['pid'])->toBe(getmypid()) + ->and($data['sessions'][0]['mode'])->toBe('normal') + ->and($data['sessions'][0]['projectPath'])->toBe('/project') + ->and($data['sessions'][0]['startedAt'])->toBeString(); + }); + + it('adds multiple sessions', function () { + $this->registry->register('/path/a/tasks.json', getmypid(), 'normal', '/project-a'); + $this->registry->register('/path/b/tasks.json', getmypid(), 'yolo', '/project-b'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(2) + ->and($data['sessions'][0]['tasksPath'])->toBe('/path/a/tasks.json') + ->and($data['sessions'][1]['tasksPath'])->toBe('/path/b/tasks.json'); + }); + + it('replaces existing entry with same tasksPath', function () { + $this->registry->register('/path/tasks.json', 100, 'normal', '/project'); + $this->registry->register('/path/tasks.json', 200, 'yolo', '/project-new'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1) + ->and($data['sessions'][0]['pid'])->toBe(200) + ->and($data['sessions'][0]['mode'])->toBe('yolo') + ->and($data['sessions'][0]['projectPath'])->toBe('/project-new'); + }); +}); + +describe('deregister', function () { + it('removes correct session by tasksPath', function () { + $this->registry->register('/path/a/tasks.json', getmypid(), 'normal', '/project-a'); + $this->registry->register('/path/b/tasks.json', getmypid(), 'yolo', '/project-b'); + + $this->registry->deregister('/path/a/tasks.json'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1) + ->and($data['sessions'][0]['tasksPath'])->toBe('/path/b/tasks.json'); + }); + + it('does nothing when tasksPath not found', function () { + $this->registry->register('/path/tasks.json', getmypid(), 'normal', '/project'); + + $this->registry->deregister('/nonexistent/tasks.json'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1); + }); + + it('handles deregister on empty registry', function () { + $this->registry->deregister('/path/tasks.json'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(0); + }); +}); + +describe('getActiveSessions', function () { + it('returns sessions with live PIDs', function () { + $this->registry->register('/path/tasks.json', getmypid(), 'normal', '/project'); + + $active = $this->registry->getActiveSessions(); + + expect($active)->toHaveCount(1) + ->and($active[0]['pid'])->toBe(getmypid()); + }); + + it('filters out dead PIDs', function () { + $this->registry->register('/path/live.json', getmypid(), 'normal', '/project-live'); + $this->registry->register('/path/dead.json', 999999, 'yolo', '/project-dead'); + + $active = $this->registry->getActiveSessions(); + + expect($active)->toHaveCount(1) + ->and($active[0]['tasksPath'])->toBe('/path/live.json'); + }); + + it('returns empty array when no sessions exist', function () { + expect($this->registry->getActiveSessions())->toBe([]); + }); + + it('returns empty array when all PIDs are dead', function () { + $this->registry->register('/path/a.json', 999998, 'normal', '/a'); + $this->registry->register('/path/b.json', 999999, 'normal', '/b'); + + expect($this->registry->getActiveSessions())->toBe([]); + }); +}); + +describe('cleanup', function () { + it('removes all dead PID entries', function () { + $this->registry->register('/path/live.json', getmypid(), 'normal', '/project-live'); + $this->registry->register('/path/dead1.json', 999998, 'normal', '/dead1'); + $this->registry->register('/path/dead2.json', 999999, 'yolo', '/dead2'); + + $this->registry->cleanup(); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1) + ->and($data['sessions'][0]['tasksPath'])->toBe('/path/live.json'); + }); + + it('results in empty sessions when all PIDs are dead', function () { + $this->registry->register('/path/a.json', 999998, 'normal', '/a'); + $this->registry->register('/path/b.json', 999999, 'normal', '/b'); + + $this->registry->cleanup(); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(0); + }); +}); + +describe('edge cases', function () { + it('handles missing registry file on read', function () { + expect($this->registry->getActiveSessions())->toBe([]); + }); + + it('handles corrupt JSON in registry', function () { + file_put_contents($this->registryPath, 'not valid json {{{'); + + expect($this->registry->getActiveSessions())->toBe([]); + }); + + it('handles empty registry file', function () { + file_put_contents($this->registryPath, ''); + + expect($this->registry->getActiveSessions())->toBe([]); + }); + + it('recovers from corrupt JSON on write operations', function () { + file_put_contents($this->registryPath, 'corrupt data'); + + $this->registry->register('/path/tasks.json', getmypid(), 'normal', '/project'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1) + ->and($data['sessions'][0]['tasksPath'])->toBe('/path/tasks.json'); + }); + + it('creates directory if missing', function () { + $nestedDir = $this->tempDir.'/nested/deep'; + $nestedPath = $nestedDir.'/sessions.json'; + $registry = new SessionRegistry($nestedPath); + + expect(is_dir($nestedDir))->toBeTrue(); + + // cleanup + if (file_exists($nestedPath)) { + unlink($nestedPath); + } + rmdir($nestedDir); + rmdir($this->tempDir.'/nested'); + }); + + it('exposes registry path via getRegistryPath', function () { + expect($this->registry->getRegistryPath())->toBe($this->registryPath); + }); +}); From 7fea9c65abdd67a5709c8ddff4c6bd81ef311fd7 Mon Sep 17 00:00:00 2001 From: Raphael Owino Date: Fri, 27 Feb 2026 05:17:55 +0300 Subject: [PATCH 2/2] Add persistent sessions --- app/Commands/BuildCommand.php | 8 +- app/Commands/ShowCommand.php | 117 +++++++++++++++- app/Tui/Components/KeyHelp.php | 41 ++++-- app/Tui/Components/SessionList.php | 34 ++++- app/Tui/SessionRegistry.php | 50 ++++++- app/Tui/Terminal/FocusResult.php | 33 +++++ app/Tui/Terminal/ItermStrategy.php | 112 +++++++++++++++ app/Tui/Terminal/TerminalFocuser.php | 45 ++++++ app/Tui/Terminal/TerminalStrategy.php | 12 ++ app/Tui/Terminal/TmuxStrategy.php | 114 +++++++++++++++ tests/Feature/BuildCommandModeTest.php | 10 ++ tests/Feature/BuildCommandTest.php | 10 ++ tests/Unit/Tui/Components/KeyHelpTest.php | 52 +++++++ tests/Unit/Tui/Components/SessionListTest.php | 128 +++++++++++++---- tests/Unit/Tui/SessionRegistryTest.php | 131 +++++++++++++++--- tests/Unit/Tui/Terminal/FocusResultTest.php | 35 +++++ tests/Unit/Tui/Terminal/ItermStrategyTest.php | 25 ++++ .../Unit/Tui/Terminal/TerminalFocuserTest.php | 88 ++++++++++++ tests/Unit/Tui/Terminal/TmuxStrategyTest.php | 25 ++++ 19 files changed, 997 insertions(+), 73 deletions(-) create mode 100644 app/Tui/Terminal/FocusResult.php create mode 100644 app/Tui/Terminal/ItermStrategy.php create mode 100644 app/Tui/Terminal/TerminalFocuser.php create mode 100644 app/Tui/Terminal/TerminalStrategy.php create mode 100644 app/Tui/Terminal/TmuxStrategy.php create mode 100644 tests/Unit/Tui/Terminal/FocusResultTest.php create mode 100644 tests/Unit/Tui/Terminal/ItermStrategyTest.php create mode 100644 tests/Unit/Tui/Terminal/TerminalFocuserTest.php create mode 100644 tests/Unit/Tui/Terminal/TmuxStrategyTest.php diff --git a/app/Commands/BuildCommand.php b/app/Commands/BuildCommand.php index 6f75690..5d7ae11 100644 --- a/app/Commands/BuildCommand.php +++ b/app/Commands/BuildCommand.php @@ -4,6 +4,7 @@ namespace App\Commands; +use App\Agents\AgentRegistry; use App\Enums\BuildMode; use App\Services\AgentRunner; use App\Services\Settings\SettingsService; @@ -32,6 +33,7 @@ public function __construct( private DashboardRenderer $renderer, private TaskSelector $taskSelector, private SessionRegistry $registry, + private AgentRegistry $agentRegistry, ) { parent::__construct(); } @@ -95,7 +97,7 @@ public function handle(): int $lockPath = dirname($realTasksPath ?: $tasksPath).'/index.lock'; $canonicalTasksPath = $realTasksPath ?: $tasksPath; - $this->registry->register($canonicalTasksPath, (int) getmypid(), $mode->value, $projectPath); + $this->registry->register($canonicalTasksPath, (int) getmypid(), $mode->value, $this->agentRegistry->getDefaultName(), $projectPath); $this->renderDashboard($tasks, 0, $maxIterations, $startTime, null, $mode); @@ -124,7 +126,7 @@ public function handle(): int $nextTask = $this->taskSelector->selectNextTask($tasks['tasks']); if ($nextTask === null) { - $this->registry->deregister($canonicalTasksPath); + $this->registry->markCompleted($canonicalTasksPath); $this->renderDashboard($tasks, $iteration, $maxIterations, $startTime, null, $mode, 'All tasks completed!'); $this->captureTermwindOutput(fn () => $this->renderer->renderFinalStats($tasks)); @@ -169,7 +171,7 @@ public function handle(): int } } - $this->registry->deregister($canonicalTasksPath); + $this->registry->markCompleted($canonicalTasksPath); $this->renderDashboard($tasks, $iteration, $maxIterations, $startTime, null, $mode, "Reached max iterations ({$maxIterations})"); return self::SUCCESS; diff --git a/app/Commands/ShowCommand.php b/app/Commands/ShowCommand.php index ff83642..9c01e42 100644 --- a/app/Commands/ShowCommand.php +++ b/app/Commands/ShowCommand.php @@ -12,6 +12,7 @@ use App\Tui\Components\TaskList; use App\Tui\DashboardState; use App\Tui\SessionRegistry; +use App\Tui\Terminal\TerminalFocuser; use LaravelZero\Framework\Commands\Command; use Symfony\Component\Console\Output\BufferedOutput; @@ -31,13 +32,15 @@ class ShowCommand extends Command private int $selectedIndex = 0; - /** @var array */ + /** @var array */ private array $sessions = []; private bool $running = true; private int $exitCode = self::SUCCESS; + private ?string $flashMessage = null; + public function __construct( private SessionRegistry $registry, private SessionList $sessionList, @@ -46,13 +49,14 @@ public function __construct( private TaskDetail $taskDetail, private ProgressBar $progressBar, private StatusBar $statusBar, + private TerminalFocuser $terminalFocuser, ) { parent::__construct(); } public function handle(): int { - $this->sessions = $this->registry->getActiveSessions(); + $this->sessions = $this->registry->getSessions(); $this->setupTerminal(); $this->registerSignalHandlers(); @@ -73,7 +77,7 @@ public function handle(): int $now = time(); if ($now - $lastRefresh >= 2) { - $this->sessions = $this->registry->getActiveSessions(); + $this->sessions = $this->registry->getSessions(); $sessionCount = count($this->sessions); if ($this->selectedIndex >= $sessionCount) { $this->selectedIndex = max(0, $sessionCount - 1); @@ -161,6 +165,8 @@ private function readKey(): ?string 'q' => 'quit', 'j' => 'down', 'k' => 'up', + 'd' => 'dismiss', + 'f' => 'focus', "\n", "\r" => 'enter', "\x7f" => 'backspace', default => null, @@ -188,6 +194,8 @@ private function handleListKey(string $key): bool 'up' => $this->moveSelection(-1), 'down' => $this->moveSelection(1), 'enter' => $this->enterDetailView(), + 'dismiss' => $this->dismissSession(), + 'focus' => $this->focusSession(), default => false, }; } @@ -196,6 +204,7 @@ private function handleDetailKey(string $key): bool { return match ($key) { 'esc', 'backspace' => $this->backToList(), + 'dismiss' => $this->dismissSession(), default => false, }; } @@ -235,6 +244,69 @@ private function backToList(): bool return true; } + private function dismissSession(): bool + { + if (! isset($this->sessions[$this->selectedIndex])) { + return false; + } + + $session = $this->sessions[$this->selectedIndex]; + $isDismissable = $session['status'] === 'completed' + || $session['status'] === 'crashed' + || ! $this->isProcessAlive($session['pid']); + + if (! $isDismissable) { + return false; + } + + $this->registry->deregister($session['tasksPath']); + $this->sessions = $this->registry->getSessions(); + + $sessionCount = count($this->sessions); + if ($this->selectedIndex >= $sessionCount) { + $this->selectedIndex = max(0, $sessionCount - 1); + } + + if ($this->view === 'detail' && $this->sessions === []) { + $this->view = 'list'; + } + + $this->flashMessage = 'Session dismissed'; + + return true; + } + + private function focusSession(): bool + { + if (! isset($this->sessions[$this->selectedIndex])) { + return false; + } + + $session = $this->sessions[$this->selectedIndex]; + + if ($session['status'] !== 'running' || ! $this->isProcessAlive($session['pid'])) { + return false; + } + + $result = $this->terminalFocuser->focus($session['pid']); + $this->flashMessage = $result->message; + + return true; + } + + private function isProcessAlive(int $pid): bool + { + if ($pid <= 0) { + return false; + } + + if (! function_exists('posix_kill')) { + return file_exists("/proc/$pid"); + } + + return posix_kill($pid, 0); + } + private function renderCurrentView(): void { ob_start(); @@ -263,10 +335,22 @@ private function renderListView(): void
    HTML; + $canDismiss = false; + $canFocus = false; + if (isset($this->sessions[$this->selectedIndex])) { + $selected = $this->sessions[$this->selectedIndex]; + $canDismiss = $selected['status'] === 'completed' + || $selected['status'] === 'crashed' + || ! $this->isProcessAlive($selected['pid']); + $canFocus = $selected['status'] === 'running' && $this->isProcessAlive($selected['pid']); + } + $sessionListHtml = $this->sessionList->render($this->sessions, $this->selectedIndex); - $keyHelpHtml = $this->keyHelp->render('list'); + $keyHelpHtml = $this->keyHelp->render('list', $canDismiss, $canFocus); - $this->renderHtml("
    {$header}{$sessionListHtml}{$keyHelpHtml}
    "); + $flashHtml = $this->consumeFlashHtml(); + + $this->renderHtml("
    {$header}{$sessionListHtml}{$flashHtml}{$keyHelpHtml}
    "); } private function renderDetailView(): void @@ -304,6 +388,12 @@ private function renderDetailView(): void
    HTML; + $canDismiss = $session['status'] === 'completed' + || $session['status'] === 'crashed' + || ! $this->isProcessAlive($session['pid']); + + $flashHtml = $this->consumeFlashHtml(); + $html = '
    ' .implode("\n", [ $header, @@ -312,15 +402,28 @@ private function renderDetailView(): void '
    ', $this->taskDetail->render($state), $this->statusBar->render($state), - $this->keyHelp->render('detail'), + $flashHtml, + $this->keyHelp->render('detail', $canDismiss), ]) .'
    '; $this->renderHtml($html); } + private function consumeFlashHtml(): string + { + if ($this->flashMessage === null) { + return ''; + } + + $message = htmlspecialchars($this->flashMessage); + $this->flashMessage = null; + + return "
    {$message}
    "; + } + /** - * @param array{tasksPath: string, pid: int, startedAt: string, mode: string, projectPath: string} $session + * @param array{tasksPath: string, pid: int, startedAt: string, mode: string, agent: string, projectPath: string, status: string, completedAt?: string} $session */ private function buildDashboardState(array $session): ?DashboardState { diff --git a/app/Tui/Components/KeyHelp.php b/app/Tui/Components/KeyHelp.php index 3039f13..bcdca6d 100644 --- a/app/Tui/Components/KeyHelp.php +++ b/app/Tui/Components/KeyHelp.php @@ -9,11 +9,11 @@ */ class KeyHelp { - public function render(string $view): string + public function render(string $view, bool $canDismiss = false, bool $canFocus = false): string { $hints = match ($view) { - 'detail' => $this->detailHints(), - default => $this->listHints(), + 'detail' => $this->detailHints($canDismiss), + default => $this->listHints($canDismiss, $canFocus), }; return <<↑↓ Navigate' - .' ' - .'Enter Details' + $hints = '↑↓ Navigate' .' ' + .'Enter Details'; + + if ($canDismiss) { + $hints .= ' ' + .'d Dismiss'; + } + + if ($canFocus) { + $hints .= ' ' + .'f Focus'; + } + + $hints .= ' ' .'q Quit'; + + return $hints; } - private function detailHints(): string + private function detailHints(bool $canDismiss): string { - return 'Esc Back' - .' ' + $hints = 'Esc Back'; + + if ($canDismiss) { + $hints .= ' ' + .'d Dismiss'; + } + + $hints .= ' ' .'q Quit'; + + return $hints; } } diff --git a/app/Tui/Components/SessionList.php b/app/Tui/Components/SessionList.php index 7715f00..df38509 100644 --- a/app/Tui/Components/SessionList.php +++ b/app/Tui/Components/SessionList.php @@ -10,7 +10,7 @@ class SessionList { /** - * @param array $sessions + * @param array $sessions */ public function render(array $sessions, int $selectedIndex): string { @@ -33,7 +33,7 @@ public function render(array $sessions, int $selectedIndex): string } /** - * @param array{tasksPath: string, pid: int, startedAt: string, mode: string, projectPath: string} $session + * @param array{tasksPath: string, pid: int, startedAt: string, mode: string, agent: string, projectPath: string, status?: string, completedAt?: string} $session */ private function renderRow(array $session, bool $selected): string { @@ -57,14 +57,17 @@ private function renderRow(array $session, bool $selected): string : ''; $activeTaskName = $this->activeTaskName($tasks); - $statusLabel = $this->sessionStatus($tasks); + $registryStatus = $session['status'] ?? 'running'; + $statusLabel = $this->sessionStatus($tasks, $registryStatus, (int) $session['pid']); $selectedClass = $selected ? 'text-white' : ''; $line1 = "{$selector} {$title}"; $line2 = " {$path}{$branchHtml}"; $tasksColor = ($total > 0 && $completed === $total) ? 'text-green-400' : 'text-yellow-400'; - $line3 = " Tasks: {$completed}/{$total}Status: {$statusLabel}Elapsed: {$elapsed}"; + $modeLabel = htmlspecialchars($session['mode']); + $agentLabel = htmlspecialchars($session['agent']); + $line3 = " Mode: {$modeLabel}Agent: {$agentLabel}Tasks: {$completed}/{$total}Status: {$statusLabel}Elapsed: {$elapsed}"; $html = "
    {$line1}
    " ."
    {$line2}
    "; @@ -128,8 +131,16 @@ private function activeTaskName(array $tasks): ?string /** * @param array $tasks */ - private function sessionStatus(array $tasks): string + private function sessionStatus(array $tasks, string $registryStatus, int $pid): string { + if ($registryStatus === 'completed') { + return 'complete'; + } + + if (! $this->isProcessAlive($pid)) { + return 'crashed'; + } + foreach ($tasks as $task) { if ($task['status'] === 'in_progress') { return 'active'; @@ -143,6 +154,19 @@ private function sessionStatus(array $tasks): string return 'running'; } + private function isProcessAlive(int $pid): bool + { + if ($pid <= 0) { + return false; + } + + if (! function_exists('posix_kill')) { + return file_exists("/proc/$pid"); + } + + return posix_kill($pid, 0); + } + /** * @return array{title?: string, branch?: string, tasks?: array} */ diff --git a/app/Tui/SessionRegistry.php b/app/Tui/SessionRegistry.php index ca0969e..389d4ff 100644 --- a/app/Tui/SessionRegistry.php +++ b/app/Tui/SessionRegistry.php @@ -23,9 +23,9 @@ public function __construct(?string $registryPath = null) $this->registryPath = $registryPath ?? $dir.'/sessions.json'; } - public function register(string $tasksPath, int $pid, string $mode, string $projectPath): void + public function register(string $tasksPath, int $pid, string $mode, string $agent, string $projectPath): void { - $this->withLock(function (array &$data) use ($tasksPath, $pid, $mode, $projectPath) { + $this->withLock(function (array &$data) use ($tasksPath, $pid, $mode, $agent, $projectPath) { $data['sessions'] = array_values(array_filter( $data['sessions'] ?? [], fn (array $session) => $session['tasksPath'] !== $tasksPath @@ -36,11 +36,50 @@ public function register(string $tasksPath, int $pid, string $mode, string $proj 'pid' => $pid, 'startedAt' => date('c'), 'mode' => $mode, + 'agent' => $agent, 'projectPath' => $projectPath, + 'status' => 'running', ]; }); } + public function markCompleted(string $tasksPath): void + { + $this->withLock(function (array &$data) use ($tasksPath) { + if (! isset($data['sessions'])) { + return; + } + + foreach ($data['sessions'] as $index => $session) { + if ($session['tasksPath'] === $tasksPath) { + $data['sessions'][$index]['status'] = 'completed'; + $data['sessions'][$index]['completedAt'] = date('c'); + + return; + } + } + }); + } + + /** + * @return array + */ + public function getSessions(): array + { + $data = $this->readRegistry(); + + return array_values(array_map( + function (array $session): array { + if ($session['status'] !== 'completed' && ! $this->isProcessAlive((int) $session['pid'])) { + $session['status'] = 'crashed'; + } + + return $session; + }, + $data['sessions'] ?? [] + )); + } + public function deregister(string $tasksPath): void { $this->withLock(function (array &$data) use ($tasksPath) { @@ -52,7 +91,7 @@ public function deregister(string $tasksPath): void } /** - * @return array + * @return array */ public function getActiveSessions(): array { @@ -69,7 +108,8 @@ public function cleanup(): void $this->withLock(function (array &$data) { $data['sessions'] = array_values(array_filter( $data['sessions'] ?? [], - fn (array $session) => $this->isProcessAlive((int) $session['pid']) + fn (array $session) => $session['status'] === 'completed' + || $this->isProcessAlive((int) $session['pid']) )); }); } @@ -80,7 +120,7 @@ public function getRegistryPath(): string } /** - * @return array{sessions?: array} + * @return array{sessions?: array} */ private function readRegistry(): array { diff --git a/app/Tui/Terminal/FocusResult.php b/app/Tui/Terminal/FocusResult.php new file mode 100644 index 0000000..75cd44f --- /dev/null +++ b/app/Tui/Terminal/FocusResult.php @@ -0,0 +1,33 @@ + */ + private array $env; + + /** @param array|null $env */ + public function __construct(?array $env = null) + { + $this->env = $env ?? getenv(); + } + + public function isAvailable(): bool + { + return ($this->env['TERM_PROGRAM'] ?? '') === 'iTerm.app'; + } + + public function focus(int $pid): FocusResult + { + $tty = $this->getTtyForPid($pid); + if ($tty === null) { + return FocusResult::notFound(); + } + + $script = $this->buildAppleScript($tty); + $output = []; + // Safe: AppleScript is built with escaped TTY path + exec('osascript -e '.escapeshellarg($script).' 2>/dev/null', $output, $exitCode); + + if ($exitCode !== 0) { + return FocusResult::error('Failed to focus iTerm2 session'); + } + + $result = trim(implode('', $output)); + + return $result === 'found' ? FocusResult::success() : FocusResult::notFound(); + } + + private function getTtyForPid(int $pid): ?string + { + $current = $pid; + $visited = []; + + while ($current > 1 && ! isset($visited[$current])) { + $visited[$current] = true; + + $output = []; + // Safe: $current is an integer, no injection risk + exec(sprintf('lsof -p %d -a -d 0 -Fn 2>/dev/null', $current), $output); + + foreach ($output as $line) { + if (str_starts_with($line, 'n') && str_contains($line, '/dev/ttys')) { + return substr($line, 1); + } + } + + $parent = $this->getParentPid($current); + if ($parent === null || $parent === $current) { + break; + } + + $current = $parent; + } + + return null; + } + + private function getParentPid(int $pid): ?int + { + $output = []; + // Safe: $pid is an integer, no injection risk + exec(sprintf('ps -o ppid= -p %d 2>/dev/null', $pid), $output); + + if ($output === []) { + return null; + } + + $ppid = (int) trim($output[0]); + + return $ppid > 0 ? $ppid : null; + } + + private function buildAppleScript(string $tty): string + { + $escapedTty = addslashes($tty); + + return << */ + private array $strategies; + + /** @param list|null $strategies */ + public function __construct(?array $strategies = null) + { + $this->strategies = $strategies ?? [ + new TmuxStrategy, + new ItermStrategy, + ]; + } + + public function focus(int $pid): FocusResult + { + foreach ($this->strategies as $strategy) { + if ($strategy->isAvailable()) { + return $strategy->focus($pid); + } + } + + return FocusResult::unsupported(); + } + + public function isSupported(): bool + { + foreach ($this->strategies as $strategy) { + if ($strategy->isAvailable()) { + return true; + } + } + + return false; + } +} diff --git a/app/Tui/Terminal/TerminalStrategy.php b/app/Tui/Terminal/TerminalStrategy.php new file mode 100644 index 0000000..50a5911 --- /dev/null +++ b/app/Tui/Terminal/TerminalStrategy.php @@ -0,0 +1,12 @@ + */ + private array $env; + + /** @param array|null $env */ + public function __construct(?array $env = null) + { + $this->env = $env ?? getenv(); + } + + public function isAvailable(): bool + { + return ! empty($this->env['TMUX']); + } + + public function focus(int $pid): FocusResult + { + $panes = $this->listPanes(); + if ($panes === []) { + return FocusResult::notFound(); + } + + $paneId = $this->findPaneForPid($pid, $panes); + if ($paneId === null) { + return FocusResult::notFound(); + } + + $this->selectPane($paneId); + + return FocusResult::success(); + } + + /** + * @return array Map of pane PID => pane ID + */ + private function listPanes(): array + { + $output = []; + // Safe: no user input in command + exec("tmux list-panes -a -F '#{pane_id} #{pane_pid}' 2>/dev/null", $output, $exitCode); + + if ($exitCode !== 0) { + return []; + } + + $panes = []; + foreach ($output as $line) { + $parts = explode(' ', trim($line), 2); + if (count($parts) === 2) { + $panes[(int) $parts[1]] = $parts[0]; + } + } + + return $panes; + } + + /** + * @param array $panes + */ + private function findPaneForPid(int $pid, array $panes): ?string + { + $current = $pid; + $visited = []; + + while ($current > 1 && ! isset($visited[$current])) { + if (isset($panes[$current])) { + return $panes[$current]; + } + + $visited[$current] = true; + $parent = $this->getParentPid($current); + + if ($parent === null || $parent === $current) { + break; + } + + $current = $parent; + } + + return null; + } + + private function getParentPid(int $pid): ?int + { + $output = []; + // Safe: $pid is an integer, no injection risk + exec(sprintf('ps -o ppid= -p %d 2>/dev/null', $pid), $output); + + if ($output === []) { + return null; + } + + $ppid = (int) trim($output[0]); + + return $ppid > 0 ? $ppid : null; + } + + private function selectPane(string $paneId): void + { + $escaped = escapeshellarg($paneId); + exec("tmux select-window -t $escaped 2>/dev/null"); + exec("tmux select-pane -t $escaped 2>/dev/null"); + } +} diff --git a/tests/Feature/BuildCommandModeTest.php b/tests/Feature/BuildCommandModeTest.php index 94e10eb..d98e39f 100644 --- a/tests/Feature/BuildCommandModeTest.php +++ b/tests/Feature/BuildCommandModeTest.php @@ -5,6 +5,7 @@ use App\Commands\BuildCommand; use App\Services\AgentRunner; use App\Services\Settings\SettingsService; +use App\Tui\SessionRegistry; use Illuminate\Support\Facades\File; function mockAgentRunnerForModeTest(): void @@ -17,11 +18,20 @@ function mockAgentRunnerForModeTest(): void (new ReflectionProperty(BuildCommand::class, 'agentRunner'))->setValue($command, $mock); } +function isolateSessionRegistryForModeTest(string $registryPath): void +{ + $kernel = app(Illuminate\Contracts\Console\Kernel::class); + $command = (new ReflectionMethod($kernel, 'getArtisan'))->invoke($kernel)->find('build'); + (new ReflectionProperty(BuildCommand::class, 'registry'))->setValue($command, new SessionRegistry($registryPath)); +} + beforeEach(function () { $this->testPath = sys_get_temp_dir().'/laracode-mode-test-'.uniqid(); mkdir($this->testPath.'/.laracode/specs/test-feature', 0755, true); mkdir($this->testPath.'/.claude', 0755, true); + isolateSessionRegistryForModeTest($this->testPath.'/.laracode/sessions.json'); + // Create valid tasks.json $this->tasksPath = $this->testPath.'/.laracode/specs/test-feature/tasks.json'; file_put_contents($this->tasksPath, json_encode([ diff --git a/tests/Feature/BuildCommandTest.php b/tests/Feature/BuildCommandTest.php index 284d1db..d591891 100644 --- a/tests/Feature/BuildCommandTest.php +++ b/tests/Feature/BuildCommandTest.php @@ -4,6 +4,7 @@ use App\Commands\BuildCommand; use App\Services\AgentRunner; +use App\Tui\SessionRegistry; use Illuminate\Support\Facades\File; function mockAgentRunner(): void @@ -16,10 +17,19 @@ function mockAgentRunner(): void (new ReflectionProperty(BuildCommand::class, 'agentRunner'))->setValue($command, $mock); } +function isolateSessionRegistry(string $registryPath): void +{ + $kernel = app(Illuminate\Contracts\Console\Kernel::class); + $command = (new ReflectionMethod($kernel, 'getArtisan'))->invoke($kernel)->find('build'); + (new ReflectionProperty(BuildCommand::class, 'registry'))->setValue($command, new SessionRegistry($registryPath)); +} + beforeEach(function () { $this->testPath = sys_get_temp_dir().'/laracode-build-test-'.uniqid(); mkdir($this->testPath.'/.laracode/specs/test-feature', 0755, true); mkdir($this->testPath.'/.claude', 0755, true); + + isolateSessionRegistry($this->testPath.'/.laracode/sessions.json'); }); afterEach(function () { diff --git a/tests/Unit/Tui/Components/KeyHelpTest.php b/tests/Unit/Tui/Components/KeyHelpTest.php index 17a7456..f9d7e46 100644 --- a/tests/Unit/Tui/Components/KeyHelpTest.php +++ b/tests/Unit/Tui/Components/KeyHelpTest.php @@ -51,4 +51,56 @@ expect($html)->not->toContain('Navigate') ->and($html)->not->toContain('Details'); }); + + it('shows dismiss hint in list view when canDismiss is true', function () { + $html = (new KeyHelp)->render('list', canDismiss: true); + + expect($html)->toContain('d') + ->and($html)->toContain('Dismiss'); + }); + + it('hides dismiss hint in list view when canDismiss is false', function () { + $html = (new KeyHelp)->render('list', canDismiss: false); + + expect($html)->not->toContain('Dismiss'); + }); + + it('shows focus hint in list view when canFocus is true', function () { + $html = (new KeyHelp)->render('list', canFocus: true); + + expect($html)->toContain('f') + ->and($html)->toContain('Focus'); + }); + + it('hides focus hint in list view when canFocus is false', function () { + $html = (new KeyHelp)->render('list', canFocus: false); + + expect($html)->not->toContain('Focus'); + }); + + it('shows dismiss hint in detail view when canDismiss is true', function () { + $html = (new KeyHelp)->render('detail', canDismiss: true); + + expect($html)->toContain('d') + ->and($html)->toContain('Dismiss'); + }); + + it('hides dismiss hint in detail view when canDismiss is false', function () { + $html = (new KeyHelp)->render('detail'); + + expect($html)->not->toContain('Dismiss'); + }); + + it('does not show focus hint in detail view even when canFocus is true', function () { + $html = (new KeyHelp)->render('detail', canFocus: true); + + expect($html)->not->toContain('Focus'); + }); + + it('preserves default behavior with no extra params', function () { + $html = (new KeyHelp)->render('list'); + + expect($html)->not->toContain('Dismiss') + ->and($html)->not->toContain('Focus'); + }); }); diff --git a/tests/Unit/Tui/Components/SessionListTest.php b/tests/Unit/Tui/Components/SessionListTest.php index a2f4a4c..f99d2d3 100644 --- a/tests/Unit/Tui/Components/SessionListTest.php +++ b/tests/Unit/Tui/Components/SessionListTest.php @@ -41,7 +41,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -59,7 +59,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -77,7 +77,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -96,7 +96,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -113,7 +113,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/home/user/projects/myproject'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/home/user/projects/myproject'], ]; $html = $this->component->render($sessions, 0); @@ -133,7 +133,7 @@ $longPath = '/home/user/very/deeply/nested/directory/structure/that/goes/on/and/on/project'; $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => $longPath], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => $longPath], ]; $html = $this->component->render($sessions, 0); @@ -143,6 +143,26 @@ ->and($html)->toContain('project'); }); + it('renders mode and agent on line 3', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Test', + 'branch' => 'main', + 'tasks' => [], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toContain('Mode:') + ->and($html)->toMatch('/text-cyan-400[^>]*>normaland($html)->toContain('Agent:') + ->and($html)->toMatch('/text-cyan-400[^>]*>claudetempDir.'/tasks.json'; file_put_contents($tasksPath, json_encode([ @@ -157,7 +177,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -181,7 +201,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -201,7 +221,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -221,7 +241,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -241,7 +261,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -258,7 +278,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -276,8 +296,8 @@ file_put_contents($tasksPath2, json_encode(['title' => 'Second', 'branch' => 'dev', 'tasks' => []])); $sessions = [ - ['tasksPath' => $tasksPath1, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/a'], - ['tasksPath' => $tasksPath2, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/b'], + ['tasksPath' => $tasksPath1, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/a'], + ['tasksPath' => $tasksPath2, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/b'], ]; $html = $this->component->render($sessions, 0); @@ -296,7 +316,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -313,7 +333,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -330,7 +350,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -347,7 +367,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -368,7 +388,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -378,10 +398,70 @@ }); }); +describe('registry status rendering', function () { + it('renders green complete label when registry status is completed', function () { + $tasksPath = $this->tempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Finished Feature', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'completed', 'title' => 'Done'], + ['id' => 2, 'status' => 'pending', 'title' => 'Skipped'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project', 'status' => 'completed'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toMatch('/text-green-400[^>]*>completetempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Crashed Session', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Was working'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => 999999999, 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project', 'status' => 'running'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toMatch('/text-red-400[^>]*>crashedtempDir.'/tasks.json'; + file_put_contents($tasksPath, json_encode([ + 'title' => 'Active Session', + 'branch' => 'main', + 'tasks' => [ + ['id' => 1, 'status' => 'in_progress', 'title' => 'Working'], + ], + ])); + + $sessions = [ + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project', 'status' => 'running'], + ]; + + $html = $this->component->render($sessions, 0); + + expect($html)->toMatch('/text-cyan-400[^>]*>active '/nonexistent/tasks.json', 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => '/nonexistent/tasks.json', 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -395,7 +475,7 @@ file_put_contents($tasksPath, 'not valid json'); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -408,7 +488,7 @@ file_put_contents($tasksPath, ''); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -425,7 +505,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); @@ -446,7 +526,7 @@ ])); $sessions = [ - ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'projectPath' => '/project'], + ['tasksPath' => $tasksPath, 'pid' => getmypid(), 'startedAt' => date('c'), 'mode' => 'normal', 'agent' => 'claude', 'projectPath' => '/project'], ]; $html = $this->component->render($sessions, 0); diff --git a/tests/Unit/Tui/SessionRegistryTest.php b/tests/Unit/Tui/SessionRegistryTest.php index 79d1e65..d8abb64 100644 --- a/tests/Unit/Tui/SessionRegistryTest.php +++ b/tests/Unit/Tui/SessionRegistryTest.php @@ -22,7 +22,7 @@ describe('register', function () { it('creates registry file and adds session entry', function () { - $this->registry->register('/path/to/tasks.json', getmypid(), 'normal', '/project'); + $this->registry->register('/path/to/tasks.json', getmypid(), 'normal', 'claude', '/project'); expect(file_exists($this->registryPath))->toBeTrue(); @@ -32,13 +32,14 @@ ->and($data['sessions'][0]['tasksPath'])->toBe('/path/to/tasks.json') ->and($data['sessions'][0]['pid'])->toBe(getmypid()) ->and($data['sessions'][0]['mode'])->toBe('normal') + ->and($data['sessions'][0]['agent'])->toBe('claude') ->and($data['sessions'][0]['projectPath'])->toBe('/project') ->and($data['sessions'][0]['startedAt'])->toBeString(); }); it('adds multiple sessions', function () { - $this->registry->register('/path/a/tasks.json', getmypid(), 'normal', '/project-a'); - $this->registry->register('/path/b/tasks.json', getmypid(), 'yolo', '/project-b'); + $this->registry->register('/path/a/tasks.json', getmypid(), 'normal', 'claude', '/project-a'); + $this->registry->register('/path/b/tasks.json', getmypid(), 'yolo', 'claude', '/project-b'); $data = json_decode(file_get_contents($this->registryPath), true); @@ -48,8 +49,8 @@ }); it('replaces existing entry with same tasksPath', function () { - $this->registry->register('/path/tasks.json', 100, 'normal', '/project'); - $this->registry->register('/path/tasks.json', 200, 'yolo', '/project-new'); + $this->registry->register('/path/tasks.json', 100, 'normal', 'claude', '/project'); + $this->registry->register('/path/tasks.json', 200, 'yolo', 'claude', '/project-new'); $data = json_decode(file_get_contents($this->registryPath), true); @@ -60,10 +61,88 @@ }); }); +describe('register status', function () { + it('includes status running in session entry', function () { + $this->registry->register('/path/to/tasks.json', getmypid(), 'normal', 'claude', '/project'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'][0]['status'])->toBe('running'); + }); +}); + +describe('markCompleted', function () { + it('sets status to completed and adds completedAt timestamp', function () { + $this->registry->register('/path/tasks.json', getmypid(), 'normal', 'claude', '/project'); + + $this->registry->markCompleted('/path/tasks.json'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'][0]['status'])->toBe('completed') + ->and($data['sessions'][0]['completedAt'])->toBeString(); + }); + + it('does nothing when tasksPath not found', function () { + $this->registry->register('/path/tasks.json', getmypid(), 'normal', 'claude', '/project'); + + $this->registry->markCompleted('/nonexistent/tasks.json'); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1) + ->and($data['sessions'][0]['status'])->toBe('running'); + }); +}); + +describe('getSessions', function () { + it('returns sessions with live PIDs as running', function () { + $this->registry->register('/path/tasks.json', getmypid(), 'normal', 'claude', '/project'); + + $sessions = $this->registry->getSessions(); + + expect($sessions)->toHaveCount(1) + ->and($sessions[0]['status'])->toBe('running'); + }); + + it('returns completed sessions even with dead PIDs', function () { + $this->registry->register('/path/tasks.json', 999999, 'normal', 'claude', '/project'); + $this->registry->markCompleted('/path/tasks.json'); + + $sessions = $this->registry->getSessions(); + + expect($sessions)->toHaveCount(1) + ->and($sessions[0]['status'])->toBe('completed'); + }); + + it('returns crashed sessions with dead PID and status running', function () { + $this->registry->register('/path/tasks.json', 999999, 'normal', 'claude', '/project'); + + $sessions = $this->registry->getSessions(); + + expect($sessions)->toHaveCount(1) + ->and($sessions[0]['status'])->toBe('crashed'); + }); + + it('returns mix of running completed and crashed', function () { + $this->registry->register('/path/live.json', getmypid(), 'normal', 'claude', '/live'); + $this->registry->register('/path/completed.json', 999998, 'normal', 'claude', '/completed'); + $this->registry->markCompleted('/path/completed.json'); + $this->registry->register('/path/crashed.json', 999999, 'normal', 'claude', '/crashed'); + + $sessions = $this->registry->getSessions(); + $statuses = array_column($sessions, 'status'); + sort($statuses); + + expect($sessions)->toHaveCount(3) + ->and($statuses)->toBe(['completed', 'crashed', 'running']); + }); +}); + describe('deregister', function () { it('removes correct session by tasksPath', function () { - $this->registry->register('/path/a/tasks.json', getmypid(), 'normal', '/project-a'); - $this->registry->register('/path/b/tasks.json', getmypid(), 'yolo', '/project-b'); + $this->registry->register('/path/a/tasks.json', getmypid(), 'normal', 'claude', '/project-a'); + $this->registry->register('/path/b/tasks.json', getmypid(), 'yolo', 'claude', '/project-b'); $this->registry->deregister('/path/a/tasks.json'); @@ -74,7 +153,7 @@ }); it('does nothing when tasksPath not found', function () { - $this->registry->register('/path/tasks.json', getmypid(), 'normal', '/project'); + $this->registry->register('/path/tasks.json', getmypid(), 'normal', 'claude', '/project'); $this->registry->deregister('/nonexistent/tasks.json'); @@ -94,7 +173,7 @@ describe('getActiveSessions', function () { it('returns sessions with live PIDs', function () { - $this->registry->register('/path/tasks.json', getmypid(), 'normal', '/project'); + $this->registry->register('/path/tasks.json', getmypid(), 'normal', 'claude', '/project'); $active = $this->registry->getActiveSessions(); @@ -103,8 +182,8 @@ }); it('filters out dead PIDs', function () { - $this->registry->register('/path/live.json', getmypid(), 'normal', '/project-live'); - $this->registry->register('/path/dead.json', 999999, 'yolo', '/project-dead'); + $this->registry->register('/path/live.json', getmypid(), 'normal', 'claude', '/project-live'); + $this->registry->register('/path/dead.json', 999999, 'yolo', 'claude', '/project-dead'); $active = $this->registry->getActiveSessions(); @@ -117,8 +196,8 @@ }); it('returns empty array when all PIDs are dead', function () { - $this->registry->register('/path/a.json', 999998, 'normal', '/a'); - $this->registry->register('/path/b.json', 999999, 'normal', '/b'); + $this->registry->register('/path/a.json', 999998, 'normal', 'claude', '/a'); + $this->registry->register('/path/b.json', 999999, 'normal', 'claude', '/b'); expect($this->registry->getActiveSessions())->toBe([]); }); @@ -126,9 +205,9 @@ describe('cleanup', function () { it('removes all dead PID entries', function () { - $this->registry->register('/path/live.json', getmypid(), 'normal', '/project-live'); - $this->registry->register('/path/dead1.json', 999998, 'normal', '/dead1'); - $this->registry->register('/path/dead2.json', 999999, 'yolo', '/dead2'); + $this->registry->register('/path/live.json', getmypid(), 'normal', 'claude', '/project-live'); + $this->registry->register('/path/dead1.json', 999998, 'normal', 'claude', '/dead1'); + $this->registry->register('/path/dead2.json', 999999, 'yolo', 'claude', '/dead2'); $this->registry->cleanup(); @@ -139,8 +218,8 @@ }); it('results in empty sessions when all PIDs are dead', function () { - $this->registry->register('/path/a.json', 999998, 'normal', '/a'); - $this->registry->register('/path/b.json', 999999, 'normal', '/b'); + $this->registry->register('/path/a.json', 999998, 'normal', 'claude', '/a'); + $this->registry->register('/path/b.json', 999999, 'normal', 'claude', '/b'); $this->registry->cleanup(); @@ -148,6 +227,20 @@ expect($data['sessions'])->toHaveCount(0); }); + + it('preserves completed sessions with dead PIDs', function () { + $this->registry->register('/path/completed.json', 999998, 'normal', 'claude', '/completed'); + $this->registry->markCompleted('/path/completed.json'); + $this->registry->register('/path/crashed.json', 999999, 'normal', 'claude', '/crashed'); + + $this->registry->cleanup(); + + $data = json_decode(file_get_contents($this->registryPath), true); + + expect($data['sessions'])->toHaveCount(1) + ->and($data['sessions'][0]['tasksPath'])->toBe('/path/completed.json') + ->and($data['sessions'][0]['status'])->toBe('completed'); + }); }); describe('edge cases', function () { @@ -170,7 +263,7 @@ it('recovers from corrupt JSON on write operations', function () { file_put_contents($this->registryPath, 'corrupt data'); - $this->registry->register('/path/tasks.json', getmypid(), 'normal', '/project'); + $this->registry->register('/path/tasks.json', getmypid(), 'normal', 'claude', '/project'); $data = json_decode(file_get_contents($this->registryPath), true); diff --git a/tests/Unit/Tui/Terminal/FocusResultTest.php b/tests/Unit/Tui/Terminal/FocusResultTest.php new file mode 100644 index 0000000..3078548 --- /dev/null +++ b/tests/Unit/Tui/Terminal/FocusResultTest.php @@ -0,0 +1,35 @@ +success)->toBeTrue() + ->and($result->message)->toBe('Focused terminal pane'); + }); + + it('creates notFound result', function () { + $result = FocusResult::notFound(); + + expect($result->success)->toBeFalse() + ->and($result->message)->toBe('Could not find terminal pane for this session'); + }); + + it('creates unsupported result', function () { + $result = FocusResult::unsupported(); + + expect($result->success)->toBeFalse() + ->and($result->message)->toBe('No supported terminal multiplexer detected'); + }); + + it('creates error result with custom message', function () { + $result = FocusResult::error('Something went wrong'); + + expect($result->success)->toBeFalse() + ->and($result->message)->toBe('Something went wrong'); + }); +}); diff --git a/tests/Unit/Tui/Terminal/ItermStrategyTest.php b/tests/Unit/Tui/Terminal/ItermStrategyTest.php new file mode 100644 index 0000000..bcb5757 --- /dev/null +++ b/tests/Unit/Tui/Terminal/ItermStrategyTest.php @@ -0,0 +1,25 @@ + 'iTerm.app']); + + expect($strategy->isAvailable())->toBeTrue(); + }); + + it('is not available when TERM_PROGRAM is different', function () { + $strategy = new ItermStrategy(['TERM_PROGRAM' => 'Apple_Terminal']); + + expect($strategy->isAvailable())->toBeFalse(); + }); + + it('is not available when TERM_PROGRAM is missing', function () { + $strategy = new ItermStrategy([]); + + expect($strategy->isAvailable())->toBeFalse(); + }); +}); diff --git a/tests/Unit/Tui/Terminal/TerminalFocuserTest.php b/tests/Unit/Tui/Terminal/TerminalFocuserTest.php new file mode 100644 index 0000000..7e42e90 --- /dev/null +++ b/tests/Unit/Tui/Terminal/TerminalFocuserTest.php @@ -0,0 +1,88 @@ +focus(1234); + + expect($result->success)->toBeFalse() + ->and($result->message)->toBe('No supported terminal multiplexer detected'); + }); + + it('delegates to first available strategy', function () { + $strategy = Mockery::mock(TerminalStrategy::class); + $strategy->shouldReceive('isAvailable')->andReturn(true); + $strategy->shouldReceive('focus')->with(1234)->andReturn(FocusResult::success()); + + $focuser = new TerminalFocuser([$strategy]); + + $result = $focuser->focus(1234); + + expect($result->success)->toBeTrue(); + }); + + it('skips unavailable strategies and uses next available', function () { + $unavailable = Mockery::mock(TerminalStrategy::class); + $unavailable->shouldReceive('isAvailable')->andReturn(false); + $unavailable->shouldNotReceive('focus'); + + $available = Mockery::mock(TerminalStrategy::class); + $available->shouldReceive('isAvailable')->andReturn(true); + $available->shouldReceive('focus')->with(5678)->andReturn(FocusResult::success()); + + $focuser = new TerminalFocuser([$unavailable, $available]); + + $result = $focuser->focus(5678); + + expect($result->success)->toBeTrue(); + }); + + it('returns unsupported when all strategies are unavailable', function () { + $strategy1 = Mockery::mock(TerminalStrategy::class); + $strategy1->shouldReceive('isAvailable')->andReturn(false); + + $strategy2 = Mockery::mock(TerminalStrategy::class); + $strategy2->shouldReceive('isAvailable')->andReturn(false); + + $focuser = new TerminalFocuser([$strategy1, $strategy2]); + + $result = $focuser->focus(1234); + + expect($result->success)->toBeFalse() + ->and($result->message)->toBe('No supported terminal multiplexer detected'); + }); + + it('reports supported when at least one strategy is available', function () { + $unavailable = Mockery::mock(TerminalStrategy::class); + $unavailable->shouldReceive('isAvailable')->andReturn(false); + + $available = Mockery::mock(TerminalStrategy::class); + $available->shouldReceive('isAvailable')->andReturn(true); + + $focuser = new TerminalFocuser([$unavailable, $available]); + + expect($focuser->isSupported())->toBeTrue(); + }); + + it('reports unsupported when no strategies are available', function () { + $strategy = Mockery::mock(TerminalStrategy::class); + $strategy->shouldReceive('isAvailable')->andReturn(false); + + $focuser = new TerminalFocuser([$strategy]); + + expect($focuser->isSupported())->toBeFalse(); + }); + + it('reports unsupported with empty strategies', function () { + $focuser = new TerminalFocuser([]); + + expect($focuser->isSupported())->toBeFalse(); + }); +}); diff --git a/tests/Unit/Tui/Terminal/TmuxStrategyTest.php b/tests/Unit/Tui/Terminal/TmuxStrategyTest.php new file mode 100644 index 0000000..1365d92 --- /dev/null +++ b/tests/Unit/Tui/Terminal/TmuxStrategyTest.php @@ -0,0 +1,25 @@ + '/tmp/tmux-501/default,12345,0']); + + expect($strategy->isAvailable())->toBeTrue(); + }); + + it('is not available when TMUX env var is empty', function () { + $strategy = new TmuxStrategy(['TMUX' => '']); + + expect($strategy->isAvailable())->toBeFalse(); + }); + + it('is not available when TMUX env var is missing', function () { + $strategy = new TmuxStrategy([]); + + expect($strategy->isAvailable())->toBeFalse(); + }); +});