├── LICENSE ├── composer.json └── src └── UpdateHelper ├── ComposerPlugin.php ├── NotUpdateInterfaceInstanceException.php ├── UpdateHelper.php └── UpdateHelperInterface.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 https://github.com/pug-php 4 | Copyright (c) 2017 https://github.com/kylekatarnls 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kylekatarnls/update-helper", 3 | "description": "Update helper", 4 | "type": "composer-plugin", 5 | "license": "MIT", 6 | "minimum-stability": "dev", 7 | "prefer-stable": true, 8 | "authors": [ 9 | { 10 | "name": "Kyle", 11 | "email": "kylekatarnls@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.0", 16 | "composer-plugin-api": "^1.1.0 || ^2.0.0" 17 | }, 18 | "require-dev": { 19 | "composer/composer": "2.0.x-dev || ^2.0.0-dev", 20 | "phpunit/phpunit": ">=4.8.35 <6.0", 21 | "codeclimate/php-test-reporter": "dev-master" 22 | }, 23 | "autoload": { 24 | "psr-0": { 25 | "UpdateHelper\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "UpdateHelper\\Tests\\": "tests/UpdateHelper/" 31 | } 32 | }, 33 | "scripts": { 34 | "post-install-cmd": [ 35 | "UpdateHelper\\UpdateHelper::check" 36 | ], 37 | "post-update-cmd": [ 38 | "UpdateHelper\\UpdateHelper::check" 39 | ], 40 | "post-package-install": [ 41 | "UpdateHelper\\UpdateHelper::check" 42 | ], 43 | "post-package-update": [ 44 | "UpdateHelper\\UpdateHelper::check" 45 | ] 46 | }, 47 | "extra": { 48 | "class": "UpdateHelper\\ComposerPlugin" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/UpdateHelper/ComposerPlugin.php: -------------------------------------------------------------------------------- 1 | io = $io; 18 | } 19 | 20 | public function deactivate(Composer $composer, IOInterface $io) 21 | { 22 | // Not needed 23 | } 24 | 25 | public function uninstall(Composer $composer, IOInterface $io) 26 | { 27 | // Not needed 28 | } 29 | 30 | public static function getSubscribedEvents() 31 | { 32 | return array( 33 | 'post-autoload-dump' => array( 34 | array('onAutoloadDump', 0), 35 | ), 36 | ); 37 | } 38 | 39 | public function onAutoloadDump(Event $event) 40 | { 41 | if (!class_exists('UpdateHelper\\UpdateHelper')) { 42 | return; 43 | } 44 | 45 | UpdateHelper::check($event); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/UpdateHelper/NotUpdateInterfaceInstanceException.php: -------------------------------------------------------------------------------- 1 | read(); 40 | } catch (Exception $e) { 41 | $dependencyConfig = null; 42 | } 43 | 44 | if (is_array($dependencyConfig) && isset($dependencyConfig['extra'], $dependencyConfig['extra'][$key])) { 45 | $classes[$file] = $dependencyConfig['extra'][$key]; 46 | } 47 | } 48 | 49 | protected static function getUpdateHelperConfig(Composer $composer, $key = null) 50 | { 51 | $vendorDir = $composer->getConfig()->get('vendor-dir'); 52 | 53 | $npm = array(); 54 | 55 | foreach (scandir($vendorDir) as $namespace) { 56 | if ($namespace === '.' || $namespace === '..' || !is_dir($directory = $vendorDir.DIRECTORY_SEPARATOR.$namespace)) { 57 | continue; 58 | } 59 | 60 | foreach (scandir($directory) as $dependency) { 61 | if ($dependency === '.' || $dependency === '..' || !is_dir($subDirectory = $directory.DIRECTORY_SEPARATOR.$dependency)) { 62 | continue; 63 | } 64 | 65 | static::appendConfig($npm, $subDirectory, $key); 66 | } 67 | } 68 | 69 | static::appendConfig($npm, dirname($vendorDir), $key); 70 | 71 | return $npm; 72 | } 73 | 74 | /** 75 | * @param Event $event 76 | * @param IOInterface $io 77 | * @param Composer $composer 78 | * @param string[] $subClasses 79 | */ 80 | protected static function checkHelper($event, IOInterface $io, $composer, $class) 81 | { 82 | if (!is_string($class) || !class_exists($class)) { 83 | throw new NotUpdateInterfaceInstanceException(); 84 | } 85 | 86 | try { 87 | $helper = new $class(); 88 | } catch (Exception $e) { 89 | throw new InvalidArgumentException($e->getMessage(), 1000, $e); 90 | } catch (Throwable $e) { 91 | throw new InvalidArgumentException($e->getMessage(), 1000, $e); 92 | } 93 | 94 | if (!($helper instanceof UpdateHelperInterface)) { 95 | throw new NotUpdateInterfaceInstanceException(); 96 | } 97 | 98 | $helper->check(new static($event, $io, $composer)); 99 | } 100 | 101 | /** 102 | * @param string $file 103 | * @param Event $event 104 | * @param IOInterface $io 105 | * @param Composer $composer 106 | * @param string[] $subClasses 107 | */ 108 | protected static function checkFileHelpers($file, $event, IOInterface $io, $composer, array $subClasses) 109 | { 110 | foreach ($subClasses as $class) { 111 | try { 112 | static::checkHelper($event, $io, $composer, $class); 113 | } catch (InvalidArgumentException $exception) { 114 | $io->writeError(static::getErrorMessage($exception, $file, $class)); 115 | continue; 116 | } 117 | } 118 | } 119 | 120 | protected static function getErrorMessage(InvalidArgumentException $exception, $file, $class) 121 | { 122 | if ($exception instanceof NotUpdateInterfaceInstanceException) { 123 | return 'UpdateHelper error in '.$file.":\n".JsonFile::encode($class).' is not an instance of UpdateHelperInterface.'; 124 | } 125 | 126 | return 'UpdateHelper error: '.$exception->getPrevious()->getMessage(). 127 | "\nFile: ".$exception->getPrevious()->getFile(). 128 | "\nLine:".$exception->getPrevious()->getLine(). 129 | "\n\n".$exception->getPrevious()->getTraceAsString(); 130 | } 131 | 132 | public static function check(Event $event) 133 | { 134 | if (!($event instanceof ScriptEvent) && !($event instanceof PackageEvent)) { 135 | return; 136 | } 137 | 138 | $io = $event->getIO(); 139 | $composer = $event->getComposer(); 140 | $autoload = $composer->getConfig()->get('vendor-dir').'/autoload.php'; 141 | 142 | if (file_exists($autoload)) { 143 | include_once $autoload; 144 | } 145 | 146 | $classes = static::getUpdateHelperConfig($composer); 147 | 148 | foreach ($classes as $file => $subClasses) { 149 | static::checkFileHelpers($file, $event, $io, $composer, (array) $subClasses); 150 | } 151 | } 152 | 153 | public function __construct(Event $event, IOInterface $io = null, Composer $composer = null) 154 | { 155 | $this->event = $event; 156 | $this->io = $io ?: (method_exists($event, 'getIO') ? $event->getIO() : null); 157 | $this->composer = $composer ?: (method_exists($event, 'getComposer') ? $event->getComposer() : null); 158 | 159 | if ($this->composer && 160 | ($directory = $this->composer->getConfig()->get('archive-dir')) && 161 | file_exists($file = $directory.'/composer.json') 162 | ) { 163 | $this->composerFilePath = $file; 164 | $this->file = new JsonFile($this->composerFilePath); 165 | $this->dependencies = $this->file->read(); 166 | } 167 | } 168 | 169 | /** 170 | * @return JsonFile 171 | */ 172 | public function getFile() 173 | { 174 | return $this->file; 175 | } 176 | 177 | /** 178 | * @return string 179 | */ 180 | public function getComposerFilePath() 181 | { 182 | return $this->composerFilePath; 183 | } 184 | 185 | /** 186 | * @return Composer 187 | */ 188 | public function getComposer() 189 | { 190 | return $this->composer; 191 | } 192 | 193 | /** 194 | * @return Event 195 | */ 196 | public function getEvent() 197 | { 198 | return $this->event; 199 | } 200 | 201 | /** 202 | * @return IOInterface|null 203 | */ 204 | public function getIo() 205 | { 206 | return $this->io; 207 | } 208 | 209 | /** 210 | * @return array 211 | */ 212 | public function getDependencies() 213 | { 214 | return $this->dependencies; 215 | } 216 | 217 | /** 218 | * @return array 219 | */ 220 | public function getDevDependencies() 221 | { 222 | return isset($this->dependencies['require-dev']) ? $this->dependencies['require-dev'] : array(); 223 | } 224 | 225 | /** 226 | * @return array 227 | */ 228 | public function getProdDependencies() 229 | { 230 | return isset($this->dependencies['require']) ? $this->dependencies['require'] : array(); 231 | } 232 | 233 | /** 234 | * @return array 235 | */ 236 | public function getFlattenDependencies() 237 | { 238 | return array_merge($this->getDevDependencies(), $this->getProdDependencies()); 239 | } 240 | 241 | /** 242 | * @param string $dependency 243 | * 244 | * @return bool 245 | */ 246 | public function hasAsDevDependency($dependency) 247 | { 248 | return isset($this->dependencies['require-dev'][$dependency]); 249 | } 250 | 251 | /** 252 | * @param string $dependency 253 | * 254 | * @return bool 255 | */ 256 | public function hasAsProdDependency($dependency) 257 | { 258 | return isset($this->dependencies['require'][$dependency]); 259 | } 260 | 261 | /** 262 | * @param string $dependency 263 | * 264 | * @return bool 265 | */ 266 | public function hasAsDependency($dependency) 267 | { 268 | return $this->hasAsDevDependency($dependency) || $this->hasAsProdDependency($dependency); 269 | } 270 | 271 | /** 272 | * @param string $dependency 273 | * @param string $version 274 | * 275 | * @return bool 276 | */ 277 | public function isDependencyAtLeast($dependency, $version) 278 | { 279 | if ($this->hasAsProdDependency($dependency)) { 280 | return Semver::satisfies($version, $this->dependencies['require'][$dependency]); 281 | } 282 | 283 | if ($this->hasAsDevDependency($dependency)) { 284 | return Semver::satisfies($version, $this->dependencies['require-dev'][$dependency]); 285 | } 286 | 287 | return false; 288 | } 289 | 290 | /** 291 | * @param string $dependency 292 | * @param string $version 293 | * 294 | * @return bool 295 | */ 296 | public function isDependencyLesserThan($dependency, $version) 297 | { 298 | return !$this->isDependencyAtLeast($dependency, $version); 299 | } 300 | 301 | /** 302 | * @param string $dependency 303 | * @param string $version 304 | * @param array $environments 305 | * 306 | * @throws Exception 307 | * 308 | * @return $this 309 | */ 310 | public function setDependencyVersion($dependency, $version, $environments = array('require', 'require-dev')) 311 | { 312 | return $this->setDependencyVersions(array($dependency => $version), $environments); 313 | } 314 | 315 | /** 316 | * @param array $dependencies 317 | * @param array $environments 318 | * 319 | * @throws Exception 320 | * 321 | * @return $this 322 | */ 323 | public function setDependencyVersions($dependencies, $environments = array('require', 'require-dev')) 324 | { 325 | if (!$this->composerFilePath) { 326 | throw new RuntimeException('No composer instance detected.'); 327 | } 328 | 329 | $touched = false; 330 | 331 | foreach ($environments as $environment) { 332 | foreach ($dependencies as $dependency => $version) { 333 | if (isset($this->dependencies[$environment], $this->dependencies[$environment][$dependency])) { 334 | $this->dependencies[$environment][$dependency] = $version; 335 | $touched = true; 336 | } 337 | } 338 | } 339 | 340 | if ($touched) { 341 | if (!$this->composerFilePath) { 342 | throw new RuntimeException('composer.json not found (custom vendor-dir are not yet supported).'); 343 | } 344 | 345 | $file = new JsonFile($this->composerFilePath); 346 | $file->write($this->dependencies); 347 | } 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * @return $this 354 | */ 355 | public function update() 356 | { 357 | $output = shell_exec('composer update --no-scripts'); 358 | 359 | if (!empty($output)) { 360 | $this->write($output); 361 | } 362 | 363 | return $this; 364 | } 365 | 366 | /** 367 | * @param string|array $text 368 | */ 369 | public function write($text) 370 | { 371 | if ($this->io) { 372 | $this->io->write($text); 373 | 374 | return; 375 | } 376 | 377 | if (is_array($text)) { 378 | $text = implode("\n", $text); 379 | } 380 | 381 | echo $text; 382 | } 383 | 384 | /** 385 | * @return bool 386 | */ 387 | public function isInteractive() 388 | { 389 | return $this->io && $this->io->isInteractive(); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/UpdateHelper/UpdateHelperInterface.php: -------------------------------------------------------------------------------- 1 |