From 37abd6acfe78becc168a9667121c1053826b0d94 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 12:18:34 +0100 Subject: [PATCH 01/13] Add Laravel Pint lint checks --- .gitignore | 1 + composer.json | 16 +++++++++++++++- pint.json | 27 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 pint.json diff --git a/.gitignore b/.gitignore index 22d0d82..7579f74 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ vendor +composer.lock diff --git a/composer.json b/composer.json index 1b32ed5..eaddea1 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,21 @@ "require": { "php": "^8.2" }, + "require-dev": { + "laravel/pint": "^1.27" + }, "bin": [ "cpx" - ] + ], + "scripts": { + "lint": [ + "pint --parallel" + ], + "lint:check": [ + "pint --parallel --test" + ], + "test": [ + "@lint:check" + ] + } } diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..3ed0230 --- /dev/null +++ b/pint.json @@ -0,0 +1,27 @@ +{ + "preset": "laravel", + "rules": { + "blank_line_before_statement": { + "statements": [ + "break", + "continue", + "declare", + "if", + "return", + "throw", + "try" + ] + }, + "global_namespace_import": { + "import_classes": true + }, + "trailing_comma_in_multiline": { + "elements": [ + "arguments", + "arrays", + "match", + "parameters" + ] + } + } +} From 2f1fdffa5e5201df8ffcc265d37d93a53d12dd52 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 12:19:21 +0100 Subject: [PATCH 02/13] Apply linter fixes --- files/psysh-config.php | 6 +++-- src/ClassAliasAutoloader.php | 17 +++++++------ src/Commands/AliasesCommand.php | 4 ++-- src/Commands/CleanCommand.php | 10 ++++---- src/Commands/Command.php | 18 ++++++++++---- src/Commands/ExecCommand.php | 8 ++++--- src/Commands/HelpCommand.php | 28 +++++++++++----------- src/Commands/ListCommand.php | 3 ++- src/Commands/RunPackageCommand.php | 4 +--- src/Commands/TestCommand.php | 1 - src/Commands/TinkerCommand.php | 4 +--- src/Commands/UpdateCommand.php | 8 +++---- src/Commands/UpgradeCommand.php | 2 +- src/Commands/VersionCommand.php | 6 ++--- src/Console.php | 38 ++++++++++++++---------------- src/Metadata.php | 9 +++---- src/Package.php | 25 ++++++++++---------- src/PackageMetadata.php | 2 -- src/PhpExecutionHelper.php | 23 +++++++++--------- src/functions.php | 23 +++++++++--------- 20 files changed, 121 insertions(+), 118 deletions(-) diff --git a/files/psysh-config.php b/files/psysh-config.php index 7dce159..beeac03 100644 --- a/files/psysh-config.php +++ b/files/psysh-config.php @@ -1,7 +1,9 @@ $path) { - if (!str_contains($class, '\\')) { + if (! str_contains($class, '\\')) { continue; } $name = basename(str_replace('\\', '/', $class)); - if (!isset($this->classes[$name]) && class_exists($name)) { + if (! isset($this->classes[$name]) && class_exists($name)) { $this->classes[$name] = $class; } } @@ -38,12 +38,12 @@ public function addAliases(string $autoloadRootDirectory): void foreach ($psr4 as $namespace => $directories) { foreach ($directories as $directory) { - if (!file_exists($directory)) { + if (! file_exists($directory)) { continue; } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory), - RecursiveIteratorIterator::LEAVES_ONLY + RecursiveIteratorIterator::LEAVES_ONLY, ); foreach ($iterator as $file) { @@ -53,13 +53,12 @@ public function addAliases(string $autoloadRootDirectory): void $relativePath = str_replace($directory, '', $file->getPath()); - if (!empty($relativePath)) { - $classNamespace .= strtr($relativePath, DIRECTORY_SEPARATOR, '\\') . '\\'; + if (! empty($relativePath)) { + $classNamespace .= strtr($relativePath, DIRECTORY_SEPARATOR, '\\').'\\'; } $basename = $file->getBasename('.php'); - $class = str_replace('\\\\', '\\', $classNamespace . $basename); - + $class = str_replace('\\\\', '\\', $classNamespace.$basename); if (str_ends_with($basename, 'Test')) { continue; diff --git a/src/Commands/AliasesCommand.php b/src/Commands/AliasesCommand.php index ac03438..aae5304 100644 --- a/src/Commands/AliasesCommand.php +++ b/src/Commands/AliasesCommand.php @@ -8,13 +8,13 @@ class AliasesCommand extends Command { public function __invoke() { - $this->line('Aliased packages:' . PHP_EOL); + $this->line('Aliased packages:'.PHP_EOL); $packages = PackageAliases::$packages; usort($packages, fn ($a, $b) => strcmp($a['command'], $b['command'])); foreach ($packages as $package) { $paddedCommand = str_pad($package['command'], 15); - $this->line(' ' . Command::COLOR_GREEN . 'cpx ' . $paddedCommand . Command::COLOR_RESET . ' ' . $package['description']); + $this->line(' '.Command::COLOR_GREEN.'cpx '.$paddedCommand.Command::COLOR_RESET.' '.$package['description']); } } } diff --git a/src/Commands/CleanCommand.php b/src/Commands/CleanCommand.php index 47b9d81..3682aed 100644 --- a/src/Commands/CleanCommand.php +++ b/src/Commands/CleanCommand.php @@ -2,8 +2,8 @@ namespace Cpx\Commands; -use Cpx\Package; use Cpx\Metadata; +use Cpx\Package; class CleanCommand extends Command { @@ -20,7 +20,7 @@ public function __invoke() if ($this->console->hasOption('all') || $lastRun < $timeLimit) { $package = Package::parse($packageKey); - $this->line(Command::COLOR_GREEN . "Removing unused package {$package}..."); + $this->line(Command::COLOR_GREEN."Removing unused package {$package}..."); $package->delete(); unset($metadata->packages[$packageKey]); $cleanedSomething = true; @@ -33,7 +33,7 @@ public function __invoke() if ($this->console->hasOption('all') || $lastRun < $timeLimit) { $packageDirectory = cpx_path(".exec_cache/{$sandboxDir}"); exec("rm -rf {$packageDirectory}"); - $this->line(Command::COLOR_GREEN . "Removing exec sandbox cache {$sandboxDir}..."); + $this->line(Command::COLOR_GREEN."Removing exec sandbox cache {$sandboxDir}..."); unset($metadata->execCache[$sandboxDir]); $cleanedSomething = true; } @@ -41,8 +41,8 @@ public function __invoke() $metadata->save(); - if (!$cleanedSomething) { - $this->success("There were no packages to clean."); + if (! $cleanedSomething) { + $this->success('There were no packages to clean.'); } } } diff --git a/src/Commands/Command.php b/src/Commands/Command.php index d7426dc..0f0b870 100644 --- a/src/Commands/Command.php +++ b/src/Commands/Command.php @@ -9,21 +9,31 @@ abstract class Command public const COLOR_RESET = "\033[0m"; public const COLOR_GREEN = "\033[1;32m"; + public const COLOR_RED = "\033[1;31m"; + public const COLOR_YELLOW = "\033[1;33m"; + public const COLOR_BLUE = "\033[1;34m"; + public const COLOR_MAGENTA = "\033[1;35m"; + public const COLOR_CYAN = "\033[1;36m"; public const BACKGROUND_GREEN = "\033[42m"; + public const BACKGROUND_RED = "\033[41m"; + public const BACKGROUND_YELLOW = "\033[43m"; + public const BACKGROUND_BLUE = "\033[44m"; + public const BACKGROUND_MAGENTA = "\033[45m"; + public const BACKGROUND_CYAN = "\033[46m"; public function __construct( - protected Console $console + protected Console $console, ) {} protected function line(string $message, string $color = Command::COLOR_RESET): void @@ -32,13 +42,13 @@ protected function line(string $message, string $color = Command::COLOR_RESET): $padding = str_repeat(' ', $isBackgroundColor ? 3 : 0); if ($isBackgroundColor) { - echo $color . str_repeat(' ', mb_strlen($message) + (strlen($padding) * 2)) . Command::COLOR_RESET . PHP_EOL; + echo $color.str_repeat(' ', mb_strlen($message) + (strlen($padding) * 2)).Command::COLOR_RESET.PHP_EOL; } - echo $color . $padding . $message . $padding . Command::COLOR_RESET . PHP_EOL; + echo $color.$padding.$message.$padding.Command::COLOR_RESET.PHP_EOL; if ($isBackgroundColor) { - echo $color . str_repeat(' ', mb_strlen($message) + (strlen($padding) * 2)) . Command::COLOR_RESET . PHP_EOL; + echo $color.str_repeat(' ', mb_strlen($message) + (strlen($padding) * 2)).Command::COLOR_RESET.PHP_EOL; echo PHP_EOL; } } diff --git a/src/Commands/ExecCommand.php b/src/Commands/ExecCommand.php index 072a0f6..abaa245 100644 --- a/src/Commands/ExecCommand.php +++ b/src/Commands/ExecCommand.php @@ -2,7 +2,6 @@ namespace Cpx\Commands; -use Cpx\ClassAliasAutoloader; use Cpx\PhpExecutionHelper; class ExecCommand extends Command @@ -16,6 +15,7 @@ public function __invoke() if (empty($code)) { $this->error('Please supply code to execute with the -r option.'); + return; } @@ -27,7 +27,7 @@ public function __invoke() } } - if (!str_ends_with(trim($code), ';')) { + if (! str_ends_with(trim($code), ';')) { $code .= ';'; } @@ -41,13 +41,15 @@ public function __invoke() if (empty($this->console->arguments[0])) { $this->error('Please supply the path to a file to execute.'); + return; } $this->path = realpath($this->console->arguments[0]); - if (!file_exists($this->path)) { + if (! file_exists($this->path)) { $this->error("File does not exist at '{$this->path}'"); + return; } diff --git a/src/Commands/HelpCommand.php b/src/Commands/HelpCommand.php index 09b2bd3..8d85876 100644 --- a/src/Commands/HelpCommand.php +++ b/src/Commands/HelpCommand.php @@ -12,19 +12,19 @@ public function __invoke(bool $unknownCommand = false) $this->success('cpx - A Composer package runner with on-demand execution and package management.'); $this->line('Usage:'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx [args] ' . Command::COLOR_RESET . 'Run a Composer package\'s bin command'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx check ' . Command::COLOR_RESET . 'Run a static analysis tool over a project'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx test ' . Command::COLOR_RESET . 'Run a testing framework over a project'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx format ' . Command::COLOR_RESET . 'Run a code formatter over a project'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx update ' . Command::COLOR_RESET . 'Update all packages'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx update ' . Command::COLOR_RESET . 'Update all versions of a package'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx clean ' . Command::COLOR_RESET . 'Clean unused packages (older than 30 days)'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx clean --all ' . Command::COLOR_RESET . 'Clean all packages'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx exec ' . Command::COLOR_RESET . 'Invoke a PHP file'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx exec -r ' . Command::COLOR_RESET . 'Run PHP code without tags'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx tinker ' . Command::COLOR_RESET . 'Open an interactive REPL'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx list ' . Command::COLOR_RESET . 'List all installed packages'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx aliases ' . Command::COLOR_RESET . 'Show aliased package names to run via `cpx `'); - $this->line(' ' . Command::COLOR_GREEN . 'cpx help ' . Command::COLOR_RESET . 'Show this help message'); + $this->line(' '.Command::COLOR_GREEN.'cpx [args] '.Command::COLOR_RESET.'Run a Composer package\'s bin command'); + $this->line(' '.Command::COLOR_GREEN.'cpx check '.Command::COLOR_RESET.'Run a static analysis tool over a project'); + $this->line(' '.Command::COLOR_GREEN.'cpx test '.Command::COLOR_RESET.'Run a testing framework over a project'); + $this->line(' '.Command::COLOR_GREEN.'cpx format '.Command::COLOR_RESET.'Run a code formatter over a project'); + $this->line(' '.Command::COLOR_GREEN.'cpx update '.Command::COLOR_RESET.'Update all packages'); + $this->line(' '.Command::COLOR_GREEN.'cpx update '.Command::COLOR_RESET.'Update all versions of a package'); + $this->line(' '.Command::COLOR_GREEN.'cpx clean '.Command::COLOR_RESET.'Clean unused packages (older than 30 days)'); + $this->line(' '.Command::COLOR_GREEN.'cpx clean --all '.Command::COLOR_RESET.'Clean all packages'); + $this->line(' '.Command::COLOR_GREEN.'cpx exec '.Command::COLOR_RESET.'Invoke a PHP file'); + $this->line(' '.Command::COLOR_GREEN.'cpx exec -r '.Command::COLOR_RESET.'Run PHP code without tags'); + $this->line(' '.Command::COLOR_GREEN.'cpx tinker '.Command::COLOR_RESET.'Open an interactive REPL'); + $this->line(' '.Command::COLOR_GREEN.'cpx list '.Command::COLOR_RESET.'List all installed packages'); + $this->line(' '.Command::COLOR_GREEN.'cpx aliases '.Command::COLOR_RESET.'Show aliased package names to run via `cpx `'); + $this->line(' '.Command::COLOR_GREEN.'cpx help '.Command::COLOR_RESET.'Show this help message'); } } diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index 7e5b303..fb2611a 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -9,6 +9,7 @@ class ListCommand extends Command public function __invoke() { $metadata = Metadata::open(); + if (empty($metadata->packages)) { $this->line('There are no installed packages.'); exit(); @@ -17,7 +18,7 @@ public function __invoke() $this->line('Installed Packages:'); foreach ($metadata->packages as $packageKey => $packageMetadata) { - $this->line(Command::COLOR_GREEN . " {$packageMetadata->package->fullPackageString()}" . Command::COLOR_RESET . ' (Last Run: ' . ($packageMetadata->lastRunAt ?? 'N/A') . ')'); + $this->line(Command::COLOR_GREEN." {$packageMetadata->package->fullPackageString()}".Command::COLOR_RESET.' (Last Run: '.($packageMetadata->lastRunAt ?? 'N/A').')'); } } } diff --git a/src/Commands/RunPackageCommand.php b/src/Commands/RunPackageCommand.php index 5733abc..fca61ba 100644 --- a/src/Commands/RunPackageCommand.php +++ b/src/Commands/RunPackageCommand.php @@ -4,7 +4,5 @@ class RunPackageCommand extends Command { - public function __invoke() - { - } + public function __invoke() {} } diff --git a/src/Commands/TestCommand.php b/src/Commands/TestCommand.php index 20948b0..d8d0a53 100644 --- a/src/Commands/TestCommand.php +++ b/src/Commands/TestCommand.php @@ -4,7 +4,6 @@ use Cpx\Console; use Cpx\Exceptions\ConsoleException; -use Exception; class TestCommand extends Command { diff --git a/src/Commands/TinkerCommand.php b/src/Commands/TinkerCommand.php index 4b2bdb6..3699b8a 100644 --- a/src/Commands/TinkerCommand.php +++ b/src/Commands/TinkerCommand.php @@ -4,14 +4,12 @@ use Cpx\Console; use Cpx\Package; -use Cpx\Commands\Command; -use Cpx\Composer; class TinkerCommand extends Command { public function __invoke() { - $psyshConfig = realpath(__DIR__ . '/../../files/psysh-config.php'); + $psyshConfig = realpath(__DIR__.'/../../files/psysh-config.php'); return Package::parse('psy/psysh')->runCommand(Console::parse("psysh --config {$psyshConfig}")); } diff --git a/src/Commands/UpdateCommand.php b/src/Commands/UpdateCommand.php index 9cdf8b7..2227c75 100644 --- a/src/Commands/UpdateCommand.php +++ b/src/Commands/UpdateCommand.php @@ -9,9 +9,9 @@ class UpdateCommand extends Command { public function __invoke() { - match(true) { + match (true) { str_contains($this->console->arguments[0] ?? '', '/') => $this->updatePackage(Package::parse($this->console->arguments[0])), - !empty($this->console->arguments[0]) => $this->updateVendor($this->console->arguments[0]), + ! empty($this->console->arguments[0]) => $this->updateVendor($this->console->arguments[0]), default => $this->updateAllPackages(), }; } @@ -63,7 +63,7 @@ protected function updatePackage(Package $package): void protected function updateDirectory(string $directory): void { - $this->line('Updating ' . Command::COLOR_GREEN . str_replace(cpx_path(), '', $directory)); - Composer::runCommand("update", $directory); + $this->line('Updating '.Command::COLOR_GREEN.str_replace(cpx_path(), '', $directory)); + Composer::runCommand('update', $directory); } } diff --git a/src/Commands/UpgradeCommand.php b/src/Commands/UpgradeCommand.php index 55fbf4d..c0c64ed 100644 --- a/src/Commands/UpgradeCommand.php +++ b/src/Commands/UpgradeCommand.php @@ -8,7 +8,7 @@ class UpgradeCommand extends Command { public function __invoke() { - $this->line('Updating ' . Command::COLOR_GREEN . 'cpx'); + $this->line('Updating '.Command::COLOR_GREEN.'cpx'); Composer::runCommand('global update cpx/cpx'); } } diff --git a/src/Commands/VersionCommand.php b/src/Commands/VersionCommand.php index d958bed..2c56667 100644 --- a/src/Commands/VersionCommand.php +++ b/src/Commands/VersionCommand.php @@ -8,8 +8,8 @@ class VersionCommand extends Command { public function __invoke() { - $cpxVersion = json_decode(file_get_contents(__DIR__ . '/../../composer.json'), true)['version'] ?? 'unknown'; - $this->line('cpx version: ' . Command::COLOR_GREEN . $cpxVersion); - $this->line('php version: ' . Command::COLOR_GREEN . PHP_VERSION); + $cpxVersion = json_decode(file_get_contents(__DIR__.'/../../composer.json'), true)['version'] ?? 'unknown'; + $this->line('cpx version: '.Command::COLOR_GREEN.$cpxVersion); + $this->line('php version: '.Command::COLOR_GREEN.PHP_VERSION); } } diff --git a/src/Console.php b/src/Console.php index 65fd177..fd53cdb 100644 --- a/src/Console.php +++ b/src/Console.php @@ -8,11 +8,9 @@ class Console { /** - * @param string $rawInput - * @param string $command - * @param array $arguments - * @param array> $options - * @param array $flags + * @param array $arguments + * @param array> $options + * @param array $flags */ public function __construct( public string $rawInput, @@ -25,11 +23,9 @@ public function __construct( /** * Parses $argv to get the command, arguments, options, and flags. * - * @param string|array $input The $argv variable. - * @param array $shortOptions Optional. An array with keys set to short options and their values set to the long option they're assigned to. - * @param array $flagOptions Optional. An array of options to be treated as flags. If a flag is not defined here, it will be treated as an option. - * - * @return Console + * @param string|array $input The $argv variable. + * @param array $shortOptions Optional. An array with keys set to short options and their values set to the long option they're assigned to. + * @param array $flagOptions Optional. An array of options to be treated as flags. If a flag is not defined here, it will be treated as an option. */ public static function parse(string|array $input, $shortOptions = [], $flagOptions = []): Console { @@ -40,7 +36,7 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio if (is_string($input)) { $input = trim($input); $input = preg_split('/\s+(?=([^"]*"[^"]*")*[^"]*$)/', $input); - $input = array_map(function($item) { + $input = array_map(function ($item) { return trim($item, '"\''); }, $input); } @@ -51,7 +47,7 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio $flags = []; $lastOption = null; - foreach($input as $arg) { + foreach ($input as $arg) { $value = null; if (substr($arg, 0, 1) !== '-') { @@ -61,6 +57,7 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio } else { $arguments[] = $arg; $lastOption = null; + continue; } } else { @@ -72,12 +69,13 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio $value = $arg_split[2]; } } + if (array_key_exists($arg, $shortOptions)) { $arg = $shortOptions[$arg]; } if (in_array($arg, $flagOptions)) { - if (!in_array($arg, $flags)) { + if (! in_array($arg, $flags)) { $flags[] = $arg; } @@ -89,8 +87,8 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio } else { if (is_null($options[$arg])) { $options[$arg] = $value; - } elseif (!is_null($value)) { - $options[$arg] = array($options[$arg], $value); + } elseif (! is_null($value)) { + $options[$arg] = [$options[$arg], $value]; } } } else { @@ -101,7 +99,7 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio } } - return new Console(join(' ', $input), $command, $arguments, $options, $flags); + return new Console(implode(' ', $input), $command, $arguments, $options, $flags); } public function hasOption(string $option): bool @@ -131,7 +129,7 @@ public function getCommandInput(): string public function getArgumentsString(): string { - return join(' ', $this->arguments); + return implode(' ', $this->arguments); } public function getOptionsString(): string @@ -139,11 +137,11 @@ public function getOptionsString(): string return implode(' ', array_map( function ($key, $value) { return implode(' ', array_map(function ($v) use ($key) { - return $v === null ? "--{$key}" : "--{$key}=" . escapeshellarg($v); + return $v === null ? "--{$key}" : "--{$key}=".escapeshellarg($v); }, $value === null ? [null] : (array) $value)); }, array_keys($this->options), - $this->options + $this->options, )); } @@ -161,7 +159,7 @@ public function exec(bool $verbose = false) ]; if ($verbose) { - echo Command::BACKGROUND_CYAN . " Running command: '{$this}' " . Command::COLOR_RESET; + echo Command::BACKGROUND_CYAN." Running command: '{$this}' ".Command::COLOR_RESET; } $process = proc_open($this, $descriptors, $pipes); diff --git a/src/Metadata.php b/src/Metadata.php index 7e0968e..205a8e1 100644 --- a/src/Metadata.php +++ b/src/Metadata.php @@ -2,9 +2,6 @@ namespace Cpx; -use Cpx\Package; -use Cpx\PackageMetadata; - class Metadata { /** @param array $packages */ @@ -35,15 +32,15 @@ public static function open(): Metadata ); } - return new Metadata(); + return new Metadata; } - function updateLastCheckTime(Package $package, string $type = 'run'): Metadata + public function updateLastCheckTime(Package $package, string $type = 'run'): Metadata { $packageKey = $package->fullPackageString(); $currentTime = date('Y-m-d H:i:s'); - if (!isset($this->packages[$packageKey])) { + if (! isset($this->packages[$packageKey])) { $this->packages[$packageKey] = new PackageMetadata($package); } diff --git a/src/Package.php b/src/Package.php index aacb329..801bb8e 100644 --- a/src/Package.php +++ b/src/Package.php @@ -2,7 +2,6 @@ namespace Cpx; -use Cpx\Utils; use Cpx\Commands\Command; use InvalidArgumentException; @@ -20,7 +19,7 @@ public static function parse(string $str): Package throw new InvalidArgumentException('A package name must be provided.'); } - if (!str_contains($str, '/')) { + if (! str_contains($str, '/')) { throw new InvalidArgumentException('A package name should be in the format "/'); } @@ -48,7 +47,7 @@ public function versionName(): string public function fullPackageString(): string { return "{$this->vendor}/{$this->name}" - . ($this->version ? ':' . $this->version : ''); + .($this->version ? ':'.$this->version : ''); } public function delete(): void @@ -79,24 +78,26 @@ public function runCommand(Console $console, bool $autoUpdate = true): void foreach ($possibleCommands as $possibleCommand) { if (in_array($possibleCommand, $binScripts)) { - if ($console->arguments[0] ?? null === $possibleCommand) { + if ($console->arguments[0] ?? $possibleCommand === null) { unset($console->arguments[0]); $console->arguments = array_values($console->arguments); } $command = $possibleCommand; + break; } elseif (array_key_exists($possibleCommand, $binScripts)) { - if ($console->arguments[0] ?? null === $possibleCommand) { + if ($console->arguments[0] ?? $possibleCommand === null) { unset($console->arguments[0]); $console->arguments = array_values($console->arguments); } $command = $binScripts[$possibleCommand]; + break; } } - if (!isset($command)) { - echo Command::BACKGROUND_RED . " More than 1 bin command found for {$this}: " . join(', ', array_keys($binScripts)) . ' ' . Command::COLOR_RESET . PHP_EOL; + if (! isset($command)) { + echo Command::BACKGROUND_RED." More than 1 bin command found for {$this}: ".implode(', ', array_keys($binScripts)).' '.Command::COLOR_RESET.PHP_EOL; exit(); } } else { @@ -134,11 +135,11 @@ public function installOrUpdatePackage(bool $updateCheck = true): string { $installDir = cpx_path($this->folder()); - if (!is_dir($installDir)) { + if (! is_dir($installDir)) { mkdir($installDir, 0755, true); } - if (!is_dir("$installDir/vendor")) { + if (! is_dir("$installDir/vendor")) { printColor("Installing {$this}..."); file_put_contents("{$installDir}/composer.json", json_encode([ 'name' => "cpx-{$this->vendor}/cpx-{$this->name}", @@ -159,7 +160,7 @@ public function installOrUpdatePackage(bool $updateCheck = true): string } elseif ($updateCheck && $this->shouldCheckForUpdates($this)) { printColor("Checking for updates for {$this}..."); $previousVersion = Composer::getCurrentVersion($installDir); - Composer::runCommand("update", $installDir); + Composer::runCommand('update', $installDir); $newVersion = Composer::getCurrentVersion($installDir); if ($previousVersion !== $newVersion) { @@ -176,12 +177,12 @@ public function installOrUpdatePackage(bool $updateCheck = true): string return $installDir; } - function shouldCheckForUpdates(): bool + public function shouldCheckForUpdates(): bool { $metadata = Metadata::open(); $packageKey = $this->fullPackageString(); - if (!$metadata->hasPackage($this)) { + if (! $metadata->hasPackage($this)) { return true; } diff --git a/src/PackageMetadata.php b/src/PackageMetadata.php index 3fa98ab..3c6ab05 100644 --- a/src/PackageMetadata.php +++ b/src/PackageMetadata.php @@ -2,8 +2,6 @@ namespace Cpx; -use Cpx\Package; - class PackageMetadata { public function __construct( diff --git a/src/PhpExecutionHelper.php b/src/PhpExecutionHelper.php index 78a4693..94d6e56 100644 --- a/src/PhpExecutionHelper.php +++ b/src/PhpExecutionHelper.php @@ -14,20 +14,19 @@ public static function init( bool $shouldLoadLaravelBootstrap = true, bool $shouldAliasClasses = true, bool $shouldBeVerbose = false, - ): void - { - if (!$shouldFindAutoloader) { + ): void { + if (! $shouldFindAutoloader) { return; } $autoloadRootDirectory = $path; $autoloadFileSuffix = '/vendor/autoload.php'; - $autoloadFile = $autoloadRootDirectory . $autoloadFileSuffix; + $autoloadFile = $autoloadRootDirectory.$autoloadFileSuffix; - while (!file_exists($autoloadFile)) { + while (! file_exists($autoloadFile)) { $autoloadRootDirectory = realpath(dirname($autoloadRootDirectory)); - $autoloadFile = $autoloadRootDirectory . $autoloadFileSuffix; + $autoloadFile = $autoloadRootDirectory.$autoloadFileSuffix; if ($autoloadRootDirectory === '/') { break; @@ -36,26 +35,26 @@ public static function init( if (file_exists($autoloadFile)) { if ($shouldBeVerbose) { - echo "Found autoload file at '{$autoloadFile}'" . PHP_EOL; + echo "Found autoload file at '{$autoloadFile}'".PHP_EOL; } require_once $autoloadFile; - if ($shouldLoadLaravelBootstrap && file_exists($autoloadRootDirectory . '/bootstrap/app.php')) { + if ($shouldLoadLaravelBootstrap && file_exists($autoloadRootDirectory.'/bootstrap/app.php')) { if ($shouldBeVerbose) { - echo "Found Laravel bootstrap file at '{$autoloadRootDirectory}/bootstrap/app.php'" . PHP_EOL; + echo "Found Laravel bootstrap file at '{$autoloadRootDirectory}/bootstrap/app.php'".PHP_EOL; } - if (!defined('LARAVEL_START')) { + if (! defined('LARAVEL_START')) { define('LARAVEL_START', microtime(true)); } - require_once $autoloadRootDirectory . '/bootstrap/app.php'; + require_once $autoloadRootDirectory.'/bootstrap/app.php'; } if ($shouldAliasClasses) { if ($shouldBeVerbose) { - echo 'Aliasing classes' . PHP_EOL; + echo 'Aliasing classes'.PHP_EOL; } static::getClassAliasAutoloader($shouldBeVerbose)->addAliases($autoloadRootDirectory); diff --git a/src/functions.php b/src/functions.php index efe3858..a24aa41 100644 --- a/src/functions.php +++ b/src/functions.php @@ -5,11 +5,12 @@ use Cpx\Metadata; use Cpx\PhpExecutionHelper; -if (!function_exists('composer_require')) { +if (! function_exists('composer_require')) { /** * Dynamically requires Composer packages in a sandboxed environment. * - * @param string ...$packages List of packages to require in the format vendor/package[:version]. + * @param string ...$packages List of packages to require in the format vendor/package[:version]. + * * @throws Exception If the Composer require command fails. */ function composer_require(string ...$packages): void @@ -21,14 +22,14 @@ function composer_require(string ...$packages): void $metadata = Metadata::open(); - if (!is_dir($sandboxDir)) { + if (! is_dir($sandboxDir)) { mkdir($sandboxDir, 0755, true); - file_put_contents($sandboxDir . '/composer.json', json_encode([ - 'require' => new stdClass(), + file_put_contents($sandboxDir.'/composer.json', json_encode([ + 'require' => new stdClass, 'config' => [ - 'vendor-dir' => './vendor' - ] + 'vendor-dir' => './vendor', + ], ], JSON_PRETTY_PRINT)); // Run `composer require` for each package @@ -44,7 +45,7 @@ function composer_require(string ...$packages): void if (isset($metadata->execCache[$hash]['last_updated']) && time() - $metadata->execCache[$hash]['last_updated'] >= 3600) { // Composer update was not run within the last hour try { - Composer::runCommand("update --no-interaction --quiet", $sandboxDir); + Composer::runCommand('update --no-interaction --quiet', $sandboxDir); $metadata->execCache[$hash]['last_updated'] = time(); } catch (Exception $e) { // Update failed, let's just use the existing folder. @@ -58,7 +59,7 @@ function composer_require(string ...$packages): void $autoloadFile = "{$sandboxDir}/vendor/autoload.php"; - if (!file_exists($autoloadFile)) { + if (! file_exists($autoloadFile)) { throw new Exception("Autoload file not found in {$sandboxDir}/vendor/. Composer installation may have failed."); } @@ -70,11 +71,11 @@ function composer_require(string ...$packages): void } } -if (!function_exists('cpx_path')) { +if (! function_exists('cpx_path')) { function cpx_path(string $path = ''): string { $home = $_SERVER['HOME'] ?? __DIR__; - return "{$home}/.cpx/" . trim($path, '/'); + return "{$home}/.cpx/".trim($path, '/'); } } From dbfa410e83d4ee80e31897120b6669c7ba4e3070 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 12:39:35 +0100 Subject: [PATCH 03/13] Add PHPStan analysis checks --- composer.json | 7 ++++++- phpstan.neon.dist | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 phpstan.neon.dist diff --git a/composer.json b/composer.json index eaddea1..51d9fee 100644 --- a/composer.json +++ b/composer.json @@ -32,12 +32,16 @@ "php": "^8.2" }, "require-dev": { - "laravel/pint": "^1.27" + "laravel/pint": "^1.29", + "phpstan/phpstan": "^2.2" }, "bin": [ "cpx" ], "scripts": { + "analyse": [ + "phpstan analyse" + ], "lint": [ "pint --parallel" ], @@ -45,6 +49,7 @@ "pint --parallel --test" ], "test": [ + "@analyse", "@lint:check" ] } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..1d5823a --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,4 @@ +parameters: + level: 7 + paths: + - src From 6e22ecd966db37dd6bbd50261cda766b167e6dc8 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 13:02:59 +0100 Subject: [PATCH 04/13] Fix PHPStan type issues --- cpx | 5 --- src/ClassAliasAutoloader.php | 2 +- src/Composer.php | 22 ++++++++-- src/Console.php | 81 +++++++++++++++++++++++------------- src/Metadata.php | 22 ++++++++-- src/Package.php | 26 ++++++++---- src/PackageAliases.php | 1 + src/PhpExecutionHelper.php | 8 +++- src/Utils.php | 10 +++++ src/functions.php | 7 ++++ 10 files changed, 130 insertions(+), 54 deletions(-) diff --git a/cpx b/cpx index 3a767d2..824075b 100755 --- a/cpx +++ b/cpx @@ -34,11 +34,6 @@ if (isset($GLOBALS['_composer_autoload_path'])) { unset($file); } -function printColor(string $message, string $color = "\033[1;32m"): void -{ - echo $color . $message . "\033[0m" . PHP_EOL; -} - array_shift($argv); $console = Console::parse($argv ?? []); diff --git a/src/ClassAliasAutoloader.php b/src/ClassAliasAutoloader.php index d7d614c..8fc5b07 100644 --- a/src/ClassAliasAutoloader.php +++ b/src/ClassAliasAutoloader.php @@ -8,7 +8,7 @@ class ClassAliasAutoloader { - /** All of the discovered classes. */ + /** @var array */ protected array $classes = []; public function __construct( diff --git a/src/Composer.php b/src/Composer.php index 2e84086..918e64f 100644 --- a/src/Composer.php +++ b/src/Composer.php @@ -6,6 +6,7 @@ class Composer { + /** @return list */ public static function runCommand(string $command, ?string $directory = null): array { $output = []; @@ -30,9 +31,15 @@ public static function detectBinFromComposer(string $directory): array $composerFile = "{$directory}/composer.json"; if (file_exists($composerFile)) { - $composerData = json_decode(file_get_contents($composerFile), true); + $contents = file_get_contents($composerFile); - if (isset($composerData['bin'])) { + if ($contents === false) { + return []; + } + + $composerData = json_decode($contents, true); + + if (is_array($composerData) && isset($composerData['bin'])) { return (array) $composerData['bin']; } } @@ -45,9 +52,16 @@ public static function getCurrentVersion(string $directory): string $composerLock = "{$directory}/composer.lock"; if (file_exists($composerLock)) { - $lockData = json_decode(file_get_contents($composerLock), true); + $contents = file_get_contents($composerLock); + + if ($contents === false) { + return 'unknown'; + } + + $lockData = json_decode($contents, true); + $version = is_array($lockData) ? ($lockData['packages'][0]['version'] ?? null) : null; - return $lockData['packages'][0]['version'] ?? 'unknown'; + return is_string($version) ? $version : 'unknown'; } return 'unknown'; diff --git a/src/Console.php b/src/Console.php index fd53cdb..733cc73 100644 --- a/src/Console.php +++ b/src/Console.php @@ -8,9 +8,9 @@ class Console { /** - * @param array $arguments - * @param array> $options - * @param array $flags + * @param list $arguments + * @param array> $options + * @param list $flags */ public function __construct( public string $rawInput, @@ -23,11 +23,11 @@ public function __construct( /** * Parses $argv to get the command, arguments, options, and flags. * - * @param string|array $input The $argv variable. - * @param array $shortOptions Optional. An array with keys set to short options and their values set to the long option they're assigned to. - * @param array $flagOptions Optional. An array of options to be treated as flags. If a flag is not defined here, it will be treated as an option. + * @param string|list $input The $argv variable. + * @param array $shortOptions Optional. An array with keys set to short options and their values set to the long option they're assigned to. + * @param list $flagOptions Optional. An array of options to be treated as flags. If a flag is not defined here, it will be treated as an option. */ - public static function parse(string|array $input, $shortOptions = [], $flagOptions = []): Console + public static function parse(string|array $input, array $shortOptions = [], array $flagOptions = []): Console { if (empty($input)) { return new Console('', ''); @@ -35,13 +35,13 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio if (is_string($input)) { $input = trim($input); - $input = preg_split('/\s+(?=([^"]*"[^"]*")*[^"]*$)/', $input); - $input = array_map(function ($item) { + $parts = preg_split('/\s+(?=([^"]*"[^"]*")*[^"]*$)/', $input); + $input = array_map(function (string $item): string { return trim($item, '"\''); - }, $input); + }, $parts === false ? [] : $parts); } - $command = array_shift($input); + $command = array_shift($input) ?? ''; $arguments = []; $options = []; $flags = []; @@ -61,12 +61,19 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio continue; } } else { - $arg_split = []; - preg_match('/^--?([A-Z\d\-_]+)=?(.+)?$/i', $arg, $arg_split); - $arg = $arg_split[1]; + $argSplit = []; - if (count($arg_split) > 2) { - $value = $arg_split[2]; + if (preg_match('/^--?([A-Z\d\-_]+)=?(.+)?$/i', $arg, $argSplit) !== 1) { + $arguments[] = $arg; + $lastOption = null; + + continue; + } + + $arg = $argSplit[1]; + + if (isset($argSplit[2])) { + $value = $argSplit[2]; } } @@ -74,8 +81,8 @@ public static function parse(string|array $input, $shortOptions = [], $flagOptio $arg = $shortOptions[$arg]; } - if (in_array($arg, $flagOptions)) { - if (! in_array($arg, $flags)) { + if (in_array($arg, $flagOptions, true)) { + if (! in_array($arg, $flags, true)) { $flags[] = $arg; } @@ -109,12 +116,24 @@ public function hasOption(string $option): bool public function getOption(string $option): ?string { - return $this->options[$option] ?? null; + $value = $this->options[$option] ?? null; + + if (! is_array($value)) { + return $value; + } + + foreach ($value as $optionValue) { + if ($optionValue !== null) { + return $optionValue; + } + } + + return null; } public function hasFlag(string $flag): bool { - return in_array($flag, $this->flags); + return in_array($flag, $this->flags, true); } public function __toString(): string @@ -134,15 +153,17 @@ public function getArgumentsString(): string public function getOptionsString(): string { - return implode(' ', array_map( - function ($key, $value) { - return implode(' ', array_map(function ($v) use ($key) { - return $v === null ? "--{$key}" : "--{$key}=".escapeshellarg($v); - }, $value === null ? [null] : (array) $value)); - }, - array_keys($this->options), - $this->options, - )); + $options = []; + + foreach ($this->options as $key => $value) { + $values = is_array($value) ? $value : [$value]; + + foreach ($values as $optionValue) { + $options[] = $optionValue === null ? "--{$key}" : "--{$key}=".escapeshellarg($optionValue); + } + } + + return implode(' ', $options); } public function getFlagsString(): string @@ -150,7 +171,7 @@ public function getFlagsString(): string return implode(' ', $this->flags); } - public function exec(bool $verbose = false) + public function exec(bool $verbose = false): void { $descriptors = [ 0 => STDIN, diff --git a/src/Metadata.php b/src/Metadata.php index 205a8e1..1902ae2 100644 --- a/src/Metadata.php +++ b/src/Metadata.php @@ -4,7 +4,10 @@ class Metadata { - /** @param array $packages */ + /** + * @param array $packages + * @param array, last_updated?: int, last_run?: int}> $execCache + */ protected function __construct( public array $packages = [], public array $execCache = [], @@ -15,7 +18,12 @@ public static function open(): Metadata $metadataFile = cpx_path('.cpx_metadata.json'); if (file_exists($metadataFile)) { - $json = json_decode(file_get_contents($metadataFile), true); + $contents = file_get_contents($metadataFile); + $json = $contents === false ? [] : json_decode($contents, true); + + if (! is_array($json)) { + $json = []; + } return new Metadata( packages: Utils::arrayMapAssoc( @@ -26,9 +34,9 @@ public static function open(): Metadata lastRunAt: $value['last_run'] ?? null, ), ], - $json['packages'] ?? [], + is_array($json['packages'] ?? null) ? $json['packages'] : [], ), - execCache: $json['execCache'] ?? [], + execCache: is_array($json['execCache'] ?? null) ? $json['execCache'] : [], ); } @@ -68,6 +76,12 @@ public function hasPackage(string|Package $package): bool return array_key_exists($package, $this->packages); } + /** + * @return array{ + * packages: array, + * execCache: array, last_updated?: int, last_run?: int}> + * } + */ public function toArray(): array { return [ diff --git a/src/Package.php b/src/Package.php index 801bb8e..374bd01 100644 --- a/src/Package.php +++ b/src/Package.php @@ -77,18 +77,16 @@ public function runCommand(Console $console, bool $autoUpdate = true): void ]))); foreach ($possibleCommands as $possibleCommand) { - if (in_array($possibleCommand, $binScripts)) { - if ($console->arguments[0] ?? $possibleCommand === null) { - unset($console->arguments[0]); - $console->arguments = array_values($console->arguments); + if (in_array($possibleCommand, $binScripts, true)) { + if (($console->arguments[0] ?? null) === $possibleCommand) { + $console->arguments = array_slice($console->arguments, 1); } $command = $possibleCommand; break; } elseif (array_key_exists($possibleCommand, $binScripts)) { - if ($console->arguments[0] ?? $possibleCommand === null) { - unset($console->arguments[0]); - $console->arguments = array_values($console->arguments); + if (($console->arguments[0] ?? null) === $possibleCommand) { + $console->arguments = array_slice($console->arguments, 1); } $command = $binScripts[$possibleCommand]; @@ -157,7 +155,7 @@ public function installOrUpdatePackage(bool $updateCheck = true): string } Metadata::open()->updateLastCheckTime($this, 'updated')->save(); - } elseif ($updateCheck && $this->shouldCheckForUpdates($this)) { + } elseif ($updateCheck && $this->shouldCheckForUpdates()) { printColor("Checking for updates for {$this}..."); $previousVersion = Composer::getCurrentVersion($installDir); Composer::runCommand('update', $installDir); @@ -186,7 +184,17 @@ public function shouldCheckForUpdates(): bool return true; } - $lastCheck = strtotime($metadata->packages[$packageKey]->lastUpdatedAt); + $lastUpdatedAt = $metadata->packages[$packageKey]->lastUpdatedAt; + + if ($lastUpdatedAt === null) { + return true; + } + + $lastCheck = strtotime($lastUpdatedAt); + + if ($lastCheck === false) { + return true; + } return (time() - $lastCheck) > 3600; // 1 hour } diff --git a/src/PackageAliases.php b/src/PackageAliases.php index 4f3525b..88307f8 100644 --- a/src/PackageAliases.php +++ b/src/PackageAliases.php @@ -4,6 +4,7 @@ class PackageAliases { + /** @var array */ public static array $packages = [ 'psalm' => [ 'name' => 'Psalm', diff --git a/src/PhpExecutionHelper.php b/src/PhpExecutionHelper.php index 94d6e56..d35870b 100644 --- a/src/PhpExecutionHelper.php +++ b/src/PhpExecutionHelper.php @@ -25,7 +25,13 @@ public static function init( $autoloadFile = $autoloadRootDirectory.$autoloadFileSuffix; while (! file_exists($autoloadFile)) { - $autoloadRootDirectory = realpath(dirname($autoloadRootDirectory)); + $parentDirectory = realpath(dirname($autoloadRootDirectory)); + + if ($parentDirectory === false) { + break; + } + + $autoloadRootDirectory = $parentDirectory; $autoloadFile = $autoloadRootDirectory.$autoloadFileSuffix; if ($autoloadRootDirectory === '/') { diff --git a/src/Utils.php b/src/Utils.php index 3198b5e..a7af926 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -4,6 +4,16 @@ class Utils { + /** + * @template TKey of array-key + * @template TValue + * @template TReturnKey of array-key + * @template TReturnValue + * + * @param callable(TKey, TValue): array $f + * @param array $a + * @return array + */ public static function arrayMapAssoc(callable $f, array $a): array { return array_merge(...array_map($f, array_keys($a), $a)); diff --git a/src/functions.php b/src/functions.php index a24aa41..89da97f 100644 --- a/src/functions.php +++ b/src/functions.php @@ -79,3 +79,10 @@ function cpx_path(string $path = ''): string return "{$home}/.cpx/".trim($path, '/'); } } + +if (! function_exists('printColor')) { + function printColor(string $message, string $color = "\033[1;32m"): void + { + echo $color.$message."\033[0m".PHP_EOL; + } +} From ea388848f6bcf742ec0f531aeaca6258b577845c Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 13:07:08 +0100 Subject: [PATCH 05/13] Add command return types --- src/Commands/AliasesCommand.php | 2 +- src/Commands/CheckCommand.php | 14 ++++++++--- src/Commands/CleanCommand.php | 4 +-- src/Commands/ExecCommand.php | 39 +++++++++++++++++++++++------- src/Commands/FormatCommand.php | 14 ++++++++--- src/Commands/HelpCommand.php | 2 +- src/Commands/ListCommand.php | 2 +- src/Commands/RunPackageCommand.php | 2 +- src/Commands/TestCommand.php | 18 ++++++++++---- src/Commands/TinkerCommand.php | 10 ++++++-- src/Commands/UpdateCommand.php | 2 +- src/Commands/UpgradeCommand.php | 2 +- src/Commands/VersionCommand.php | 16 ++++++++++-- 13 files changed, 93 insertions(+), 34 deletions(-) diff --git a/src/Commands/AliasesCommand.php b/src/Commands/AliasesCommand.php index aae5304..8b0bdf0 100644 --- a/src/Commands/AliasesCommand.php +++ b/src/Commands/AliasesCommand.php @@ -6,7 +6,7 @@ class AliasesCommand extends Command { - public function __invoke() + public function __invoke(): void { $this->line('Aliased packages:'.PHP_EOL); $packages = PackageAliases::$packages; diff --git a/src/Commands/CheckCommand.php b/src/Commands/CheckCommand.php index 2f2bbbd..e4423a9 100644 --- a/src/Commands/CheckCommand.php +++ b/src/Commands/CheckCommand.php @@ -7,24 +7,30 @@ class CheckCommand extends Command { - public function __invoke() + public function __invoke(): void { if (file_exists('vendor/bin/phpstan')) { $command = 'vendor/bin/phpstan analyse'; - return Console::parse($command)->exec(); + Console::parse($command)->exec(); + + return; } if (file_exists('vendor/bin/psalm')) { $command = 'vendor/bin/psalm'; - return Console::parse($command)->exec(); + Console::parse($command)->exec(); + + return; } if (file_exists('vendor/bin/phan')) { $command = 'vendor/bin/phan'; - return Console::parse($command)->exec(); + Console::parse($command)->exec(); + + return; } throw new ConsoleException('No static analyzers found in the project.'); diff --git a/src/Commands/CleanCommand.php b/src/Commands/CleanCommand.php index 3682aed..61e2e8a 100644 --- a/src/Commands/CleanCommand.php +++ b/src/Commands/CleanCommand.php @@ -7,7 +7,7 @@ class CleanCommand extends Command { - public function __invoke() + public function __invoke(): void { $days = (int) ($this->console->getOption('days') ?? 30); @@ -28,7 +28,7 @@ public function __invoke() } foreach ($metadata->execCache as $sandboxDir => $packageMetadata) { - $lastRun = strtotime($packageMetadata->lastRunAt ?? '1970-01-01 00:00:00'); + $lastRun = $packageMetadata['last_run'] ?? 0; if ($this->console->hasOption('all') || $lastRun < $timeLimit) { $packageDirectory = cpx_path(".exec_cache/{$sandboxDir}"); diff --git a/src/Commands/ExecCommand.php b/src/Commands/ExecCommand.php index abaa245..0b0f1f6 100644 --- a/src/Commands/ExecCommand.php +++ b/src/Commands/ExecCommand.php @@ -8,7 +8,7 @@ class ExecCommand extends Command { public string $path; - public function __invoke() + public function __invoke(): void { if ($this->console->hasOption('r')) { $code = $this->console->getOption('r'); @@ -31,7 +31,15 @@ public function __invoke() $code .= ';'; } - $this->autoload(getcwd()); + $directory = getcwd(); + + if ($directory === false) { + $this->error('Unable to determine the current working directory.'); + + return; + } + + $this->autoload($directory); eval($code); echo PHP_EOL; @@ -45,14 +53,16 @@ public function __invoke() return; } - $this->path = realpath($this->console->arguments[0]); + $path = realpath($this->console->arguments[0]); - if (! file_exists($this->path)) { - $this->error("File does not exist at '{$this->path}'"); + if ($path === false || ! file_exists($path)) { + $this->error("File does not exist at '{$this->console->arguments[0]}'"); return; } + $this->path = $path; + $this->autoload(dirname($this->path)); $this->runFile(); @@ -60,14 +70,25 @@ public function __invoke() protected function autoload(string $directory): void { - $shouldFindAutoloader = $this->console->getOption('find-autoloader') ?? true; - $shouldLoadLaravelBootstrap = $this->console->getOption('load-laravel-bootstrap') ?? true; - $shouldAliasClasses = $this->console->getOption('alias-classes') ?? true; - $shouldBeVerbose = $this->console->getOption('verbose') ?? false; + $shouldFindAutoloader = $this->booleanOption('find-autoloader', true); + $shouldLoadLaravelBootstrap = $this->booleanOption('load-laravel-bootstrap', true); + $shouldAliasClasses = $this->booleanOption('alias-classes', true); + $shouldBeVerbose = $this->booleanOption('verbose', false); PhpExecutionHelper::init($directory, $shouldFindAutoloader, $shouldLoadLaravelBootstrap, $shouldAliasClasses, $shouldBeVerbose); } + protected function booleanOption(string $option, bool $default): bool + { + $value = $this->console->getOption($option); + + if ($value === null) { + return $default; + } + + return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? $default; + } + public function runFile(): void { require $this->path; diff --git a/src/Commands/FormatCommand.php b/src/Commands/FormatCommand.php index 1bb4a61..9c15f72 100644 --- a/src/Commands/FormatCommand.php +++ b/src/Commands/FormatCommand.php @@ -7,7 +7,7 @@ class FormatCommand extends Command { - public function __invoke() + public function __invoke(): void { $directory = $this->console->arguments[0] ?? '.'; @@ -18,7 +18,9 @@ public function __invoke() $command .= ' --test'; } - return Console::parse($command)->exec(); + Console::parse($command)->exec(); + + return; } if (file_exists('vendor/bin/php-cs-fixer')) { @@ -30,13 +32,17 @@ public function __invoke() $command .= ' --allow-risky=yes'; - return Console::parse($command)->exec(); + Console::parse($command)->exec(); + + return; } if (file_exists('vendor/bin/phpcbf')) { $command = "vendor/bin/phpcbf {$directory}"; - return Console::parse($command)->exec(); + Console::parse($command)->exec(); + + return; } throw new ConsoleException('No code formatters found in the project.'); diff --git a/src/Commands/HelpCommand.php b/src/Commands/HelpCommand.php index 8d85876..ceef605 100644 --- a/src/Commands/HelpCommand.php +++ b/src/Commands/HelpCommand.php @@ -4,7 +4,7 @@ class HelpCommand extends Command { - public function __invoke(bool $unknownCommand = false) + public function __invoke(bool $unknownCommand = false): void { if ($unknownCommand) { $this->error("Unrecognised command {$this->console->command}"); diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index fb2611a..6f077df 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -6,7 +6,7 @@ class ListCommand extends Command { - public function __invoke() + public function __invoke(): void { $metadata = Metadata::open(); diff --git a/src/Commands/RunPackageCommand.php b/src/Commands/RunPackageCommand.php index fca61ba..dcf7522 100644 --- a/src/Commands/RunPackageCommand.php +++ b/src/Commands/RunPackageCommand.php @@ -4,5 +4,5 @@ class RunPackageCommand extends Command { - public function __invoke() {} + public function __invoke(): void {} } diff --git a/src/Commands/TestCommand.php b/src/Commands/TestCommand.php index d8d0a53..411ba63 100644 --- a/src/Commands/TestCommand.php +++ b/src/Commands/TestCommand.php @@ -7,22 +7,30 @@ class TestCommand extends Command { - public function __invoke() + public function __invoke(): void { if (file_exists('vendor/bin/pest')) { - return Console::parse('vendor/bin/pest')->exec(); + Console::parse('vendor/bin/pest')->exec(); + + return; } if (file_exists('bin/phpunit')) { - return Console::parse('bin/phpunit')->exec(); + Console::parse('bin/phpunit')->exec(); + + return; } if (file_exists('vendor/bin/phpunit')) { - return Console::parse('vendor/bin/phpunit')->exec(); + Console::parse('vendor/bin/phpunit')->exec(); + + return; } if (file_exists('vendor/bin/codecept')) { - return Console::parse('vendor/bin/codecept')->exec(); + Console::parse('vendor/bin/codecept')->exec(); + + return; } throw new ConsoleException('No test runner found in the project.'); diff --git a/src/Commands/TinkerCommand.php b/src/Commands/TinkerCommand.php index 3699b8a..95c3bb3 100644 --- a/src/Commands/TinkerCommand.php +++ b/src/Commands/TinkerCommand.php @@ -7,10 +7,16 @@ class TinkerCommand extends Command { - public function __invoke() + public function __invoke(): void { $psyshConfig = realpath(__DIR__.'/../../files/psysh-config.php'); - return Package::parse('psy/psysh')->runCommand(Console::parse("psysh --config {$psyshConfig}")); + if ($psyshConfig === false) { + $this->error('Unable to find the PsySH configuration file.'); + + return; + } + + Package::parse('psy/psysh')->runCommand(Console::parse("psysh --config {$psyshConfig}")); } } diff --git a/src/Commands/UpdateCommand.php b/src/Commands/UpdateCommand.php index 2227c75..a73f478 100644 --- a/src/Commands/UpdateCommand.php +++ b/src/Commands/UpdateCommand.php @@ -7,7 +7,7 @@ class UpdateCommand extends Command { - public function __invoke() + public function __invoke(): void { match (true) { str_contains($this->console->arguments[0] ?? '', '/') => $this->updatePackage(Package::parse($this->console->arguments[0])), diff --git a/src/Commands/UpgradeCommand.php b/src/Commands/UpgradeCommand.php index c0c64ed..a55b27d 100644 --- a/src/Commands/UpgradeCommand.php +++ b/src/Commands/UpgradeCommand.php @@ -6,7 +6,7 @@ class UpgradeCommand extends Command { - public function __invoke() + public function __invoke(): void { $this->line('Updating '.Command::COLOR_GREEN.'cpx'); Composer::runCommand('global update cpx/cpx'); diff --git a/src/Commands/VersionCommand.php b/src/Commands/VersionCommand.php index 2c56667..f389412 100644 --- a/src/Commands/VersionCommand.php +++ b/src/Commands/VersionCommand.php @@ -6,9 +6,21 @@ class VersionCommand extends Command { - public function __invoke() + public function __invoke(): void { - $cpxVersion = json_decode(file_get_contents(__DIR__.'/../../composer.json'), true)['version'] ?? 'unknown'; + $contents = file_get_contents(__DIR__.'/../../composer.json'); + $composerData = []; + + if ($contents !== false) { + $decoded = json_decode($contents, true); + + if (is_array($decoded)) { + $composerData = $decoded; + } + } + + $cpxVersion = is_string($composerData['version'] ?? null) ? $composerData['version'] : 'unknown'; + $this->line('cpx version: '.Command::COLOR_GREEN.$cpxVersion); $this->line('php version: '.Command::COLOR_GREEN.PHP_VERSION); } From d5687570cedc6d0bdc2d924c0c63bdcc328da658 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 13:15:29 +0100 Subject: [PATCH 06/13] Add laravel/pao --- composer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/composer.json b/composer.json index 51d9fee..f28ed8e 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "php": "^8.2" }, "require-dev": { + "laravel/pao": "^1.1", "laravel/pint": "^1.29", "phpstan/phpstan": "^2.2" }, @@ -52,5 +53,8 @@ "@analyse", "@lint:check" ] + }, + "config": { + "sort-packages": true } } From 3dd5ee0888398b752c7472606a657bf20e57f643 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 13:27:01 +0100 Subject: [PATCH 07/13] Add Pest test setup --- composer.json | 15 ++++++++++- phpunit.xml | 18 ++++++++++++++ tests/Feature/ExampleTest.php | 5 ++++ tests/Pest.php | 47 +++++++++++++++++++++++++++++++++++ tests/TestCase.php | 10 ++++++++ tests/Unit/ExampleTest.php | 5 ++++ 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 phpunit.xml create mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ExampleTest.php diff --git a/composer.json b/composer.json index f28ed8e..6eb3af5 100644 --- a/composer.json +++ b/composer.json @@ -28,12 +28,18 @@ "src/functions.php" ] }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, "require": { "php": "^8.2" }, "require-dev": { "laravel/pao": "^1.1", "laravel/pint": "^1.29", + "pestphp/pest": "^4.7", "phpstan/phpstan": "^2.2" }, "bin": [ @@ -49,12 +55,19 @@ "lint:check": [ "pint --parallel --test" ], + "test:unit": [ + "pest --parallel" + ], "test": [ "@analyse", - "@lint:check" + "@lint:check", + "@test:unit" ] }, "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + }, "sort-packages": true } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e6198e0 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests + + + + + app + src + + + diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..61cd84c --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,5 @@ +toBeTrue(); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..d4ffffb --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,47 @@ +extend(TestCase::class)->in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..cfb05b6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +toBeTrue(); +}); From 37af8a029837f1ff9714d70256ca8ad619c87c3d Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 13:46:16 +0100 Subject: [PATCH 08/13] Add architecture tests --- src/ClassAliasAutoloader.php | 2 ++ src/Commands/AliasesCommand.php | 2 ++ src/Commands/CheckCommand.php | 2 ++ src/Commands/CleanCommand.php | 5 ++- src/Commands/Command.php | 2 ++ src/Commands/ExecCommand.php | 2 ++ src/Commands/FormatCommand.php | 2 ++ src/Commands/HelpCommand.php | 2 ++ src/Commands/ListCommand.php | 5 ++- src/Commands/RunPackageCommand.php | 2 ++ src/Commands/TestCommand.php | 2 ++ src/Commands/TinkerCommand.php | 2 ++ src/Commands/UpdateCommand.php | 2 ++ src/Commands/UpgradeCommand.php | 2 ++ src/Composer.php | 36 +++++++++++++++++--- src/Console.php | 4 ++- src/Exceptions/ComposerInstallException.php | 2 ++ src/Exceptions/ConsoleException.php | 2 ++ src/Metadata.php | 2 ++ src/Package.php | 12 +++---- src/PackageAliases.php | 2 ++ src/PackageMetadata.php | 2 ++ src/Utils.php | 30 +++++++++++++++++ src/functions.php | 4 ++- tests/ArchTest.php | 37 +++++++++++++++++++++ 25 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 tests/ArchTest.php diff --git a/src/ClassAliasAutoloader.php b/src/ClassAliasAutoloader.php index 8fc5b07..68111ee 100644 --- a/src/ClassAliasAutoloader.php +++ b/src/ClassAliasAutoloader.php @@ -1,5 +1,7 @@ console->hasOption('all') || $lastRun < $timeLimit) { $packageDirectory = cpx_path(".exec_cache/{$sandboxDir}"); - exec("rm -rf {$packageDirectory}"); + Utils::deleteDirectory($packageDirectory); $this->line(Command::COLOR_GREEN."Removing exec sandbox cache {$sandboxDir}..."); unset($metadata->execCache[$sandboxDir]); $cleanedSomething = true; diff --git a/src/Commands/Command.php b/src/Commands/Command.php index 0f0b870..a479679 100644 --- a/src/Commands/Command.php +++ b/src/Commands/Command.php @@ -1,5 +1,7 @@ packages)) { $this->line('There are no installed packages.'); - exit(); + + return; } $this->line('Installed Packages:'); diff --git a/src/Commands/RunPackageCommand.php b/src/Commands/RunPackageCommand.php index dcf7522..6aabfb7 100644 --- a/src/Commands/RunPackageCommand.php +++ b/src/Commands/RunPackageCommand.php @@ -1,5 +1,7 @@ */ public static function runCommand(string $command, ?string $directory = null): array { - $output = []; $workingDirectory = $directory ? "--working-dir={$directory}" : ''; + $process = proc_open( + "composer {$command} --no-interaction --quiet {$workingDirectory}", + [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + ); + + if (! is_resource($process)) { + throw new Exception("Composer command failed: {$command}"); + } + + fclose($pipes[0]); + + $output = stream_get_contents($pipes[1]); + $errorOutput = stream_get_contents($pipes[2]); - exec("composer {$command} --no-interaction --quiet {$workingDirectory}", $output, $resultCode); + fclose($pipes[1]); + fclose($pipes[2]); + + $resultCode = proc_close($process); if ($resultCode !== 0) { - throw new Exception("Composer command failed: {$command}"); + $message = trim($errorOutput) ?: "Composer command failed: {$command}"; + + throw new Exception($message); + } + + if ($output === false || trim($output) === '') { + return []; } - return $output; + return explode(PHP_EOL, trim($output)); } /** diff --git a/src/Console.php b/src/Console.php index 733cc73..0cf8ab4 100644 --- a/src/Console.php +++ b/src/Console.php @@ -1,5 +1,7 @@ folder()}"); - exec("rm -rf {$packageDirectory}"); + Utils::deleteDirectory(cpx_path("{$this->folder()}")); } public function runCommand(Console $console, bool $autoUpdate = true): void @@ -63,8 +65,7 @@ public function runCommand(Console $console, bool $autoUpdate = true): void $binScripts = Composer::detectBinFromComposer("{$installDir}/vendor/{$this->vendor}/{$this->name}"); if (empty($binScripts)) { - printColor("Error: No bin command found in {$this}.", "\033[1;31m"); - exit(1); + throw new RuntimeException("No bin command found in {$this}."); } $binScripts = Utils::arrayMapAssoc(fn ($key, $value) => [basename($value) => $value], $binScripts); @@ -95,8 +96,7 @@ public function runCommand(Console $console, bool $autoUpdate = true): void } if (! isset($command)) { - echo Command::BACKGROUND_RED." More than 1 bin command found for {$this}: ".implode(', ', array_keys($binScripts)).' '.Command::COLOR_RESET.PHP_EOL; - exit(); + throw new RuntimeException("More than 1 bin command found for {$this}: ".implode(', ', array_keys($binScripts)).'.'); } } else { $command = $binScripts[array_key_first($binScripts)]; diff --git a/src/PackageAliases.php b/src/PackageAliases.php index 88307f8..5a166c7 100644 --- a/src/PackageAliases.php +++ b/src/PackageAliases.php @@ -1,5 +1,7 @@ isDir() && ! $file->isLink()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + + rmdir($directory); + } } diff --git a/src/functions.php b/src/functions.php index 89da97f..215caf1 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,5 +1,7 @@ preset()->php(); + +arch('it will not use dd(), ddd(), env(), or exit()') + ->expect(['dd', 'ddd', 'env', 'exit']) + ->each->not->toBeUsed(); + +arch('it will not use risky security functions') + ->expect([ + 'md5', + 'sha1', + 'uniqid', + 'rand', + 'mt_rand', + 'tempnam', + 'str_shuffle', + 'shuffle', + 'array_rand', + 'exec', + 'shell_exec', + 'system', + 'passthru', + 'create_function', + 'unserialize', + 'extract', + 'mb_parse_str', + 'dl', + 'assert', + ]) + ->each->not->toBeUsed(); + +arch('the package source declares strict types') + ->expect('Cpx') + ->toUseStrictTypes(); From 7d141220b991ff998d7cbdc1ab5af1a5e2ec7aba Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 14:24:06 +0100 Subject: [PATCH 09/13] Add pest type coverage plugin --- composer.json | 5 +++++ phpunit.xml | 3 +-- src/Commands/AliasesCommand.php | 2 +- src/Metadata.php | 4 ++-- src/Package.php | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 6eb3af5..ffb1875 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "laravel/pao": "^1.1", "laravel/pint": "^1.29", "pestphp/pest": "^4.7", + "pestphp/pest-plugin-type-coverage": "^4.0", "phpstan/phpstan": "^2.2" }, "bin": [ @@ -55,12 +56,16 @@ "lint:check": [ "pint --parallel --test" ], + "test:types": [ + "pest --type-coverage --min=100" + ], "test:unit": [ "pest --parallel" ], "test": [ "@analyse", "@lint:check", + "@test:types", "@test:unit" ] }, diff --git a/phpunit.xml b/phpunit.xml index e6198e0..dba4012 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,8 +11,7 @@ - app - src + src diff --git a/src/Commands/AliasesCommand.php b/src/Commands/AliasesCommand.php index 9f68c05..7c51391 100644 --- a/src/Commands/AliasesCommand.php +++ b/src/Commands/AliasesCommand.php @@ -12,7 +12,7 @@ public function __invoke(): void { $this->line('Aliased packages:'.PHP_EOL); $packages = PackageAliases::$packages; - usort($packages, fn ($a, $b) => strcmp($a['command'], $b['command'])); + usort($packages, fn (array $a, array $b): int => strcmp($a['command'], $b['command'])); foreach ($packages as $package) { $paddedCommand = str_pad($package['command'], 15); diff --git a/src/Metadata.php b/src/Metadata.php index 6b8ca8f..25a2382 100644 --- a/src/Metadata.php +++ b/src/Metadata.php @@ -29,7 +29,7 @@ public static function open(): Metadata return new Metadata( packages: Utils::arrayMapAssoc( - fn ($key, $value) => [ + fn (string $key, array $value): array => [ $key => new PackageMetadata( package: Package::parse($key), lastUpdatedAt: $value['last_updated'] ?? null, @@ -88,7 +88,7 @@ public function toArray(): array { return [ 'packages' => Utils::arrayMapAssoc( - fn ($key, PackageMetadata $packageMetadata) => [ + fn (string $key, PackageMetadata $packageMetadata): array => [ $packageMetadata->package->fullPackageString() => [ 'last_updated' => $packageMetadata->lastUpdatedAt, 'last_run' => $packageMetadata->lastRunAt, diff --git a/src/Package.php b/src/Package.php index 422a3e6..00e7022 100644 --- a/src/Package.php +++ b/src/Package.php @@ -68,7 +68,7 @@ public function runCommand(Console $console, bool $autoUpdate = true): void throw new RuntimeException("No bin command found in {$this}."); } - $binScripts = Utils::arrayMapAssoc(fn ($key, $value) => [basename($value) => $value], $binScripts); + $binScripts = Utils::arrayMapAssoc(fn (int $_, string $value): array => [basename($value) => $value], $binScripts); if (count($binScripts) > 1) { $possibleCommands = array_values(array_unique(array_filter([ From c8fc9ad91ccc717e8bcfd9e297fcf34ba0b388e3 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 16:01:51 +0100 Subject: [PATCH 10/13] Add CI workflow for supported PHP versions --- .github/workflows/tests.yml | 53 +++++++++++++++++++++++++++++++++++++ composer.json | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..d634c4e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,53 @@ +name: tests + +on: + push: + branches: + - main + - 1.x + - 2.x + pull_request: + branches: + - main + - 1.x + - 2.x + +permissions: + contents: read + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-version: ['8.3', '8.4', '8.5'] + + steps: + - name: Checkout Code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer:v2 + coverage: none + + - name: Install Dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Run Static Analysis + run: composer analyse + + - name: Run Lint Check + run: composer lint:check + + - name: Run Type Coverage + run: composer test:types + + - name: Run Unit Tests + run: composer test:unit diff --git a/composer.json b/composer.json index ffb1875..3c58859 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ } }, "require": { - "php": "^8.2" + "php": "^8.3" }, "require-dev": { "laravel/pao": "^1.1", From af75df9cba321c396907b857b546627ed529707e Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Tue, 23 Jun 2026 16:42:37 +0100 Subject: [PATCH 11/13] Add dependabot config --- .github/dependabot.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..38e1ca9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directories: + - "/" + schedule: + interval: "weekly" + cooldown: + default-days: 5 + groups: + github-actions: + patterns: + - "*" From 971c841af0b41c396f9b3872fa71ed829c46fa71 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Fri, 26 Jun 2026 10:30:15 -0400 Subject: [PATCH 12/13] remove boilerplate --- tests/Pest.php | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/tests/Pest.php b/tests/Pest.php index d4ffffb..afcf075 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -2,46 +2,4 @@ use Tests\TestCase; -/* -|-------------------------------------------------------------------------- -| Test Case -|-------------------------------------------------------------------------- -| -| The closure you provide to your test functions is always bound to a specific PHPUnit test -| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may -| need to change it using the "pest()" function to bind different classes or traits. -| -*/ - pest()->extend(TestCase::class)->in('Feature'); - -/* -|-------------------------------------------------------------------------- -| Expectations -|-------------------------------------------------------------------------- -| -| When you're writing tests, you often need to check that values meet certain conditions. The -| "expect()" function gives you access to a set of "expectations" methods that you can use -| to assert different things. Of course, you may extend the Expectation API at any time. -| -*/ - -expect()->extend('toBeOne', function () { - return $this->toBe(1); -}); - -/* -|-------------------------------------------------------------------------- -| Functions -|-------------------------------------------------------------------------- -| -| While Pest is very powerful out-of-the-box, you may have some testing code specific to your -| project that you don't want to repeat in every file. Here you can also expose helpers as -| global functions to help you to reduce the number of lines of code in your test files. -| -*/ - -function something() -{ - // .. -} From 3502182b1019cf9ad5feea6692c42e1eae2cc98a Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Fri, 26 Jun 2026 10:58:25 -0400 Subject: [PATCH 13/13] removed unused import --- src/Package.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Package.php b/src/Package.php index 00e7022..6b3c9bb 100644 --- a/src/Package.php +++ b/src/Package.php @@ -4,7 +4,6 @@ namespace Cpx; -use Cpx\Commands\Command; use InvalidArgumentException; use RuntimeException;