From 97dd64587ef778143ea9ebea2710a0e0cb6b1f48 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 26 Jun 2025 20:19:33 +0100 Subject: [PATCH 1/3] Added specific exception when forwardslash is missing --- .../PieComposerRequest.php | 2 +- src/DependencyResolver/InvalidPackageName.php | 28 ++++++++++++++ .../RequestedPackageAndVersion.php | 7 ++++ .../UnableToResolveRequirement.php | 37 ++++++++++++------- 4 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 src/DependencyResolver/InvalidPackageName.php diff --git a/src/ComposerIntegration/PieComposerRequest.php b/src/ComposerIntegration/PieComposerRequest.php index 405f78b9..ef6697cd 100644 --- a/src/ComposerIntegration/PieComposerRequest.php +++ b/src/ComposerIntegration/PieComposerRequest.php @@ -39,7 +39,7 @@ public static function noOperation( return new PieComposerRequest( $pieOutput, $targetPlatform, - new RequestedPackageAndVersion('null', null), + new RequestedPackageAndVersion('null/null', null), PieOperation::Resolve, [], null, diff --git a/src/DependencyResolver/InvalidPackageName.php b/src/DependencyResolver/InvalidPackageName.php new file mode 100644 index 00000000..ae54015f --- /dev/null +++ b/src/DependencyResolver/InvalidPackageName.php @@ -0,0 +1,28 @@ +package, + ), + $requestedPackageAndVersion, + ); + } +} diff --git a/src/DependencyResolver/RequestedPackageAndVersion.php b/src/DependencyResolver/RequestedPackageAndVersion.php index b7de62cb..6b252e90 100644 --- a/src/DependencyResolver/RequestedPackageAndVersion.php +++ b/src/DependencyResolver/RequestedPackageAndVersion.php @@ -4,6 +4,8 @@ namespace Php\Pie\DependencyResolver; +use function str_contains; + /** * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks * @@ -14,10 +16,15 @@ final class RequestedPackageAndVersion /** * @param non-empty-string $package * @param non-empty-string|null $version + * + * @throws InvalidPackageName */ public function __construct( public readonly string $package, public readonly string|null $version, ) { + if (! str_contains($this->package, '/')) { + throw InvalidPackageName::fromMissingForwardSlash($this); + } } } diff --git a/src/DependencyResolver/UnableToResolveRequirement.php b/src/DependencyResolver/UnableToResolveRequirement.php index cdd9e41c..8d32e09f 100644 --- a/src/DependencyResolver/UnableToResolveRequirement.php +++ b/src/DependencyResolver/UnableToResolveRequirement.php @@ -16,28 +16,39 @@ class UnableToResolveRequirement extends RuntimeException { + public function __construct(string $message, public readonly RequestedPackageAndVersion $requestedPackageAndVersion) + { + parent::__construct($message); + } + public static function fromRequirement( RequestedPackageAndVersion $requestedPackageAndVersion, QuieterConsoleIO $io, ): self { $errors = $io->errors; - return new self(sprintf( - 'Unable to find an installable package %s for %s, with minimum stability %s.%s', - $requestedPackageAndVersion->package, - $requestedPackageAndVersion->version !== null ? sprintf('version %s', $requestedPackageAndVersion->version) : 'the latest compatible version', - DetermineMinimumStability::fromRequestedVersion($requestedPackageAndVersion->version), - count($errors) ? "\n\n" . implode("\n\n", array_map(static fn ($e) => strip_tags($e), $errors)) : '', - )); + return new self( + sprintf( + 'Unable to find an installable package %s for %s, with minimum stability %s.%s', + $requestedPackageAndVersion->package, + $requestedPackageAndVersion->version !== null ? sprintf('version %s', $requestedPackageAndVersion->version) : 'the latest compatible version', + DetermineMinimumStability::fromRequestedVersion($requestedPackageAndVersion->version), + count($errors) ? "\n\n" . implode("\n\n", array_map(static fn ($e) => strip_tags($e), $errors)) : '', + ), + $requestedPackageAndVersion, + ); } public static function toPhpOrZendExtension(PackageInterface $locatedComposerPackage, RequestedPackageAndVersion $requestedPackageAndVersion): self { - return new self(sprintf( - 'Package %s was not of type php-ext or php-ext-zend (requested %s%s).', - $locatedComposerPackage->getName(), - $requestedPackageAndVersion->package, - $requestedPackageAndVersion->version !== null ? sprintf(' for version %s', $requestedPackageAndVersion->version) : '', - )); + return new self( + sprintf( + 'Package %s was not of type php-ext or php-ext-zend (requested %s%s).', + $locatedComposerPackage->getName(), + $requestedPackageAndVersion->package, + $requestedPackageAndVersion->version !== null ? sprintf(' for version %s', $requestedPackageAndVersion->version) : '', + ), + $requestedPackageAndVersion, + ); } } From 9971a116381387c2c8d0156f02ef657fdf17e6af Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 26 Jun 2025 21:37:06 +0100 Subject: [PATCH 2/3] Handle InvalidPackageName and UnableToResolveRequirement exceptions --- src/Command/BuildCommand.php | 42 +++++++++--- src/Command/CommandHelper.php | 66 +++++++++++++++++++ src/Command/DownloadCommand.php | 42 +++++++++--- src/Command/InfoCommand.php | 39 +++++++++-- src/Command/InstallCommand.php | 42 +++++++++--- .../InstallExtensionsForProjectCommand.php | 2 +- .../FindMatchingPackages.php | 9 ++- .../Command/DownloadCommandTest.php | 10 ++- 8 files changed, 212 insertions(+), 40 deletions(-) diff --git a/src/Command/BuildCommand.php b/src/Command/BuildCommand.php index fd5588fa..72c3cd57 100644 --- a/src/Command/BuildCommand.php +++ b/src/Command/BuildCommand.php @@ -10,6 +10,9 @@ use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; use Php\Pie\DependencyResolver\DependencyResolver; +use Php\Pie\DependencyResolver\InvalidPackageName; +use Php\Pie\DependencyResolver\UnableToResolveRequirement; +use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -28,6 +31,7 @@ public function __construct( private readonly ContainerInterface $container, private readonly DependencyResolver $dependencyResolver, private readonly ComposerIntegrationHandler $composerIntegrationHandler, + private readonly FindMatchingPackages $findMatchingPackages, ) { parent::__construct(); } @@ -41,8 +45,19 @@ public function configure(): void public function execute(InputInterface $input, OutputInterface $output): int { - $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); - $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); + try { + $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + } catch (InvalidPackageName $invalidPackageName) { + return CommandHelper::handlePackageNotFound( + $invalidPackageName, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } + $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input); $composer = PieComposerFactory::createPieComposer( @@ -58,12 +73,23 @@ public function execute(InputInterface $input, OutputInterface $output): int ), ); - $package = ($this->dependencyResolver)( - $composer, - $targetPlatform, - $requestedNameAndVersion, - $forceInstallPackageVersion, - ); + try { + $package = ($this->dependencyResolver)( + $composer, + $targetPlatform, + $requestedNameAndVersion, + $forceInstallPackageVersion, + ); + } catch (UnableToResolveRequirement $unableToResolveRequirement) { + return CommandHelper::handlePackageNotFound( + $unableToResolveRequirement, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } + $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); // Now we know what package we have, we can validate the configure options for the command and re-create the diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 4f53e09f..840331c2 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -11,13 +11,20 @@ use Composer\Repository\VcsRepository; use Composer\Util\Platform; use InvalidArgumentException; +use OutOfRangeException; +use Php\Pie\ComposerIntegration\PieComposerFactory; +use Php\Pie\ComposerIntegration\PieComposerRequest; +use Php\Pie\DependencyResolver\InvalidPackageName; use Php\Pie\DependencyResolver\Package; use Php\Pie\DependencyResolver\RequestedPackageAndVersion; +use Php\Pie\DependencyResolver\UnableToResolveRequirement; +use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Php\Pie\Platform as PiePlatform; use Php\Pie\Platform\OperatingSystem; use Php\Pie\Platform\TargetPhp\PhpBinaryPath; use Php\Pie\Platform\TargetPhp\PhpizePath; use Php\Pie\Platform\TargetPlatform; +use Psr\Container\ContainerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -26,11 +33,15 @@ use Webmozart\Assert\Assert; use function array_key_exists; +use function array_map; +use function count; use function is_array; use function is_string; use function reset; use function sprintf; +use function str_starts_with; use function strtolower; +use function substr; use function trim; use const PHP_VERSION; @@ -326,4 +337,59 @@ public static function listRepositories(Composer $composer, OutputInterface $out )); } } + + public static function handlePackageNotFound( + InvalidPackageName|UnableToResolveRequirement $exception, + FindMatchingPackages $findMatchingPackages, + OutputInterface $output, + TargetPlatform $targetPlatform, + ContainerInterface $container, + ): int { + $pieComposer = PieComposerFactory::createPieComposer( + $container, + PieComposerRequest::noOperation( + $output, + $targetPlatform, + ), + ); + + $package = $exception->requestedPackageAndVersion->package; + if (str_starts_with($package, 'ext-')) { + $package = substr($package, 4); + } + + $output->writeln(''); + $output->writeln(sprintf('Could not install package: %s', $package)); + $output->writeln($exception->getMessage()); + + try { + $matches = $findMatchingPackages->for($pieComposer, $package); + + if (count($matches)) { + $output->writeln(''); + if (count($matches) === 1) { + $output->writeln('Did you mean this?'); + } else { + $output->writeln('Did you mean one of these?'); + } + + array_map( + static function (array $match) use ($output): void { + $output->writeln(sprintf(' - %s: %s', $match['name'], $match['description'] ?? 'no description available')); + }, + $matches, + ); + } + } catch (OutOfRangeException) { + $output->writeln( + sprintf( + 'Tried searching for "%s", but nothing was found.', + $package, + ), + OutputInterface::VERBOSITY_VERBOSE, + ); + } + + return 1; + } } diff --git a/src/Command/DownloadCommand.php b/src/Command/DownloadCommand.php index 9c156459..5481dabc 100644 --- a/src/Command/DownloadCommand.php +++ b/src/Command/DownloadCommand.php @@ -10,6 +10,9 @@ use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; use Php\Pie\DependencyResolver\DependencyResolver; +use Php\Pie\DependencyResolver\InvalidPackageName; +use Php\Pie\DependencyResolver\UnableToResolveRequirement; +use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -28,6 +31,7 @@ public function __construct( private readonly ContainerInterface $container, private readonly DependencyResolver $dependencyResolver, private readonly ComposerIntegrationHandler $composerIntegrationHandler, + private readonly FindMatchingPackages $findMatchingPackages, ) { parent::__construct(); } @@ -43,8 +47,19 @@ public function execute(InputInterface $input, OutputInterface $output): int { CommandHelper::validateInput($input, $this); - $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); - $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); + try { + $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + } catch (InvalidPackageName $invalidPackageName) { + return CommandHelper::handlePackageNotFound( + $invalidPackageName, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } + $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input); $composer = PieComposerFactory::createPieComposer( @@ -60,12 +75,23 @@ public function execute(InputInterface $input, OutputInterface $output): int ), ); - $package = ($this->dependencyResolver)( - $composer, - $targetPlatform, - $requestedNameAndVersion, - $forceInstallPackageVersion, - ); + try { + $package = ($this->dependencyResolver)( + $composer, + $targetPlatform, + $requestedNameAndVersion, + $forceInstallPackageVersion, + ); + } catch (UnableToResolveRequirement $unableToResolveRequirement) { + return CommandHelper::handlePackageNotFound( + $unableToResolveRequirement, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } + $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); try { diff --git a/src/Command/InfoCommand.php b/src/Command/InfoCommand.php index 4cb56949..fa3d6593 100644 --- a/src/Command/InfoCommand.php +++ b/src/Command/InfoCommand.php @@ -8,6 +8,9 @@ use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; use Php\Pie\DependencyResolver\DependencyResolver; +use Php\Pie\DependencyResolver\InvalidPackageName; +use Php\Pie\DependencyResolver\UnableToResolveRequirement; +use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -26,6 +29,7 @@ final class InfoCommand extends Command public function __construct( private readonly ContainerInterface $container, private readonly DependencyResolver $dependencyResolver, + private readonly FindMatchingPackages $findMatchingPackages, ) { parent::__construct(); } @@ -43,7 +47,17 @@ public function execute(InputInterface $input, OutputInterface $output): int $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); - $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + try { + $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + } catch (InvalidPackageName $invalidPackageName) { + return CommandHelper::handlePackageNotFound( + $invalidPackageName, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } $composer = PieComposerFactory::createPieComposer( $this->container, @@ -58,12 +72,23 @@ public function execute(InputInterface $input, OutputInterface $output): int ), ); - $package = ($this->dependencyResolver)( - $composer, - $targetPlatform, - $requestedNameAndVersion, - CommandHelper::determineForceInstallingPackageVersion($input), - ); + try { + $package = ($this->dependencyResolver)( + $composer, + $targetPlatform, + $requestedNameAndVersion, + CommandHelper::determineForceInstallingPackageVersion($input), + ); + } catch (UnableToResolveRequirement $unableToResolveRequirement) { + return CommandHelper::handlePackageNotFound( + $unableToResolveRequirement, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } + $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); $output->writeln(sprintf('Extension name: %s', $package->extensionName()->name())); diff --git a/src/Command/InstallCommand.php b/src/Command/InstallCommand.php index fd2c8fde..ba9a53f8 100644 --- a/src/Command/InstallCommand.php +++ b/src/Command/InstallCommand.php @@ -10,6 +10,9 @@ use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; use Php\Pie\DependencyResolver\DependencyResolver; +use Php\Pie\DependencyResolver\InvalidPackageName; +use Php\Pie\DependencyResolver\UnableToResolveRequirement; +use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Php\Pie\Platform\TargetPlatform; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; @@ -30,6 +33,7 @@ public function __construct( private readonly DependencyResolver $dependencyResolver, private readonly ComposerIntegrationHandler $composerIntegrationHandler, private readonly InvokeSubCommand $invokeSubCommand, + private readonly FindMatchingPackages $findMatchingPackages, ) { parent::__construct(); } @@ -56,8 +60,19 @@ public function execute(InputInterface $input, OutputInterface $output): int $output->writeln('This command may need elevated privileges, and may prompt you for your password.'); } - $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); - $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); + try { + $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input); + } catch (InvalidPackageName $invalidPackageName) { + return CommandHelper::handlePackageNotFound( + $invalidPackageName, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } + $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input); $composer = PieComposerFactory::createPieComposer( @@ -73,12 +88,23 @@ public function execute(InputInterface $input, OutputInterface $output): int ), ); - $package = ($this->dependencyResolver)( - $composer, - $targetPlatform, - $requestedNameAndVersion, - $forceInstallPackageVersion, - ); + try { + $package = ($this->dependencyResolver)( + $composer, + $targetPlatform, + $requestedNameAndVersion, + $forceInstallPackageVersion, + ); + } catch (UnableToResolveRequirement $unableToResolveRequirement) { + return CommandHelper::handlePackageNotFound( + $unableToResolveRequirement, + $this->findMatchingPackages, + $output, + $targetPlatform, + $this->container, + ); + } + $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); // Now we know what package we have, we can validate the configure options for the command and re-create the diff --git a/src/Command/InstallExtensionsForProjectCommand.php b/src/Command/InstallExtensionsForProjectCommand.php index ebfe2b9d..73dd00da 100644 --- a/src/Command/InstallExtensionsForProjectCommand.php +++ b/src/Command/InstallExtensionsForProjectCommand.php @@ -201,7 +201,7 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac )); try { - $matches = $this->findMatchingPackages->for($pieComposer, $extension); + $matches = $this->findMatchingPackages->for($pieComposer, $extension->name()); } catch (OutOfRangeException) { $anyErrorsHappened = true; diff --git a/src/Installing/InstallForPhpProject/FindMatchingPackages.php b/src/Installing/InstallForPhpProject/FindMatchingPackages.php index dbb6c401..8886ad4d 100644 --- a/src/Installing/InstallForPhpProject/FindMatchingPackages.php +++ b/src/Installing/InstallForPhpProject/FindMatchingPackages.php @@ -7,7 +7,6 @@ use Composer\Composer; use Composer\Repository\RepositoryInterface; use OutOfRangeException; -use Php\Pie\ExtensionName; use function array_merge; use function count; @@ -21,16 +20,16 @@ class FindMatchingPackages { /** @return MatchingPackages */ - public function for(Composer $pieComposer, ExtensionName $extension): array + public function for(Composer $pieComposer, string $searchTerm): array { $matches = []; foreach ($pieComposer->getRepositoryManager()->getRepositories() as $repo) { - $matches = array_merge($matches, $repo->search($extension->name(), RepositoryInterface::SEARCH_FULLTEXT, 'php-ext')); - $matches = array_merge($matches, $repo->search($extension->name(), RepositoryInterface::SEARCH_FULLTEXT, 'php-ext-zend')); + $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext')); + $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext-zend')); } if (! count($matches)) { - throw new OutOfRangeException('No matches found for ' . $extension->name()); + throw new OutOfRangeException('No matches found for ' . $searchTerm); } usort($matches, static function (array $a, array $b): int { diff --git a/test/integration/Command/DownloadCommandTest.php b/test/integration/Command/DownloadCommandTest.php index e892ce4d..4fd71ad6 100644 --- a/test/integration/Command/DownloadCommandTest.php +++ b/test/integration/Command/DownloadCommandTest.php @@ -7,7 +7,6 @@ use Composer\Util\Platform; use Php\Pie\Command\DownloadCommand; use Php\Pie\Container; -use Php\Pie\DependencyResolver\UnableToResolveRequirement; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; @@ -145,9 +144,14 @@ public function testDownloadingWithPhpPath(string $requestedVersion, string $exp #[RequiresPhp('<8.2')] public function testDownloadCommandFailsWhenUsingIncompatiblePhpVersion(): void { - $this->expectException(UnableToResolveRequirement::class); // 1.0.0 is only compatible with PHP 8.3.0 - $this->commandTester->execute(['requested-package-and-version' => self::TEST_PACKAGE . ':1.0.0']); + self::assertSame(1, $this->commandTester->execute(['requested-package-and-version' => self::TEST_PACKAGE . ':1.0.0'])); + + $output = $this->commandTester->getDisplay(); + self::assertStringContainsString( + 'Unable to find an installable package asgrim/example-pie-extension for version 1.0.0, with minimum stability stable.', + $output, + ); } #[RequiresOperatingSystemFamily('Linux')] From 411acc5d5a4b2f5cf8925d305d92da1bf78f7221 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Fri, 4 Jul 2025 17:05:43 +0100 Subject: [PATCH 3/3] Write the extension name when suggesting alternatives when package is not found --- src/Command/CommandHelper.php | 49 ++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 840331c2..61987fd8 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -5,6 +5,7 @@ namespace Php\Pie\Command; use Composer\Composer; +use Composer\Package\CompletePackageInterface; use Composer\Package\Version\VersionParser; use Composer\Repository\ComposerRepository; use Composer\Repository\PathRepository; @@ -30,6 +31,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Throwable; use Webmozart\Assert\Assert; use function array_key_exists; @@ -353,17 +355,43 @@ public static function handlePackageNotFound( ), ); - $package = $exception->requestedPackageAndVersion->package; - if (str_starts_with($package, 'ext-')) { - $package = substr($package, 4); + $requestedPackageName = $exception->requestedPackageAndVersion->package; + if (str_starts_with($requestedPackageName, 'ext-')) { + $requestedPackageName = substr($requestedPackageName, 4); } $output->writeln(''); - $output->writeln(sprintf('Could not install package: %s', $package)); + $output->writeln(sprintf('Could not install package: %s', $requestedPackageName)); $output->writeln($exception->getMessage()); try { - $matches = $findMatchingPackages->for($pieComposer, $package); + $matches = array_map( + static function (array $match) use ($output, $pieComposer): array { + $composerMatchingPackage = $pieComposer->getRepositoryManager()->findPackage($match['name'], '*'); + + // Attempts to augment the Composer packages found with the PIE extension name + if ($composerMatchingPackage instanceof CompletePackageInterface) { + try { + $match['extension-name'] = Package + ::fromComposerCompletePackage($composerMatchingPackage) + ->extensionName() + ->name(); + } catch (Throwable $t) { + $output->writeln( + sprintf( + 'Tried looking up extension name for %s, but failed: %s', + $match['name'], + $t->getMessage(), + ), + OutputInterface::VERBOSITY_VERY_VERBOSE, + ); + } + } + + return $match; + }, + $findMatchingPackages->for($pieComposer, $requestedPackageName), + ); if (count($matches)) { $output->writeln(''); @@ -375,7 +403,14 @@ public static function handlePackageNotFound( array_map( static function (array $match) use ($output): void { - $output->writeln(sprintf(' - %s: %s', $match['name'], $match['description'] ?? 'no description available')); + $output->writeln(sprintf( + ' - %s%s: %s', + $match['name'], + array_key_exists('extension-name', $match) && is_string($match['extension-name']) + ? ' (provides extension: ' . $match['extension-name'] . ')' + : '', + $match['description'] ?? 'no description available', + )); }, $matches, ); @@ -384,7 +419,7 @@ static function (array $match) use ($output): void { $output->writeln( sprintf( 'Tried searching for "%s", but nothing was found.', - $package, + $requestedPackageName, ), OutputInterface::VERBOSITY_VERBOSE, );