diff --git a/doc/03-cli.md b/doc/03-cli.md index 0dfc72e95..123dd4044 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -313,6 +313,7 @@ php composer.phar show monolog/monolog 1.0.2 ### Options +* **--latest (-l):** List all installed packages including their latest version. * **--all (-a):** List all packages available in all your repositories. * **--installed (-i):** List the packages that are installed (this is enabled by default, and deprecated). * **--platform (-p):** List only platform packages (php & extensions). @@ -321,6 +322,18 @@ php composer.phar show monolog/monolog 1.0.2 * **--name-only (-N):** List package names only. * **--path (-P):** List package paths. +## outdated + +The `outdated` command shows a list of installed packages including their +current and latest versions. This is basically an alias for `composer show -l`. + +The color coding is as such: + +- **green**: Dependency is in the latest version and is up to date. +- **yellow**: Dependency has a new version available that includes backwards compatibility breaks according to semver, so upgrade when + you can but it may involve work. +- **red**: Dependency has a new version that is semver-compatible and you should upgrade it. + ## browse / home The `browse` (aliased to `home`) opens a package's repository URL or homepage diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php new file mode 100644 index 000000000..9b6a667ae --- /dev/null +++ b/src/Composer/Command/OutdatedCommand.php @@ -0,0 +1,58 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class OutdatedCommand extends ShowCommand +{ + protected function configure() + { + $this + ->setName('outdated') + ->setDescription('Shows a list of installed packages including their latest version.') + ->setDefinition(array( + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'), + )) + ->setHelp(<<getArgument('package'))); + + return $this->getApplication()->run($input, $output); + } +} diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 687013a42..6b3c1ffa8 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -16,6 +16,7 @@ use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\DefaultPolicy; use Composer\Package\CompletePackageInterface; use Composer\Package\Version\VersionParser; +use Composer\Package\BasePackage; use Composer\Package\Version\VersionSelector; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; @@ -34,6 +35,8 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryFactory; use Composer\Spdx\SpdxLicenses; +use Composer\Composer; +use Composer\Semver\Semver; /** * @author Robert Schönthal @@ -46,9 +49,6 @@ class ShowCommand extends BaseCommand protected $versionParser; protected $colors; - /** @var CompositeRepository */ - protected $repos; - /** @var Pool */ private $pool; @@ -106,6 +106,7 @@ EOT $platformOverrides = $composer->getConfig()->get('platform') ?: array(); } $platformRepo = new PlatformRepository(array(), $platformOverrides); + $phpVersion = $platformRepo->findPackage('php', '*')->getVersion(); if ($input->getOption('self')) { $package = $this->getComposer()->getPackage(); @@ -139,6 +140,11 @@ EOT $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); } + if ($input->getOption('latest') && null === $composer) { + $io->writeError('No composer.json found in the current directory, disabling "latest" option'); + $input->setOption('latest', false); + } + $packageFilter = $input->getArgument('package'); // show single package or single version @@ -157,8 +163,8 @@ EOT $this->displayPackageTree($package, $installedRepo, $repos); } else { $latestVersion = null; - if ($input->getOption('latest')) { - $latestVersion = $this->findBestVersionForPackage($package->getName(), null); + if ($input->getOption('latest') && $composer) { + $latestVersion = $this->findBestVersionForPackage($package->getName(), $composer, $phpVersion); } $this->printMeta($package, $versions, $installedRepo, $latestVersion); $this->printLinks($package, 'requires'); @@ -285,9 +291,9 @@ EOT } if ($writeLatest) { - $latestVersion = $this->findBestVersionForPackage($package->getName()); - $type = $latestVersion == $package->getFullPrettyVersion() ? 'info' : 'comment'; - $io->write(' <'.$type.'>' . str_pad($latestVersion, $latestLength, ' ') . '', false); + $latestVersion = $this->findBestVersionForPackage($package->getName(), $composer, $phpVersion); + $style = $this->getVersionStyle($latestVersion, $package); + $io->write(' <'.$style.'>' . str_pad($latestVersion, $latestLength, ' ') . '', false); } if ($writeDescription) { @@ -315,6 +321,22 @@ EOT } } + protected function getVersionStyle($latestVersion, $package) + { + if ($latestVersion === $package->getFullPrettyVersion()) { + // print green as it's up to date + return 'info'; + } + + if ($latestVersion && Semver::satisfies($latestVersion, '^'.$package->getVersion())) { + // print red as it needs an immediate semver-compliant upgrade + return 'highlight'; + } + + // print yellow as it needs an upgrade but has potential BC breaks so is not urgent + return 'comment'; + } + /** * finds a package by name and version if provided * @@ -376,7 +398,8 @@ EOT $io->write('keywords : ' . join(', ', $package->getKeywords() ?: array())); $this->printVersions($package, $versions, $installedRepo); if ($latestVersion) { - $io->write('latest : ' . $latestVersion); + $style = $this->getVersionStyle($latestVersion, $package); + $io->write('latest : <'.$style.'>' . $latestVersion . ''); } $io->write('type : ' . $package->getType()); $this->printLicenses($package); @@ -621,41 +644,30 @@ EOT * @throws \InvalidArgumentException * @return string|null */ - private function findBestVersionForPackage($name) + private function findBestVersionForPackage($name, Composer $composer, $phpVersion) { // find the latest version allowed in this pool - $versionSelector = new VersionSelector($this->getPool()); - $package = $versionSelector->findBestCandidate($name); + $versionSelector = new VersionSelector($this->getPool($composer)); + $stability = $composer->getPackage()->getMinimumStability(); + $flags = $composer->getPackage()->getStabilityFlags(); + if (isset($flags[$name])) { + $stability = array_search($flags[$name], BasePackage::$stabilities, true); + } + + $package = $versionSelector->findBestCandidate($name, null, $phpVersion, $stability); if ($package) { return $package->getPrettyVersion(); } } - protected function getRepos() - { - if (!$this->repos) { - $this->repos = new CompositeRepository(array_merge( - array(new PlatformRepository), - RepositoryFactory::defaultRepos($this->getIO()) - )); - } - - return $this->repos; - } - - private function getPool() + private function getPool(Composer $composer) { if (!$this->pool) { - $this->pool = new Pool($this->getMinimumStability()); - $this->pool->addRepository($this->getRepos()); + $this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); + $this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); } return $this->pool; } - - private function getMinimumStability() - { - return 'stable'; - } } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index c066ed9f2..106464bcb 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -116,7 +116,7 @@ class Application extends BaseApplication } } - if ($commandName !== 'global') { + if ($commandName !== 'global' && $commandName !== 'outdated') { $io->writeError(sprintf( 'Running %s (%s) with %s on %s', Composer::VERSION, @@ -339,6 +339,7 @@ class Application extends BaseApplication new Command\RemoveCommand(), new Command\HomeCommand(), new Command\ExecCommand(), + new Command\OutdatedCommand(), )); if ('phar:' === substr(__FILE__, 0, 5)) {