1
0
Fork 0

Improve interactive package updates (#11990)

* Improve interactive package updates

* Exclude platform packages and up to date packages, follow stability flags, ignore-platform-reqs etc

* Add tests and support for lock file + empty lock/vendor

---------

Co-authored-by: Jordi Boggiano <j.boggiano@seld.be>
pull/12086/head
Job Vink 2024-09-18 10:43:42 +02:00 committed by GitHub
parent 12031542ba
commit be7d9abc66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 94 additions and 35 deletions

View File

@ -16,20 +16,27 @@ use Composer\Composer;
use Composer\DependencyResolver\Request;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\Loader\RootPackageLoader;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Package\Version\VersionParser;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositorySet;
use Composer\Semver\Intervals;
use Composer\Util\HttpDownloader;
use Composer\Advisory\Auditor;
use Composer\Util\Platform;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
@ -261,48 +268,53 @@ EOT
throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.');
}
$platformReqFilter = $this->getPlatformRequirementFilter($input);
$stabilityFlags = $composer->getPackage()->getStabilityFlags();
$requires = array_merge(
$composer->getPackage()->getRequires(),
$composer->getPackage()->getDevRequires()
);
$filter = \count($packages) > 0 ? BasePackage::packageNamesToRegexp($packages) : null;
$io->writeError('<info>Loading packages that can be updated...</info>');
$autocompleterValues = [];
foreach ($requires as $require) {
$target = $require->getTarget();
$autocompleterValues[strtolower($target)] = $target;
}
$installedPackages = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
$installedPackages = $composer->getLocker()->isLocked() ? $composer->getLocker()->getLockedRepository(true)->getPackages() : $composer->getRepositoryManager()->getLocalRepository()->getPackages();
$versionSelector = $this->createVersionSelector($composer);
foreach ($installedPackages as $package) {
$autocompleterValues[$package->getName()] = $package->getPrettyName();
if ($filter !== null && !Preg::isMatch($filter, $package->getName())) {
continue;
}
$currentVersion = $package->getPrettyVersion();
$constraint = isset($requires[$package->getName()]) ? $requires[$package->getName()]->getPrettyConstraint() : null;
$stability = isset($stabilityFlags[$package->getName()]) ? (string) array_search($stabilityFlags[$package->getName()], BasePackage::STABILITIES, true) : $composer->getPackage()->getMinimumStability();
$latestVersion = $versionSelector->findBestCandidate($package->getName(), $constraint, $stability, $platformReqFilter);
if ($latestVersion !== false && ($package->getVersion() !== $latestVersion->getVersion() || $latestVersion->isDev())) {
$autocompleterValues[$package->getName()] = '<comment>' . $currentVersion . '</comment> => <comment>' . $latestVersion->getPrettyVersion() . '</comment>';
}
}
if (0 === \count($installedPackages)) {
foreach ($requires as $req => $constraint) {
if (PlatformRepository::isPlatformPackage($req)) {
continue;
}
$autocompleterValues[$req] = '';
}
}
$helper = $this->getHelper('question');
$question = new Question('<comment>Enter package name: </comment>', null);
$io->writeError('<info>Press enter without value to end submission</info>');
do {
$autocompleterValues = array_diff($autocompleterValues, $packages);
$question->setAutocompleterValues($autocompleterValues);
$addedPackage = $helper->ask($input, $output, $question);
if (!is_string($addedPackage) || empty($addedPackage)) {
break;
}
$addedPackage = strtolower($addedPackage);
if (!in_array($addedPackage, $packages)) {
$packages[] = $addedPackage;
}
} while (true);
$packages = array_filter($packages, function (string $pkg) {
return $pkg !== '';
});
if (!$packages) {
throw new \InvalidArgumentException('You must enter minimum one package.');
if (0 === \count($autocompleterValues)) {
throw new \RuntimeException('Could not find any package with new versions available');
}
$packages = $io->select(
'Select packages: (Select more than one value separated by comma) ',
$autocompleterValues,
false,
1,
'No package named "%s" is installed.',
true
);
$table = new Table($output);
$table->setHeaders(['Selected packages']);
foreach ($packages as $package) {
@ -319,4 +331,14 @@ EOT
throw new \RuntimeException('Installation aborted.');
}
private function createVersionSelector(Composer $composer): VersionSelector
{
$repositorySet = new RepositorySet();
$repositorySet->addRepository(new CompositeRepository(array_filter($composer->getRepositoryManager()->getRepositories(), function (RepositoryInterface $repository) {
return !$repository instanceof PlatformRepository;
})));
return new VersionSelector($repositorySet);
}
}

View File

@ -128,10 +128,47 @@ OUTPUT
];
}
public function testInteractiveModeThrowsIfNoPackageToUpdate(): void
{
$this->initTempComposer([
'repositories' => [
'packages' => [
'type' => 'package',
'package' => [
['name' => 'root/req', 'version' => '1.0.0'],
],
],
],
'require' => [
'root/req' => '1.*',
],
]);
$this->createComposerLock([self::getPackage('root/req', '1.0.0')]);
self::expectExceptionMessage('Could not find any package with new versions available');
$appTester = $this->getApplicationTester();
$appTester->setInputs(['']);
$appTester->run(['command' => 'update', '--interactive' => true]);
}
public function testInteractiveModeThrowsIfNoPackageEntered(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('You must enter minimum one package.');
$this->initTempComposer([
'repositories' => [
'packages' => [
'type' => 'package',
'package' => [
['name' => 'root/req', 'version' => '1.0.0'],
['name' => 'root/req', 'version' => '1.0.1'],
],
],
],
'require' => [
'root/req' => '1.*',
],
]);
$this->createComposerLock([self::getPackage('root/req', '1.0.0')]);
self::expectExceptionMessage('No package named "" is installed.');
$appTester = $this->getApplicationTester();
$appTester->setInputs(['']);