├── .composer-auth.json ├── src ├── PuliPluginException.php ├── PuliRunnerException.php ├── PuliPlugin.php ├── PuliPackage.php ├── PuliRunner.php └── PuliPluginImpl.php ├── LICENSE ├── composer.json ├── CHANGELOG.md └── README.md /.composer-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "github-oauth": { 3 | "github.com": "PLEASE DO NOT USE THIS TOKEN IN YOUR OWN PROJECTS/FORKS", 4 | "github.com": "This token is reserved for testing the puli/* repositories", 5 | "github.com": "a9debbffdd953ee9b3b82dbc3b807cde2086bb86" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/PuliPluginException.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 Puli\ComposerPlugin; 13 | 14 | use RuntimeException; 15 | 16 | /** 17 | * Thrown when an error occurs during the execution of the Puli plugin. 18 | * 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | class PuliPluginException extends RuntimeException 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Bernhard Schussek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puli/composer-plugin", 3 | "type": "composer-plugin", 4 | "description": "Integrates Composer into the Puli package manager.", 5 | "homepage": "http://puli.io", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Bernhard Schussek", 10 | "email": "bschussek@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": "^5.3.9|^7.0", 15 | "composer-plugin-api": "^1.0", 16 | "symfony/filesystem": "^2.3|^3.0", 17 | "symfony/process": "^2.3|^3.0", 18 | "webmozart/path-util": "^2.2", 19 | "webmozart/assert": "^1.0" 20 | }, 21 | "require-dev": { 22 | "composer/composer": "^1.0-alpha10", 23 | "phpunit/phpunit": "^4.6", 24 | "webmozart/glob": "^4.1", 25 | "sebastian/version": "^1.0.1", 26 | "puli/repository": "^1.0-beta9" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Puli\\ComposerPlugin\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Puli\\ComposerPlugin\\Tests\\": "tests/" 36 | } 37 | }, 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "1.0-dev" 41 | }, 42 | "class": "Puli\\ComposerPlugin\\PuliPlugin" 43 | }, 44 | "support": { 45 | "issues": "https://github.com/puli/issues/issues" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/PuliRunnerException.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 Puli\ComposerPlugin; 13 | 14 | use RuntimeException; 15 | use Symfony\Component\Process\Process; 16 | 17 | /** 18 | * Thrown when an error occurs while running Puli. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class PuliRunnerException extends RuntimeException 25 | { 26 | /** 27 | * @var string 28 | */ 29 | private $command; 30 | 31 | /** 32 | * @var int 33 | */ 34 | private $exitCode; 35 | 36 | /** 37 | * @var string 38 | */ 39 | private $shortError; 40 | 41 | /** 42 | * @var string 43 | */ 44 | private $fullError; 45 | 46 | public static function forProcess(Process $process) 47 | { 48 | $shortError = $fullError = $process->getErrorOutput(); 49 | 50 | if (preg_match('~^fatal: (.+)$~', $shortError, $matches)) { 51 | $shortError = trim($matches[1]); 52 | } elseif (preg_match('~^\s+\[([\w\\\\]+\\\\)?(\w+)\]\s+(.+)\r?\n\r?\n\S~s', $shortError, $matches)) { 53 | $shortError = trim($matches[2]).': '.trim($matches[3]); 54 | } 55 | 56 | return new static($process->getCommandLine(), $process->getExitCode(), $shortError, $fullError); 57 | } 58 | 59 | /** 60 | * @param string $command 61 | * @param int $exitCode 62 | * @param string $shortError 63 | * @param string $fullError 64 | */ 65 | public function __construct($command, $exitCode, $shortError, $fullError) 66 | { 67 | parent::__construct(sprintf( 68 | 'An error occurred while running: %s (status %s): %s', 69 | $command, 70 | $exitCode, 71 | $shortError 72 | )); 73 | 74 | $this->command = $command; 75 | $this->exitCode = $exitCode; 76 | $this->shortError = $shortError; 77 | $this->fullError = $fullError; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getCommand() 84 | { 85 | return $this->command; 86 | } 87 | 88 | /** 89 | * @return int 90 | */ 91 | public function getExitCode() 92 | { 93 | return $this->exitCode; 94 | } 95 | 96 | /** 97 | * @return string 98 | */ 99 | public function getShortError() 100 | { 101 | return $this->shortError; 102 | } 103 | 104 | /** 105 | * @return string 106 | */ 107 | public function getFullError() 108 | { 109 | return $this->fullError; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/PuliPlugin.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 Puli\ComposerPlugin; 13 | 14 | use Composer\Composer; 15 | use Composer\EventDispatcher\EventSubscriberInterface; 16 | use Composer\IO\IOInterface; 17 | use Composer\Plugin\PluginInterface; 18 | use Composer\Script\Event; 19 | use Composer\Script\ScriptEvents; 20 | 21 | /** 22 | * A Puli plugin for Composer. 23 | * 24 | * The plugin updates the Puli package repository based on the Composer 25 | * packages whenever `composer install` or `composer update` is executed. 26 | * 27 | * @since 1.0 28 | * 29 | * @author Bernhard Schussek 30 | */ 31 | class PuliPlugin implements PluginInterface, EventSubscriberInterface 32 | { 33 | /** 34 | * @var PuliPluginImpl 35 | */ 36 | private $impl; 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public static function getSubscribedEvents() 42 | { 43 | return array( 44 | ScriptEvents::POST_INSTALL_CMD => 'listen', 45 | ScriptEvents::POST_UPDATE_CMD => 'listen', 46 | ScriptEvents::PRE_AUTOLOAD_DUMP => 'listen', 47 | ScriptEvents::POST_AUTOLOAD_DUMP => 'listen', 48 | ); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function activate(Composer $composer, IOInterface $io) 55 | { 56 | $composer->getEventDispatcher()->addSubscriber($this); 57 | } 58 | 59 | /** 60 | * Listens to Composer events. 61 | * 62 | * This method is very minimalist on purpose. We want to load the actual 63 | * implementation only after updating the Composer packages so that we get 64 | * the updated version (if available). 65 | * 66 | * @param Event $event The Composer event. 67 | */ 68 | public function listen(Event $event) 69 | { 70 | // Plugin has been uninstalled 71 | if (!file_exists(__FILE__) || !file_exists(__DIR__.'/PuliPluginImpl.php')) { 72 | return; 73 | } 74 | 75 | // Load the implementation only after updating Composer so that we get 76 | // the new version of the plugin when a new one was installed 77 | if (null === $this->impl) { 78 | $this->impl = new PuliPluginImpl($event); 79 | } 80 | 81 | switch ($event->getName()) { 82 | case ScriptEvents::PRE_AUTOLOAD_DUMP: 83 | $this->impl->preAutoloadDump(); 84 | break; 85 | case ScriptEvents::POST_AUTOLOAD_DUMP: 86 | $this->impl->postAutoloadDump(); 87 | break; 88 | 89 | case ScriptEvents::POST_INSTALL_CMD: 90 | case ScriptEvents::POST_UPDATE_CMD: 91 | $this->impl->postInstall(); 92 | break; 93 | } 94 | } 95 | 96 | public function setPluginImpl(PuliPluginImpl $impl) 97 | { 98 | $this->impl = $impl; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | * 1.0.0-beta10 (2016-03-31) 5 | 6 | * fixed: added symfony/filesystem dependency in non-dev environments 7 | 8 | * 1.0.0-beta9 (2016-01-14) 9 | 10 | * checking the version of puli/cli now to find out whether the plugin is compatible 11 | * fixed plugin to disable itself after uninstall 12 | * the .puli directory is now removed on "composer install" and "composer update" 13 | to fix updates to incompatible generated files 14 | * made compatible with Symfony 3.0 15 | 16 | * 1.0.0-beta8 (2015-10-05) 17 | 18 | * fixed setting of the "bootstrap-file" key 19 | * fixed escaping of arguments when calling shell commands on Windows 20 | 21 | * 1.0.0-beta7 (2015-08-24) 22 | 23 | * adapted to puli/cli 1.0.0-beta7 24 | * the environment ("dev" or "prod") of Composer packages is now passed to Puli 25 | * improved Windows compatibility 26 | 27 | * 1.0.0-beta6 (2015-08-12) 28 | 29 | * added Puli components as dependencies 30 | * fixed handling of line endings on Windows 31 | * the config key "bootstrap-file" is now automatically set to "autoload.php", 32 | if not set by the user 33 | * fixed running of .bat scripts on Windows 34 | 35 | * 1.0.0-beta5 (2015-05-29) 36 | 37 | * the plugin is now independent of puli/manager and uses a "puli"/"puli.phar" 38 | executable instead 39 | * upgraded to webmozart/path-util 2.0 40 | 41 | * 1.0.0-beta4 (2015-04-13) 42 | 43 | * removed usage of the --force option when calling "puli build" 44 | * updated use of changed Config constants 45 | 46 | * 1.0.0-beta3 (2015-03-19) 47 | 48 | * fixed error: Constant PULI_FACTORY_CLASS already defined 49 | * disabled plugins during Composer hook to fix error "PluginClass not found" 50 | * the Puli factory is now automatically regenerated after composer update/install 51 | * enabled plugins during "puli build" in the Composer hook 52 | 53 | * 1.0.0-beta2 (2015-01-27) 54 | 55 | * fixed: packages with a moved install path are reinstalled now 56 | * added `IOLogger` 57 | * errors happening during package installation are logged to the screen now 58 | instead of causing Composer to abort 59 | * errors happening during package loading are logged to the screen now instead 60 | of being silently ignored 61 | * fixed: packages installed by the user are not overwritten if a package with 62 | the same name but a different path is loaded through Composer 63 | 64 | * 1.0.0-beta (2015-01-13) 65 | 66 | * removed `ComposerPlugin`. You need to remove the plugin from your puli.json 67 | file, otherwise you'll have an exception. The package names are now set 68 | during installation by `PuliPlugin`. 69 | * the generated `PuliFactory` is now added to the class-map autoloader 70 | * the class name of the generated `PuliFactory` is now declared in the 71 | `PULI_FACTORY_CLASS` constant in the autoloader 72 | * the package name defined in composer.json is now copied to puli.json 73 | * moved code to `Puli\ComposerPlugin` namespace 74 | 75 | * 1.0.0-alpha2 (2014-12-03) 76 | 77 | * removed `PathMatcher`; its logic was moved to "webmozart/path-util" 78 | * moved `RepositoryLoader`, `OverrideConflictException` and 79 | `ResourceDefinitionException` to "puli/repository-manager" 80 | * moved code to `Puli\Extension\Composer` namespace 81 | * added `ComposerPlugin` for Puli 82 | 83 | * 1.0.0-alpha1 (2014-02-05) 84 | 85 | * first alpha release 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Puli Plugin for Composer 2 | ============================ 3 | 4 | [![Build Status](https://travis-ci.org/puli/composer-plugin.svg?branch=master)](https://travis-ci.org/puli/composer-plugin) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/ahk24l3m2tahc9ih/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/composer-plugin/branch/master) 6 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/puli/composer-plugin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/puli/composer-plugin/?branch=master) 7 | [![Latest Stable Version](https://poser.pugx.org/puli/composer-plugin/v/stable.svg)](https://packagist.org/packages/puli/composer-plugin) 8 | [![Total Downloads](https://poser.pugx.org/puli/composer-plugin/downloads.svg)](https://packagist.org/packages/puli/composer-plugin) 9 | [![Dependency Status](https://www.versioneye.com/php/puli:composer-plugin/1.0.0/badge.svg)](https://www.versioneye.com/php/puli:composer-plugin/1.0.0) 10 | 11 | Latest release: [1.0.0-beta10](https://packagist.org/packages/puli/composer-plugin#1.0.0-beta10) 12 | 13 | PHP >= 5.3.9 14 | 15 | This plugin integrates [Composer] with the [Puli Manager]. Whenever you install 16 | or update your Composer dependencies, a [Puli resource repository] and 17 | [discovery] are built from the puli.json files of all installed packages: 18 | 19 | ```json 20 | { 21 | "path-mappings": { 22 | "/acme/blog": "resources" 23 | } 24 | } 25 | ``` 26 | 27 | You can load the built repository/discovery in your code: 28 | 29 | ```php 30 | $factoryClass = PULI_FACTORY_CLASS; 31 | $factory = new $factoryClass(); 32 | 33 | // Fetch resources from the repository 34 | $repo = $factory->createRepository(); 35 | 36 | echo $repo->get('/acme/blog/config/config.yml')->getBody(); 37 | 38 | // Find resources by binding type 39 | $discovery = $factory->createFactory($repo); 40 | 41 | foreach ($discovery->findBindings('doctrine/xml-mapping') as $binding) { 42 | foreach ($binding->getResources() as $resource) { 43 | // do something... 44 | } 45 | } 46 | ``` 47 | 48 | Authors 49 | ------- 50 | 51 | * [Bernhard Schussek] a.k.a. [@webmozart] 52 | * [The Community Contributors] 53 | 54 | Installation 55 | ------------ 56 | 57 | Follow the [Installation guide] to install Puli in your project. 58 | 59 | Documentation 60 | ------------- 61 | 62 | Read [the Puli Documentation] to learn more about Puli. 63 | 64 | Contribute 65 | ---------- 66 | 67 | Contributions to are very welcome! 68 | 69 | * Report any bugs or issues you find on the [issue tracker]. 70 | * You can grab the source code at Puli’s [Git repository]. 71 | 72 | Support 73 | ------- 74 | 75 | If you are having problems, send a mail to bschussek@gmail.com or shout out to 76 | [@webmozart] on Twitter. 77 | 78 | License 79 | ------- 80 | 81 | All contents of this package are licensed under the [MIT license]. 82 | 83 | [Bernhard Schussek]: http://webmozarts.com 84 | [The Community Contributors]: https://github.com/puli/composer-plugin/graphs/contributors 85 | [Puli Manager]: https://github.com/puli/manager 86 | [Puli resource repository]: https://github.com/puli/repository 87 | [discovery]: https://github.com/puli/discovery 88 | [Composer]: https://getcomposer.org 89 | [Installation guide]: http://docs.puli.io/en/latest/installation.html 90 | [the Puli Documentation]: http://docs.puli.io/en/latest/index.html 91 | [issue tracker]: https://github.com/puli/issues/issues 92 | [Git repository]: https://github.com/puli/composer-plugin 93 | [@webmozart]: https://twitter.com/webmozart 94 | [MIT license]: LICENSE 95 | -------------------------------------------------------------------------------- /src/PuliPackage.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 Puli\ComposerPlugin; 13 | 14 | use Webmozart\Assert\Assert; 15 | 16 | /** 17 | * A Puli package. 18 | * 19 | * This class acts as data transfer object (DTO) for results delivered by the 20 | * "puli package" command. 21 | * 22 | * @since 1.0 23 | * 24 | * @author Bernhard Schussek 25 | */ 26 | class PuliPackage 27 | { 28 | /** 29 | * State: The package is enabled. 30 | */ 31 | const STATE_ENABLED = 'enabled'; 32 | 33 | /** 34 | * State: The package was not found on the filesystem. 35 | */ 36 | const STATE_NOT_FOUND = 'not-found'; 37 | 38 | /** 39 | * State: The package could not be loaded. 40 | */ 41 | const STATE_NOT_LOADABLE = 'not-loadable'; 42 | 43 | /** 44 | * Environment: The production environment. 45 | */ 46 | const ENV_PROD = 'prod'; 47 | 48 | /** 49 | * Environment: The development environment. 50 | */ 51 | const ENV_DEV = 'dev'; 52 | 53 | private static $states = array( 54 | self::STATE_ENABLED, 55 | self::STATE_NOT_FOUND, 56 | self::STATE_NOT_LOADABLE, 57 | ); 58 | 59 | private static $envs = array( 60 | self::ENV_PROD, 61 | self::ENV_DEV, 62 | ); 63 | 64 | /** 65 | * @var string 66 | */ 67 | private $name; 68 | 69 | /** 70 | * @var string 71 | */ 72 | private $installerName; 73 | 74 | /** 75 | * @var string 76 | */ 77 | private $installPath; 78 | 79 | /** 80 | * @var string 81 | */ 82 | private $state; 83 | 84 | /** 85 | * @var string 86 | */ 87 | private $env; 88 | 89 | /** 90 | * Creates a new package DTO. 91 | * 92 | * @param string $name The package name. 93 | * @param string $installerName The name of the installer. 94 | * @param string $installPath The absolute install path. 95 | * @param string $state One of the STATE_* constants in this class. 96 | * @param string $env The environment that the package is 97 | * installed in. 98 | */ 99 | public function __construct($name, $installerName, $installPath, $state, $env) 100 | { 101 | Assert::stringNotEmpty($name, 'The package name must be a non-empty string. Got: %s'); 102 | Assert::string($installerName, 'The installer name must be a string. Got: %s'); 103 | Assert::stringNotEmpty($installPath, 'The install path must be a non-empty string. Got: %s'); 104 | Assert::oneOf($state, self::$states, 'The package state must be one of %2$s. Got: %s'); 105 | Assert::oneOf($env, self::$envs, 'The package environment must be one of %2$s. Got: %s'); 106 | 107 | $this->name = $name; 108 | $this->installerName = $installerName; 109 | $this->installPath = $installPath; 110 | $this->state = $state; 111 | $this->env = $env; 112 | } 113 | 114 | /** 115 | * Returns the package name. 116 | * 117 | * @return string The package name. 118 | */ 119 | public function getName() 120 | { 121 | return $this->name; 122 | } 123 | 124 | /** 125 | * Returns the name of the package installer. 126 | * 127 | * @return string The installer name. 128 | */ 129 | public function getInstallerName() 130 | { 131 | return $this->installerName; 132 | } 133 | 134 | /** 135 | * Returns the absolute path to where the package is installed. 136 | * 137 | * @return string The install path. 138 | */ 139 | public function getInstallPath() 140 | { 141 | return $this->installPath; 142 | } 143 | 144 | /** 145 | * Returns the state of the package. 146 | * 147 | * @return string One of the STATE_* constants in this class. 148 | */ 149 | public function getState() 150 | { 151 | return $this->state; 152 | } 153 | 154 | /** 155 | * Returns the environment that the package is installed in. 156 | * 157 | * @return string One of the ENV_* constants in this class. 158 | */ 159 | public function getEnvironment() 160 | { 161 | return $this->env; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/PuliRunner.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 Puli\ComposerPlugin; 13 | 14 | use RuntimeException; 15 | use Symfony\Component\Process\ExecutableFinder; 16 | use Symfony\Component\Process\PhpExecutableFinder; 17 | use Symfony\Component\Process\Process; 18 | use Symfony\Component\Process\ProcessUtils; 19 | 20 | /** 21 | * Executes the "puli" command. 22 | * 23 | * @since 1.0 24 | * 25 | * @author Bernhard Schussek 26 | */ 27 | class PuliRunner 28 | { 29 | /** 30 | * @var string 31 | */ 32 | private $puli; 33 | 34 | /** 35 | * Creates a new runner. 36 | * 37 | * @param string|null $binDir The path to Composer's "bin-dir". 38 | */ 39 | public function __construct($binDir = null) 40 | { 41 | $phpFinder = new PhpExecutableFinder(); 42 | 43 | if (!($php = $phpFinder->find())) { 44 | throw new RuntimeException('The "php" command could not be found.'); 45 | } 46 | 47 | $finder = new ExecutableFinder(); 48 | 49 | // Search: 50 | // 1. in the current working directory 51 | // 2. in Composer's "bin-dir" 52 | // 3. in the system path 53 | $searchPath = array_merge(array(getcwd()), (array) $binDir); 54 | 55 | // Search "puli.phar" in the PATH and the current directory 56 | if (!($puli = $this->find('puli.phar', $searchPath, $finder))) { 57 | // Search "puli" in the PATH and Composer's "bin-dir" 58 | if (!($puli = $this->find('puli', $searchPath, $finder))) { 59 | throw new RuntimeException('The "puli"/"puli.phar" command could not be found.'); 60 | } 61 | } 62 | 63 | // Fix slashes 64 | $php = strtr($php, '\\', '/'); 65 | $puli = strtr($puli, '\\', '/'); 66 | 67 | $content = file_get_contents($puli, null, null, -1, 18); 68 | 69 | if ($content === '#!/usr/bin/env php' || 0 === strpos($content, 'puli = ProcessUtils::escapeArgument($php).' '.ProcessUtils::escapeArgument($puli); 71 | } else { 72 | $this->puli = ProcessUtils::escapeArgument($puli); 73 | } 74 | } 75 | 76 | /** 77 | * Returns the command used to execute Puli. 78 | * 79 | * @return string The "puli" command. 80 | */ 81 | public function getPuliCommand() 82 | { 83 | return $this->puli; 84 | } 85 | 86 | /** 87 | * Runs a Puli command. 88 | * 89 | * @param string $command The Puli command to run. 90 | * @param string[] $args Arguments to quote and insert into the command. 91 | * For each key "key" in this array, the placeholder 92 | * "%key%" should be present in the command string. 93 | * 94 | * @return string The lines of the output. 95 | */ 96 | public function run($command, array $args = array()) 97 | { 98 | $replacements = array(); 99 | 100 | foreach ($args as $key => $arg) { 101 | $replacements['%'.$key.'%'] = ProcessUtils::escapeArgument($arg); 102 | } 103 | 104 | // Disable colorization so that we can process the output 105 | // Enable exception traces by using the "-vv" switch 106 | $fullCommand = sprintf('%s %s --no-ansi -vv', $this->puli, strtr($command, $replacements)); 107 | 108 | $process = new Process($fullCommand); 109 | $process->run(); 110 | 111 | if (!$process->isSuccessful()) { 112 | throw PuliRunnerException::forProcess($process); 113 | } 114 | 115 | // Normalize line endings across systems 116 | return str_replace("\r\n", "\n", $process->getOutput()); 117 | } 118 | 119 | private function find($name, array $dirs, ExecutableFinder $finder) 120 | { 121 | $suffixes = array(''); 122 | 123 | if ('\\' === DIRECTORY_SEPARATOR) { 124 | $suffixes[] = '.bat'; 125 | } 126 | 127 | // The finder first looks in the system directories and then in the 128 | // user-defined ones. We want to check the user-defined ones first. 129 | foreach ($dirs as $dir) { 130 | foreach ($suffixes as $suffix) { 131 | $file = $dir.DIRECTORY_SEPARATOR.$name.$suffix; 132 | 133 | if (is_file($file) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) { 134 | return $file; 135 | } 136 | } 137 | } 138 | 139 | return $finder->find($name); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/PuliPluginImpl.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 Puli\ComposerPlugin; 13 | 14 | use Composer\Composer; 15 | use Composer\Config; 16 | use Composer\IO\IOInterface; 17 | use Composer\Package\AliasPackage; 18 | use Composer\Package\PackageInterface; 19 | use Composer\Script\Event; 20 | use Exception; 21 | use RuntimeException; 22 | use Symfony\Component\Filesystem\Filesystem; 23 | use Webmozart\PathUtil\Path; 24 | 25 | /** 26 | * Implementation of the Puli plugin. 27 | * 28 | * This class is separate from the main {@link PuliPlugin} class so that it can 29 | * be loaded lazily after updating the sources of this package in the project 30 | * that requires the package. 31 | * 32 | * @author Bernhard Schussek 33 | */ 34 | class PuliPluginImpl 35 | { 36 | /** 37 | * The version of the Puli plugin. 38 | */ 39 | const VERSION = '@package_version@'; 40 | 41 | /** 42 | * The minimum version of the Puli CLI. 43 | */ 44 | const MIN_CLI_VERSION = '1.0.0-beta10'; 45 | 46 | /** 47 | * The maximum version of the Puli CLI. 48 | */ 49 | const MAX_CLI_VERSION = '1.999.99999'; 50 | 51 | /** 52 | * The name of the installer. 53 | */ 54 | const INSTALLER_NAME = 'composer'; 55 | 56 | /** 57 | * @var Composer 58 | */ 59 | private $composer; 60 | 61 | /** 62 | * @var IOInterface 63 | */ 64 | private $io; 65 | 66 | /** 67 | * @var Config 68 | */ 69 | private $config; 70 | 71 | /** 72 | * @var bool 73 | */ 74 | private $isDev; 75 | 76 | /** 77 | * @var PuliRunner 78 | */ 79 | private $puliRunner; 80 | 81 | /** 82 | * @var string 83 | */ 84 | private $rootDir; 85 | 86 | /** 87 | * @var bool 88 | */ 89 | private $runPreAutoloadDump = true; 90 | 91 | /** 92 | * @var bool 93 | */ 94 | private $runPostAutoloadDump = true; 95 | 96 | /** 97 | * @var bool 98 | */ 99 | private $runPostInstall = true; 100 | 101 | /** 102 | * @var bool 103 | */ 104 | private $initialized = false; 105 | 106 | /** 107 | * @var string 108 | */ 109 | private $autoloadFile; 110 | 111 | public function __construct(Event $event, PuliRunner $puliRunner = null) 112 | { 113 | $this->composer = $event->getComposer(); 114 | $this->io = $event->getIO(); 115 | $this->config = $this->composer->getConfig(); 116 | $this->isDev = $event->isDevMode(); 117 | $this->puliRunner = $puliRunner; 118 | $this->rootDir = Path::normalize(getcwd()); 119 | 120 | $vendorDir = $this->config->get('vendor-dir'); 121 | 122 | // On TravisCI, $vendorDir is a relative path. Probably an old Composer 123 | // build or something. Usually, $vendorDir should be absolute already. 124 | $vendorDir = Path::makeAbsolute($vendorDir, $this->rootDir); 125 | 126 | $this->autoloadFile = $vendorDir.'/autoload.php'; 127 | } 128 | 129 | public function preAutoloadDump() 130 | { 131 | if (!$this->initialized) { 132 | $this->initialize(); 133 | } 134 | 135 | // This method is called twice. Run it only once. 136 | if (!$this->runPreAutoloadDump) { 137 | return; 138 | } 139 | 140 | $this->runPreAutoloadDump = false; 141 | 142 | try { 143 | $factoryClass = $this->getConfigKey('factory.in.class'); 144 | $factoryFile = $this->getConfigKey('factory.in.file'); 145 | } catch (PuliRunnerException $e) { 146 | $this->printWarning('Could not load Puli configuration', $e); 147 | 148 | return; 149 | } 150 | 151 | $factoryFile = Path::makeAbsolute($factoryFile, $this->rootDir); 152 | 153 | $autoload = $this->composer->getPackage()->getAutoload(); 154 | $autoload['classmap'][] = $factoryFile; 155 | 156 | $this->composer->getPackage()->setAutoload($autoload); 157 | 158 | if (!file_exists($factoryFile)) { 159 | $filesystem = new Filesystem(); 160 | // Let Composer find the factory class with a temporary stub 161 | 162 | $namespace = explode('\\', ltrim($factoryClass, '\\')); 163 | $className = array_pop($namespace); 164 | 165 | if (count($namespace)) { 166 | $stub = 'dumpFile($factoryFile, $stub); 172 | } 173 | } 174 | 175 | public function postAutoloadDump() 176 | { 177 | if (!$this->initialized) { 178 | $this->initialize(); 179 | } 180 | 181 | // This method is called twice. Run it only once. 182 | if (!$this->runPostAutoloadDump) { 183 | return; 184 | } 185 | 186 | $this->runPostAutoloadDump = false; 187 | 188 | try { 189 | $factoryClass = $this->getConfigKey('factory.in.class'); 190 | } catch (PuliRunnerException $e) { 191 | $this->printWarning('Could not load Puli configuration', $e); 192 | 193 | return; 194 | } 195 | 196 | $this->insertFactoryClassConstant($this->autoloadFile, $factoryClass); 197 | $this->setBootstrapFile($this->autoloadFile); 198 | } 199 | 200 | /** 201 | * Updates the Puli repository after Composer installations/updates. 202 | */ 203 | public function postInstall() 204 | { 205 | if (!$this->initialized) { 206 | $this->initialize(); 207 | } 208 | 209 | // This method is called twice. Run it only once. 210 | if (!$this->runPostInstall) { 211 | return; 212 | } 213 | 214 | $this->runPostInstall = false; 215 | 216 | $this->io->write('Synchronizing Puli with Composer'); 217 | 218 | $rootPackage = $this->composer->getPackage(); 219 | $composerPackages = $this->loadComposerPackages(); 220 | $prodPackageNames = $this->filterProdPackageNames($composerPackages, $rootPackage); 221 | $env = $this->isDev ? PuliPackage::ENV_DEV : PuliPackage::ENV_PROD; 222 | 223 | try { 224 | $puliPackages = $this->loadPuliPackages(); 225 | } catch (PuliRunnerException $e) { 226 | $this->printWarning('Could not load Puli packages', $e); 227 | 228 | return; 229 | } 230 | 231 | // Don't remove non-existing packages in production environment 232 | // Removed packages could be dev dependencies (i.e. "require-dev" 233 | // of the root package or "require" of another dev dependency), and 234 | // we can't find out whether they are since Composer doesn't load them 235 | if (PuliPackage::ENV_PROD !== $env) { 236 | $this->removeRemovedPackages($composerPackages, $puliPackages); 237 | } 238 | 239 | $this->installNewPackages($composerPackages, $prodPackageNames, $puliPackages); 240 | 241 | // Don't print warnings for non-existing packages in production 242 | if (PuliPackage::ENV_PROD !== $env) { 243 | $this->checkForNotFoundErrors($puliPackages); 244 | } 245 | 246 | $this->checkForNotLoadableErrors($puliPackages); 247 | $this->adoptComposerName($puliPackages); 248 | $this->removePuliDir(); 249 | $this->buildPuli(); 250 | } 251 | 252 | private function initialize() 253 | { 254 | if (!file_exists($this->autoloadFile)) { 255 | $filesystem = new Filesystem(); 256 | // Avoid problems if using the runner before autoload.php has been 257 | // generated 258 | $filesystem->dumpFile($this->autoloadFile, ''); 259 | } 260 | 261 | $this->initialized = true; 262 | 263 | // Keep the manually set runner 264 | if (null === $this->puliRunner) { 265 | try { 266 | // Add Composer's bin directory in case the "puli" executable is 267 | // installed with Composer 268 | $this->puliRunner = new PuliRunner($this->config->get('bin-dir')); 269 | } catch (RuntimeException $e) { 270 | $this->printWarning('Plugin initialization failed', $e); 271 | $this->runPreAutoloadDump = false; 272 | $this->runPostAutoloadDump = false; 273 | $this->runPostInstall = false; 274 | } 275 | } 276 | 277 | // Use the runner to verify if Puli has the right version 278 | try { 279 | $this->verifyPuliVersion(); 280 | } catch (RuntimeException $e) { 281 | $this->printWarning('Version check failed', $e); 282 | $this->runPreAutoloadDump = false; 283 | $this->runPostAutoloadDump = false; 284 | $this->runPostInstall = false; 285 | } 286 | } 287 | 288 | /** 289 | * @param PackageInterface[] $composerPackages 290 | * @param bool[] $prodPackageNames 291 | * @param PuliPackage[] $puliPackages 292 | */ 293 | private function installNewPackages(array $composerPackages, array $prodPackageNames, array &$puliPackages) 294 | { 295 | $installationManager = $this->composer->getInstallationManager(); 296 | 297 | foreach ($composerPackages as $packageName => $package) { 298 | if ($package instanceof AliasPackage) { 299 | $package = $package->getAliasOf(); 300 | } 301 | 302 | // We need to normalize the system-dependent paths returned by Composer 303 | $installPath = Path::normalize($installationManager->getInstallPath($package)); 304 | $env = isset($prodPackageNames[$packageName]) ? PuliPackage::ENV_PROD : PuliPackage::ENV_DEV; 305 | 306 | // Skip meta packages 307 | if ('' === $installPath) { 308 | continue; 309 | } 310 | 311 | if (isset($puliPackages[$packageName])) { 312 | $puliPackage = $puliPackages[$packageName]; 313 | 314 | // Only proceed if the install path or environment has changed 315 | if ($installPath === $puliPackage->getInstallPath() && $env === $puliPackage->getEnvironment()) { 316 | continue; 317 | } 318 | 319 | // Only remove packages installed by Composer 320 | if (self::INSTALLER_NAME === $puliPackage->getInstallerName()) { 321 | $this->io->write(sprintf( 322 | 'Reinstalling %s (%s) in %s', 323 | $packageName, 324 | Path::makeRelative($installPath, $this->rootDir), 325 | $env 326 | )); 327 | 328 | try { 329 | $this->removePackage($packageName); 330 | } catch (PuliRunnerException $e) { 331 | $this->printPackageWarning('Could not remove package "%s" (at "%s")', $packageName, $installPath, $e); 332 | 333 | continue; 334 | } 335 | } 336 | } else { 337 | $this->io->write(sprintf( 338 | 'Installing %s (%s) in %s', 339 | $packageName, 340 | Path::makeRelative($installPath, $this->rootDir), 341 | $env 342 | )); 343 | } 344 | 345 | try { 346 | $this->installPackage($installPath, $packageName, $env); 347 | } catch (PuliRunnerException $e) { 348 | $this->printPackageWarning('Could not install package "%s" (at "%s")', $packageName, $installPath, $e); 349 | 350 | continue; 351 | } 352 | 353 | $puliPackages[$packageName] = new PuliPackage( 354 | $packageName, 355 | self::INSTALLER_NAME, 356 | $installPath, 357 | PuliPackage::STATE_ENABLED, 358 | $env 359 | ); 360 | } 361 | } 362 | 363 | /** 364 | * @param PackageInterface[] $composerPackages 365 | * @param PuliPackage[] $puliPackages 366 | */ 367 | private function removeRemovedPackages(array $composerPackages, array &$puliPackages) 368 | { 369 | /** @var PuliPackage[] $notFoundPackages */ 370 | $notFoundPackages = array_filter($puliPackages, function (PuliPackage $package) { 371 | return PuliPackage::STATE_NOT_FOUND === $package->getState() 372 | && PuliPluginImpl::INSTALLER_NAME === $package->getInstallerName(); 373 | }); 374 | 375 | foreach ($notFoundPackages as $packageName => $package) { 376 | // Check whether package was only moved 377 | if (isset($composerPackages[$packageName])) { 378 | continue; 379 | } 380 | 381 | $this->io->write(sprintf( 382 | 'Removing %s (%s)', 383 | $packageName, 384 | Path::makeRelative($package->getInstallPath(), $this->rootDir) 385 | )); 386 | 387 | try { 388 | $this->removePackage($packageName); 389 | } catch (PuliRunnerException $e) { 390 | $this->printPackageWarning('Could not remove package "%s" (at "%s")', $packageName, $package->getInstallPath(), $e); 391 | 392 | continue; 393 | } 394 | 395 | unset($puliPackages[$packageName]); 396 | } 397 | } 398 | 399 | private function checkForNotFoundErrors(array $puliPackages) 400 | { 401 | /** @var PuliPackage[] $notFoundPackages */ 402 | $notFoundPackages = array_filter($puliPackages, 403 | function (PuliPackage $package) { 404 | return PuliPackage::STATE_NOT_FOUND === $package->getState() 405 | && PuliPluginImpl::INSTALLER_NAME === $package->getInstallerName(); 406 | }); 407 | 408 | foreach ($notFoundPackages as $package) { 409 | $this->printPackageWarning( 410 | 'The package "%s" (at "%s") could not be found', 411 | $package->getName(), 412 | $package->getInstallPath() 413 | ); 414 | } 415 | } 416 | 417 | private function checkForNotLoadableErrors(array $puliPackages) 418 | { 419 | /** @var PuliPackage[] $notLoadablePackages */ 420 | $notLoadablePackages = array_filter($puliPackages, function (PuliPackage $package) { 421 | return PuliPackage::STATE_NOT_LOADABLE === $package->getState() 422 | && PuliPluginImpl::INSTALLER_NAME === $package->getInstallerName(); 423 | }); 424 | 425 | foreach ($notLoadablePackages as $package) { 426 | $this->printPackageWarning( 427 | 'The package "%s" (at "%s") could not be loaded', 428 | $package->getName(), 429 | $package->getInstallPath() 430 | ); 431 | } 432 | } 433 | 434 | private function adoptComposerName(array $puliPackages) 435 | { 436 | $rootDir = $this->rootDir; 437 | 438 | /** @var PuliPackage[] $rootPackages */ 439 | $rootPackages = array_filter($puliPackages, function (PuliPackage $package) use ($rootDir) { 440 | return !$package->getInstallerName() && $rootDir === $package->getInstallPath(); 441 | }); 442 | 443 | if (0 === count($rootPackages)) { 444 | // This should never happen 445 | $this->printWarning('No root package could be found'); 446 | 447 | return; 448 | } 449 | 450 | if (count($rootPackages) > 1) { 451 | // This should never happen 452 | $this->printWarning('More than one root package was found'); 453 | 454 | return; 455 | } 456 | 457 | /** @var PuliPackage $rootPackage */ 458 | $rootPackage = reset($rootPackages); 459 | $name = $rootPackage->getName(); 460 | $newName = $this->composer->getPackage()->getName(); 461 | 462 | // Rename the root package after changing the name in composer.json 463 | if ($name !== $newName) { 464 | try { 465 | $this->renamePackage($name, $newName); 466 | } catch (PuliRunnerException $e) { 467 | $this->printWarning(sprintf( 468 | 'Could not rename root package to "%s"', 469 | $newName 470 | ), $e); 471 | } 472 | } 473 | } 474 | 475 | private function insertFactoryClassConstant($autoloadFile, $factoryClass) 476 | { 477 | if (!file_exists($autoloadFile)) { 478 | throw new PuliPluginException(sprintf( 479 | 'Could not adjust autoloader: The file %s was not found.', 480 | $autoloadFile 481 | )); 482 | } 483 | 484 | $this->io->write('Generating the "PULI_FACTORY_CLASS" constant'); 485 | 486 | $contents = file_get_contents($autoloadFile); 487 | $escFactoryClass = var_export($factoryClass, true); 488 | $constant = "if (!defined('PULI_FACTORY_CLASS')) {\n"; 489 | $constant .= sprintf(" define('PULI_FACTORY_CLASS', %s);\n", $escFactoryClass); 490 | $constant .= "}\n\n"; 491 | 492 | // Regex modifiers: 493 | // "m": \s matches newlines 494 | // "D": $ matches at EOF only 495 | // Translation: insert before the last "return" in the file 496 | $contents = preg_replace('/\n(?=return [^;]+;\s*$)/mD', "\n".$constant, 497 | $contents); 498 | 499 | file_put_contents($autoloadFile, $contents); 500 | } 501 | 502 | private function setBootstrapFile($autoloadFile) 503 | { 504 | $bootstrapFile = $this->getConfigKey('bootstrap-file'); 505 | 506 | // Don't change user-defined bootstrap files 507 | if (!empty($bootstrapFile)) { 508 | return; 509 | } 510 | 511 | $relAutoloadFile = Path::makeRelative($autoloadFile, $this->rootDir); 512 | 513 | $this->io->write(sprintf('Setting "bootstrap-file" to "%s"', $relAutoloadFile)); 514 | 515 | $this->setConfigKey('bootstrap-file', $relAutoloadFile); 516 | } 517 | 518 | /** 519 | * Loads Composer's currently installed packages. 520 | * 521 | * @return PackageInterface[] The installed packages indexed by their names. 522 | */ 523 | private function loadComposerPackages() 524 | { 525 | $repository = $this->composer->getRepositoryManager()->getLocalRepository(); 526 | $packages = array(); 527 | 528 | foreach ($repository->getPackages() as $package) { 529 | /* @var PackageInterface $package */ 530 | $packages[$package->getName()] = $package; 531 | } 532 | 533 | return $packages; 534 | } 535 | 536 | private function getConfigKey($key) 537 | { 538 | $value = trim($this->puliRunner->run('config %key% --parsed', array( 539 | 'key' => $key, 540 | ))); 541 | 542 | switch ($value) { 543 | case 'null': 544 | return null; 545 | case 'true': 546 | return true; 547 | case 'false': 548 | return false; 549 | default: 550 | return $value; 551 | } 552 | } 553 | 554 | private function setConfigKey($key, $value) 555 | { 556 | $this->puliRunner->run('config %key% %value%', array( 557 | 'key' => $key, 558 | 'value' => $value, 559 | )); 560 | } 561 | 562 | /** 563 | * @return PuliPackage[] 564 | */ 565 | private function loadPuliPackages() 566 | { 567 | $packages = array(); 568 | 569 | $output = $this->puliRunner->run('package --list --format %format%', array( 570 | 'format' => '%name%;%installer%;%install_path%;%state%;%env%', 571 | )); 572 | 573 | // PuliRunner replaces \r\n by \n for those Windows boxes 574 | foreach (explode("\n", $output) as $packageLine) { 575 | if (!$packageLine) { 576 | continue; 577 | } 578 | 579 | $packageParts = explode(';', $packageLine); 580 | 581 | $packages[$packageParts[0]] = new PuliPackage( 582 | $packageParts[0], 583 | $packageParts[1], 584 | $packageParts[2], 585 | $packageParts[3], 586 | $packageParts[4] 587 | ); 588 | } 589 | 590 | return $packages; 591 | } 592 | 593 | private function installPackage($installPath, $packageName, $env) 594 | { 595 | $env = PuliPackage::ENV_DEV === $env ? ' --dev' : ''; 596 | 597 | $this->puliRunner->run('package --install %path% %package_name% --installer %installer%'.$env, array( 598 | 'path' => $installPath, 599 | 'package_name' => $packageName, 600 | 'installer' => self::INSTALLER_NAME, 601 | )); 602 | } 603 | 604 | private function removePackage($packageName) 605 | { 606 | $this->puliRunner->run('package --delete %package_name%', array( 607 | 'package_name' => $packageName, 608 | )); 609 | } 610 | 611 | private function removePuliDir() 612 | { 613 | $relativePuliDir = rtrim($this->getConfigKey('puli-dir'), '/'); 614 | 615 | $puliDir = Path::makeAbsolute($relativePuliDir, $this->rootDir); 616 | 617 | // Only remove existing sub-directories of the root directory 618 | if (!file_exists($puliDir) || 0 !== strpos($puliDir, $this->rootDir.'/')) { 619 | return; 620 | } 621 | 622 | $this->io->write(sprintf('Deleting the "%s" directory', $relativePuliDir)); 623 | 624 | // Remove the .puli directory to prevent upgrade problems 625 | $filesystem = new Filesystem(); 626 | $filesystem->remove($puliDir); 627 | } 628 | 629 | private function buildPuli() 630 | { 631 | $this->io->write('Running "puli build"'); 632 | 633 | $this->puliRunner->run('build'); 634 | } 635 | 636 | private function renamePackage($name, $newName) 637 | { 638 | $this->puliRunner->run('package --rename %old_name% %new_name%', array( 639 | 'old_name' => $name, 640 | 'new_name' => $newName, 641 | )); 642 | } 643 | 644 | /** 645 | * @param $message 646 | * @param Exception|null $exception 647 | */ 648 | private function printWarning($message, Exception $exception = null) 649 | { 650 | if (!$exception) { 651 | $reasonPhrase = ''; 652 | } elseif ($this->io->isVerbose()) { 653 | $reasonPhrase = $exception instanceof PuliRunnerException 654 | ? $exception->getFullError() 655 | : $exception->getMessage()."\n\n".$exception->getTraceAsString(); 656 | } else { 657 | $reasonPhrase = $exception instanceof PuliRunnerException 658 | ? $exception->getShortError() 659 | : $exception->getMessage(); 660 | } 661 | 662 | $this->io->writeError(sprintf( 663 | 'Warning: %s%s', 664 | $message, 665 | $reasonPhrase ? ': '.$reasonPhrase : '.' 666 | )); 667 | } 668 | 669 | private function printPackageWarning($message, $packageName, $installPath, PuliRunnerException $exception = null) 670 | { 671 | $this->printWarning(sprintf( 672 | $message, 673 | $packageName, 674 | Path::makeRelative($installPath, $this->rootDir) 675 | ), $exception); 676 | } 677 | 678 | private function filterProdPackageNames(array $composerPackages, PackageInterface $package, array &$result = array()) 679 | { 680 | // Resolve aliases 681 | if ($package instanceof AliasPackage) { 682 | $package = $package->getAliasOf(); 683 | } 684 | 685 | // Package was processed already 686 | if (isset($result[$package->getName()])) { 687 | return $result; 688 | } 689 | 690 | $result[$package->getName()] = true; 691 | 692 | // Recursively filter package names 693 | foreach ($package->getRequires() as $packageName => $link) { 694 | if (isset($composerPackages[$packageName])) { 695 | $this->filterProdPackageNames($composerPackages, $composerPackages[$packageName], $result); 696 | } 697 | } 698 | 699 | return $result; 700 | } 701 | 702 | private function verifyPuliVersion() 703 | { 704 | $versionString = $this->puliRunner->run('-V'); 705 | 706 | if (!preg_match('~^Puli version (\S+)$~', $versionString, $matches)) { 707 | throw new RuntimeException(sprintf( 708 | 'Could not determine Puli version. "puli -V" returned: %s', 709 | $versionString 710 | )); 711 | } 712 | 713 | // the development build of the plugin is always considered compatible 714 | // with the development build of the CLI 715 | // Split strings to prevent replacement during release 716 | if ('@package_'.'version@' === self::VERSION && '@package_'.'version@' === $matches[1]) { 717 | return; 718 | } 719 | 720 | if (version_compare($matches[1], self::MIN_CLI_VERSION, '<')) { 721 | throw new RuntimeException(sprintf( 722 | 'Found an unsupported version of the Puli CLI: %s. Please '. 723 | 'upgrade to version %s or higher. You can also install the '. 724 | 'puli/cli dependency at version %s in your project.', 725 | $matches[1], 726 | self::MIN_CLI_VERSION, 727 | self::MIN_CLI_VERSION 728 | )); 729 | } 730 | 731 | if (version_compare($matches[1], self::MAX_CLI_VERSION, '>')) { 732 | throw new RuntimeException(sprintf( 733 | 'Found an unsupported version of the Puli CLI: %s. Please '. 734 | 'downgrade to a lower version than %s. You can also install '. 735 | 'the puli/cli dependency in your project.', 736 | $matches[1], 737 | self::MAX_CLI_VERSION 738 | )); 739 | } 740 | } 741 | } 742 | --------------------------------------------------------------------------------