diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..83b8054 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,44 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "case \"$CLAUDE_FILE_PATH\" in */vendor/*|*/composer.lock) echo 'BLOCK: Do not edit vendor or lock files directly' >&2; exit 1;; esac" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "case \"$CLAUDE_FILE_PATH\" in *.php) vendor/bin/phpcbf --standard=phpcs.xml \"$CLAUDE_FILE_PATH\" 2>/dev/null; true;; esac", + "timeout": 10000 + } + ] + } + ] + }, + "permissions": { + "allow": [ + "Bash(composer test:*)", + "Bash(composer lint:*)", + "Bash(composer lint:all:*)", + "Bash(composer format:*)", + "Bash(composer format:md:*)", + "Bash(composer coverage:*)", + "Bash(composer build:*)", + "Bash(vendor/bin/pest:*)", + "Bash(vendor/bin/phpcs:*)", + "Bash(vendor/bin/phpcbf:*)", + "Bash(git log:*)", + "Bash(git diff:*)", + "Bash(git status:*)" + ] + } +} diff --git a/.claude/skills/build-phar/SKILL.md b/.claude/skills/build-phar/SKILL.md new file mode 100644 index 0000000..6f4baee --- /dev/null +++ b/.claude/skills/build-phar/SKILL.md @@ -0,0 +1,16 @@ +--- +name: build-phar +description: Build PHAR executable and run smoke test +disable-model-invocation: true +--- + +# Build PHAR + +Build the PHAR executable and verify it works. + +## Steps + +1. Run `composer test` — abort if any tests fail +2. Run `composer build` — build PHAR to `builds/branch-usage-checker` +3. Run `builds/branch-usage-checker --version` — verify it launches successfully +4. Report the file size of `builds/branch-usage-checker` and confirm success diff --git a/.claude/skills/release-check/SKILL.md b/.claude/skills/release-check/SKILL.md new file mode 100644 index 0000000..04303d0 --- /dev/null +++ b/.claude/skills/release-check/SKILL.md @@ -0,0 +1,18 @@ +--- +name: release-check +description: Run full pre-release verification suite +disable-model-invocation: true +--- + +# Pre-Release Verification + +Run the complete verification suite before a release. + +## Steps + +1. Run `composer lint:all` — all linters must pass (PHPCS, EditorConfig, markdownlint) +2. Run `composer test` — all tests must pass +3. Run `composer build` — PHAR must build successfully +4. Run `builds/branch-usage-checker --version` — smoke test the PHAR +5. Run `git status` — working tree should be clean (no uncommitted changes) +6. Report go/no-go summary with results of each step diff --git a/.editorconfig b/.editorconfig index 40006b5..957db0f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,12 +9,14 @@ indent_size = 4 trim_trailing_whitespace = true [*.md] +indent_size = 2 trim_trailing_whitespace = false -[*.json] +[*.xml] indent_size = 2 -insert_final_newline = true -[*.yml] -indent_style = space +[*.{json,jsonc}] +indent_size = 2 + +[*.{yml,yaml}] indent_size = 2 diff --git a/.editorconfig-checker.json b/.editorconfig-checker.json new file mode 100644 index 0000000..1b8d0a4 --- /dev/null +++ b/.editorconfig-checker.json @@ -0,0 +1,7 @@ +{ + "Exclude": [ + "\\.md$", + "builds/", + "vendor/" + ] +} diff --git a/.gitignore b/.gitignore index b9292c5..fe68da1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,18 @@ +# OS files +.DS_Store +Thumbs.db + /vendor /.idea /.vscode /.vagrant + +# PHPUnit .phpunit.result.cache +.phpunit.cache + +# Build output /builds/branch-usage-checker + +# Claude Code local settings +.claude/settings.local.json diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..9816dd5 --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,3 @@ +{ + "ignores": ["vendor/**", "node_modules/**"] +} diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..afd337c --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,5 @@ +{ + "line-length": { "line_length": 80 }, + "no-duplicate-heading": false, + "required-headings": false +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..55b4f02 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,81 @@ +# Branch Usage Checker + +This file provides guidance to Claude Code +(claude.ai/code) when working with code in this +repository. + +## Project Overview + +Branch Usage Checker is a Laravel Zero CLI tool +that cross-references GitHub branches with +Packagist download statistics to identify branches +safe to delete. Built as a PHAR-distributable PHP +application. + +## Commands + +- `composer install` — Install dependencies +- `composer test` — Run tests (Pest v4) +- `composer build` — Build PHAR executable + to `builds/branch-usage-checker` +- `composer lint` — Check code style (PHPCS) +- `composer lint:phpmd` — Static analysis (PHPMD) +- `composer lint:md` — Lint markdown files +- `composer lint:ec` — Check EditorConfig compliance +- `composer lint:all` — Run all linters +- `composer format` — Auto-fix code style (PHPCBF) +- `composer format:md` — Format Markdown tables +- `composer x` — Run the built PHAR +- `vendor/bin/pest --filter "test name"` — Run a + single test + +## Code Standards + +- PSR-12 via PHP CodeSniffer (`phpcs.xml`), + with `PSR12.Operators.OperatorSpacing` excluded +- PHP 8.4 required +- Composer normalize runs automatically on + autoload dump +- CaptainHook pre-commit hook runs PHPCBF + then PHPCS on staged PHP files automatically + +## Architecture + +This is a Laravel Zero console application. +Entry point is `./application`, which bootstraps +via `bootstrap/app.php`. + +### Core Flow (CheckCommand) + +`check {vendor} {package?} {months=9}` — the main +(and only functional) command. The `vendor` argument +accepts a combined `vendor/package` form, making +`package` optional in that case: + +1. Fetches package metadata from + `packagist.org/packages/{vendor}/{package}.json` +2. Extracts branches (versions prefixed with + `dev-`) +3. For each branch, fetches monthly download + stats from Packagist over the configured + lookback window +4. Displays a statistics table and a suggestions + table (branches with zero downloads) + +### Key Directories + +- `app/Commands/` — CLI commands + (CheckCommand is the primary one) +- `app/Dto/` — Spatie DataTransferObject classes + for Packagist API responses +- `tests/Feature/Commands/` — Feature tests + for commands +- `builds/` — PHAR output directory + +### Dependencies of Note + +- HTTP requests use `Illuminate\Http\Client\Factory` + (Guzzle-backed), injected via the container +- DTOs use `spatie/data-transfer-object` with + `MapFrom` attributes for JSON field mapping +- PHAR building configured in `box.json` diff --git a/app/Commands/CheckCommand.php b/app/Commands/CheckCommand.php index a871a15..e18cb69 100644 --- a/app/Commands/CheckCommand.php +++ b/app/Commands/CheckCommand.php @@ -3,14 +3,15 @@ namespace App\Commands; use App\Dto\PackagistApiPackagePayload; -use Illuminate\Support\Facades\Http; +use Illuminate\Http\Client\Factory as HttpFactory; +use Illuminate\Http\Client\Pool; use LaravelZero\Framework\Commands\Command; class CheckCommand extends Command { protected $signature = 'check - {vendor : Package vendor (required)} - {package : Package name (required)} + {vendor : Package vendor or vendor/package} + {package? : Package name} {months=9 : How many months should we return for review (optional)} '; protected $description = 'Check package branch usage'; @@ -20,23 +21,30 @@ class CheckCommand extends Command private string $filter = ''; private int $totalBranches = 0; + private const NAME_PATTERN = '/^[a-z0-9]([_.\-]?[a-z0-9]+)*$/'; + private const TIMEOUT_SECONDS = 10; + private const PACKAGIST_URL = 'https://packagist.org/packages/%s/%s'; + + private HttpFactory $http; + + /** Execute the check command. */ public function handle(): int { - $this->vendor = (string)$this->argument('vendor'); - $this->package = (string)$this->argument('package'); - $months = (int)$this->argument('months'); + $this->http = resolve(HttpFactory::class); - $this->info('Checking: ' . sprintf('%s/%s', $this->vendor, $this->package)); + if (!$this->resolveInput()) { + return 1; + } + + $months = (int) $this->argument('months'); + + $this->info(sprintf('Checking: %s/%s', $this->vendor, $this->package)); $this->info('Months: ' . $months); - $payload = Http::get( - sprintf( - 'https://packagist.org/packages/%s/%s.json', - $this->vendor, - $this->package - ) - ); - + $payload = $this->fetchPackageMetadata(); + if ($payload === null) { + return 1; + } $this->filter = now()->subMonths($months)->day(1)->toDateString(); try { @@ -45,9 +53,9 @@ class CheckCommand extends Command $versions = collect($pkg->versions ?? []) ->keys() - // Filter actual versions out. ->filter(fn ($version) => \str_starts_with($version, 'dev-')) - ->sort(); + ->sort() + ->values(); $this->totalBranches = $versions->count(); @@ -58,48 +66,150 @@ class CheckCommand extends Command ) ); - $statistics = collect($versions) - ->mapWithKeys(fn ($branch) => $this->getStatistics($branch)) - ->toArray(); + $responses = $this->http->pool( + fn (Pool $pool) => $versions->map( + fn ($branch) => $pool->as($branch)->timeout(self::TIMEOUT_SECONDS)->get($this->getStatsUrl($branch)) + )->toArray() + ); + + $statistics = $this->collectBranchStats($versions, $responses); $this->info('Downloaded statistics...'); - $this->outputTable($statistics); - $this->outputSuggestions($statistics); - } catch (\Exception $e) { - $this->error($e->getMessage(), $e); + if ($this->outputTable($statistics)) { + $this->outputSuggestions($statistics); + } + } catch (\Throwable $e) { + if ($e instanceof \TypeError) { + throw $e; + } + $this->error($e->getMessage()); + return 1; } return 0; } - private function getStatistics(string $branch): array + /** Parse and validate vendor/package input arguments. */ + private function resolveInput(): bool { - $payload = Http::get( + $vendor = strtolower((string) $this->argument('vendor')); + $package = $this->argument('package'); + + if (str_contains($vendor, '/')) { + if ($package !== null) { + $this->error( + 'Conflicting arguments: vendor/package format' + . ' and separate package argument cannot be used together.' + ); + return false; + } + [$vendor, $package] = explode('/', $vendor, 2); + } + + if ($package === null || $package === '') { + $this->error('Missing package name. Usage: check vendor/package or check vendor package'); + return false; + } + + $package = strtolower((string) $package); + + if (!preg_match(self::NAME_PATTERN, $vendor)) { + $this->error("Invalid vendor name: {$vendor}"); + return false; + } + + if (!preg_match(self::NAME_PATTERN, $package)) { + $this->error("Invalid package name: {$package}"); + return false; + } + + $this->vendor = $vendor; + $this->package = $package; + + return true; + } + + /** Fetch package metadata from Packagist. */ + private function fetchPackageMetadata(): ?\Illuminate\Http\Client\Response + { + $payload = $this->http->timeout(self::TIMEOUT_SECONDS)->get( sprintf( - 'https://packagist.org/packages/%s/%s/stats/%s.json?average=monthly&from=%s', + self::PACKAGIST_URL . '.json', $this->vendor, - $this->package, - $branch, - $this->filter + $this->package ) ); - $data = collect($payload->json()); - $labels = collect($data->get('labels', []))->toArray(); - $values = collect($data->get('values', []))->flatten()->toArray(); + if ($payload->failed()) { + if ($payload->status() === 404) { + $this->error("Package not found: {$this->vendor}/{$this->package}"); + return null; + } + $this->error("Failed to fetch package metadata (HTTP {$payload->status()})"); + return null; + } - $labels[] = 'Total'; - $values[] = array_sum($values); - - return [$branch => \array_combine($labels, $values)]; + return $payload; } - private function outputTable(array $statistics): void + /** Build the Packagist stats API URL for a branch. */ + private function getStatsUrl(string $branch): string + { + return sprintf( + self::PACKAGIST_URL . '/stats/%s.json?average=monthly&from=%s', + $this->vendor, + $this->package, + $branch, + $this->filter + ); + } + + /** Parse pooled responses into branch download statistics. */ + private function collectBranchStats($versions, array $responses): array + { + $statistics = []; + foreach ($versions as $branch) { + $response = $responses[$branch]; + + if ($response instanceof \Throwable) { + $this->warn("Failed to fetch stats for {$branch}, skipping."); + continue; + } + + if ($response->failed()) { + $this->warn("Failed to fetch stats for {$branch} (HTTP {$response->status()}), skipping."); + continue; + } + + $data = collect($response->json()); + $labels = collect($data->get('labels', []))->toArray(); + $values = collect($data->get('values', []))->flatten()->toArray(); + + $labels[] = 'Total'; + $values[] = array_sum($values); + + if (count($labels) !== count($values)) { + $this->warn(sprintf( + 'Malformed stats for %s (labels: %d, values: %d), skipping.', + $branch, + count($labels), + count($values) + )); + continue; + } + $statistics[$branch] = \array_combine($labels, $values); + } + + return $statistics; + } + + /** Render the download statistics table. */ + private function outputTable(array $statistics): bool { if (empty($statistics)) { $this->info('No statistics found... Stopping.'); - exit(0); + return false; } $tableHeaders = ['' => 'Branch']; @@ -107,44 +217,41 @@ class CheckCommand extends Command foreach ($statistics as $branch => $stats) { foreach ($stats as $m => $v) { - $tableHeaders[$m] = (string)$m; + $tableHeaders[$m] = (string) $m; $tableBranches[$branch][$branch] = $branch; - $tableBranches[$branch][$m] = (string)$v; + $tableBranches[$branch][$m] = (string) $v; } } $this->line(''); $this->table($tableHeaders, $tableBranches); + + return true; } - private function outputSuggestions(array $statistics = []): void + /** Render suggestions for zero-download branches. */ + private function outputSuggestions(array $statistics): void { $deletable = []; - if (empty($statistics)) { - $this->info('No statistics to give suggestions for. Quitting...'); - exit(0); - } foreach ($statistics as $k => $values) { if (!empty($values['Total'])) { continue; } - $deletable[$k] = $values['Total']; + $deletable[] = $k; } if (empty($deletable)) { $this->info('No suggestions available. Good job!'); - exit(0); + return; } - $keys = array_keys($deletable); - - $branches = collect($keys)->mapWithKeys(function ($branch) { + $branches = collect($deletable)->mapWithKeys(function ($branch) { return [ $branch => [ $branch, sprintf( - 'https://packagist.org/packages/%s/%s#%s', + self::PACKAGIST_URL . '#%s', $this->vendor, $this->package, $branch diff --git a/app/Commands/InspireCommand.php b/app/Commands/InspireCommand.php deleted file mode 100644 index 58c856a..0000000 --- a/app/Commands/InspireCommand.php +++ /dev/null @@ -1,54 +0,0 @@ - -
Laravel Zero
- Simplicity is the ultimate sophistication. - - HTML - ); - } - - /** - * Define the command's schedule. - * - * @param \Illuminate\Console\Scheduling\Schedule $schedule - * - * @return void - */ - public function schedule(Schedule $schedule) - { - // $schedule->command(static::class)->everyMinute(); - } -} diff --git a/app/Dto/GitHubApiBranch.php b/app/Dto/GitHubApiBranch.php deleted file mode 100644 index 9eabb18..0000000 --- a/app/Dto/GitHubApiBranch.php +++ /dev/null @@ -1,11 +0,0 @@ -flatten(1) - ->toArray(); - - return $pages; - } - - public static function downloader($vendor, $package): array - { - $responses = []; - - $continue = true; - $page = 1; - $gh_api = sprintf( - 'https://api.github.com/repos/%s/%s/branches?per_page=100', - $vendor, - $package - ); - - while ($continue) { - $response = Http::get($gh_api . '&page=' . $page); - - if (empty($response)) { - $continue = false; - } - - $responses[$page] = $response; - $page++; - } - - return $responses; - } -} diff --git a/builds/branch-usage-checker b/builds/branch-usage-checker index 2d71eb8..37e4fa5 100755 Binary files a/builds/branch-usage-checker and b/builds/branch-usage-checker differ diff --git a/captainhook.json b/captainhook.json new file mode 100644 index 0000000..0dcb521 --- /dev/null +++ b/captainhook.json @@ -0,0 +1,51 @@ +{ + "pre-commit": { + "enabled": true, + "actions": [ + { + "action": "vendor/bin/phpcbf --standard=phpcs.xml {$STAGED_FILES|of-type:php}", + "config": { + "label": "Fix code style with PHPCBF" + } + }, + { + "action": "vendor/bin/phpcs --standard=phpcs.xml {$STAGED_FILES|of-type:php}", + "config": { + "label": "Check code style with PHPCS" + } + }, + { + "action": "vendor/bin/phpmd {$STAGED_FILES|of-type:php|separated-by:,} text phpmd.xml", + "conditions": [ + { + "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType", + "args": ["php"] + } + ], + "config": { + "label": "Check code quality with PHPMD" + } + }, + { + "action": "npx editorconfig-checker {$STAGED_FILES}", + "config": { + "label": "Check EditorConfig compliance" + } + }, + { + "action": "npx markdownlint-cli2 {$STAGED_FILES|of-type:md}", + "config": { + "label": "Lint Markdown files" + } + } + ] + }, + "pre-push": { + "enabled": false, + "actions": [] + }, + "commit-msg": { + "enabled": false, + "actions": [] + } +} diff --git a/composer.json b/composer.json index d210dd8..1b65289 100644 --- a/composer.json +++ b/composer.json @@ -23,26 +23,28 @@ }, "require": { "php": "^8.4", - "guzzlehttp/guzzle": "^7", "illuminate/http": "^12.17", - "laravel-zero/phar-updater": "^1.2", - "nunomaduro/termwind": "^2", "spatie/data-transfer-object": "^3.7" }, "require-dev": { + "captainhook/captainhook": "^5.28", + "captainhook/hook-installer": "^1.0", "ergebnis/composer-normalize": "^2", "laravel-zero/framework": "^12", "mockery/mockery": "^1", "pestphp/pest": "^4", - "roave/security-advisories": "dev-latest" + "phpmd/phpmd": "^2.15", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^4.0" + }, + "suggest": { + "ext-pcov": "Required for code coverage reporting (composer coverage)" }, "minimum-stability": "dev", "prefer-stable": true, "autoload": { "psr-4": { - "App\\": "app/", - "Database\\Factories\\": "database/factories/", - "Database\\Seeders\\": "database/seeders/" + "App\\": "app/" } }, "autoload-dev": { @@ -55,7 +57,7 @@ ], "config": { "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, + "captainhook/hook-installer": true, "ergebnis/composer-normalize": true, "pestphp/pest-plugin": true }, @@ -72,6 +74,22 @@ "@php application app:build branch-usage-checker", "rm -f application.phar || true" ], + "coverage": "php -d pcov.enabled=1 vendor/bin/pest --coverage", + "format": "vendor/bin/phpcbf || true", + "format:md": "npx markdown-table-formatter *.md docs/**/*.md", + "lint": "vendor/bin/phpcs", + "lint:all": [ + "@lint", + "@lint:phpmd", + "@lint:md", + "@lint:ec" + ], + "lint:ec": "npx editorconfig-checker", + "lint:md": [ + "npx markdownlint-cli2 '**/*.md' '#vendor' '#node_modules'", + "npx markdown-table-formatter --check *.md docs/**/*.md" + ], + "lint:phpmd": "vendor/bin/phpmd app,tests text phpmd.xml", "test": "vendor/bin/pest", "x": "@php builds/branch-usage-checker" } diff --git a/composer.lock b/composer.lock index adb3c60..01e9c94 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": "25bc97c24cf5394a1d670f4ba526e36d", + "content-hash": "cf1f39464e98b53a5308da63ab9b7094", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -649,16 +649,16 @@ }, { "name": "illuminate/collections", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "1fd7db2203ce5a935fffd2ad40955248fb9f581c" + "reference": "f35c084f0d9bc57895515cb4d0665797c66285fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/1fd7db2203ce5a935fffd2ad40955248fb9f581c", - "reference": "1fd7db2203ce5a935fffd2ad40955248fb9f581c", + "url": "https://api.github.com/repos/illuminate/collections/zipball/f35c084f0d9bc57895515cb4d0665797c66285fd", + "reference": "f35c084f0d9bc57895515cb4d0665797c66285fd", "shasum": "" }, "require": { @@ -705,11 +705,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-09T13:43:38+00:00" + "time": "2026-02-16T14:10:38+00:00" }, { "name": "illuminate/conditionable", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", @@ -755,16 +755,16 @@ }, { "name": "illuminate/contracts", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a" + "reference": "620d82bad07f55fcb7f5125f44c9d9b93335fb8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/3d4eeab332c04a9eaea90968c19a66f78745e47a", - "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/620d82bad07f55fcb7f5125f44c9d9b93335fb8c", + "reference": "620d82bad07f55fcb7f5125f44c9d9b93335fb8c", "shasum": "" }, "require": { @@ -799,20 +799,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-28T15:26:27+00:00" + "time": "2026-02-10T18:16:44+00:00" }, { "name": "illuminate/filesystem", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "62c225e046a4c469779c0855dce7abd8c0b1fa1e" + "reference": "c4c3f8612f218afcf09f3c7f5c7dc9e282626800" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/62c225e046a4c469779c0855dce7abd8c0b1fa1e", - "reference": "62c225e046a4c469779c0855dce7abd8c0b1fa1e", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/c4c3f8612f218afcf09f3c7f5c7dc9e282626800", + "reference": "c4c3f8612f218afcf09f3c7f5c7dc9e282626800", "shasum": "" }, "require": { @@ -866,20 +866,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-09T20:42:48+00:00" + "time": "2026-02-13T20:26:32+00:00" }, { "name": "illuminate/http", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/http.git", - "reference": "3f764166fab80c79afc33b21e14c608aa7f67a72" + "reference": "90f63e595e913feccba8e6e5a77982291fd3b0cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/http/zipball/3f764166fab80c79afc33b21e14c608aa7f67a72", - "reference": "3f764166fab80c79afc33b21e14c608aa7f67a72", + "url": "https://api.github.com/repos/illuminate/http/zipball/90f63e595e913feccba8e6e5a77982291fd3b0cd", + "reference": "90f63e595e913feccba8e6e5a77982291fd3b0cd", "shasum": "" }, "require": { @@ -928,11 +928,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-09T22:07:11+00:00" + "time": "2026-02-15T19:14:33+00:00" }, { "name": "illuminate/macroable", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -978,7 +978,7 @@ }, { "name": "illuminate/reflection", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/reflection.git", @@ -1029,16 +1029,16 @@ }, { "name": "illuminate/session", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/session.git", - "reference": "4ee5e68ce5e1ee88f32f77694bc2e564c5809ffa" + "reference": "98802e67dd5e059c0b978b3fe8f5f0a3ac17ec4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/session/zipball/4ee5e68ce5e1ee88f32f77694bc2e564c5809ffa", - "reference": "4ee5e68ce5e1ee88f32f77694bc2e564c5809ffa", + "url": "https://api.github.com/repos/illuminate/session/zipball/98802e67dd5e059c0b978b3fe8f5f0a3ac17ec4e", + "reference": "98802e67dd5e059c0b978b3fe8f5f0a3ac17ec4e", "shasum": "" }, "require": { @@ -1082,20 +1082,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-09T13:23:11+00:00" + "time": "2026-02-14T23:03:41+00:00" }, { "name": "illuminate/support", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "fcc84cf4fba138f1835d6a5978ac4434f11a55aa" + "reference": "6e9de455bcb232372db11531b72a01d4eca83ef2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/fcc84cf4fba138f1835d6a5978ac4434f11a55aa", - "reference": "fcc84cf4fba138f1835d6a5978ac4434f11a55aa", + "url": "https://api.github.com/repos/illuminate/support/zipball/6e9de455bcb232372db11531b72a01d4eca83ef2", + "reference": "6e9de455bcb232372db11531b72a01d4eca83ef2", "shasum": "" }, "require": { @@ -1162,68 +1162,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-10T16:02:09+00:00" - }, - { - "name": "laravel-zero/phar-updater", - "version": "v1.4.2", - "source": { - "type": "git", - "url": "https://github.com/laravel-zero/phar-updater.git", - "reference": "131bc5e7477c9233fab8087f7d0bbb234616b417" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel-zero/phar-updater/zipball/131bc5e7477c9233fab8087f7d0bbb234616b417", - "reference": "131bc5e7477c9233fab8087f7d0bbb234616b417", - "shasum": "" - }, - "require": { - "php": "^8.2" - }, - "conflict": { - "padraic/phar-updater": "*" - }, - "require-dev": { - "ext-json": "*", - "laravel/pint": "^1.21", - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Humbug\\SelfUpdate\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Padraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Owen Voke", - "email": "development@voke.dev", - "homepage": "https://voke.dev" - } - ], - "description": "A thing to make PHAR self-updating easy and secure.", - "keywords": [ - "humbug", - "phar", - "self-update", - "update" - ], - "support": { - "issues": "https://github.com/laravel-zero/phar-updater/issues", - "source": "https://github.com/laravel-zero/phar-updater/tree/v1.4.2" - }, - "time": "2025-04-07T12:28:11+00:00" + "time": "2026-02-14T23:03:41+00:00" }, { "name": "nesbot/carbon", @@ -1330,93 +1269,6 @@ ], "time": "2026-01-29T09:26:29+00:00" }, - { - "name": "nunomaduro/termwind", - "version": "v2.3.3", - "source": { - "type": "git", - "url": "https://github.com/nunomaduro/termwind.git", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": "^8.2", - "symfony/console": "^7.3.6" - }, - "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" - }, - "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/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "Its like Tailwind CSS, but for the console.", - "keywords": [ - "cli", - "console", - "css", - "package", - "php", - "style" - ], - "support": { - "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" - }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - }, - { - "url": "https://github.com/xiCO2k", - "type": "github" - } - ], - "time": "2025-11-20T02:34:59+00:00" - }, { "name": "psr/clock", "version": "1.0.0", @@ -2014,104 +1866,6 @@ ], "time": "2025-11-12T15:46:48+00:00" }, - { - "name": "symfony/console", - "version": "v7.4.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "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" - }, - "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" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/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-13T11:36:38+00:00" - }, { "name": "symfony/deprecation-contracts", "version": "v3.6.0", @@ -2863,88 +2617,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "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": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "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": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" - }, - "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-06-27T09:58:17+00:00" - }, { "name": "symfony/polyfill-intl-idn", "version": "v1.33.0", @@ -3526,183 +3198,6 @@ ], "time": "2025-06-23T16:12:55+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": "v8.0.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "758b372d6882506821ed666032e43020c4f57194" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", - "reference": "758b372d6882506821ed666032e43020c4f57194", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "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.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/intl": "^7.4|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^7.4|^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/v8.0.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-12T12:37:40+00:00" - }, { "name": "symfony/translation", "version": "v8.0.4", @@ -4043,16 +3538,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.17.0", + "version": "v7.19.0", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "53cb90a6aa3ef3840458781600628ade058a18b9" + "reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/53cb90a6aa3ef3840458781600628ade058a18b9", - "reference": "53cb90a6aa3ef3840458781600628ade058a18b9", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6", + "reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6", "shasum": "" }, "require": { @@ -4063,13 +3558,13 @@ "fidry/cpu-core-counter": "^1.3.0", "jean85/pretty-package-versions": "^2.1.1", "php": "~8.3.0 || ~8.4.0 || ~8.5.0", - "phpunit/php-code-coverage": "^12.5.2", - "phpunit/php-file-iterator": "^6", - "phpunit/php-timer": "^8", - "phpunit/phpunit": "^12.5.8", - "sebastian/environment": "^8.0.3", - "symfony/console": "^7.3.4 || ^8.0.0", - "symfony/process": "^7.3.4 || ^8.0.0" + "phpunit/php-code-coverage": "^12.5.3 || ^13.0.1", + "phpunit/php-file-iterator": "^6.0.1 || ^7", + "phpunit/php-timer": "^8 || ^9", + "phpunit/phpunit": "^12.5.9 || ^13", + "sebastian/environment": "^8.0.3 || ^9", + "symfony/console": "^7.4.4 || ^8.0.4", + "symfony/process": "^7.4.5 || ^8.0.5" }, "require-dev": { "doctrine/coding-standard": "^14.0.0", @@ -4080,7 +3575,7 @@ "phpstan/phpstan-deprecation-rules": "^2.0.3", "phpstan/phpstan-phpunit": "^2.0.12", "phpstan/phpstan-strict-rules": "^2.0.8", - "symfony/filesystem": "^7.3.2 || ^8.0.0" + "symfony/filesystem": "^7.4.0 || ^8.0.1" }, "bin": [ "bin/paratest", @@ -4120,7 +3615,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.17.0" + "source": "https://github.com/paratestphp/paratest/tree/v7.19.0" }, "funding": [ { @@ -4132,7 +3627,7 @@ "type": "paypal" } ], - "time": "2026-02-05T09:14:44+00:00" + "time": "2026-02-06T10:53:26+00:00" }, { "name": "brick/math", @@ -4194,6 +3689,338 @@ ], "time": "2026-02-10T14:33:43+00:00" }, + { + "name": "captainhook/captainhook", + "version": "5.28.3", + "source": { + "type": "git", + "url": "https://github.com/captainhook-git/captainhook.git", + "reference": "5d35b249f3843ef36ead119f4347e649278ad6d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/captainhook-git/captainhook/zipball/5d35b249f3843ef36ead119f4347e649278ad6d8", + "reference": "5d35b249f3843ef36ead119f4347e649278ad6d8", + "shasum": "" + }, + "require": { + "captainhook/secrets": "^0.9.4", + "ext-json": "*", + "ext-spl": "*", + "ext-xml": "*", + "php": ">=8.0", + "sebastianfeldmann/camino": "^0.9.2", + "sebastianfeldmann/cli": "^3.3", + "sebastianfeldmann/git": "^3.16.0", + "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + }, + "replace": { + "sebastianfeldmann/captainhook": "*" + }, + "require-dev": { + "composer/composer": "~1 || ^2.0", + "mikey179/vfsstream": "~1" + }, + "bin": [ + "bin/captainhook" + ], + "type": "library", + "extra": { + "captainhook": { + "config": "captainhook.json" + }, + "branch-alias": { + "dev-main": "6.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "CaptainHook\\App\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Feldmann", + "email": "sf@sebastian-feldmann.info" + } + ], + "description": "PHP git hook manager", + "homepage": "https://php.captainhook.info/", + "keywords": [ + "commit-msg", + "git", + "hooks", + "post-merge", + "pre-commit", + "pre-push", + "prepare-commit-msg" + ], + "support": { + "issues": "https://github.com/captainhook-git/captainhook/issues", + "source": "https://github.com/captainhook-git/captainhook/tree/5.28.3" + }, + "funding": [ + { + "url": "https://github.com/sponsors/sebastianfeldmann", + "type": "github" + } + ], + "time": "2026-02-16T14:08:58+00:00" + }, + { + "name": "captainhook/hook-installer", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/captainhook-git/hook-installer.git", + "reference": "fb3c45f6204b08baba999f4ffc4ae707bf684e8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/captainhook-git/hook-installer/zipball/fb3c45f6204b08baba999f4ffc4ae707bf684e8b", + "reference": "fb3c45f6204b08baba999f4ffc4ae707bf684e8b", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1|^2.0", + "php": ">=8.0" + }, + "require-dev": { + "composer/composer": "*" + }, + "type": "composer-plugin", + "extra": { + "class": "CaptainHook\\HookInstaller\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "CaptainHook\\HookInstaller\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Feldmann", + "email": "sf@sebastian-feldmann.info" + } + ], + "description": "Composer Plugin that makes everyone activate the CaptainHook git hooks locally", + "support": { + "issues": "https://github.com/captainhook-git/hook-installer/issues", + "source": "https://github.com/captainhook-git/hook-installer/tree/1.0.4" + }, + "time": "2025-04-08T07:12:26+00:00" + }, + { + "name": "captainhook/secrets", + "version": "0.9.7", + "source": { + "type": "git", + "url": "https://github.com/captainhook-git/secrets.git", + "reference": "d62c97f75f81ac98e22f1c282482bd35fa82f631" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/captainhook-git/secrets/zipball/d62c97f75f81ac98e22f1c282482bd35fa82f631", + "reference": "d62c97f75f81ac98e22f1c282482bd35fa82f631", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "CaptainHook\\Secrets\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Feldmann", + "email": "sf@sebastian-feldmann.info" + } + ], + "description": "Utility classes to detect secrets", + "keywords": [ + "commit-msg", + "keys", + "passwords", + "post-merge", + "prepare-commit-msg", + "secrets", + "tokens" + ], + "support": { + "issues": "https://github.com/captainhook-git/secrets/issues", + "source": "https://github.com/captainhook-git/secrets/tree/0.9.7" + }, + "funding": [ + { + "url": "https://github.com/sponsors/sebastianfeldmann", + "type": "github" + } + ], + "time": "2025-04-08T07:10:48+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, { "name": "doctrine/deprecations", "version": "1.1.6", @@ -5017,16 +4844,16 @@ }, { "name": "illuminate/bus", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/bus.git", - "reference": "0e19c4978558bd29000dfde28e4f1db2fb41b209" + "reference": "d1362729e3d37efca1f1cbc7a02f752cf24af442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/bus/zipball/0e19c4978558bd29000dfde28e4f1db2fb41b209", - "reference": "0e19c4978558bd29000dfde28e4f1db2fb41b209", + "url": "https://api.github.com/repos/illuminate/bus/zipball/d1362729e3d37efca1f1cbc7a02f752cf24af442", + "reference": "d1362729e3d37efca1f1cbc7a02f752cf24af442", "shasum": "" }, "require": { @@ -5066,20 +4893,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-05T14:28:47+00:00" + "time": "2026-02-12T15:22:19+00:00" }, { "name": "illuminate/cache", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/cache.git", - "reference": "a7f0de6a2cff1948a09f3b4a0531f9e363447674" + "reference": "58aee25d94f3376eb15f40e1cc5963c6c8b37800" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/cache/zipball/a7f0de6a2cff1948a09f3b4a0531f9e363447674", - "reference": "a7f0de6a2cff1948a09f3b4a0531f9e363447674", + "url": "https://api.github.com/repos/illuminate/cache/zipball/58aee25d94f3376eb15f40e1cc5963c6c8b37800", + "reference": "58aee25d94f3376eb15f40e1cc5963c6c8b37800", "shasum": "" }, "require": { @@ -5095,7 +4922,7 @@ "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.", + "ext-memcached": "Required to use the memcached 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).", @@ -5128,11 +4955,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-09T20:42:48+00:00" + "time": "2026-02-16T18:49:43+00:00" }, { "name": "illuminate/config", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/config.git", @@ -5180,16 +5007,16 @@ }, { "name": "illuminate/console", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/console.git", - "reference": "edd1d00fe086108b80d5c095aba72a58f52d2d3b" + "reference": "275125420a7505c193d718efc934f91f83e7a8fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/console/zipball/edd1d00fe086108b80d5c095aba72a58f52d2d3b", - "reference": "edd1d00fe086108b80d5c095aba72a58f52d2d3b", + "url": "https://api.github.com/repos/illuminate/console/zipball/275125420a7505c193d718efc934f91f83e7a8fe", + "reference": "275125420a7505c193d718efc934f91f83e7a8fe", "shasum": "" }, "require": { @@ -5242,20 +5069,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-10T16:01:17+00:00" + "time": "2026-02-12T15:23:36+00:00" }, { "name": "illuminate/container", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f" + "reference": "648307e8f54bcd9450c858f99abd11bc50c364a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/a605f25d0e014b6e521bcb142a4eba578966a24f", - "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f", + "url": "https://api.github.com/repos/illuminate/container/zipball/648307e8f54bcd9450c858f99abd11bc50c364a0", + "reference": "648307e8f54bcd9450c858f99abd11bc50c364a0", "shasum": "" }, "require": { @@ -5304,11 +5131,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-29T03:13:58+00:00" + "time": "2026-02-12T16:13:27+00:00" }, { "name": "illuminate/events", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/events.git", @@ -5363,7 +5190,7 @@ }, { "name": "illuminate/pipeline", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/pipeline.git", @@ -5415,7 +5242,7 @@ }, { "name": "illuminate/process", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/process.git", @@ -5466,16 +5293,16 @@ }, { "name": "illuminate/testing", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/testing.git", - "reference": "021b86baa1e8da0f8c75b47a94cdf664c134feca" + "reference": "41ed43b5b51f0606a25b0162b922c17d1fb9bc03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/testing/zipball/021b86baa1e8da0f8c75b47a94cdf664c134feca", - "reference": "021b86baa1e8da0f8c75b47a94cdf664c134feca", + "url": "https://api.github.com/repos/illuminate/testing/zipball/41ed43b5b51f0606a25b0162b922c17d1fb9bc03", + "reference": "41ed43b5b51f0606a25b0162b922c17d1fb9bc03", "shasum": "" }, "require": { @@ -5522,20 +5349,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-09T13:46:06+00:00" + "time": "2026-02-16T22:49:32+00:00" }, { "name": "illuminate/view", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/illuminate/view.git", - "reference": "5b3d7ae07665b98cca46846074edc008ed5e3864" + "reference": "dc0106f1f09d3bffdfba1c6a637b01d00f0fc4ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/view/zipball/5b3d7ae07665b98cca46846074edc008ed5e3864", - "reference": "5b3d7ae07665b98cca46846074edc008ed5e3864", + "url": "https://api.github.com/repos/illuminate/view/zipball/dc0106f1f09d3bffdfba1c6a637b01d00f0fc4ba", + "reference": "dc0106f1f09d3bffdfba1c6a637b01d00f0fc4ba", "shasum": "" }, "require": { @@ -5576,7 +5403,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-20T15:07:29+00:00" + "time": "2026-02-14T22:58:48+00:00" }, { "name": "jean85/pretty-package-versions", @@ -6582,39 +6409,36 @@ }, { "name": "nunomaduro/collision", - "version": "v8.8.3", + "version": "v8.9.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", - "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", "shasum": "" }, "require": { - "filp/whoops": "^2.18.1", - "nunomaduro/termwind": "^2.3.1", + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.3.0" + "symfony/console": "^7.4.4 || ^8.0.4" }, "conflict": { - "laravel/framework": "<11.44.2 || >=13.0.0", - "phpunit/phpunit": "<11.5.15 || >=13.0.0" + "laravel/framework": "<11.48.0 || >=14.0.0", + "phpunit/phpunit": "<11.5.50 || >=14.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" + "brianium/paratest": "^7.8.5", + "larastan/larastan": "^3.9.2", + "laravel/framework": "^11.48.0 || ^12.52.0", + "laravel/pint": "^1.27.1", + "orchestra/testbench-core": "^9.12.0 || ^10.9.0", + "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" }, "type": "library", "extra": { @@ -6677,7 +6501,7 @@ "type": "patreon" } ], - "time": "2025-11-20T02:55:25+00:00" + "time": "2026-02-17T17:33:08+00:00" }, { "name": "nunomaduro/laravel-console-summary", @@ -6874,42 +6698,192 @@ "time": "2025-02-19T11:22:09+00:00" }, { - "name": "pestphp/pest", - "version": "v4.3.2", + "name": "nunomaduro/termwind", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/pestphp/pest.git", - "reference": "3a4329ddc7a2b67c19fca8342a668b39be3ae398" + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/3a4329ddc7a2b67c19fca8342a668b39be3ae398", - "reference": "3a4329ddc7a2b67c19fca8342a668b39be3ae398", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", "shasum": "" }, "require": { - "brianium/paratest": "^7.16.1", - "nunomaduro/collision": "^8.8.3", - "nunomaduro/termwind": "^2.3.3", + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "require-dev": { + "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" + }, + "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/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "It's like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2026-02-16T23:10:27+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" + }, + { + "name": "pestphp/pest", + "version": "v4.4.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "f96a1b27864b585b0b29b0ee7331176726f7e54a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/f96a1b27864b585b0b29b0ee7331176726f7e54a", + "reference": "f96a1b27864b585b0b29b0ee7331176726f7e54a", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.19.0", + "nunomaduro/collision": "^8.9.0", + "nunomaduro/termwind": "^2.4.0", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.1", "pestphp/pest-plugin-profanity": "^4.2.1", "php": "^8.3.0", - "phpunit/phpunit": "^12.5.8", - "symfony/process": "^7.4.4|^8.0.0" + "phpunit/phpunit": "^12.5.12", + "symfony/process": "^7.4.5|^8.0.5" }, "conflict": { "filp/whoops": "<2.18.3", - "phpunit/phpunit": ">12.5.8", + "phpunit/phpunit": ">12.5.12", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { - "pestphp/pest-dev-tools": "^4.0.0", - "pestphp/pest-plugin-browser": "^4.2.1", + "pestphp/pest-dev-tools": "^4.1.0", + "pestphp/pest-plugin-browser": "^4.3.0", "pestphp/pest-plugin-type-coverage": "^4.0.3", - "psy/psysh": "^0.12.18" + "psy/psysh": "^0.12.20" }, "bin": [ "bin/pest" @@ -6975,7 +6949,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v4.3.2" + "source": "https://github.com/pestphp/pest/tree/v4.4.1" }, "funding": [ { @@ -6987,7 +6961,7 @@ "type": "github" } ], - "time": "2026-01-28T01:01:19+00:00" + "time": "2026-02-17T15:27:18+00:00" }, { "name": "pestphp/pest-plugin", @@ -7554,6 +7528,89 @@ }, "time": "2025-11-21T15:09:14+00:00" }, + { + "name": "phpmd/phpmd", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "shasum": "" + }, + "require": { + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", + "keywords": [ + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2023-12-11T08:22:20+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.5", @@ -8024,16 +8081,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.8", + "version": "12.5.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "37ddb96c14bfee10304825edbb7e66d341ec6889" + "reference": "418e06b3b46b0d54bad749ff4907fc7dfb530199" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/37ddb96c14bfee10304825edbb7e66d341ec6889", - "reference": "37ddb96c14bfee10304825edbb7e66d341ec6889", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/418e06b3b46b0d54bad749ff4907fc7dfb530199", + "reference": "418e06b3b46b0d54bad749ff4907fc7dfb530199", "shasum": "" }, "require": { @@ -8047,8 +8104,8 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.2", - "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-code-coverage": "^12.5.3", + "phpunit/php-file-iterator": "^6.0.1", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", "phpunit/php-timer": "^8.0.0", @@ -8059,6 +8116,7 @@ "sebastian/exporter": "^7.0.2", "sebastian/global-state": "^8.0.2", "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", "sebastian/type": "^6.0.3", "sebastian/version": "^6.0.0", "staabm/side-effects-detector": "^1.0.5" @@ -8101,7 +8159,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.12" }, "funding": [ { @@ -8125,7 +8183,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T06:12:29+00:00" + "time": "2026-02-16T08:34:36+00:00" }, { "name": "ramsey/collection", @@ -8287,12 +8345,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "7f3e95c9ebf1b16e002dd2c913d30d962c2a6a16" + "reference": "92c5ec5685cfbcd7ef721a502e6622516728011c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/7f3e95c9ebf1b16e002dd2c913d30d962c2a6a16", - "reference": "7f3e95c9ebf1b16e002dd2c913d30d962c2a6a16", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/92c5ec5685cfbcd7ef721a502e6622516728011c", + "reference": "92c5ec5685cfbcd7ef721a502e6622516728011c", "shasum": "" }, "conflict": { @@ -8460,6 +8518,7 @@ "devgroup/dotplant": "<2020.09.14-dev", "digimix/wp-svg-upload": "<=1", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "directorytree/imapengine": "<1.22.3", "dl/yag": "<3.0.1", "dmk/webkitpdf": "<1.1.4", "dnadesign/silverstripe-elemental": "<5.3.12", @@ -8562,7 +8621,7 @@ "filegator/filegator": "<7.8", "filp/whoops": "<2.1.13", "fineuploader/php-traditional-server": "<=1.2.2", - "firebase/php-jwt": "<6", + "firebase/php-jwt": "<7", "fisharebest/webtrees": "<=2.1.18", "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6", @@ -8600,7 +8659,7 @@ "genix/cms": "<=1.1.11", "georgringer/news": "<1.3.3", "geshi/geshi": "<=1.0.9.1", - "getformwork/formwork": "<2.2", + "getformwork/formwork": "<=2.3.3", "getgrav/grav": "<1.11.0.0-beta1", "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<=5.2.1", "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", @@ -8723,7 +8782,7 @@ "leantime/leantime": "<3.3", "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", "libreform/libreform": ">=2,<=2.0.8", - "librenms/librenms": "<25.12", + "librenms/librenms": "<26.2", "liftkit/database": "<2.13.2", "lightsaml/lightsaml": "<1.3.5", "limesurvey/limesurvey": "<6.5.12", @@ -8931,7 +8990,7 @@ "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", - "pterodactyl/panel": "<1.12", + "pterodactyl/panel": "<1.12.1", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", @@ -9034,7 +9093,7 @@ "starcitizentools/short-description": ">=4,<4.0.1", "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<5.73.6|>=6,<6.2.5", + "statamic/cms": "<5.73.9|>=6,<6.3.2", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<=2.1.64", "studiomitte/friendlycaptcha": "<0.1.4", @@ -9206,7 +9265,7 @@ "wpanel/wpanel4-cms": "<=4.3.1", "wpcloud/wp-stateless": "<3.2", "wpglobus/wpglobus": "<=1.9.6", - "wwbn/avideo": "<14.3", + "wwbn/avideo": "<21", "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", @@ -9265,7 +9324,8 @@ "zf-commons/zfc-user": "<1.2.2", "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", "zfr/zfr-oauth2-server-module": "<0.1.2", - "zoujingli/thinkadmin": "<=6.1.53" + "zoujingli/thinkadmin": "<=6.1.53", + "zumba/json-serializer": "<3.2.3" }, "default-branch": true, "type": "metapackage", @@ -9303,7 +9363,7 @@ "type": "tidelift" } ], - "time": "2026-02-13T23:11:21+00:00" + "time": "2026-02-20T22:06:39+00:00" }, { "name": "sebastian/cli-parser", @@ -10202,6 +10262,261 @@ ], "time": "2025-02-07T05:00:38+00:00" }, + { + "name": "sebastianfeldmann/camino", + "version": "0.9.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianfeldmann/camino.git", + "reference": "bf2e4c8b2a029e9eade43666132b61331e3e8184" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianfeldmann/camino/zipball/bf2e4c8b2a029e9eade43666132b61331e3e8184", + "reference": "bf2e4c8b2a029e9eade43666132b61331e3e8184", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "SebastianFeldmann\\Camino\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Feldmann", + "email": "sf@sebastian-feldmann.info" + } + ], + "description": "Path management the OO way", + "homepage": "https://github.com/sebastianfeldmann/camino", + "keywords": [ + "file system", + "path" + ], + "support": { + "issues": "https://github.com/sebastianfeldmann/camino/issues", + "source": "https://github.com/sebastianfeldmann/camino/tree/0.9.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianfeldmann", + "type": "github" + } + ], + "time": "2022-01-03T13:15:10+00:00" + }, + { + "name": "sebastianfeldmann/cli", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianfeldmann/cli.git", + "reference": "6fa122afd528dae7d7ec988a604aa6c600f5d9b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianfeldmann/cli/zipball/6fa122afd528dae7d7ec988a604aa6c600f5d9b5", + "reference": "6fa122afd528dae7d7ec988a604aa6c600f5d9b5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "symfony/process": "^4.3 | ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "SebastianFeldmann\\Cli\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Feldmann", + "email": "sf@sebastian-feldmann.info" + } + ], + "description": "PHP cli helper classes", + "homepage": "https://github.com/sebastianfeldmann/cli", + "keywords": [ + "cli" + ], + "support": { + "issues": "https://github.com/sebastianfeldmann/cli/issues", + "source": "https://github.com/sebastianfeldmann/cli/tree/3.4.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianfeldmann", + "type": "github" + } + ], + "time": "2024-11-26T10:19:01+00:00" + }, + { + "name": "sebastianfeldmann/git", + "version": "3.16.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianfeldmann/git.git", + "reference": "40a5cc043f0957228767f639e370ec92590e940f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/40a5cc043f0957228767f639e370ec92590e940f", + "reference": "40a5cc043f0957228767f639e370ec92590e940f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "php": ">=8.0", + "sebastianfeldmann/cli": "^3.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "SebastianFeldmann\\Git\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian Feldmann", + "email": "sf@sebastian-feldmann.info" + } + ], + "description": "PHP git wrapper", + "homepage": "https://github.com/sebastianfeldmann/git", + "keywords": [ + "git" + ], + "support": { + "issues": "https://github.com/sebastianfeldmann/git/issues", + "source": "https://github.com/sebastianfeldmann/git/tree/3.16.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianfeldmann", + "type": "github" + } + ], + "time": "2026-01-26T20:59:18+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0525c73950de35ded110cffafb9892946d7771b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=7.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-10T16:43:36+00:00" + }, { "name": "staabm/side-effects-detector", "version": "1.0.5", @@ -10254,6 +10569,419 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/config", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/4275b53b8ab0cf37f48bf273dc2285c8178efdfb", + "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1|^8.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/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-13T11:36:38+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "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" + }, + "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" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/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-13T11:36:38+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.5" + }, + "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-27T16:16:02+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + }, + "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-11-27T13:27:24+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "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": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "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-06-27T09:58:17+00:00" + }, { "name": "symfony/process", "version": "v7.4.5", @@ -10320,24 +11048,281 @@ "time": "2026-01-26T15:07:59+00:00" }, { - "name": "ta-tikoma/phpunit-architecture-test", - "version": "0.8.6", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", - "reference": "ad48430b92901fd7d003fdaf2d7b139f96c0906e" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/ad48430b92901fd7d003fdaf2d7b139f96c0906e", - "reference": "ad48430b92901fd7d003fdaf2d7b139f96c0906e", + "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": "v8.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "758b372d6882506821ed666032e43020c4f57194" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", + "reference": "758b372d6882506821ed666032e43020c4f57194", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "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.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^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/v8.0.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-12T12:37:40+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04", + "reference": "7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "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": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v8.0.0" + }, + "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-11-05T18:53:00+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.7", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/1248f3f506ca9641d4f68cebcd538fa489754db8", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8", "shasum": "" }, "require": { "nikic/php-parser": "^4.18.0 || ^5.0.0", "php": "^8.1.0", "phpdocumentor/reflection-docblock": "^5.3.0 || ^6.0.0", - "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0 || ^13.0.0", "symfony/finder": "^6.4.0 || ^7.0.0 || ^8.0.0" }, "require-dev": { @@ -10374,9 +11359,9 @@ ], "support": { "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", - "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.6" + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.7" }, - "time": "2026-01-30T07:16:00+00:00" + "time": "2026-02-17T17:25:14+00:00" }, { "name": "theseer/tokenizer", @@ -10514,16 +11499,16 @@ }, { "name": "webmozart/assert", - "version": "2.1.3", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "6976757ba8dd70bf8cbaea0914ad84d8b51a9f46" + "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6976757ba8dd70bf8cbaea0914ad84d8b51a9f46", - "reference": "6976757ba8dd70bf8cbaea0914ad84d8b51a9f46", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/79155f94852fa27e2f73b459f6503f5e87e2c188", + "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188", "shasum": "" }, "require": { @@ -10570,9 +11555,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.3" + "source": "https://github.com/webmozarts/assert/tree/2.1.5" }, - "time": "2026-02-13T21:01:40+00:00" + "time": "2026-02-18T14:09:36+00:00" } ], "aliases": [], diff --git a/config/commands.php b/config/commands.php index 7678731..9e01bd8 100644 --- a/config/commands.php +++ b/config/commands.php @@ -53,7 +53,6 @@ return [ * below a list of commands that you don't to see in your app. */ 'remove' => [ - \App\Commands\InspireCommand::class, Illuminate\Console\Scheduling\ScheduleRunCommand::class, Illuminate\Console\Scheduling\ScheduleListCommand::class, Illuminate\Console\Scheduling\ScheduleFinishCommand::class, diff --git a/docs/plans/2026-02-23-fix-sonar-duplications-design.md b/docs/plans/2026-02-23-fix-sonar-duplications-design.md new file mode 100644 index 0000000..85e9391 --- /dev/null +++ b/docs/plans/2026-02-23-fix-sonar-duplications-design.md @@ -0,0 +1,57 @@ +# Fix SonarCloud Duplication Quality Gate + +## Problem + +PR #43 fails the Sonar Way quality gate: `new_duplicated_lines_density` is +7.5% (threshold: <=3%). SonarCloud detects 2 duplicated blocks / 28 lines, +all within `tests/Feature/Commands/CheckCommandTest.php`. + +The duplication is between 4 structurally identical input-validation tests +(lines 71-93) that each follow the same 3-line pattern: + +```php +$this->artisan('') + ->expectsOutputToContain('') + ->assertExitCode(1); +``` + +## Solution + +Replace the 4 separate tests with one Pest parameterized test using a named +`with()` dataset: + +```php +test('rejects invalid input', function ( + string $args, + string $expected, +) { + $this->artisan($args) + ->expectsOutputToContain($expected) + ->assertExitCode(1); +})->with([ + 'missing package' => [ + 'check ivuorinen', + 'Missing package name', + ], + 'conflicting arguments' => [ + 'check ivuorinen/branch-usage-checker extra', + 'Conflicting arguments', + ], + 'invalid vendor' => [ + 'check INVALID!/package-name', + 'Invalid vendor name', + ], + 'invalid package' => [ + 'check valid-vendor INVALID!', + 'Invalid package name', + ], +]); +``` + +## Impact + +- **File:** `tests/Feature/Commands/CheckCommandTest.php` +- **Lines removed:** ~24 (4 test blocks) +- **Lines added:** ~10 (1 parameterized test) +- **Test count:** Stays at 14 (Pest expands each dataset row) +- **Expected duplication:** 0% on new code diff --git a/docs/plans/2026-02-23-fix-sonar-duplications-plan.md b/docs/plans/2026-02-23-fix-sonar-duplications-plan.md new file mode 100644 index 0000000..936a0f6 --- /dev/null +++ b/docs/plans/2026-02-23-fix-sonar-duplications-plan.md @@ -0,0 +1,135 @@ +# Fix SonarCloud Duplication Quality Gate — Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use +> superpowers:executing-plans to implement this +> plan task-by-task. + +**Goal:** Eliminate test code duplication that +fails the Sonar Way quality gate on PR #43. + +**Architecture:** Replace 4 structurally identical +input-validation tests with one Pest parameterized +test using a named `with()` dataset. No production +code changes. + +**Tech Stack:** Pest v4, PHP 8.4 + +--- + +## Task 1: Replace validation tests with dataset + +Files to modify: +`tests/Feature/Commands/CheckCommandTest.php:71-93` + +### Step 1: Replace the 4 test blocks + +Replace these 4 tests (lines 71-93): + +```php +test('check command with missing package shows error', + function () { + $this->artisan('check ivuorinen') + ->expectsOutputToContain('Missing package name') + ->assertExitCode(1); +}); + +test('check command with conflicting arguments shows error', + function () { + $this->artisan( + 'check ivuorinen/branch-usage-checker extra' + ) + ->expectsOutputToContain( + 'Conflicting arguments' + ) + ->assertExitCode(1); +}); + +test('check command with invalid vendor shows error', + function () { + $this->artisan('check INVALID!/package-name') + ->expectsOutputToContain('Invalid vendor name') + ->assertExitCode(1); +}); + +test('check command with invalid package name shows error', + function () { + $this->artisan('check valid-vendor INVALID!') + ->expectsOutputToContain( + 'Invalid package name' + ) + ->assertExitCode(1); +}); +``` + +With one parameterized test: + +```php +test('check command rejects invalid input', + function (string $args, string $expected) { + $this->artisan($args) + ->expectsOutputToContain($expected) + ->assertExitCode(1); +})->with([ + 'missing package' => [ + 'check ivuorinen', + 'Missing package name', + ], + 'conflicting arguments' => [ + 'check ivuorinen/branch-usage-checker extra', + 'Conflicting arguments', + ], + 'invalid vendor' => [ + 'check INVALID!/package-name', + 'Invalid vendor name', + ], + 'invalid package' => [ + 'check valid-vendor INVALID!', + 'Invalid package name', + ], +]); +``` + +### Step 2: Run tests to verify all pass + +Run: `composer test` + +Expected: 14 passed (Pest expands dataset rows +into individual test runs, so `with missing +package`, `with conflicting arguments`, etc. each +appear separately). + +### Step 3: Run linter + +Run: `composer lint` + +Expected: Clean (no PHPCS errors). + +### Step 4: Commit + +```bash +git add tests/Feature/Commands/CheckCommandTest.php +git commit -m "refactor(tests): parameterize \ +input-validation tests to fix duplication gate" +``` + +## Task 2: Push and verify quality gate + +### Step 1: Push + +```bash +git push +``` + +### Step 2: Verify on SonarCloud + +Check the quality gate status via API: + +```bash +curl -s 'https://sonarcloud.io/api/qualitygates/\ +project_status?projectKey=\ +ivuorinen_branch-usage-checker&pullRequest=43' \ +| python3 -m json.tool +``` + +Expected: `new_duplicated_lines_density` condition +status changes from `ERROR` to `OK`. diff --git a/docs/plans/2026-02-23-phpcs-captainhook-design.md b/docs/plans/2026-02-23-phpcs-captainhook-design.md new file mode 100644 index 0000000..7fe8bcd --- /dev/null +++ b/docs/plans/2026-02-23-phpcs-captainhook-design.md @@ -0,0 +1,49 @@ +# PHPCS + PHPCBF + CaptainHook Integration + +Date: 2026-02-23 + +## Goal + +Add local code formatting enforcement via PHPCS/PHPCBF +with automatic pre-commit hooks managed by CaptainHook. + +## New Dependencies + +- `squizlabs/php_codesniffer` (dev) — linter (`phpcs`) + and auto-fixer (`phpcbf`) +- `captainhook/captainhook` (dev) — git hook manager +- `captainhook/hook-installer` (dev) — Composer plugin + that auto-installs hooks on `composer install` + +## Composer Scripts + +- `composer lint` — runs `phpcs` to report violations +- `composer format` — runs `phpcbf` to auto-fix + +## Config Files + +### phpcs.xml (existing, unchanged) + +PSR-12 with two exclusions: + +- `PSR12.Operators.OperatorSpacing` +- `PSR1.Files.SideEffects.FoundWithSymbols` + +### captainhook.json (new) + +Pre-commit hook that runs `phpcbf` on staged PHP files. +If unfixable issues remain, the commit is blocked. + +## Pre-commit Hook Behavior + +1. Developer commits +2. CaptainHook triggers pre-commit +3. Runs `phpcbf` on staged PHP files +4. If all issues auto-fixed, commit proceeds +5. If unfixable issues remain, commit blocked + +## What Does NOT Change + +- `phpcs.xml` rules stay the same +- CI workflow unchanged (Codacy handles remote checks) +- No functional code changes diff --git a/docs/plans/2026-02-23-phpcs-captainhook-plan.md b/docs/plans/2026-02-23-phpcs-captainhook-plan.md new file mode 100644 index 0000000..597dd8a --- /dev/null +++ b/docs/plans/2026-02-23-phpcs-captainhook-plan.md @@ -0,0 +1,340 @@ +# PHPCS + CaptainHook Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use +> superpowers:executing-plans to implement this plan +> task-by-task. + +**Goal:** Add PHPCS/PHPCBF as a dev dependency with +composer scripts and CaptainHook-managed pre-commit +hook for automatic formatting. + +**Architecture:** Install three dev packages +(`squizlabs/php_codesniffer`, `captainhook/captainhook`, +`captainhook/hook-installer`). Add `composer lint` and +`composer format` scripts. Configure CaptainHook to run +PHPCBF on staged PHP files before each commit. The +existing `phpcs.xml` is unchanged. + +**Tech Stack:** PHP 8.4, Composer, PHPCS/PHPCBF, +CaptainHook + +--- + +## Task 1: Install squizlabs/php\_codesniffer + +**Files:** + +- Modify: `composer.json` (auto-updated by composer) +- Modify: `composer.lock` (auto-updated by composer) + +### Step 1: Install the package + +Run: + +```bash +composer require --dev squizlabs/php_codesniffer +``` + +Expected: Package installs successfully. +`composer.json` now lists `squizlabs/php_codesniffer` +in `require-dev`. + +### Step 2: Verify phpcs works with existing config + +Run: + +```bash +vendor/bin/phpcs --standard=phpcs.xml app/ tests/ +``` + +Expected: Either clean output or a list of violations. +The command should not error out — it should find and +use `phpcs.xml`. + +### Step 3: Verify phpcbf works + +Run: + +```bash +vendor/bin/phpcbf --standard=phpcs.xml app/ tests/ +``` + +Note: `phpcbf` exits non-zero when it finds fixable +issues. The `|| true` prevents that from stopping +execution. What matters is it runs without crashing. + +### Step 4: Commit + +```bash +git add composer.json composer.lock +git commit -m "build(deps): add squizlabs/php_codesniffer" +``` + +--- + +## Task 2: Add composer lint and format scripts + +**Files:** + +- Modify: `composer.json` — add two entries to + `"scripts"` + +### Step 1: Add the scripts + +In `composer.json`, add these two entries inside the +`"scripts"` object: + +```json +"lint": "vendor/bin/phpcs", +"format": "vendor/bin/phpcbf || true" +``` + +Note: `phpcbf` returns exit code 1 when it fixes files +(which is normal behavior, not an error). The `|| true` +prevents composer from treating successful fixes as +failures. If there are unfixable errors, `phpcbf` +returns exit code 2, but `|| true` masks that too — +this is acceptable since `composer lint` is the proper +check command. + +### Step 2: Verify `composer lint` works + +Run: + +```bash +composer lint +``` + +Expected: Runs `phpcs` against the project. Either +reports violations or shows no output (clean). + +### Step 3: Verify `composer format` works + +Run: + +```bash +composer format +``` + +Expected: Runs `phpcbf`. Auto-fixes any fixable +violations. + +### Step 4: Run tests to confirm nothing broke + +Run: + +```bash +composer test +``` + +Expected: All 14 tests pass. + +### Step 5: Commit + +```bash +git add composer.json +git commit -m "build: add composer lint and format scripts" +``` + +--- + +## Task 3: Install CaptainHook + +**Files:** + +- Modify: `composer.json` (auto-updated by composer) +- Modify: `composer.lock` (auto-updated by composer) + +**Step 1: Install captainhook and the hook-installer +plugin** + +Run: + +```bash +composer require --dev captainhook/captainhook captainhook/hook-installer +``` + +Note: The installer may prompt about allowing the +plugin. Answer yes. If `composer.json`'s +`config.allow-plugins` needs updating, composer will +do it automatically when you approve. + +### Step 2: Verify CaptainHook is available + +Run: + +```bash +vendor/bin/captainhook --version +``` + +Expected: Prints a version string +(e.g. `CaptainHook x.x.x`). + +### Step 3: Commit + +```bash +git add composer.json composer.lock +git commit -m "build(deps): add captainhook and hook-installer" +``` + +--- + +## Task 4: Configure CaptainHook pre-commit hook + +**Files:** + +- Create: `captainhook.json` + +### Step 1: Create the CaptainHook config + +Create `captainhook.json` in the project root with +this content: + +```json +{ + "pre-commit": { + "enabled": true, + "actions": [ + { + "action": "vendor/bin/phpcbf --standard=phpcs.xml {$STAGED_FILES|of-type:php}", + "config": { + "label": "Fix code style with PHPCBF" + } + }, + { + "action": "vendor/bin/phpcs --standard=phpcs.xml {$STAGED_FILES|of-type:php}", + "config": { + "label": "Check code style with PHPCS" + } + } + ] + }, + "pre-push": { + "enabled": false, + "actions": [] + }, + "commit-msg": { + "enabled": false, + "actions": [] + } +} +``` + +The two pre-commit actions run in order: + +1. `phpcbf` auto-fixes what it can +2. `phpcs` checks for remaining violations — if any + exist, the commit is blocked + +### Step 2: Install the hooks into `.git/hooks` + +Run: + +```bash +vendor/bin/captainhook install --force +``` + +Expected: CaptainHook installs hook scripts into +`.git/hooks/`. Output mentions installing hooks. + +### Step 3: Verify the hook is installed + +Run: + +```bash +head -5 .git/hooks/pre-commit +``` + +Expected: Shows a CaptainHook-generated script +(not a sample hook). + +### Step 4: Commit + +```bash +git add captainhook.json +git commit -m "build: configure CaptainHook pre-commit hook for PHPCS" +``` + +Note: This commit itself will trigger the pre-commit +hook for the first time. If it blocks due to formatting +issues in existing files, run `composer format` first, +then re-stage and commit. + +--- + +## Task 5: Fix existing violations and final verify + +### Step 1: Run the formatter on the whole project + +Run: + +```bash +composer format +``` + +Expected: Fixes any existing violations across `app/` +and `tests/`. + +### Step 2: Run the linter to confirm clean + +Run: + +```bash +composer lint +``` + +Expected: No violations reported (clean exit). + +### Step 3: Run tests to confirm nothing broke + +Run: + +```bash +composer test +``` + +Expected: All 14 tests pass. + +### Step 4: Commit any formatting changes + +```bash +git add -A +git status +git commit -m "style: auto-fix code style with phpcbf" +``` + +Note: Only commit if there are actual changes. If +`composer format` made no changes, skip this step. + +--- + +## Task 6: Update CLAUDE.md + +**Files:** + +- Modify: `CLAUDE.md` — add `composer lint` and + `composer format` to Commands section, note + CaptainHook in Code Standards + +### Step 1: Update CLAUDE.md + +In the `## Commands` section, add: + +```markdown +- `composer lint` — Check code style (PHPCS) +- `composer format` — Auto-fix code style (PHPCBF) +``` + +In the `## Code Standards` section, add a note: + +```markdown +- CaptainHook pre-commit hook runs PHPCBF then + PHPCS on staged PHP files automatically +``` + +### Step 2: Commit + +```bash +git add CLAUDE.md +git commit -m "docs: add lint/format commands and hook info to CLAUDE.md" +``` diff --git a/phpcs.xml b/phpcs.xml index 2260283..27ecceb 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,6 +1,8 @@ PHP_CodeSniffer configuration + app + tests diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..9899e36 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,24 @@ + + + PHPMD rules for Branch Usage Checker + + + + + + + + + + + + + + + + + diff --git a/tests/Feature/Commands/CheckCommandTest.php b/tests/Feature/Commands/CheckCommandTest.php index aa08b0e..2ad60ba 100644 --- a/tests/Feature/Commands/CheckCommandTest.php +++ b/tests/Feature/Commands/CheckCommandTest.php @@ -1,7 +1,165 @@ artisan('check ivuorinen branch-usage-checker') - // ->expectsOutput('') - ->assertExitCode(0); +use Illuminate\Support\Facades\Http; + +const TEST_VENDOR = 'test-vendor'; +const TEST_PACKAGE = 'test-package'; +const TEST_COMMAND = 'check ' . TEST_VENDOR . ' ' . TEST_PACKAGE; +const TEST_METADATA_URL = 'packagist.org/packages/' . TEST_VENDOR . '/' . TEST_PACKAGE . '.json'; +const TEST_STATS_URL = 'packagist.org/packages/' . TEST_VENDOR . '/' . TEST_PACKAGE . '/stats'; + +beforeEach(function () { + Http::preventStrayRequests(); +}); + +function validMetadata(): array +{ + return [ + 'package' => [ + 'name' => TEST_VENDOR . '/' . TEST_PACKAGE, + 'description' => 'Test', + 'time' => '2024-01-01T00:00:00+00:00', + 'type' => 'library', + 'repository' => 'https://github.com/' . TEST_VENDOR . '/' . TEST_PACKAGE, + 'language' => 'PHP', + 'versions' => [ + 'dev-main' => ['version' => 'dev-main'], + 'dev-feature' => ['version' => 'dev-feature'], + '1.0.0' => ['version' => '1.0.0'], + ], + ], + ]; +} + +function statsResponse(array $downloads): array +{ + return [ + 'labels' => ['2024-01', '2024-02', '2024-03'], + 'values' => [$downloads], + ]; +} + +function fakePackageResponses(array $statsPerBranch = []): void +{ + $fakes = [TEST_METADATA_URL => Http::response(validMetadata())]; + foreach ($statsPerBranch as $branch => $response) { + $fakes[TEST_STATS_URL . '/' . $branch . '.json*'] = $response; + } + Http::fake($fakes); +} + +test('check command with slash format', function () { + fakePackageResponses([ + 'dev-feature' => Http::response(statsResponse([1, 2, 3])), + 'dev-main' => Http::response(statsResponse([1, 2, 3])), + ]); + + $this->artisan('check ' . TEST_VENDOR . '/' . TEST_PACKAGE) + ->assertExitCode(0); +}); + +test('check command with two arguments', function () { + fakePackageResponses([ + 'dev-feature' => Http::response(statsResponse([1, 2, 3])), + 'dev-main' => Http::response(statsResponse([1, 2, 3])), + ]); + + $this->artisan('check ' . TEST_VENDOR . ' ' . TEST_PACKAGE) + ->assertExitCode(0); +}); + +test('check command rejects invalid input', function (string $args, string $expected) { + $this->artisan($args) + ->expectsOutputToContain($expected) + ->assertExitCode(1); +})->with([ + 'missing package' => ['check ivuorinen', 'Missing package name'], + 'conflicting arguments' => ['check ivuorinen/branch-usage-checker extra', 'Conflicting arguments'], + 'invalid vendor' => ['check INVALID!/package-name', 'Invalid vendor name'], + 'invalid package' => ['check valid-vendor INVALID!', 'Invalid package name'], +]); + +test('check command with 404 shows package not found', function () { + Http::fake([ + 'packagist.org/packages/test-vendor/nonexistent-pkg.json' => Http::response([], 404), + ]); + + $this->artisan('check test-vendor nonexistent-pkg') + ->expectsOutputToContain('Package not found') + ->assertExitCode(1); +}); + +test('check command with 500 shows server error', function () { + Http::fake([ + TEST_METADATA_URL => Http::response([], 500), + ]); + + $this->artisan(TEST_COMMAND) + ->expectsOutputToContain('Failed to fetch package metadata (HTTP 500)') + ->assertExitCode(1); +}); + +test('check command skips branch when stats fetch fails', function () { + fakePackageResponses([ + 'dev-feature' => Http::response([], 500), + 'dev-main' => Http::response(statsResponse([10, 20, 30])), + ]); + + $this->artisan(TEST_COMMAND) + ->expectsOutputToContain('Failed to fetch stats for dev-feature') + ->assertExitCode(0); +}); + +test('check command skips branch on connection failure', function () { + fakePackageResponses([ + 'dev-feature' => Http::failedConnection(), + 'dev-main' => Http::response(statsResponse([10, 20, 30])), + ]); + + $this->artisan(TEST_COMMAND) + ->expectsOutputToContain('Failed to fetch stats for dev-feature') + ->assertExitCode(0); +}); + +test('check command stops when all stats fail', function () { + fakePackageResponses([ + 'dev-feature' => Http::response([], 500), + 'dev-main' => Http::response([], 500), + ]); + + $this->artisan(TEST_COMMAND) + ->expectsOutputToContain('No statistics found... Stopping.') + ->assertExitCode(0); +}); + +test('check command lets TypeError propagate from malformed payload', function () { + Http::fake([ + TEST_METADATA_URL => Http::response([ + 'package' => ['versions' => 'not-an-array'], + ]), + ]); + + $this->artisan(TEST_COMMAND); +})->throws(\TypeError::class); + +test('check command shows no suggestions when all branches have downloads', function () { + fakePackageResponses([ + 'dev-main' => Http::response(statsResponse([10, 20, 30])), + 'dev-feature' => Http::response(statsResponse([5, 10, 15])), + ]); + + $this->artisan(TEST_COMMAND) + ->expectsOutputToContain('No suggestions available. Good job!') + ->assertExitCode(0); +}); + +test('check command suggests branches with zero downloads', function () { + fakePackageResponses([ + 'dev-main' => Http::response(statsResponse([10, 20, 30])), + 'dev-feature' => Http::response(statsResponse([0, 0, 0])), + ]); + + $this->artisan(TEST_COMMAND) + ->expectsOutputToContain('Found 1 branches') + ->assertExitCode(0); });