From 5c734a7215f001623523e31f803593deb1cd7693 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 18 May 2025 12:25:37 +0200 Subject: [PATCH 1/3] [command] kick of raise to installed --- composer.json | 2 +- src/Command/OpenVersionsCommand.php | 12 +- src/Command/RaiseToInstalledCommand.php | 164 ++++++++++++++++++ .../ComposerJsonPackageVersionUpdater.php | 22 +++ src/Utils/JsonFileLoader.php | 24 +++ 5 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 src/Command/RaiseToInstalledCommand.php create mode 100644 src/FileSystem/ComposerJsonPackageVersionUpdater.php create mode 100644 src/Utils/JsonFileLoader.php diff --git a/composer.json b/composer.json index 1bd9901..e90213c 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "require": { "php": ">=8.2", "composer/semver": "^3.4", - "illuminate/container": "^12.0", + "illuminate/container": "^12.13", "nette/utils": "^4.0", "symfony/console": "^6.4", "symfony/finder": "^7.2", diff --git a/src/Command/OpenVersionsCommand.php b/src/Command/OpenVersionsCommand.php index 29db5c9..de79eb8 100644 --- a/src/Command/OpenVersionsCommand.php +++ b/src/Command/OpenVersionsCommand.php @@ -6,10 +6,10 @@ use Nette\Utils\FileSystem; use Nette\Utils\Json; -use Nette\Utils\Strings; use Rector\Jack\Composer\ComposerOutdatedResponseProvider; use Rector\Jack\Composer\NextVersionResolver; use Rector\Jack\Enum\ComposerKey; +use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater; use Rector\Jack\OutdatedComposerFactory; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -109,12 +109,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $openedVersion = $composerVersion . '|' . $nextVersion; // replace using regex, to keep original composer.json format - $composerJsonContents = Strings::replace( + $composerJsonContents = ComposerJsonPackageVersionUpdater::update( $composerJsonContents, - // find - sprintf('#"%s": "(.*?)"#', $outdatedPackage->getName()), - // replace - sprintf('"%s": "%s"', $outdatedPackage->getName(), $openedVersion) + $outdatedPackage->getName(), + $openedVersion ); $symfonyStyle->writeln(sprintf( @@ -132,7 +130,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($isDryRun === false) { // update composer.json file, only if no --dry-run - FileSystem::write($composerJsonFilePath, $composerJsonContents . PHP_EOL); + FileSystem::write($composerJsonFilePath, $composerJsonContents . PHP_EOL, null); } $symfonyStyle->success( diff --git a/src/Command/RaiseToInstalledCommand.php b/src/Command/RaiseToInstalledCommand.php new file mode 100644 index 0000000..514a501 --- /dev/null +++ b/src/Command/RaiseToInstalledCommand.php @@ -0,0 +1,164 @@ +setName('raise-to-lock'); + + $this->setDescription( + 'Raise your version in "composer.json" to installed one to get the latest version available in any composer update' + ); + + // @todo add dry-run mode + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $symfonyStyle = new SymfonyStyle($input, $output); + + $symfonyStyle->writeln('Analyzing "/vendor/composer/installed.json" for versions'); + + $installedPackagesToVersions = $this->resolveInstalledPackagesToVersions(); + + // load composer.json and replace versions in "require" and "require-dev", + $composerJsonFilePath = getcwd() . '/composer.json'; + + Assert::fileExists($composerJsonFilePath); + $composerJsonContents = FileSystem::read($composerJsonFilePath); + $composerJson = Json::decode($composerJsonContents, true); + + $hasChanged = false; + + // iterate require and require-dev sections and check if installed version is newer one than in composer.json + // if so, replace it + foreach ($composerJson['require'] ?? [] as $packageName => $packageVersion) { + if (! isset($installedPackagesToVersions[$packageName])) { + continue; + } + + $installedVersion = $installedPackagesToVersions[$packageName]; + + // special case for unions + if (str_contains((string) $packageVersion, '|')) { + $passingVersionKeys = []; + + $unionPackageVersions = explode('|', (string) $packageVersion); + foreach ($unionPackageVersions as $key => $unionPackageVersion) { + $unionPackageConstraint = $this->versionParser->parseConstraints($unionPackageVersion); + + if (Comparator::greaterThanOrEqualTo( + $installedVersion, + $unionPackageConstraint->getLowerBound() + ->getVersion() + )) { + $passingVersionKeys[] = $key; + } + } + + // nothing we can do, as lower union version is passing + if ($passingVersionKeys === [0]) { + continue; + } + + // higher version is meet, let's drop the lower one + if ($passingVersionKeys === [0, 1]) { + $newPackageVersion = $unionPackageVersions[1]; + + $composerJsonContents = ComposerJsonPackageVersionUpdater::update( + $composerJsonContents, + $packageName, + $newPackageVersion + ); + + $hasChanged = true; + continue; + } + } + + $normalizedInstalledVersion = $this->versionParser->normalize($installedVersion); + $installedPackageConstraint = $this->versionParser->parseConstraints($packageVersion); + + $normalizedConstraintVersion = $this->versionParser->normalize( + $installedPackageConstraint->getLowerBound() + ->getVersion() + ); + + // remove "-dev" suffix + $normalizedConstraintVersion = str_replace('-dev', '', $normalizedConstraintVersion); + + // all equal + if ($normalizedConstraintVersion === $normalizedInstalledVersion) { + continue; + } + + [$major, $minor, $patch] = explode('.', $normalizedInstalledVersion); + + $newRequiredVersion = sprintf('^%s.%s', $major, $minor); + + // lets update + $composerJsonContents = ComposerJsonPackageVersionUpdater::update( + $composerJsonContents, + $packageName, + $newRequiredVersion + ); + + $hasChanged = true; + continue; + // focus on minor only + // or on patch in case of 0.* + } + + if ($hasChanged) { + $symfonyStyle->success('Updating "composer.json" with installed versions'); + FileSystem::write($composerJsonFilePath, $composerJsonContents, null); + } else { + $symfonyStyle->success('No changes made to "composer.json"'); + } + + return self::SUCCESS; + } + + /** + * @return array + */ + private function resolveInstalledPackagesToVersions(): array + { + $installedJsonFilePath = getcwd() . '/vendor/composer/installed.json'; + + $installedJson = JsonFileLoader::loadFileToJson($installedJsonFilePath); + Assert::keyExists($installedJson, 'packages'); + + $installedPackagesToVersions = []; + foreach ($installedJson['packages'] as $installedPackage) { + $packageName = $installedPackage['name']; + $packageVersion = $installedPackage['version']; + + $installedPackagesToVersions[$packageName] = $packageVersion; + } + + return $installedPackagesToVersions; + } +} diff --git a/src/FileSystem/ComposerJsonPackageVersionUpdater.php b/src/FileSystem/ComposerJsonPackageVersionUpdater.php new file mode 100644 index 0000000..7940a6d --- /dev/null +++ b/src/FileSystem/ComposerJsonPackageVersionUpdater.php @@ -0,0 +1,22 @@ + + */ + public static function loadFileToJson(string $filePath): array + { + Assert::fileExists($filePath); + + $fileContents = FileSystem::read($filePath); + + return Json::decode($fileContents, true); + } +} From 969a5068ddbcb71107ad364a49403780ccfe86ff Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 19 May 2025 10:41:29 +0200 Subject: [PATCH 2/3] cs --- composer.json | 3 +- src/Command/RaiseToInstalledCommand.php | 151 +++++------------- src/Composer/InstalledVersionResolver.php | 32 ++++ src/Composer/VersionComparator.php | 15 ++ .../RaiseToInstalledComposerProcessor.php | 120 ++++++++++++++ src/ValueObject/ChangedPackageVersion.php | 31 ++++ .../RaiseToInstalledResult.php | 34 ++++ .../Fixture/some-outdated-composer.json | 5 + .../RaiseToInstalledComposerProcessorTest.php | 33 ++++ 9 files changed, 310 insertions(+), 114 deletions(-) create mode 100644 src/Composer/InstalledVersionResolver.php create mode 100644 src/Composer/VersionComparator.php create mode 100644 src/ComposerProcessor/RaiseToInstalledComposerProcessor.php create mode 100644 src/ValueObject/ChangedPackageVersion.php create mode 100644 src/ValueObject/ComposerProcessorResult/RaiseToInstalledResult.php create mode 100644 tests/ComposerProcessor/RaiseToInstalledComposerProcessor/Fixture/some-outdated-composer.json create mode 100644 tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php diff --git a/composer.json b/composer.json index e90213c..6ee6316 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "require": { "php": ">=8.2", "composer/semver": "^3.4", - "illuminate/container": "^12.13", + "illuminate/container": "^12.14", "nette/utils": "^4.0", "symfony/console": "^6.4", "symfony/finder": "^7.2", @@ -67,3 +67,4 @@ + diff --git a/src/Command/RaiseToInstalledCommand.php b/src/Command/RaiseToInstalledCommand.php index 514a501..045f9af 100644 --- a/src/Command/RaiseToInstalledCommand.php +++ b/src/Command/RaiseToInstalledCommand.php @@ -4,14 +4,12 @@ namespace Rector\Jack\Command; -use Composer\Semver\Comparator; -use Composer\Semver\VersionParser; use Nette\Utils\FileSystem; use Nette\Utils\Json; -use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater; -use Rector\Jack\Utils\JsonFileLoader; +use Rector\Jack\ComposerProcessor\RaiseToInstalledComposerProcessor; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Webmozart\Assert\Assert; @@ -19,7 +17,7 @@ final class RaiseToInstalledCommand extends Command { public function __construct( - private readonly VersionParser $versionParser + private readonly RaiseToInstalledComposerProcessor $raiseToInstalledComposerProcessor, ) { parent::__construct(); } @@ -32,133 +30,60 @@ protected function configure(): void 'Raise your version in "composer.json" to installed one to get the latest version available in any composer update' ); - // @todo add dry-run mode + $this->addOption( + 'dry-run', + null, + InputOption::VALUE_NONE, + 'Only show diff of "composer.json" changes, do not write the file' + ); } protected function execute(InputInterface $input, OutputInterface $output): int { $symfonyStyle = new SymfonyStyle($input, $output); + $isDryRun = (bool) $input->getOption('dry-run'); $symfonyStyle->writeln('Analyzing "/vendor/composer/installed.json" for versions'); - $installedPackagesToVersions = $this->resolveInstalledPackagesToVersions(); - // load composer.json and replace versions in "require" and "require-dev", $composerJsonFilePath = getcwd() . '/composer.json'; Assert::fileExists($composerJsonFilePath); $composerJsonContents = FileSystem::read($composerJsonFilePath); - $composerJson = Json::decode($composerJsonContents, true); - - $hasChanged = false; - - // iterate require and require-dev sections and check if installed version is newer one than in composer.json - // if so, replace it - foreach ($composerJson['require'] ?? [] as $packageName => $packageVersion) { - if (! isset($installedPackagesToVersions[$packageName])) { - continue; - } - - $installedVersion = $installedPackagesToVersions[$packageName]; - - // special case for unions - if (str_contains((string) $packageVersion, '|')) { - $passingVersionKeys = []; - - $unionPackageVersions = explode('|', (string) $packageVersion); - foreach ($unionPackageVersions as $key => $unionPackageVersion) { - $unionPackageConstraint = $this->versionParser->parseConstraints($unionPackageVersion); - - if (Comparator::greaterThanOrEqualTo( - $installedVersion, - $unionPackageConstraint->getLowerBound() - ->getVersion() - )) { - $passingVersionKeys[] = $key; - } - } - - // nothing we can do, as lower union version is passing - if ($passingVersionKeys === [0]) { - continue; - } - - // higher version is meet, let's drop the lower one - if ($passingVersionKeys === [0, 1]) { - $newPackageVersion = $unionPackageVersions[1]; - - $composerJsonContents = ComposerJsonPackageVersionUpdater::update( - $composerJsonContents, - $packageName, - $newPackageVersion - ); - - $hasChanged = true; - continue; - } - } - - $normalizedInstalledVersion = $this->versionParser->normalize($installedVersion); - $installedPackageConstraint = $this->versionParser->parseConstraints($packageVersion); - - $normalizedConstraintVersion = $this->versionParser->normalize( - $installedPackageConstraint->getLowerBound() - ->getVersion() - ); - - // remove "-dev" suffix - $normalizedConstraintVersion = str_replace('-dev', '', $normalizedConstraintVersion); - - // all equal - if ($normalizedConstraintVersion === $normalizedInstalledVersion) { - continue; - } - - [$major, $minor, $patch] = explode('.', $normalizedInstalledVersion); - - $newRequiredVersion = sprintf('^%s.%s', $major, $minor); - - // lets update - $composerJsonContents = ComposerJsonPackageVersionUpdater::update( - $composerJsonContents, - $packageName, - $newRequiredVersion - ); - - $hasChanged = true; - continue; - // focus on minor only - // or on patch in case of 0.* - } - if ($hasChanged) { - $symfonyStyle->success('Updating "composer.json" with installed versions'); - FileSystem::write($composerJsonFilePath, $composerJsonContents, null); - } else { + $raiseToInstalledResult = $this->raiseToInstalledComposerProcessor->process($composerJsonContents); + + $changedPackages = $raiseToInstalledResult->getChangedPackageVersions(); + if ($changedPackages === []) { $symfonyStyle->success('No changes made to "composer.json"'); + return self::SUCCESS; } - return self::SUCCESS; - } - - /** - * @return array - */ - private function resolveInstalledPackagesToVersions(): array - { - $installedJsonFilePath = getcwd() . '/vendor/composer/installed.json'; - - $installedJson = JsonFileLoader::loadFileToJson($installedJsonFilePath); - Assert::keyExists($installedJson, 'packages'); - - $installedPackagesToVersions = []; - foreach ($installedJson['packages'] as $installedPackage) { - $packageName = $installedPackage['name']; - $packageVersion = $installedPackage['version']; + if ($isDryRun === false) { + $changedComposerJsonContents = $raiseToInstalledResult->getComposerJsonContents(); + FileSystem::write($composerJsonFilePath, $changedComposerJsonContents . PHP_EOL, null); + } - $installedPackagesToVersions[$packageName] = $packageVersion; + $symfonyStyle->success(sprintf( + '%d package%s %s changed to installed versions.%s%s "composer update --lock" to update "composer.lock" hash', + count($changedPackages), + count($changedPackages) === 1 ? '' : 's', + $isDryRun ? 'would be (is "--dry-run")' : 'were updated', + PHP_EOL, + $isDryRun ? 'Then you would run' : 'Now run', + )); + + foreach ($changedPackages as $changedPackage) { + $symfonyStyle->writeln(sprintf( + ' * %s (%s => %s)', + $changedPackage->getPackageName(), + $changedPackage->getOldVersion(), + $changedPackage->getNewVersion() + )); } - return $installedPackagesToVersions; + $symfonyStyle->newLine(); + + return self::SUCCESS; } } diff --git a/src/Composer/InstalledVersionResolver.php b/src/Composer/InstalledVersionResolver.php new file mode 100644 index 0000000..c6a0af0 --- /dev/null +++ b/src/Composer/InstalledVersionResolver.php @@ -0,0 +1,32 @@ + + */ + public function resolve(): array + { + $installedJsonFilePath = getcwd() . '/vendor/composer/installed.json'; + + $installedJson = JsonFileLoader::loadFileToJson($installedJsonFilePath); + Assert::keyExists($installedJson, 'packages'); + + $installedPackagesToVersions = []; + foreach ($installedJson['packages'] as $installedPackage) { + $packageName = $installedPackage['name']; + $packageVersion = $installedPackage['version']; + + $installedPackagesToVersions[$packageName] = $packageVersion; + } + + return $installedPackagesToVersions; + } +} diff --git a/src/Composer/VersionComparator.php b/src/Composer/VersionComparator.php new file mode 100644 index 0000000..ebf1632 --- /dev/null +++ b/src/Composer/VersionComparator.php @@ -0,0 +1,15 @@ +installedVersionResolver->resolve(); + + $composerJson = Json::decode($composerJsonContents, true); + + $changedPackageVersions = []; + + // iterate require and require-dev sections and check if installed version is newer one than in composer.json + // if so, replace it + foreach ($composerJson['require'] ?? [] as $packageName => $packageVersion) { + if (! isset($installedPackagesToVersions[$packageName])) { + continue; + } + + $installedVersion = $installedPackagesToVersions[$packageName]; + + // special case for unions + if (str_contains((string) $packageVersion, '|')) { + $passingVersionKeys = []; + + $unionPackageVersions = explode('|', (string) $packageVersion); + foreach ($unionPackageVersions as $key => $unionPackageVersion) { + $unionPackageConstraint = $this->versionParser->parseConstraints($unionPackageVersion); + + if (Comparator::greaterThanOrEqualTo( + $installedVersion, + $unionPackageConstraint->getLowerBound() + ->getVersion() + )) { + $passingVersionKeys[] = $key; + } + } + + // nothing we can do, as lower union version is passing + if ($passingVersionKeys === [0]) { + continue; + } + + // higher version is meet, let's drop the lower one + if ($passingVersionKeys === [0, 1]) { + $newPackageVersion = $unionPackageVersions[1]; + + $composerJsonContents = ComposerJsonPackageVersionUpdater::update( + $composerJsonContents, + $packageName, + $newPackageVersion + ); + + $changedPackageVersions[] = new ChangedPackageVersion( + $packageName, + $packageVersion, + $newPackageVersion + ); + continue; + } + } + + $normalizedInstalledVersion = $this->versionParser->normalize($installedVersion); + $installedPackageConstraint = $this->versionParser->parseConstraints($packageVersion); + + $normalizedConstraintVersion = $this->versionParser->normalize( + $installedPackageConstraint->getLowerBound() + ->getVersion() + ); + + // remove "-dev" suffix + $normalizedConstraintVersion = str_replace('-dev', '', $normalizedConstraintVersion); + + // are major + minor equal? + if (VersionComparator::areAndMinorVersionsEqual( + $normalizedConstraintVersion, + $normalizedInstalledVersion + )) { + continue; + } + + [$major, $minor, $patch] = explode('.', $normalizedInstalledVersion); + + $newRequiredVersion = sprintf('^%s.%s', $major, $minor); + + // lets update + $composerJsonContents = ComposerJsonPackageVersionUpdater::update( + $composerJsonContents, + $packageName, + $newRequiredVersion + ); + + // focus on minor only + // or on patch in case of 0.* + $changedPackageVersions[] = new ChangedPackageVersion($packageName, $packageVersion, $newRequiredVersion); + } + + return new RaiseToInstalledResult($composerJsonContents, $changedPackageVersions); + } +} diff --git a/src/ValueObject/ChangedPackageVersion.php b/src/ValueObject/ChangedPackageVersion.php new file mode 100644 index 0000000..54dabee --- /dev/null +++ b/src/ValueObject/ChangedPackageVersion.php @@ -0,0 +1,31 @@ +packageName; + } + + public function getOldVersion(): string + { + return $this->oldVersion; + } + + public function getNewVersion(): string + { + return $this->newVersion; + } +} diff --git a/src/ValueObject/ComposerProcessorResult/RaiseToInstalledResult.php b/src/ValueObject/ComposerProcessorResult/RaiseToInstalledResult.php new file mode 100644 index 0000000..0f14da1 --- /dev/null +++ b/src/ValueObject/ComposerProcessorResult/RaiseToInstalledResult.php @@ -0,0 +1,34 @@ +composerJsonContents; + } + + /** + * @return ChangedPackageVersion[] + */ + public function getChangedPackageVersions(): array + { + return $this->changedPackageVersions; + } +} diff --git a/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/Fixture/some-outdated-composer.json b/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/Fixture/some-outdated-composer.json new file mode 100644 index 0000000..0659019 --- /dev/null +++ b/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/Fixture/some-outdated-composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "illuminate/container": "^9.0" + } +} diff --git a/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php b/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php new file mode 100644 index 0000000..d394697 --- /dev/null +++ b/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php @@ -0,0 +1,33 @@ +make(RaiseToInstalledComposerProcessor::class); + $raiseToInstalledResult = $raiseToInstalledComposerProcessor->process($composerJsonContents); + + $this->assertCount(1, $raiseToInstalledResult->getChangedPackageVersions()); + $this->assertContainsOnlyInstancesOf( + ChangedPackageVersion::class, + $raiseToInstalledResult->getChangedPackageVersions() + ); + + $changedPackageVersion = $raiseToInstalledResult->getChangedPackageVersions()[0]; + + $this->assertSame('illuminate/container', $changedPackageVersion->getPackageName()); + $this->assertSame('^9.0', $changedPackageVersion->getOldVersion()); + $this->assertSame('^12.14', $changedPackageVersion->getNewVersion()); + } +} From c31455bf367885ee78b77a8812c930e046b40294 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 19 May 2025 12:58:11 +0200 Subject: [PATCH 3/3] decouple open logic to OpenVersionsComposerProcessor --- src/Command/OpenVersionsCommand.php | 59 ++++++---------- .../OpenVersionsComposerProcessor.php | 67 +++++++++++++++++++ .../RaiseToInstalledComposerProcessor.php | 8 ++- ...t.php => ChangedPackageVersionsResult.php} | 2 +- src/ValueObject/OutdatedComposer.php | 3 + .../Fixture/expected-opened-composer.json | 5 ++ .../Fixture/some-closed-composer.json | 5 ++ .../OpenVersionsComposerProcessorTest.php | 52 ++++++++++++++ .../RaiseToInstalledComposerProcessorTest.php | 14 +++- 9 files changed, 171 insertions(+), 44 deletions(-) create mode 100644 src/ComposerProcessor/OpenVersionsComposerProcessor.php rename src/ValueObject/ComposerProcessorResult/{RaiseToInstalledResult.php => ChangedPackageVersionsResult.php} (94%) create mode 100644 tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/expected-opened-composer.json create mode 100644 tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/some-closed-composer.json create mode 100644 tests/ComposerProcessor/OpenVersionsComposerProcessor/OpenVersionsComposerProcessorTest.php diff --git a/src/Command/OpenVersionsCommand.php b/src/Command/OpenVersionsCommand.php index de79eb8..1b6ab8d 100644 --- a/src/Command/OpenVersionsCommand.php +++ b/src/Command/OpenVersionsCommand.php @@ -7,9 +7,8 @@ use Nette\Utils\FileSystem; use Nette\Utils\Json; use Rector\Jack\Composer\ComposerOutdatedResponseProvider; -use Rector\Jack\Composer\NextVersionResolver; +use Rector\Jack\ComposerProcessor\OpenVersionsComposerProcessor; use Rector\Jack\Enum\ComposerKey; -use Rector\Jack\FileSystem\ComposerJsonPackageVersionUpdater; use Rector\Jack\OutdatedComposerFactory; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -20,9 +19,9 @@ final class OpenVersionsCommand extends Command { public function __construct( - private readonly NextVersionResolver $nextVersionResolver, private readonly OutdatedComposerFactory $outdatedComposerFactory, private readonly ComposerOutdatedResponseProvider $composerOutdatedResponseProvider, + private readonly OpenVersionsComposerProcessor $openVersionsComposerProcessor, ) { parent::__construct(); } @@ -93,56 +92,40 @@ protected function execute(InputInterface $input, OutputInterface $output): int $composerJsonContents = FileSystem::read($composerJsonFilePath); - $outdatedPackages = $outdatedComposer->getPackagesShuffled($onlyDev, $packagePrefix); - - $openedPackageCount = 0; - foreach ($outdatedPackages as $outdatedPackage) { - $composerVersion = $outdatedPackage->getComposerVersion(); - - // already filled with open version - if (str_contains($composerVersion, '|')) { - continue; - } - - // convert composer version to next version - $nextVersion = $this->nextVersionResolver->resolve($outdatedPackage->getName(), $composerVersion); - $openedVersion = $composerVersion . '|' . $nextVersion; - - // replace using regex, to keep original composer.json format - $composerJsonContents = ComposerJsonPackageVersionUpdater::update( - $composerJsonContents, - $outdatedPackage->getName(), - $openedVersion - ); - - $symfonyStyle->writeln(sprintf( - ' * Opened "%s" package to "%s" version', - $outdatedPackage->getName(), - $openedVersion - )); + $changedPackageVersionsResult = $this->openVersionsComposerProcessor->process( + $composerJsonContents, + $outdatedComposer, + $limit, + $onlyDev, + $packagePrefix + ); - ++$openedPackageCount; - if ($openedPackageCount >= $limit) { - // we've reached the limit, so we can stop - break; - } - } + $openedPackages = $changedPackageVersionsResult->getChangedPackageVersions(); + $changedComposerJson = $changedPackageVersionsResult->getComposerJsonContents(); if ($isDryRun === false) { // update composer.json file, only if no --dry-run - FileSystem::write($composerJsonFilePath, $composerJsonContents . PHP_EOL, null); + FileSystem::write($composerJsonFilePath, $changedComposerJson . PHP_EOL, null); } $symfonyStyle->success( sprintf( '%d packages %s opened up to the next nearest version.%s%s "composer update" to push versions up', - $openedPackageCount, + count($openedPackages), $isDryRun ? 'would be (is "--dry-run")' : 'were', PHP_EOL, $isDryRun ? 'Then you would run' : 'Now run' ) ); + foreach ($openedPackages as $openedPackage) { + $symfonyStyle->writeln(sprintf( + ' * Opened "%s" package to "%s" version', + $openedPackage->getPackageName(), + $openedPackage->getNewVersion() + )); + } + return self::SUCCESS; } } diff --git a/src/ComposerProcessor/OpenVersionsComposerProcessor.php b/src/ComposerProcessor/OpenVersionsComposerProcessor.php new file mode 100644 index 0000000..0135386 --- /dev/null +++ b/src/ComposerProcessor/OpenVersionsComposerProcessor.php @@ -0,0 +1,67 @@ +getPackagesShuffled($onlyDev, $packagePrefix); + + $openedPackages = []; + + foreach ($outdatedPackages as $outdatedPackage) { + $composerVersion = $outdatedPackage->getComposerVersion(); + + // already filled with open version + if (str_contains($composerVersion, '|')) { + continue; + } + + // convert composer version to next version + $nextVersion = $this->nextVersionResolver->resolve($outdatedPackage->getName(), $composerVersion); + $openedVersion = $composerVersion . '|' . $nextVersion; + + // replace using regex, to keep original composer.json format + $composerJsonContents = ComposerJsonPackageVersionUpdater::update( + $composerJsonContents, + $outdatedPackage->getName(), + $openedVersion + ); + + $openedPackages[] = new ChangedPackageVersion( + $outdatedPackage->getName(), + $composerVersion, + $openedVersion, + ); + + if (count($openedPackages) >= $limit) { + // we've reached the limit, so we can stop + break; + } + } + + return new ChangedPackageVersionsResult($composerJsonContents, $openedPackages); + } +} diff --git a/src/ComposerProcessor/RaiseToInstalledComposerProcessor.php b/src/ComposerProcessor/RaiseToInstalledComposerProcessor.php index aad2863..a7ddb00 100644 --- a/src/ComposerProcessor/RaiseToInstalledComposerProcessor.php +++ b/src/ComposerProcessor/RaiseToInstalledComposerProcessor.php @@ -1,5 +1,7 @@ installedVersionResolver->resolve(); @@ -115,6 +117,6 @@ public function process(string $composerJsonContents): RaiseToInstalledResult $changedPackageVersions[] = new ChangedPackageVersion($packageName, $packageVersion, $newRequiredVersion); } - return new RaiseToInstalledResult($composerJsonContents, $changedPackageVersions); + return new ChangedPackageVersionsResult($composerJsonContents, $changedPackageVersions); } } diff --git a/src/ValueObject/ComposerProcessorResult/RaiseToInstalledResult.php b/src/ValueObject/ComposerProcessorResult/ChangedPackageVersionsResult.php similarity index 94% rename from src/ValueObject/ComposerProcessorResult/RaiseToInstalledResult.php rename to src/ValueObject/ComposerProcessorResult/ChangedPackageVersionsResult.php index 0f14da1..32de90e 100644 --- a/src/ValueObject/ComposerProcessorResult/RaiseToInstalledResult.php +++ b/src/ValueObject/ComposerProcessorResult/ChangedPackageVersionsResult.php @@ -7,7 +7,7 @@ use Rector\Jack\ValueObject\ChangedPackageVersion; use Webmozart\Assert\Assert; -final class RaiseToInstalledResult +final class ChangedPackageVersionsResult { /** * @param ChangedPackageVersion[] $changedPackageVersions diff --git a/src/ValueObject/OutdatedComposer.php b/src/ValueObject/OutdatedComposer.php index 56e8b25..dc3b36b 100644 --- a/src/ValueObject/OutdatedComposer.php +++ b/src/ValueObject/OutdatedComposer.php @@ -4,6 +4,8 @@ namespace Rector\Jack\ValueObject; +use Webmozart\Assert\Assert; + final readonly class OutdatedComposer { /** @@ -12,6 +14,7 @@ public function __construct( private array $outdatedPackages ) { + Assert::allIsInstanceOf($outdatedPackages, OutdatedPackage::class); } public function getProdPackagesCount(): int diff --git a/tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/expected-opened-composer.json b/tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/expected-opened-composer.json new file mode 100644 index 0000000..a793a18 --- /dev/null +++ b/tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/expected-opened-composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "symfony/console": "^5.4|6.0.*" + } +} diff --git a/tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/some-closed-composer.json b/tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/some-closed-composer.json new file mode 100644 index 0000000..d3f0cf5 --- /dev/null +++ b/tests/ComposerProcessor/OpenVersionsComposerProcessor/Fixture/some-closed-composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "symfony/console": "^5.4" + } +} diff --git a/tests/ComposerProcessor/OpenVersionsComposerProcessor/OpenVersionsComposerProcessorTest.php b/tests/ComposerProcessor/OpenVersionsComposerProcessor/OpenVersionsComposerProcessorTest.php new file mode 100644 index 0000000..05cf381 --- /dev/null +++ b/tests/ComposerProcessor/OpenVersionsComposerProcessor/OpenVersionsComposerProcessorTest.php @@ -0,0 +1,52 @@ +openVersionsComposerProcessor = $this->make(OpenVersionsComposerProcessor::class); + } + + public function test(): void + { + $composerJsonContents = FileSystem::read(__DIR__ . '/Fixture/some-closed-composer.json'); + + $outdatedComposer = new OutdatedComposer([ + new OutdatedPackage('symfony/console', '5.4.0', '^5.4', true, '6.4.0', '1 year'), + ]); + + $changedPackageVersionsResult = $this->openVersionsComposerProcessor->process( + $composerJsonContents, + $outdatedComposer, + 10, + false, + null + ); + + $this->assertCount(1, $changedPackageVersionsResult->getChangedPackageVersions()); + $this->assertContainsOnlyInstancesOf( + ChangedPackageVersion::class, + $changedPackageVersionsResult->getChangedPackageVersions() + ); + + $this->assertStringEqualsFile( + __DIR__ . '/Fixture/expected-opened-composer.json', + $changedPackageVersionsResult->getComposerJsonContents() + ); + } +} diff --git a/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php b/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php index d394697..f987704 100644 --- a/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php +++ b/tests/ComposerProcessor/RaiseToInstalledComposerProcessor/RaiseToInstalledComposerProcessorTest.php @@ -11,12 +11,20 @@ final class RaiseToInstalledComposerProcessorTest extends AbstractTestCase { + private RaiseToInstalledComposerProcessor $raiseToInstalledComposerProcessor; + + protected function setUp(): void + { + parent::setUp(); + + $this->raiseToInstalledComposerProcessor = $this->make(RaiseToInstalledComposerProcessor::class); + } + public function test(): void { $composerJsonContents = FileSystem::read(__DIR__ . '/Fixture/some-outdated-composer.json'); - $raiseToInstalledComposerProcessor = $this->make(RaiseToInstalledComposerProcessor::class); - $raiseToInstalledResult = $raiseToInstalledComposerProcessor->process($composerJsonContents); + $raiseToInstalledResult = $this->raiseToInstalledComposerProcessor->process($composerJsonContents); $this->assertCount(1, $raiseToInstalledResult->getChangedPackageVersions()); $this->assertContainsOnlyInstancesOf( @@ -28,6 +36,8 @@ public function test(): void $this->assertSame('illuminate/container', $changedPackageVersion->getPackageName()); $this->assertSame('^9.0', $changedPackageVersion->getOldVersion()); + + // note: this might change in near future; improve to dynamic soon $this->assertSame('^12.14', $changedPackageVersion->getNewVersion()); } }