Skip to content

Commit bd76867

Browse files
committed
cs
1 parent 5c734a7 commit bd76867

File tree

9 files changed

+310
-114
lines changed

9 files changed

+310
-114
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.13",
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/RaiseToInstalledCommand.php

Lines changed: 38 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@
44

55
namespace Rector\Jack\Command;
66

7-
use Composer\Semver\Comparator;
8-
use Composer\Semver\VersionParser;
97
use Nette\Utils\FileSystem;
108
use Nette\Utils\Json;
11-
use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater;
12-
use Rector\Jack\Utils\JsonFileLoader;
9+
use Rector\Jack\ComposerProcessor\RaiseToInstalledComposerProcessor;
1310
use Symfony\Component\Console\Command\Command;
1411
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
1513
use Symfony\Component\Console\Output\OutputInterface;
1614
use Symfony\Component\Console\Style\SymfonyStyle;
1715
use Webmozart\Assert\Assert;
1816

1917
final class RaiseToInstalledCommand extends Command
2018
{
2119
public function __construct(
22-
private readonly VersionParser $versionParser
20+
private readonly RaiseToInstalledComposerProcessor $raiseToInstalledComposerProcessor,
2321
) {
2422
parent::__construct();
2523
}
@@ -32,133 +30,60 @@ protected function configure(): void
3230
'Raise your version in "composer.json" to installed one to get the latest version available in any composer update'
3331
);
3432

35-
// @todo add dry-run mode
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+
);
3639
}
3740

3841
protected function execute(InputInterface $input, OutputInterface $output): int
3942
{
4043
$symfonyStyle = new SymfonyStyle($input, $output);
44+
$isDryRun = (bool) $input->getOption('dry-run');
4145

4246
$symfonyStyle->writeln('<fg=green>Analyzing "/vendor/composer/installed.json" for versions</>');
4347

44-
$installedPackagesToVersions = $this->resolveInstalledPackagesToVersions();
45-
4648
// load composer.json and replace versions in "require" and "require-dev",
4749
$composerJsonFilePath = getcwd() . '/composer.json';
4850

4951
Assert::fileExists($composerJsonFilePath);
5052
$composerJsonContents = FileSystem::read($composerJsonFilePath);
51-
$composerJson = Json::decode($composerJsonContents, true);
52-
53-
$hasChanged = false;
54-
55-
// iterate require and require-dev sections and check if installed version is newer one than in composer.json
56-
// if so, replace it
57-
foreach ($composerJson['require'] ?? [] as $packageName => $packageVersion) {
58-
if (! isset($installedPackagesToVersions[$packageName])) {
59-
continue;
60-
}
61-
62-
$installedVersion = $installedPackagesToVersions[$packageName];
63-
64-
// special case for unions
65-
if (str_contains((string) $packageVersion, '|')) {
66-
$passingVersionKeys = [];
67-
68-
$unionPackageVersions = explode('|', (string) $packageVersion);
69-
foreach ($unionPackageVersions as $key => $unionPackageVersion) {
70-
$unionPackageConstraint = $this->versionParser->parseConstraints($unionPackageVersion);
71-
72-
if (Comparator::greaterThanOrEqualTo(
73-
$installedVersion,
74-
$unionPackageConstraint->getLowerBound()
75-
->getVersion()
76-
)) {
77-
$passingVersionKeys[] = $key;
78-
}
79-
}
80-
81-
// nothing we can do, as lower union version is passing
82-
if ($passingVersionKeys === [0]) {
83-
continue;
84-
}
85-
86-
// higher version is meet, let's drop the lower one
87-
if ($passingVersionKeys === [0, 1]) {
88-
$newPackageVersion = $unionPackageVersions[1];
89-
90-
$composerJsonContents = ComposerJsonPackageVersionUpdater::update(
91-
$composerJsonContents,
92-
$packageName,
93-
$newPackageVersion
94-
);
95-
96-
$hasChanged = true;
97-
continue;
98-
}
99-
}
100-
101-
$normalizedInstalledVersion = $this->versionParser->normalize($installedVersion);
102-
$installedPackageConstraint = $this->versionParser->parseConstraints($packageVersion);
103-
104-
$normalizedConstraintVersion = $this->versionParser->normalize(
105-
$installedPackageConstraint->getLowerBound()
106-
->getVersion()
107-
);
108-
109-
// remove "-dev" suffix
110-
$normalizedConstraintVersion = str_replace('-dev', '', $normalizedConstraintVersion);
111-
112-
// all equal
113-
if ($normalizedConstraintVersion === $normalizedInstalledVersion) {
114-
continue;
115-
}
116-
117-
[$major, $minor, $patch] = explode('.', $normalizedInstalledVersion);
118-
119-
$newRequiredVersion = sprintf('^%s.%s', $major, $minor);
120-
121-
// lets update
122-
$composerJsonContents = ComposerJsonPackageVersionUpdater::update(
123-
$composerJsonContents,
124-
$packageName,
125-
$newRequiredVersion
126-
);
127-
128-
$hasChanged = true;
129-
continue;
130-
// focus on minor only
131-
// or on patch in case of 0.*
132-
}
13353

134-
if ($hasChanged) {
135-
$symfonyStyle->success('Updating "composer.json" with installed versions');
136-
FileSystem::write($composerJsonFilePath, $composerJsonContents, null);
137-
} else {
54+
$raiseToInstalledResult = $this->raiseToInstalledComposerProcessor->process($composerJsonContents);
55+
56+
$changedPackages = $raiseToInstalledResult->getChangedPackageVersions();
57+
if ($changedPackages === []) {
13858
$symfonyStyle->success('No changes made to "composer.json"');
59+
return self::SUCCESS;
13960
}
14061

141-
return self::SUCCESS;
142-
}
143-
144-
/**
145-
* @return array<string, string>
146-
*/
147-
private function resolveInstalledPackagesToVersions(): array
148-
{
149-
$installedJsonFilePath = getcwd() . '/vendor/composer/installed.json';
150-
151-
$installedJson = JsonFileLoader::loadFileToJson($installedJsonFilePath);
152-
Assert::keyExists($installedJson, 'packages');
153-
154-
$installedPackagesToVersions = [];
155-
foreach ($installedJson['packages'] as $installedPackage) {
156-
$packageName = $installedPackage['name'];
157-
$packageVersion = $installedPackage['version'];
62+
if ($isDryRun === false) {
63+
$changedComposerJsonContents = $raiseToInstalledResult->getComposerJsonContents();
64+
FileSystem::write($composerJsonFilePath, $changedComposerJsonContents . PHP_EOL, null);
65+
}
15866

159-
$installedPackagesToVersions[$packageName] = $packageVersion;
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+
));
16083
}
16184

