Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ChangelogNextVersionCommand
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
3 / 3
5
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configure
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Console\Command;
21
22use FastForward\DevTools\Console\Command\Traits\LogsCommandResults;
23use Throwable;
24use FastForward\DevTools\Changelog\Manager\ChangelogManagerInterface;
25use FastForward\DevTools\Console\Input\HasJsonOption;
26use FastForward\DevTools\Filesystem\FilesystemInterface;
27use Symfony\Component\Console\Attribute\AsCommand;
28use Symfony\Component\Console\Command\Command;
29use Symfony\Component\Console\Input\InputInterface;
30use Symfony\Component\Console\Input\InputOption;
31use Symfony\Component\Console\Output\OutputInterface;
32
33/**
34 * Infers the next semantic version from changelog content.
35 */
36#[AsCommand(
37    name: 'changelog:next-version',
38    description: 'Infers the next semantic version from the Unreleased changelog section.'
39)]
40 class ChangelogNextVersionCommand extends Command
41{
42    use HasJsonOption;
43    use LogsCommandResults;
44
45    /**
46     * @param FilesystemInterface $filesystem
47     * @param ChangelogManagerInterface $changelogManager
48     */
49    public function __construct(
50        private  FilesystemInterface $filesystem,
51        private  ChangelogManagerInterface $changelogManager,
52    ) {
53        parent::__construct();
54    }
55
56    /**
57     * Configures version inference options.
58     */
59    protected function configure(): void
60    {
61        $this->setHelp(
62            'This command inspects Unreleased changelog categories and prints the next semantic version inferred'
63            . ' from the current changelog state.'
64        );
65
66        $this->addJsonOption()
67            ->addOption(
68                name: 'file',
69                mode: InputOption::VALUE_REQUIRED,
70                description: 'Path to the changelog file.',
71                default: 'CHANGELOG.md',
72            )
73            ->addOption(
74                name: 'current-version',
75                mode: InputOption::VALUE_REQUIRED,
76                description: 'Explicit current version to use as the bump base.',
77            );
78    }
79
80    /**
81     * Prints the inferred next semantic version.
82     *
83     * @param InputInterface $input
84     * @param OutputInterface $output
85     */
86    protected function execute(InputInterface $input, OutputInterface $output): int
87    {
88        try {
89            $path = $this->filesystem->getAbsolutePath($input->getOption('file'));
90            $currentVersion = $input->getOption('current-version');
91            $nextVersion = $this->changelogManager->inferNextVersion($path, $currentVersion);
92
93            // This command is consumed via shell capture in changelog.yml, so
94            // plain-text mode MUST keep emitting the raw version string.
95            if (! $this->isJsonOutput($input)) {
96                $output->writeln($nextVersion);
97
98                return self::SUCCESS;
99            }
100
101            return $this->success(
102                $nextVersion,
103                $input,
104                [
105                    'current_version' => $currentVersion,
106                    'next_version' => $nextVersion,
107                ],
108            );
109        } catch (Throwable $throwable) {
110            return $this->failure(
111                'Unable to infer the next changelog version.',
112                $input,
113                [
114                    'exception_message' => $throwable->getMessage(),
115                ],
116                (string) $input->getOption('file'),
117            );
118        }
119    }
120}