Browse Source

Tweak colors in the output, make sure we load the proper version according to stability flags and add outdated command, refs #5028

Jordi Boggiano 9 years ago
parent
commit
e7069dd5e8

+ 13 - 0
doc/03-cli.md

@@ -313,6 +313,7 @@ php composer.phar show monolog/monolog 1.0.2
 
 
 ### Options
 ### Options
 
 
+* **--latest (-l):** List all installed packages including their latest version.
 * **--all (-a):** List all packages available in all your repositories.
 * **--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).
 * **--installed (-i):** List the packages that are installed (this is enabled by default, and deprecated).
 * **--platform (-p):** List only platform packages (php & extensions).
 * **--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.
 * **--name-only (-N):** List package names only.
 * **--path (-P):** List package paths.
 * **--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
 ## browse / home
 
 
 The `browse` (aliased to `home`) opens a package's repository URL or homepage
 The `browse` (aliased to `home`) opens a package's repository URL or homepage

+ 58 - 0
src/Composer/Command/OutdatedCommand.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <j.boggiano@seld.be>
+ */
+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(<<<EOT
+The outdated command is just a proxy for `composer show -l`
+
+The color coding for dependency versions 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.
+
+
+EOT
+            )
+        ;
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        // create new input without "global" command prefix
+        $input = new StringInput('show --latest '.ProcessExecutor::escape($input->getArgument('package')));
+
+        return $this->getApplication()->run($input, $output);
+    }
+}

+ 44 - 32
src/Composer/Command/ShowCommand.php

@@ -16,6 +16,7 @@ use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\DefaultPolicy;
 use Composer\DependencyResolver\DefaultPolicy;
 use Composer\Package\CompletePackageInterface;
 use Composer\Package\CompletePackageInterface;
 use Composer\Package\Version\VersionParser;
 use Composer\Package\Version\VersionParser;
+use Composer\Package\BasePackage;
 use Composer\Package\Version\VersionSelector;
 use Composer\Package\Version\VersionSelector;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
 use Composer\Plugin\PluginEvents;
@@ -34,6 +35,8 @@ use Composer\Repository\PlatformRepository;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\RepositoryFactory;
 use Composer\Repository\RepositoryFactory;
 use Composer\Spdx\SpdxLicenses;
 use Composer\Spdx\SpdxLicenses;
+use Composer\Composer;
+use Composer\Semver\Semver;
 
 
 /**
 /**
  * @author Robert Schönthal <seroscho@googlemail.com>
  * @author Robert Schönthal <seroscho@googlemail.com>
@@ -46,9 +49,6 @@ class ShowCommand extends BaseCommand
     protected $versionParser;
     protected $versionParser;
     protected $colors;
     protected $colors;
 
 
-    /** @var CompositeRepository */
-    protected $repos;
-
     /** @var Pool */
     /** @var Pool */
     private $pool;
     private $pool;
 
 
@@ -106,6 +106,7 @@ EOT
             $platformOverrides = $composer->getConfig()->get('platform') ?: array();
             $platformOverrides = $composer->getConfig()->get('platform') ?: array();
         }
         }
         $platformRepo = new PlatformRepository(array(), $platformOverrides);
         $platformRepo = new PlatformRepository(array(), $platformOverrides);
+        $phpVersion = $platformRepo->findPackage('php', '*')->getVersion();
 
 
         if ($input->getOption('self')) {
         if ($input->getOption('self')) {
             $package = $this->getComposer()->getPackage();
             $package = $this->getComposer()->getPackage();
@@ -139,6 +140,11 @@ EOT
             $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
             $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');
         $packageFilter = $input->getArgument('package');
 
 
         // show single package or single version
         // show single package or single version
@@ -157,8 +163,8 @@ EOT
                 $this->displayPackageTree($package, $installedRepo, $repos);
                 $this->displayPackageTree($package, $installedRepo, $repos);
             } else {
             } else {
                 $latestVersion = null;
                 $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->printMeta($package, $versions, $installedRepo, $latestVersion);
                 $this->printLinks($package, 'requires');
                 $this->printLinks($package, 'requires');
@@ -285,9 +291,9 @@ EOT
                         }
                         }
 
 
                         if ($writeLatest) {
                         if ($writeLatest) {
-                            $latestVersion = $this->findBestVersionForPackage($package->getName());
-                            $type = $latestVersion == $package->getFullPrettyVersion() ? 'info' : 'comment';
-                            $io->write(' <'.$type.'>' . str_pad($latestVersion, $latestLength, ' ') . '</'.$type.'>', false);
+                            $latestVersion = $this->findBestVersionForPackage($package->getName(), $composer, $phpVersion);
+                            $style = $this->getVersionStyle($latestVersion, $package);
+                            $io->write(' <'.$style.'>' . str_pad($latestVersion, $latestLength, ' ') . '</'.$style.'>', false);
                         }
                         }
 
 
                         if ($writeDescription) {
                         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
      * finds a package by name and version if provided
      *
      *
@@ -376,7 +398,8 @@ EOT
         $io->write('<info>keywords</info> : ' . join(', ', $package->getKeywords() ?: array()));
         $io->write('<info>keywords</info> : ' . join(', ', $package->getKeywords() ?: array()));
         $this->printVersions($package, $versions, $installedRepo);
         $this->printVersions($package, $versions, $installedRepo);
         if ($latestVersion) {
         if ($latestVersion) {
-            $io->write('<info>latest</info>   : ' . $latestVersion);
+            $style = $this->getVersionStyle($latestVersion, $package);
+            $io->write('<info>latest</info>   : <'.$style.'>' . $latestVersion . '</'.$style.'>');
         }
         }
         $io->write('<info>type</info>     : ' . $package->getType());
         $io->write('<info>type</info>     : ' . $package->getType());
         $this->printLicenses($package);
         $this->printLicenses($package);
@@ -621,41 +644,30 @@ EOT
      * @throws \InvalidArgumentException
      * @throws \InvalidArgumentException
      * @return string|null
      * @return string|null
      */
      */
-    private function findBestVersionForPackage($name)
+    private function findBestVersionForPackage($name, Composer $composer, $phpVersion)
     {
     {
         // find the latest version allowed in this pool
         // 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) {
         if ($package) {
             return $package->getPrettyVersion();
             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) {
         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;
         return $this->pool;
     }
     }
-
-    private function getMinimumStability()
-    {
-        return 'stable';
-    }
 }
 }

+ 2 - 1
src/Composer/Console/Application.php

@@ -116,7 +116,7 @@ class Application extends BaseApplication
             }
             }
         }
         }
 
 
-        if ($commandName !== 'global') {
+        if ($commandName !== 'global' && $commandName !== 'outdated') {
             $io->writeError(sprintf(
             $io->writeError(sprintf(
                 'Running %s (%s) with %s on %s',
                 'Running %s (%s) with %s on %s',
                 Composer::VERSION,
                 Composer::VERSION,
@@ -339,6 +339,7 @@ class Application extends BaseApplication
             new Command\RemoveCommand(),
             new Command\RemoveCommand(),
             new Command\HomeCommand(),
             new Command\HomeCommand(),
             new Command\ExecCommand(),
             new Command\ExecCommand(),
+            new Command\OutdatedCommand(),
         ));
         ));
 
 
         if ('phar:' === substr(__FILE__, 0, 5)) {
         if ('phar:' === substr(__FILE__, 0, 5)) {