diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a45bb54..29a8bd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,6 @@ concurrency: cancel-in-progress: true jobs: - sniff: runs-on: ubuntu-latest name: Sniff @@ -66,26 +65,21 @@ jobs: strategy: fail-fast: true - max-parallel: 2 + max-parallel: 3 matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php: ["7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] steps: - name: Checkout code uses: actions/checkout@v2 - - name: Setup PHP for dependencies + - name: Setup PHP for test uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: ${{ matrix.php }} - name: Install PHP dependencies uses: ramsey/composer-install@v3 - - name: Setup PHP for test - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - - name: Run test run: composer test diff --git a/app/Commander.php b/app/Commander.php index 73a566a..a27b5f1 100644 --- a/app/Commander.php +++ b/app/Commander.php @@ -6,6 +6,7 @@ use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; +use Syntatis\Version\CLI\Commands\IncrementCommand; use Syntatis\Version\CLI\Commands\ValidateCommand; final class Commander extends Application @@ -26,6 +27,7 @@ public function __construct() private function getCommands(): array { return [ + new IncrementCommand(), new ValidateCommand(), ]; } diff --git a/app/Commands/IncrementCommand.php b/app/Commands/IncrementCommand.php new file mode 100644 index 0000000..e79e7cf --- /dev/null +++ b/app/Commands/IncrementCommand.php @@ -0,0 +1,122 @@ +setName('increment'); + $this->setDescription('Increment a version.'); + $this->setHelp('This command increments the provided version by the specified part (major, minor, patch).'); + $this->setAliases(['incr', 'bump']); + $this->addArgument('version', InputArgument::REQUIRED, 'Version to increment'); + $this->addOption('part', 'p', InputArgument::OPTIONAL, 'Part to increment (major, minor, patch)', 'patch'); + $this->addOption('build', 'b', InputArgument::OPTIONAL, 'Build metadata to append to the version'); + $this->addOption('pre', null, InputArgument::OPTIONAL, 'Pre-release identifier to append to the version'); + $this->setHelp(<<<'HELP' + This command increments the provided version by the specified part (major, minor, patch). + You can also append build metadata or a pre-release identifier to the version. + + Usage: + version increment 1.0.0 + version increment 1.0.0 --part=minor + version increment 1.0.0 --build=123 + version increment 1.0.0 --pre=beta + HELP,); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $style = new SymfonyStyle($input, $output); + $part = $input->getOption('part'); + $version = $input->getArgument('version'); + $build = $input->getOption('build'); + $pre = $input->getOption('pre'); + + try { + if (! is_string($part)) { + throw new InvalidArgumentType($part); + } + } catch (Throwable $th) { + $style->error($th->getMessage()); + + return Command::FAILURE; + } + + try { + if (! is_string($version)) { + throw new InvalidArgumentType($version); + } + + /** @var Version $parsed */ + $parsed = Version::fromString($version); + $style->writeln( + (string) $this->increment( + $parsed, + $part, + $pre, + $build, + ), + ); + } catch (Throwable $th) { + $style->error($th->getMessage()); + + return Command::FAILURE; + } + + return Command::SUCCESS; + } + + /** + * @param mixed $pre + * @param mixed $build + */ + private function increment(Version $version, string $part, $pre = null, $build = null): Version + { + switch ($part) { + case 'major': + $version = $version->incrementMajor(); + break; + + case 'minor': + $version = $version->incrementMinor(); + break; + + case 'patch': + $version = $version->incrementPatch(); + break; + + default: + throw new InvalidArgumentException(sprintf("Invalid part '%s' provided. Expected 'major', 'minor', or 'patch'.", $part)); + } + + if (is_string($pre) && $pre !== '') { + $version = $version->withPreRelease($pre); + } + + if (is_string($build) && $build !== '') { + $version = $version->withBuild($build); + } + + return $version; + } +} diff --git a/app/Commands/ValidateCommand.php b/app/Commands/ValidateCommand.php index ae48ae9..f76b65a 100644 --- a/app/Commands/ValidateCommand.php +++ b/app/Commands/ValidateCommand.php @@ -9,11 +9,10 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Syntatis\Version\CLI\Exceptions\InvalidArgumentType; use Throwable; -use TypeError; use Version\Version; -use function gettype; use function is_string; use function sprintf; @@ -39,12 +38,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { if (is_string($version)) { Version::fromString($version); + $style->success(sprintf("Version string '%s' is valid and can be parsed", $version)); return Command::SUCCESS; } - throw new TypeError(sprintf("Invalid type of value to validate. Expected string, '%s' given", gettype($version))); + throw new InvalidArgumentType($version); } catch (Throwable $th) { $style->error($th->getMessage()); diff --git a/app/Exceptions/InvalidArgumentType.php b/app/Exceptions/InvalidArgumentType.php new file mode 100644 index 0000000..0132414 --- /dev/null +++ b/app/Exceptions/InvalidArgumentType.php @@ -0,0 +1,19 @@ +commander = new Commander(); + } + + /** @dataProvider dataInvalidVersionArgument */ + public function testInvalidVersionArgument(string $version): void + { + $tester = new CommandTester($this->commander->get('increment')); + $tester->execute(['version' => $version]); + + self::assertStringContainsString( + sprintf("[ERROR] Version string '%s' is not valid and cannot be parsed", $version), + $tester->getDisplay(), + ); + } + + public function testIncrementPatch(): void + { + $tester = new CommandTester($this->commander->get('increment')); + $tester->execute(['version' => '1.0.0']); + + self::assertStringContainsString('1.0.1', $tester->getDisplay()); + } + + public function testIncrementMinor(): void + { + $tester = new CommandTester($this->commander->get('increment')); + $tester->execute(['version' => '1.0.0', '--part' => 'minor']); + + self::assertStringContainsString('1.1.0', $tester->getDisplay()); + } + + public function testIncrementMajor(): void + { + $tester = new CommandTester($this->commander->get('increment')); + $tester->execute(['version' => '1.0.0', '--part' => 'major']); + + self::assertStringContainsString('2.0.0', $tester->getDisplay()); + } + + public function testIncrementWithBuildMetadata(): void + { + $tester = new CommandTester($this->commander->get('increment')); + $tester->execute(['version' => '1.0.0', '--build' => '123']); + + self::assertStringContainsString('1.0.1+123', $tester->getDisplay()); + } + + public function testIncrementWithPreRelease(): void + { + $tester = new CommandTester($this->commander->get('increment')); + $tester->execute(['version' => '1.0.0', '--pre' => 'beta']); + + self::assertStringContainsString('1.0.1-beta', $tester->getDisplay()); + } + + public static function dataInvalidVersionArgument(): iterable + { + yield ['v']; + yield ['v0']; + yield ['v0.0']; + } +}