162-
return $installedPackagesToVersions;
85+
$symfonyStyle->newLine();
86+
87+
return self::SUCCESS;
16388
}
16489
}
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: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace Rector\Jack\ComposerProcessor;
4+
5+
use Composer\Semver\Comparator;
6+
use Composer\Semver\VersionParser;
7+
use Nette\Utils\Json;
8+
use Rector\Jack\Composer\InstalledVersionResolver;
9+
use Rector\Jack\Composer\VersionComparator;
10+
use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater;
11+
use Rector\Jack\ValueObject\ChangedPackageVersion;
12+
use Rector\Jack\ValueObject\ComposerProcessorResult\RaiseToInstalledResult;
13+
14+
/**
15+
* @see \ComposerProcessor\RaiseToInstalledComposerProcessor\RaiseToInstalledComposerProcessorTest
16+
*/
17+
final class RaiseToInstalledComposerProcessor
18+
{
19+
public function __construct(
20+
private readonly VersionParser $versionParser,
21+
private readonly InstalledVersionResolver $installedVersionResolver,
22+
) {
23+
}
24+
25+
public function process(string $composerJsonContents): RaiseToInstalledResult
26+
{
27+
$installedPackagesToVersions = $this->installedVersionResolver->resolve();
28+
29+
$composerJson = Json::decode($composerJsonContents, true);
30+
31+
$changedPackageVersions = [];
32+
33+
// iterate require and require-dev sections and check if installed version is newer one than in composer.json
34+
// if so, replace it
35+
foreach ($composerJson['require'] ?? [] as $packageName => $packageVersion) {
36+
if (! isset($installedPackagesToVersions[$packageName])) {
37+
continue;
38+
}
39+
40+
$installedVersion = $installedPackagesToVersions[$packageName];
41+
42+
// special case for unions
43+
if (str_contains((string) $packageVersion, '|')) {
44+
$passingVersionKeys = [];
45+
46+
$unionPackageVersions = explode('|', (string) $packageVersion);
47+
foreach ($unionPackageVersions as $key => $unionPackageVersion) {
48+
$unionPackageConstraint = $this->versionParser->parseConstraints($unionPackageVersion);
49+
50+
if (Comparator::greaterThanOrEqualTo(
51+
$installedVersion,
52+
$unionPackageConstraint->getLowerBound()
53+
->getVersion()
54+
)) {
55+
$passingVersionKeys[] = $key;
56+
}
57+
}
58+
59+
// nothing we can do, as lower union version is passing
60+
if ($passingVersionKeys === [0]) {
61+
continue;
62+
}
63+
64+
// higher version is meet, let's drop the lower one
65+
if ($passingVersionKeys === [0, 1]) {
66+
$newPackageVersion = $unionPackageVersions[1];
67+
68+
$composerJsonContents = ComposerJsonPackageVersionUpdater::update(
69+
$composerJsonContents,
70+
$packageName,
71+
$newPackageVersion
72+
);
73+
74+
$changedPackageVersions[] = new ChangedPackageVersion(
75+
$packageName,
76+
$packageVersion,
77+
$newPackageVersion
78+
);
79+
continue;
80+
}
81+
}
82+
83+
$normalizedInstalledVersion = $this->versionParser->normalize($installedVersion);
84+
$installedPackageConstraint = $this->versionParser->parseConstraints($packageVersion);
85+
86+
$normalizedConstraintVersion = $this->versionParser->normalize(
87+
$installedPackageConstraint->getLowerBound()
88+
->getVersion()
89+
);
90+
91+
// remove "-dev" suffix
92+
$normalizedConstraintVersion = str_replace('-dev', '', $normalizedConstraintVersion);
93+
94+
// are major + minor equal?
95+
if (VersionComparator::areAndMinorVersionsEqual(
96+
$normalizedConstraintVersion,
97+
$normalizedInstalledVersion
98+
)) {
99+
continue;
100+
}
101+
102+
[$major, $minor, $patch] = explode('.', $normalizedInstalledVersion);
103+
104+
$newRequiredVersion = sprintf('^%s.%s', $major, $minor);
105+
106+
// lets update
107+
$composerJsonContents = ComposerJsonPackageVersionUpdater::update(
108+
$composerJsonContents,
109+
$packageName,
110+
$newRequiredVersion
111+
);
112+
113+
// focus on minor only
114+
// or on patch in case of 0.*
115+
$changedPackageVersions[] = new ChangedPackageVersion($packageName, $packageVersion, $newRequiredVersion);
116+
}
117+
118+
return new RaiseToInstalledResult($composerJsonContents, $changedPackageVersions);
119+
}
120+
}

0 commit comments

Comments
 (0)