• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / version-bumper / 26184546461

20 May 2026 07:18PM UTC coverage: 85.077% (-3.5%) from 88.618%
26184546461

Pull #144

github

eliashaeussler
[FEATURE] Introduce `next-version` command
Pull Request #144: [FEATURE] Introduce `next-version` command

122 of 183 new or added lines in 6 files covered. (66.67%)

1 existing line in 1 file now uncovered.

1220 of 1434 relevant lines covered (85.08%)

5.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.67
/src/Command/BaseVersionCommand.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/version-bumper".
7
 *
8
 * Copyright (C) 2024-2026 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\VersionBumper\Command;
25

26
use Composer\Command;
27
use Composer\Composer;
28
use CuyZ\Valinor;
29
use EliasHaeussler\VersionBumper\Config;
30
use EliasHaeussler\VersionBumper\Error;
31
use EliasHaeussler\VersionBumper\Exception;
32
use Symfony\Component\Console;
33
use Symfony\Component\Filesystem;
34

35
use function dirname;
36
use function getcwd;
37
use function is_string;
38
use function method_exists;
39
use function sprintf;
40
use function trim;
41

42
/**
43
 * BaseVersionCommand.
44
 *
45
 * @author Elias Häußler <elias@haeussler.dev>
46
 * @license GPL-3.0-or-later
47
 */
48
abstract class BaseVersionCommand extends Command\BaseCommand
49
{
50
    protected readonly Config\ConfigReader $configReader;
51
    protected readonly Error\DeprecationHandler $deprecationHandler;
52
    protected Console\Style\SymfonyStyle $io;
53

54
    public function __construct(
23✔
55
        string $name,
56
        ?Composer $composer = null,
57
    ) {
58
        if (null !== $composer) {
23✔
59
            $this->setComposer($composer);
1✔
60
        }
61

62
        parent::__construct($name);
23✔
63

64
        $this->configReader = new Config\ConfigReader();
23✔
65
        $this->deprecationHandler = Error\DeprecationHandler::new();
23✔
66
    }
67

68
    protected function configure(): void
23✔
69
    {
70
        $this->addOption(
23✔
71
            'config',
23✔
72
            'c',
23✔
73
            Console\Input\InputOption::VALUE_REQUIRED,
23✔
74
            'Path to configuration file (JSON, YAML or PHP)',
23✔
75
            $this->readConfigFileFromRootPackage(),
23✔
76
        );
23✔
77
    }
78

79
    protected function initialize(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): void
23✔
80
    {
81
        $this->io = new Console\Style\SymfonyStyle($input, $output);
23✔
82
    }
83

84
    protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int
23✔
85
    {
86
        $rootPath = (string) getcwd();
23✔
87
        $configFile = $input->getOption('config') ?? $this->configReader->detectFile($rootPath);
23✔
88

89
        if (null === $configFile) {
23✔
90
            $this->io->error('Please provide a config file path using the --config option.');
1✔
91

92
            return self::INVALID;
1✔
93
        }
94

95
        if (Filesystem\Path::isRelative($configFile)) {
22✔
96
            $configFile = Filesystem\Path::makeAbsolute($configFile, $rootPath);
1✔
97
        } else {
98
            $rootPath = dirname($configFile);
21✔
99
        }
100

101
        // Register custom error handler to collect deprecations from config presets
102
        $this->deprecationHandler->enable();
22✔
103

104
        try {
105
            $config = $this->configReader->readFromFile($configFile);
22✔
106

107
            // Override root path from config file
108
            if (null !== $config->rootPath()) {
21✔
109
                $rootPath = $config->rootPath();
21✔
110
            }
111

112
            return $this->executeCommand($config, $rootPath, $input, $output);
21✔
113
        } catch (Valinor\Mapper\MappingError $error) {
7✔
114
            $this->decorateMappingError($error, $configFile);
1✔
115

116
            return self::FAILURE;
1✔
117
        } catch (Exception\Exception $exception) {
6✔
118
            $this->io->error($exception->getMessage());
6✔
119

120
            return self::FAILURE;
6✔
121
        } finally {
122
            $this->deprecationHandler->decorate($this->io);
22✔
123
            $this->deprecationHandler->disable();
22✔
124
        }
125
    }
126

127
    abstract protected function executeCommand(
128
        Config\VersionBumperConfig $config,
129
        string $rootPath,
130
        Console\Input\InputInterface $input,
131
        Console\Output\OutputInterface $output,
132
    ): int;
133

134
    protected function decorateMappingError(Valinor\Mapper\MappingError $error, string $configFile): void
1✔
135
    {
136
        $errorMessages = [];
1✔
137
        $errors = $error->messages()->errors();
1✔
138

139
        $this->io->error(
1✔
140
            sprintf('The config file "%s" is invalid.', $configFile),
1✔
141
        );
1✔
142

143
        foreach ($errors as $propertyError) {
1✔
144
            $errorMessages[] = sprintf('%s: %s', $propertyError->path(), $propertyError->toString());
1✔
145
        }
146

147
        $this->io->listing($errorMessages);
1✔
148
    }
149

150
    protected function readConfigFileFromRootPackage(): ?string
23✔
151
    {
152
        $composer = $this->getComposerInstance();
23✔
153

154
        if (null === $composer) {
23✔
155
            return null;
23✔
156
        }
157

158
        $extra = $composer->getPackage()->getExtra();
1✔
159
        /* @phpstan-ignore offsetAccess.nonOffsetAccessible */
160
        $configFile = $extra['version-bumper']['config-file'] ?? null;
1✔
161

162
        if (is_string($configFile) && '' !== trim($configFile)) {
1✔
163
            return $configFile;
1✔
164
        }
165

NEW
166
        return null;
×
167
    }
168

169
    protected function getComposerInstance(): ?Composer
23✔
170
    {
171
        // Composer >= 2.3
172
        if (method_exists($this, 'tryComposer')) {
23✔
173
            return $this->tryComposer();
23✔
174
        }
175

176
        // Composer < 2.3
NEW
177
        return $this->getComposer(false);
×
178
    }
179
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc