Skip to content

Commit 1f289f8

Browse files
authored
[command] kick of "raise to installed" command + add tests (#6)
* [command] kick of raise to installed * cs * decouple open logic to OpenVersionsComposerProcessor
1 parent faa98c2 commit 1f289f8

17 files changed

+572
-41
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"require": {
99
"php": ">=8.2",
1010
"composer/semver": "^3.4",
11-
"illuminate/container": "^12.0",
11+
"illuminate/container": "^12.14",
1212
"nette/utils": "^4.0",
1313
"symfony/console": "^6.4",
1414
"symfony/finder": "^7.2",
@@ -67,3 +67,4 @@
6767

6868

6969

70+

src/Command/OpenVersionsCommand.php

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66

77
use Nette\Utils\FileSystem;
88
use Nette\Utils\Json;
9-
use Nette\Utils\Strings;
109
use Rector\Jack\Composer\ComposerOutdatedResponseProvider;
11-
use Rector\Jack\Composer\NextVersionResolver;
10+
use Rector\Jack\ComposerProcessor\OpenVersionsComposerProcessor;
1211
use Rector\Jack\Enum\ComposerKey;
1312
use Rector\Jack\OutdatedComposerFactory;
1413
use Symfony\Component\Console\Command\Command;
@@ -20,9 +19,9 @@
2019
final class OpenVersionsCommand extends Command
2120
{
2221
public function __construct(
23-
private readonly NextVersionResolver $nextVersionResolver,
2422
private readonly OutdatedComposerFactory $outdatedComposerFactory,
2523
private readonly ComposerOutdatedResponseProvider $composerOutdatedResponseProvider,
24+
private readonly OpenVersionsComposerProcessor $openVersionsComposerProcessor,
2625
) {
2726
parent::__construct();
2827
}
@@ -93,58 +92,40 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9392

9493
$composerJsonContents = FileSystem::read($composerJsonFilePath);
9594

96-
$outdatedPackages = $outdatedComposer->getPackagesShuffled($onlyDev, $packagePrefix);
97-
98-
$openedPackageCount = 0;
99-
foreach ($outdatedPackages as $outdatedPackage) {
100-
$composerVersion = $outdatedPackage->getComposerVersion();
101-
102-
// already filled with open version
103-
if (str_contains($composerVersion, '|')) {
104-
continue;
105-
}
106-
107-
// convert composer version to next version
108-
$nextVersion = $this->nextVersionResolver->resolve($outdatedPackage->getName(), $composerVersion);
109-
$openedVersion = $composerVersion . '|' . $nextVersion;
110-
111-
// replace using regex, to keep original composer.json format
112-
$composerJsonContents = Strings::replace(
113-
$composerJsonContents,
114-
// find
115-
sprintf('#"%s": "(.*?)"#', $outdatedPackage->getName()),
116-
// replace
117-
sprintf('"%s": "%s"', $outdatedPackage->getName(), $openedVersion)
118-
);
119-
120-
$symfonyStyle->writeln(sprintf(
121-
' * Opened "<fg=green>%s</>" package to "<fg=yellow>%s</>" version',
122-
$outdatedPackage->getName(),
123-
$openedVersion
124-
));
95+
$changedPackageVersionsResult = $this->openVersionsComposerProcessor->process(
96+
$composerJsonContents,
97+
$outdatedComposer,
98+
$limit,
99+
$onlyDev,
100+
$packagePrefix
101+
);
125102

126-
++$openedPackageCount;
127-
if ($openedPackageCount >= $limit) {
128-
// we've reached the limit, so we can stop
129-
break;
130-
}
131-
}
103+
$openedPackages = $changedPackageVersionsResult->getChangedPackageVersions();
104+
$changedComposerJson = $changedPackageVersionsResult->getComposerJsonContents();
132105

133106
if ($isDryRun === false) {
134107
// update composer.json file, only if no --dry-run
135-
FileSystem::write($composerJsonFilePath, $composerJsonContents . PHP_EOL);
108+
FileSystem::write($composerJsonFilePath, $changedComposerJson . PHP_EOL, null);
136109
}
137110

138111
$symfonyStyle->success(
139112
sprintf(
140113
'%d packages %s opened up to the next nearest version.%s%s "composer update" to push versions up',
141-
$openedPackageCount,
114+
count($openedPackages),
142115
$isDryRun ? 'would be (is "--dry-run")' : 'were',
143116
PHP_EOL,
144117
$isDryRun ? 'Then you would run' : 'Now run'
145118
)
146119
);
147120

121+
foreach ($openedPackages as $openedPackage) {
122+
$symfonyStyle->writeln(sprintf(
123+
' * Opened "<fg=green>%s</>" package to "<fg=yellow>%s</>" version',
124+
$openedPackage->getPackageName(),
125+
$openedPackage->getNewVersion()
126+
));
127+
}
128+
148129
return self::SUCCESS;
149130
}
150131
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Jack\Command;
6+
7+
use Nette\Utils\FileSystem;
8+
use Nette\Utils\Json;
9+
use Rector\Jack\ComposerProcessor\RaiseToInstalledComposerProcessor;
10+
use Symfony\Component\Console\Command\Command;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
use Symfony\Component\Console\Style\SymfonyStyle;
15+
use Webmozart\Assert\Assert;
16+
17+
final class RaiseToInstalledCommand extends Command
18+
{
19+
public function __construct(
20+
private readonly RaiseToInstalledComposerProcessor $raiseToInstalledComposerProcessor,
21+
) {
22+
parent::__construct();
23+
}
24+
25+
protected function configure(): void
26+
{
27+
$this->setName('raise-to-lock');
28+
29+
$this->setDescription(
30+
'Raise your version in "composer.json" to installed one to get the latest version available in any composer update'
31+
);
32+
33+
$this->addOption(
34+
'dry-run',
35+
null,
36+
InputOption::VALUE_NONE,
37+
'Only show diff of "composer.json" changes, do not write the file'
38+
);
39+
}
40+
41+
protected function execute(InputInterface $input, OutputInterface $output): int
42+
{
43+
$symfonyStyle = new SymfonyStyle($input, $output);
44+
$isDryRun = (bool) $input->getOption('dry-run');
45+
46+
$symfonyStyle->writeln('<fg=green>Analyzing "/vendor/composer/installed.json" for versions</>');
47+
48+
// load composer.json and replace versions in "require" and "require-dev",
49+
$composerJsonFilePath = getcwd() . '/composer.json';
50+
51+
Assert::fileExists($composerJsonFilePath);
52+
$composerJsonContents = FileSystem::read($composerJsonFilePath);
53+
54+
$raiseToInstalledResult = $this->raiseToInstalledComposerProcessor->process($composerJsonContents);
55+
56+
$changedPackages = $raiseToInstalledResult->getChangedPackageVersions();
57+
if ($changedPackages === []) {
58+
$symfonyStyle->success('No changes made to "composer.json"');
59+
return self::SUCCESS;
60+
}
61+
62+
if ($isDryRun === false) {
63+
$changedComposerJsonContents = $raiseToInstalledResult->getComposerJsonContents();
64+
FileSystem::write($composerJsonFilePath, $changedComposerJsonContents . PHP_EOL, null);
65+
}
66+
67+
$symfonyStyle->success(sprintf(
68+
'%d package%s %s changed to installed versions.%s%s "composer update --lock" to update "composer.lock" hash',
69+
count($changedPackages),
70+
count($changedPackages) === 1 ? '' : 's',
71+
$isDryRun ? 'would be (is "--dry-run")' : 'were updated',
72+
PHP_EOL,
73+
$isDryRun ? 'Then you would run' : 'Now run',
74+
));
75+
76+
foreach ($changedPackages as $changedPackage) {
77+
$symfonyStyle->writeln(sprintf(
78+
' * <fg=green>%s</> (<fg=yellow>%s</> => <fg=yellow>%s</>)',
79+
$changedPackage->getPackageName(),
80+
$changedPackage->getOldVersion(),
81+
$changedPackage->getNewVersion()
82+
));
83+
}
84+
85+
$symfonyStyle->newLine();
86+
87+
return self::SUCCESS;
88+
}
89+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Jack\Composer;
6+
7+
use Rector\Jack\Utils\JsonFileLoader;
8+
use Webmozart\Assert\Assert;
9+
10+
final class InstalledVersionResolver
11+
{
12+
/**
13+
* @return array<string, string>
14+
*/
15+
public function resolve(): array
16+
{
17+
$installedJsonFilePath = getcwd() . '/vendor/composer/installed.json';
18+
19+
$installedJson = JsonFileLoader::loadFileToJson($installedJsonFilePath);
20+
Assert::keyExists($installedJson, 'packages');
21+
22+
$installedPackagesToVersions = [];
23+
foreach ($installedJson['packages'] as $installedPackage) {
24+
$packageName = $installedPackage['name'];
25+
$packageVersion = $installedPackage['version'];
26+
27+
$installedPackagesToVersions[$packageName] = $packageVersion;
28+
}
29+
30+
return $installedPackagesToVersions;
31+
}
32+
}

src/Composer/VersionComparator.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Rector\Jack\Composer;
4+
5+
final class VersionComparator
6+
{
7+
public static function areAndMinorVersionsEqual(string $firstVersion, string $secondVersion): bool
8+
{
9+
[$firstMajor, $firstMinor] = explode('.', $firstVersion);
10+
[$secondMajor, $secondMinor] = explode('.', $secondVersion);
11+
12+
// if major and minor are equal, we can skip the update
13+
return $firstMajor === $secondMajor && $firstMinor === $secondMinor;
14+
}
15+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Jack\ComposerProcessor;
6+
7+
use Rector\Jack\Composer\NextVersionResolver;
8+
use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater;
9+
use Rector\Jack\ValueObject\ChangedPackageVersion;
10+
use Rector\Jack\ValueObject\ComposerProcessorResult\ChangedPackageVersionsResult;
11+
use Rector\Jack\ValueObject\OutdatedComposer;
12+
13+
/**
14+
* @see \Rector\Jack\Tests\ComposerProcessor\OpenVersionsComposerProcessor\OpenVersionsComposerProcessorTest
15+
*/
16+
final class OpenVersionsComposerProcessor
17+
{
18+
public function __construct(
19+
private NextVersionResolver $nextVersionResolver
20+
) {
21+
}
22+
23+
public function process(
24+
string $composerJsonContents,
25+
OutdatedComposer $outdatedComposer,
26+
int $limit,
27+
bool $onlyDev,
28+
?string $packagePrefix
29+
): ChangedPackageVersionsResult {
30+
$outdatedPackages = $outdatedComposer->getPackagesShuffled($onlyDev, $packagePrefix);
31+
32+
$openedPackages = [];
33+
34+
foreach ($outdatedPackages as $outdatedPackage) {
35+
$composerVersion = $outdatedPackage->getComposerVersion();
36+
37+
// already filled with open version
38+
if (str_contains($composerVersion, '|')) {
39+
continue;
40+
}
41+
42+
// convert composer version to next version
43+
$nextVersion = $this->nextVersionResolver->resolve($outdatedPackage->getName(), $composerVersion);
44+
$openedVersion = $composerVersion . '|' . $nextVersion;
45+
46+
// replace using regex, to keep original composer.json format
47+
$composerJsonContents = ComposerJsonPackageVersionUpdater::update(
48+
$composerJsonContents,
49+
$outdatedPackage->getName(),
50+
$openedVersion
51+
);
52+
53+
$openedPackages[] = new ChangedPackageVersion(
54+
$outdatedPackage->getName(),
55+
$composerVersion,
56+
$openedVersion,
57+
);
58+
59+
if (count($openedPackages) >= $limit) {
60+
// we've reached the limit, so we can stop
61+
break;
62+
}
63+
}
64+
65+
return new ChangedPackageVersionsResult($composerJsonContents, $openedPackages);
66+
}
67+
}

0 commit comments

Comments
 (0)