├── box.json ├── bin └── climb ├── src ├── Application.php ├── Commands │ ├── Command.php │ ├── UpdateCommand.php │ └── OutdatedCommand.php ├── Packagist.php ├── OutputStyle.php ├── Version.php ├── Ladder.php ├── Composer.php └── Package.php ├── LICENSE └── composer.json /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "src" 5 | ], 6 | "files": [ 7 | "LICENSE", 8 | "./vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem" 9 | ], 10 | "finder": [ 11 | { 12 | "name": "*.php", 13 | "exclude": ["Tests", "tests"], 14 | "in": "vendor" 15 | } 16 | ], 17 | "git-version": "package_version", 18 | "main": "bin/climb", 19 | "output": "climb.phar", 20 | "compression": "GZ", 21 | "stub": true 22 | } 23 | -------------------------------------------------------------------------------- /bin/climb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | if (file_exists(__DIR__.'/../vendor/autoload.php')) { 14 | require __DIR__.'/../vendor/autoload.php'; 15 | } else { 16 | require __DIR__.'/../../../autoload.php'; 17 | } 18 | 19 | $app = new Vinkla\Climb\Application( 20 | realpath(__DIR__) 21 | ); 22 | 23 | $app->run(); 24 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb; 13 | 14 | use Symfony\Component\Console\Application as Console; 15 | use Vinkla\Climb\Commands\OutdatedCommand; 16 | use Vinkla\Climb\Commands\UpdateCommand; 17 | 18 | /** 19 | * This is the application class. 20 | * 21 | * @author Vincent Klaiber 22 | */ 23 | class Application extends Console 24 | { 25 | /** 26 | * Create a new application instance. 27 | * 28 | * @return void 29 | */ 30 | public function __construct() 31 | { 32 | parent::__construct('Climb', '0.8.1'); 33 | 34 | $this->add(new OutdatedCommand()); 35 | $this->add(new UpdateCommand()); 36 | 37 | $this->setDefaultCommand('outdated'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Commands/Command.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb\Commands; 13 | 14 | use Symfony\Component\Console\Command\Command as BaseCommand; 15 | use Symfony\Component\Console\Input\InputInterface; 16 | 17 | /** 18 | * This is the command class. 19 | * 20 | * @author Vincent Klaiber 21 | */ 22 | class Command extends BaseCommand 23 | { 24 | /** 25 | * Get composer path based on user input. 26 | * 27 | * @param \Symfony\Component\Console\Input\InputInterface $input 28 | * 29 | * @return null|string 30 | */ 31 | protected function getComposerPathFromInput(InputInterface $input) 32 | { 33 | if ($input->getOption('directory')) { 34 | return $input->getOption('directory'); 35 | } 36 | 37 | if ($input->getOption('global')) { 38 | return getenv('HOME').'/.composer'; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Packagist.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb; 13 | 14 | use Guzzle\Http\Exception\ClientErrorResponseException; 15 | use Packagist\Api\Client; 16 | 17 | /** 18 | * This is the packagist class. 19 | * 20 | * @author Vincent Klaiber 21 | */ 22 | class Packagist extends Client 23 | { 24 | /** 25 | * Get a package's latest version. 26 | * 27 | * @param string $name 28 | * 29 | * @return string|void 30 | */ 31 | public function getLatestVersion($name) 32 | { 33 | try { 34 | $package = $this->get($name); 35 | 36 | $versions = array_map(function ($version) { 37 | return $version->getVersion(); 38 | }, $package->getVersions()); 39 | 40 | return Version::latest($versions); 41 | } catch (ClientErrorResponseException $e) { 42 | return; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Vincent Klaiber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/OutputStyle.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class OutputStyle extends SymfonyStyle 14 | { 15 | /** 16 | * Columize an array. 17 | * 18 | * @param array $data 19 | * 20 | * @return void 21 | */ 22 | public function columns(array $data = []) 23 | { 24 | $climate = new CLImate(); 25 | $climate->columns($data); 26 | } 27 | 28 | /** 29 | * Get the diff between the current and latest version. 30 | * 31 | * @param string $current 32 | * @param string $latest 33 | * 34 | * @return string 35 | */ 36 | public function versionDiff($current, $latest) 37 | { 38 | $needle = 0; 39 | 40 | while ($needle < strlen($current) && $needle < strlen($latest)) { 41 | if ($current[$needle] !== $latest[$needle]) { 42 | break; 43 | } 44 | 45 | $needle++; 46 | } 47 | 48 | return substr($latest, 0, $needle).''.substr($latest, $needle).''; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vinkla/climb", 3 | "description": "A Composer version manager tool", 4 | "license": "MIT", 5 | "keywords": ["composer", "version", "cli", "command"], 6 | "authors": [ 7 | { 8 | "name": "Vincent Klaiber", 9 | "email": "hello@vinkla.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^5.5.9 || ^7.0", 14 | "composer/semver": "^1.1", 15 | "knplabs/packagist-api": "^1.3", 16 | "league/climate": "^3.2", 17 | "symfony/console": "^2.6 || ^3.0", 18 | "symfony/process": "^2.6 || ^3.0" 19 | }, 20 | "require-dev": { 21 | "mockery/mockery": "^0.9.4", 22 | "phpunit/phpunit": "^4.8 || ^5.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Vinkla\\Climb\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Vinkla\\Tests\\Climb\\": "tests/" 32 | } 33 | }, 34 | "bin": [ 35 | "bin/climb" 36 | ], 37 | "config": { 38 | "preferred-install": "dist" 39 | }, 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "1.0-dev" 43 | } 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true 47 | } 48 | -------------------------------------------------------------------------------- /src/Version.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb; 13 | 14 | use Composer\Semver\Comparator; 15 | use Composer\Semver\Semver; 16 | use Composer\Semver\VersionParser; 17 | 18 | /** 19 | * This is the version class. 20 | * 21 | * @author Vincent Klaiber 22 | * @author Jens Segers 23 | */ 24 | class Version extends Semver 25 | { 26 | /** 27 | * Normalize the version number. 28 | * 29 | * @param string $version 30 | * 31 | * @return string 32 | */ 33 | public static function normalize($version) 34 | { 35 | $version = preg_replace('/^(v|\^|~)/', '', $version); 36 | 37 | if (preg_match('/^\d\.\d$/', $version)) { 38 | $version .= '.0'; 39 | } 40 | 41 | return $version; 42 | } 43 | 44 | /** 45 | * Get the last version number from a list of versions. 46 | * 47 | * @param array $versions 48 | * 49 | * @return string 50 | */ 51 | public static function latest(array $versions) 52 | { 53 | // Normalize version numbers. 54 | $versions = array_map(function ($version) { 55 | return static::normalize($version); 56 | }, $versions); 57 | 58 | // Get the highest version number. 59 | $latest = array_reduce($versions, function ($carry, $item) { 60 | // Skip unstable versions. 61 | if (VersionParser::parseStability($item) !== 'stable') { 62 | return $carry; 63 | } 64 | 65 | return Comparator::greaterThan($carry, $item) ? $carry : $item; 66 | }, '0.0.0'); 67 | 68 | return $latest; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Ladder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb; 13 | 14 | /** 15 | * This is the ladder class. 16 | * 17 | * @author Vincent Klaiber 18 | * @author Jens Segers 19 | */ 20 | class Ladder 21 | { 22 | /** 23 | * The composer instance. 24 | * 25 | * @var \Vinkla\Climb\Composer 26 | */ 27 | protected $composer; 28 | 29 | /** 30 | * Create a new ladder instance. 31 | * 32 | * @param string|null $directory 33 | * 34 | * @return void 35 | */ 36 | public function __construct($directory = null) 37 | { 38 | $this->composer = new Composer($directory ?: getcwd()); 39 | } 40 | 41 | /** 42 | * Get outdated packages with their current and latest version. 43 | * 44 | * @param array $excluded 45 | * 46 | * @return array 47 | */ 48 | public function getOutdatedPackages(array $excluded = []) 49 | { 50 | // Get all installed and required packages. 51 | $installed = $this->composer->getInstalledPackages(); 52 | $required = $this->composer->getRequiredPackages(); 53 | 54 | $outdated = []; 55 | 56 | // Get the installed version number of the required packages. 57 | $packages = array_intersect_key($installed, $required); 58 | 59 | foreach ($packages as $package) { 60 | $name = $package['name']; 61 | $version = Version::normalize($package['version']); 62 | $prettyVersion = $required[$name]['version']; 63 | $devDependency = $package['devDependency']; 64 | 65 | if (in_array($name, $excluded)) { 66 | continue; 67 | } 68 | 69 | $package = new Package($name, $version, $prettyVersion, $devDependency); 70 | 71 | if ($package->isOutdated()) { 72 | $outdated[] = $package; 73 | } 74 | } 75 | 76 | return $outdated; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Composer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Composer 14 | { 15 | /** 16 | * The directory path. 17 | * 18 | * @var string 19 | */ 20 | protected $directory; 21 | 22 | /** 23 | * Create a new composer instance. 24 | * 25 | * @param string $directory 26 | * 27 | * @return void 28 | */ 29 | public function __construct($directory) 30 | { 31 | $this->directory = $directory; 32 | } 33 | 34 | /** 35 | * Get installed package versions. 36 | * 37 | * @throws \LogicException 38 | * 39 | * @return array 40 | */ 41 | public function getInstalledPackages() 42 | { 43 | $packages = []; 44 | 45 | $content = $this->getFileContents('composer.lock'); 46 | 47 | foreach (['packages', 'packages-dev'] as $key) { 48 | if (!isset($content[$key])) { 49 | continue; 50 | } 51 | 52 | foreach ($content[$key] as $package) { 53 | $name = $package['name']; 54 | 55 | $packages[$name] = [ 56 | 'name' => $name, 57 | 'version' => $package['version'], 58 | 'devDependency' => $key === 'packages-dev', 59 | ]; 60 | } 61 | } 62 | 63 | if (empty($packages)) { 64 | throw new LogicException('We couldn\'t find any installed packages.'); 65 | } 66 | 67 | return $packages; 68 | } 69 | 70 | /** 71 | * Get required package versions. 72 | * 73 | * @throws \LogicException 74 | * 75 | * @return array 76 | */ 77 | public function getRequiredPackages() 78 | { 79 | $packages = []; 80 | 81 | $content = $this->getFileContents('composer.json'); 82 | 83 | foreach (['require', 'require-dev'] as $key) { 84 | if (!isset($content[$key])) { 85 | continue; 86 | } 87 | 88 | foreach ($content[$key] as $name => $version) { 89 | if (!strstr($name, '/')) { 90 | continue; 91 | } 92 | 93 | $packages[$name] = [ 94 | 'name' => $name, 95 | 'version' => $version, 96 | 'devDependency' => $key === 'require-dev', 97 | ]; 98 | } 99 | } 100 | 101 | if (empty($packages)) { 102 | throw new LogicException('We couldn\'t find any required packages.'); 103 | } 104 | 105 | return $packages; 106 | } 107 | 108 | /** 109 | * Get file content. 110 | * 111 | * @param string $file 112 | * 113 | * @throws \InvalidArgumentException 114 | * 115 | * @return array 116 | */ 117 | protected function getFileContents($file) 118 | { 119 | $filePath = $this->directory.'/'.$file; 120 | 121 | if (!file_exists($filePath)) { 122 | throw new InvalidArgumentException("The file [$filePath] does not exist."); 123 | } 124 | 125 | return json_decode(file_get_contents($filePath), true); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Commands/UpdateCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb\Commands; 13 | 14 | use Symfony\Component\Console\Input\InputInterface; 15 | use Symfony\Component\Console\Input\InputOption; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | use Symfony\Component\Process\Process; 18 | use Vinkla\Climb\Ladder; 19 | use Vinkla\Climb\OutputStyle; 20 | 21 | /** 22 | * This is the update command class. 23 | * 24 | * @author Vincent Klaiber 25 | * @author Joseph Cohen 26 | */ 27 | final class UpdateCommand extends Command 28 | { 29 | /** 30 | * The composer command to run. 31 | * 32 | * @return string 33 | */ 34 | protected $command = 'composer require'; 35 | 36 | /** 37 | * Configure the update command. 38 | * 39 | * @return void 40 | */ 41 | protected function configure() 42 | { 43 | $this->setName('update'); 44 | $this->setDescription('Update composer.json dependencies versions'); 45 | $this->addOption('all', null, InputOption::VALUE_NONE, 'Run update on breaking versions'); 46 | $this->addOption('directory', null, InputOption::VALUE_REQUIRED, 'Composer files directory'); 47 | $this->addOption('global', 'g', InputOption::VALUE_NONE, 'Run on globally installed packages'); 48 | } 49 | 50 | /** 51 | * Execute the command. 52 | * 53 | * @param \Symfony\Component\Console\Input\InputInterface $input 54 | * @param \Symfony\Component\Console\Output\OutputInterface $output 55 | * 56 | * @return int 57 | */ 58 | public function execute(InputInterface $input, OutputInterface $output) 59 | { 60 | $io = new OutputStyle($input, $output); 61 | 62 | $composerPath = $this->getComposerPathFromInput($input); 63 | 64 | $ladder = new Ladder($composerPath); 65 | 66 | $packages = $ladder->getOutdatedPackages(); 67 | 68 | $io->newLine(); 69 | 70 | if (!count($packages)) { 71 | $io->write('All dependencies match the latest package versions :)'); 72 | $io->newLine(); 73 | 74 | return 1; 75 | } 76 | 77 | $outdated = []; 78 | $upgradable = []; 79 | 80 | foreach ($packages as $package) { 81 | if ($package->isUpgradable()) { 82 | $upgradable[$package->getName()] = $package; 83 | } else { 84 | $outdated[$package->getName()] = $package; 85 | } 86 | } 87 | 88 | if ($input->getOption('all')) { 89 | $upgradable = array_merge($upgradable, $outdated); 90 | } 91 | 92 | if (empty($upgradable)) { 93 | $io->warning('Nothing to install or update, did you forget the flag --all?'); 94 | 95 | return 1; 96 | } 97 | 98 | foreach ($upgradable as $package) { 99 | $command = $input->getOption('global') ? 'composer global require' : 'composer require'; 100 | 101 | $command .= sprintf(' %s=^%s', $package->getName(), $package->getLatestVersion()); 102 | 103 | if ($package->getDevDependency()) { 104 | $command .= ' --dev'; 105 | } 106 | 107 | $process = new Process($command, $composerPath, array_merge($_SERVER, $_ENV), null, null); 108 | 109 | $process->run(function ($type, $line) use ($io) { 110 | $io->write($line); 111 | }); 112 | 113 | $io->newLine(); 114 | } 115 | 116 | return 0; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Package.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb; 13 | 14 | use Composer\Semver\Comparator; 15 | 16 | /** 17 | * This is the package class. 18 | * 19 | * @author Vincent Klaiber 20 | */ 21 | class Package 22 | { 23 | /** 24 | * The package's name. 25 | * 26 | * @var string 27 | */ 28 | protected $name; 29 | 30 | /** 31 | * The package's version. 32 | * 33 | * @var string 34 | */ 35 | protected $version; 36 | 37 | /** 38 | * The package's latest version. 39 | * 40 | * @var string 41 | */ 42 | protected $latestVersion; 43 | 44 | /** 45 | * The package's non-normalized version. 46 | * 47 | * @var string 48 | */ 49 | protected $prettyVersion; 50 | 51 | /** 52 | * The package dev dependency boolean. 53 | * 54 | * @var string 55 | */ 56 | protected $devDependency; 57 | 58 | /** 59 | * The Packagist instance. 60 | * 61 | * @var \Vinkla\Climb\Packagist 62 | */ 63 | protected $packagist; 64 | 65 | /** 66 | * Create a new package instance. 67 | * 68 | * @param string $name 69 | * @param string $version 70 | * @param string $prettyVersion 71 | * @param bool $devDependency 72 | * 73 | * @return void 74 | */ 75 | public function __construct($name, $version, $prettyVersion, $devDependency) 76 | { 77 | $this->name = $name; 78 | $this->version = $version; 79 | $this->prettyVersion = $prettyVersion; 80 | $this->devDependency = $devDependency; 81 | $this->packagist = new Packagist(); 82 | } 83 | 84 | /** 85 | * Check if the package is outdated. 86 | * 87 | * @return bool 88 | */ 89 | public function isOutdated() 90 | { 91 | return Comparator::lessThan($this->version, $this->getLatestVersion()); 92 | } 93 | 94 | /** 95 | * Check if the package is upgradable. 96 | * 97 | * @return bool 98 | */ 99 | public function isUpgradable() 100 | { 101 | return Version::satisfies($this->getLatestVersion(), $this->prettyVersion); 102 | } 103 | 104 | /** 105 | * Get the package's name. 106 | * 107 | * @return string 108 | */ 109 | public function getName() 110 | { 111 | return $this->name; 112 | } 113 | 114 | /** 115 | * Get the package's version. 116 | * 117 | * @return string 118 | */ 119 | public function getVersion() 120 | { 121 | return $this->version; 122 | } 123 | 124 | /** 125 | * Get the package's non-normalized version. 126 | * 127 | * @return string 128 | */ 129 | public function getPrettyVersion() 130 | { 131 | return $this->prettyVersion; 132 | } 133 | 134 | /** 135 | * Get latest version. 136 | * 137 | * @return string|void 138 | */ 139 | public function getLatestVersion() 140 | { 141 | if (!empty($this->latestVersion)) { 142 | return $this->latestVersion; 143 | } 144 | 145 | $this->latestVersion = $this->packagist->getLatestVersion($this->name); 146 | 147 | return $this->latestVersion; 148 | } 149 | 150 | /** 151 | * Get the package dev dependency boolean. 152 | * 153 | * @return string 154 | */ 155 | public function getDevDependency() 156 | { 157 | return $this->devDependency; 158 | } 159 | 160 | /** 161 | * Set the packagist instance. 162 | * 163 | * @param $packagist 164 | */ 165 | public function setPackagist($packagist) 166 | { 167 | $this->packagist = $packagist; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Commands/OutdatedCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Vinkla\Climb\Commands; 13 | 14 | use Symfony\Component\Console\Input\InputInterface; 15 | use Symfony\Component\Console\Input\InputOption; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | use Vinkla\Climb\Ladder; 18 | use Vinkla\Climb\OutputStyle; 19 | 20 | /** 21 | * This is the outdated command class. 22 | * 23 | * @author Vincent Klaiber 24 | * @author Jens Segers 25 | */ 26 | final class OutdatedCommand extends Command 27 | { 28 | /** 29 | * Configure the outdated command. 30 | * 31 | * @return void 32 | */ 33 | protected function configure() 34 | { 35 | $this->setName('outdated'); 36 | $this->setDescription('Find newer versions of dependencies than what your composer.json allows'); 37 | $this->addOption('directory', null, InputOption::VALUE_REQUIRED, 'Composer files directory'); 38 | $this->addOption('global', 'g', InputOption::VALUE_NONE, 'Run on globally installed packages'); 39 | $this->addOption('outdated', null, InputOption::VALUE_NONE, 'Only check outdated dependencies'); 40 | $this->addOption('upgradable', null, InputOption::VALUE_NONE, 'Only check upgradable dependencies'); 41 | $this->addOption('exclude', null, InputOption::VALUE_REQUIRED, 'Exclude packages by their names (comma separated)'); 42 | } 43 | 44 | /** 45 | * Execute the command. 46 | * 47 | * @param \Symfony\Component\Console\Input\InputInterface $input 48 | * @param \Symfony\Component\Console\Output\OutputInterface $output 49 | * 50 | * @return int 51 | */ 52 | public function execute(InputInterface $input, OutputInterface $output) 53 | { 54 | $excluded = []; 55 | 56 | if ($input->getOption('exclude')) { 57 | $excluded = explode(',', $input->getOption('exclude')); 58 | } 59 | 60 | $io = new OutputStyle($input, $output); 61 | 62 | $composerPath = $this->getComposerPathFromInput($input); 63 | 64 | $ladder = new Ladder($composerPath); 65 | 66 | $packages = $ladder->getOutdatedPackages($excluded); 67 | 68 | $io->newLine(); 69 | 70 | $statusCode = 0; 71 | 72 | if (!count($packages)) { 73 | $io->writeln('All dependencies match the latest package versions :)'); 74 | $io->newLine(); 75 | 76 | return $statusCode; 77 | } 78 | 79 | $outdated = []; 80 | $upgradable = []; 81 | 82 | foreach ($packages as $package) { 83 | $diff = $io->versionDiff($package->getVersion(), $package->getLatestVersion()); 84 | 85 | if ($package->isUpgradable()) { 86 | $upgradable[] = [$package->getName(), $package->getVersion(), '→', $diff]; 87 | } else { 88 | $outdated[] = [$package->getName(), $package->getVersion(), '→', $diff]; 89 | } 90 | } 91 | 92 | if (count($outdated) && !$input->getOption('upgradable')) { 93 | $statusCode = 1; 94 | 95 | $io->columns($outdated); 96 | $io->newLine(); 97 | } 98 | 99 | if (count($upgradable) && !$input->getOption('outdated')) { 100 | $statusCode = 1; 101 | 102 | $io->writeln('The following dependencies are satisfied by their declared version constraint, but the installed versions are behind. You can install the latest versions without modifying your composer.json file by using composer update.'); 103 | $io->newLine(); 104 | $io->columns($upgradable); 105 | $io->newLine(); 106 | } 107 | 108 | return $statusCode; 109 | } 110 | } 111 | --------------------------------------------------------------------------------