Skip to content

Commit 5c734a7

Browse files
committed
[command] kick of raise to installed
1 parent faa98c2 commit 5c734a7

File tree

5 files changed

+216
-8
lines changed

5 files changed

+216
-8
lines changed

composer.json

Lines changed: 1 addition & 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.13",
1212
"nette/utils": "^4.0",
1313
"symfony/console": "^6.4",
1414
"symfony/finder": "^7.2",

src/Command/OpenVersionsCommand.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
use Nette\Utils\FileSystem;
88
use Nette\Utils\Json;
9-
use Nette\Utils\Strings;
109
use Rector\Jack\Composer\ComposerOutdatedResponseProvider;
1110
use Rector\Jack\Composer\NextVersionResolver;
1211
use Rector\Jack\Enum\ComposerKey;
12+
use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater;
1313
use Rector\Jack\OutdatedComposerFactory;
1414
use Symfony\Component\Console\Command\Command;
1515
use Symfony\Component\Console\Input\InputInterface;
@@ -109,12 +109,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
109109
$openedVersion = $composerVersion . '|' . $nextVersion;
110110

111111
// replace using regex, to keep original composer.json format
112-
$composerJsonContents = Strings::replace(
112+
$composerJsonContents = ComposerJsonPackageVersionUpdater::update(
113113
$composerJsonContents,
114-
// find
115-
sprintf('#"%s": "(.*?)"#', $outdatedPackage->getName()),
116-
// replace
117-
sprintf('"%s": "%s"', $outdatedPackage->getName(), $openedVersion)
114+
$outdatedPackage->getName(),
115+
$openedVersion
118116
);
119117

120118
$symfonyStyle->writeln(sprintf(
@@ -132,7 +130,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
132130

133131
if ($isDryRun === false) {
134132
// update composer.json file, only if no --dry-run
135-
FileSystem::write($composerJsonFilePath, $composerJsonContents . PHP_EOL);
133+
FileSystem::write($composerJsonFilePath, $composerJsonContents . PHP_EOL, null);
136134
}
137135

138136
$symfonyStyle->success(
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Jack\Command;
6+
7+
use Composer\Semver\Comparator;
8+
use Composer\Semver\VersionParser;
9+
use Nette\Utils\FileSystem;
10+
use Nette\Utils\Json;
11+
use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater;
12+
use Rector\Jack\Utils\JsonFileLoader;
13+
use Symfony\Component\Console\Command\Command;
14+
use Symfony\Component\Console\Input\InputInterface;
15+
use Symfony\Component\Console\Output\OutputInterface;
16+
use Symfony\Component\Console\Style\SymfonyStyle;
17+
use Webmozart\Assert\Assert;
18+
19+
final class RaiseToInstalledCommand extends Command
20+
{
21+
public function __construct(
22+
private readonly VersionParser $versionParser
23+
) {
24+
parent::__construct();
25+
}
26+
27+
protected function configure(): void
28+
{
29+
$this->setName('raise-to-lock');
30+
31+
$this->setDescription(
32+
'Raise your version in "composer.json" to installed one to get the latest version available in any composer update'
33+
);
34+
35+
// @todo add dry-run mode
36+
}
37+
38+
protected function execute(InputInterface $input, OutputInterface $output): int
39+
{
40+
$symfonyStyle = new SymfonyStyle($input, $output);
41+
42+
$symfonyStyle->writeln('<fg=green>Analyzing "/vendor/composer/installed.json" for versions</>');
43+
44+
$installedPackagesToVersions = $this->resolveInstalledPackagesToVersions();
45+
46+
// load composer.json and replace versions in "require" and "require-dev",
47+
$composerJsonFilePath = getcwd() . '/composer.json';
48+
49+
Assert::fileExists($composerJsonFilePath);
50+
$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+
}
133+
134+
if ($hasChanged) {
135+
$symfonyStyle->success('Updating "composer.json" with installed versions');
136+
FileSystem::write($composerJsonFilePath, $composerJsonContents, null);
137+
} else {
138+
$symfonyStyle->success('No changes made to "composer.json"');
139+
}
140+
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'];
158+
159+
$installedPackagesToVersions[$packageName] = $packageVersion;
160+
}
161+
162+
return $installedPackagesToVersions;
163+
}
164+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Jack\FileSystem;
6+
7+
use Nette\Utils\Strings;
8+
9+
final class ComposerJsonPackageVersionUpdater
10+
{
11+
public static function update(string $composerJsonContents, string $packageName, string $newVersion): string
12+
{
13+
// replace using regex, to keep original composer.json format
14+
return Strings::replace(
15+
$composerJsonContents,
16+
// find
17+
sprintf('#"%s": "(.*?)"#', $packageName),
18+
// replace
19+
sprintf('"%s": "%s"', $packageName, $newVersion)
20+
);
21+
}
22+
}

src/Utils/JsonFileLoader.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Jack\Utils;
6+
7+
use Nette\Utils\FileSystem;
8+
use Nette\Utils\Json;
9+
use Webmozart\Assert\Assert;
10+
11+
final class JsonFileLoader
12+
{
13+
/**
14+
* @return array<string, mixed>
15+
*/
16+
public static function loadFileToJson(string $filePath): array
17+
{
18+
Assert::fileExists($filePath);
19+
20+
$fileContents = FileSystem::read($filePath);
21+
22+
return Json::decode($fileContents, true);
23+
}
24+
}

0 commit comments

Comments
 (0)