From 48b4e089b6a5728854c4d9707034f385d182dc8a Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Mon, 28 Jul 2025 08:39:44 +0700 Subject: [PATCH 1/6] Add increment command --- app/Commander.php | 2 + app/Commands/IncrementCommand.php | 90 ++++++++++++++++++++++++++ app/Commands/ValidateCommand.php | 6 +- app/Exceptions/InvalidArgumentType.php | 19 ++++++ 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 app/Commands/IncrementCommand.php create mode 100644 app/Exceptions/InvalidArgumentType.php 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..d7bbcf3 --- /dev/null +++ b/app/Commands/IncrementCommand.php @@ -0,0 +1,90 @@ +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']); + $this->addArgument('part', InputArgument::REQUIRED, 'Part to increment (major, minor, patch)'); + $this->addArgument('version', InputArgument::REQUIRED, 'Version to increment'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $style = new SymfonyStyle($input, $output); + $part = $input->getArgument('part'); + $version = $input->getArgument('version'); + + 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, + ), + ); + } catch (Throwable $th) { + $style->error($th->getMessage()); + + return Command::FAILURE; + } + + return Command::SUCCESS; + } + + private function increment(Version $version, string $part): Version + { + switch ($part) { + case 'major': + return $version->incrementMajor(); + + case 'minor': + return $version->incrementMinor(); + + case 'patch': + return $version->incrementPatch(); + + default: + throw new InvalidArgumentException(sprintf("Invalid part '%s' provided. Expected 'major', 'minor', or 'patch'.", $part)); + } + } +} diff --git a/app/Commands/ValidateCommand.php b/app/Commands/ValidateCommand.php index ae48ae9..e286b1d 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\Concerns\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 @@ + Date: Mon, 28 Jul 2025 10:10:27 +0700 Subject: [PATCH 2/6] Update `increment` command --- app/Commands/IncrementCommand.php | 48 ++++++++++-- composer.json | 4 +- tests/app/Commands/IncrementCommandTest.php | 82 +++++++++++++++++++++ 3 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 tests/app/Commands/IncrementCommandTest.php diff --git a/app/Commands/IncrementCommand.php b/app/Commands/IncrementCommand.php index d7bbcf3..e79e7cf 100644 --- a/app/Commands/IncrementCommand.php +++ b/app/Commands/IncrementCommand.php @@ -27,16 +27,30 @@ protected function configure(): void $this->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']); - $this->addArgument('part', InputArgument::REQUIRED, 'Part to increment (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->getArgument('part'); + $part = $input->getOption('part'); $version = $input->getArgument('version'); + $build = $input->getOption('build'); + $pre = $input->getOption('pre'); try { if (! is_string($part)) { @@ -55,11 +69,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var Version $parsed */ $parsed = Version::fromString($version); - $style->writeln( (string) $this->increment( $parsed, $part, + $pre, + $build, ), ); } catch (Throwable $th) { @@ -71,20 +86,37 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - private function increment(Version $version, string $part): Version + /** + * @param mixed $pre + * @param mixed $build + */ + private function increment(Version $version, string $part, $pre = null, $build = null): Version { switch ($part) { case 'major': - return $version->incrementMajor(); + $version = $version->incrementMajor(); + break; case 'minor': - return $version->incrementMinor(); + $version = $version->incrementMinor(); + break; case 'patch': - return $version->incrementPatch(); + $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/composer.json b/composer.json index e0cc903..8bd65c6 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "syntatis/version-cli-php", - "description": "Increment, compare SemVer-compliant version number with CLI.", + "name": "syntatis/version-cli", + "description": "Validate, increment, compare, etc. SemVer-compliant version number with CLI", "keywords": ["version", "cli", "semver"], "authors": [ { diff --git a/tests/app/Commands/IncrementCommandTest.php b/tests/app/Commands/IncrementCommandTest.php new file mode 100644 index 0000000..3f6e1e3 --- /dev/null +++ b/tests/app/Commands/IncrementCommandTest.php @@ -0,0 +1,82 @@ +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']; + } +} From d596435260c03902407365b34ac101960e03819f Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:13:26 +0700 Subject: [PATCH 3/6] Update package version --- .github/workflows/ci.yml | 12 +++--------- composer.json | 4 ++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a45bb54..9e9532f 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 @@ -68,24 +67,19 @@ jobs: fail-fast: true max-parallel: 2 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/composer.json b/composer.json index 8bd65c6..3ccd190 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,8 @@ }, "require": { "php": "^7.4 || ^8.0", - "nikolaposa/version": "^4.1", - "symfony/console": "^5.4" + "nikolaposa/version": "^4.1.1", + "symfony/console": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0", From 23156001bbc08e1eafb7a47bd3f372ce4ec64314 Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:13:54 +0700 Subject: [PATCH 4/6] Update maximum parallel --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e9532f..29a8bd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ 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"] From f2cae214c06a42b38ad0d70fe34a4129e01a6e3e Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:33:18 +0700 Subject: [PATCH 5/6] Fix import argument namespace --- app/Commands/ValidateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Commands/ValidateCommand.php b/app/Commands/ValidateCommand.php index e286b1d..f76b65a 100644 --- a/app/Commands/ValidateCommand.php +++ b/app/Commands/ValidateCommand.php @@ -9,7 +9,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Syntatis\Version\CLI\Concerns\InvalidArgumentType; +use Syntatis\Version\CLI\Exceptions\InvalidArgumentType; use Throwable; use Version\Version; From 5989cdbf3fcecf256f83c099415cfab69af059d8 Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:37:29 +0700 Subject: [PATCH 6/6] Update description --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3ccd190..ee16d62 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "syntatis/version-cli", - "description": "Validate, increment, compare, etc. SemVer-compliant version number with CLI", + "description": "Validate, increment, and compare SemVer-compliant version number with CLI", "keywords": ["version", "cli", "semver"], "authors": [ {