├── PULL_REQUEST_TEMPLATE.md ├── src ├── Bowerphp │ ├── Util │ │ ├── ZipArchive.php │ │ ├── PackageNameVersionExtractor.php │ │ ├── Filesystem.php │ │ └── ErrorHandler.php │ ├── Command │ │ ├── Helper │ │ │ └── QuestionHelper.php │ │ ├── LookupCommand.php │ │ ├── ListCommand.php │ │ ├── SearchCommand.php │ │ ├── HelpCommand.php │ │ ├── UninstallCommand.php │ │ ├── CommandListCommand.php │ │ ├── UpdateCommand.php │ │ ├── InitCommand.php │ │ ├── InfoCommand.php │ │ ├── HomeCommand.php │ │ ├── Command.php │ │ └── InstallCommand.php │ ├── Installer │ │ ├── InstallerInterface.php │ │ └── Installer.php │ ├── Repository │ │ ├── RepositoryInterface.php │ │ └── GithubRepository.php │ ├── Package │ │ ├── PackageInterface.php │ │ └── Package.php │ ├── Config │ │ ├── ConfigInterface.php │ │ └── Config.php │ ├── Output │ │ └── BowerphpConsoleOutput.php │ ├── Console │ │ └── Application.php │ ├── Compiler.php │ └── Bowerphp.php └── bootstrap.php ├── LICENSE ├── bin └── bowerphp └── composer.json /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Bowerphp/Util/ZipArchive.php: -------------------------------------------------------------------------------- 1 | numFiles; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/bootstrap.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 | $includeIfExists = function ($file) { 13 | return file_exists($file) ? include $file : false; 14 | }; 15 | 16 | if ((!$loader = $includeIfExists(__DIR__ . '/../vendor/autoload.php')) && (!$loader = $includeIfExists(__DIR__ . '/../../../autoload.php'))) { 17 | $error = 'You must set up the project dependencies, run the following commands:' . PHP_EOL . 18 | 'curl -sS https://getcomposer.org/installer | php' . PHP_EOL . 19 | 'php composer.phar install' . PHP_EOL; 20 | 21 | throw new \Exception($error); 22 | } 23 | 24 | return $loader; 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Massimiliano Arione 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /bin/bowerphp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 45 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/Helper/QuestionHelper.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 Bowerphp\Command\Helper; 13 | 14 | use Symfony\Component\Console\Helper\QuestionHelper as BaseQuestionHelper; 15 | use Symfony\Component\Console\Question\Question; 16 | 17 | /** 18 | * Copied by Composer https://github.com/composer/composer 19 | */ 20 | class QuestionHelper extends BaseQuestionHelper 21 | { 22 | /** 23 | * Build text for asking a question. For example: 24 | * 25 | * "Do you want to continue [yes]:" 26 | * 27 | * @param string $question The question you want to ask 28 | * @param mixed $default Default value to add to message, if false no default will be shown 29 | * @param string $sep Separation char for between message and user input 30 | * 31 | * @return string 32 | */ 33 | public function getQuestion($question, $default = null, $sep = ':') 34 | { 35 | return null !== $default ? 36 | new Question(sprintf('%s [%s]%s ', $question, $default, $sep)) : 37 | new Question(sprintf('%s%s ', $question, $sep)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Bowerphp/Installer/InstallerInterface.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 Bowerphp\Util; 13 | 14 | /** 15 | * PackageNameVersionExtractor 16 | */ 17 | class PackageNameVersionExtractor 18 | { 19 | /** 20 | * @var string 21 | */ 22 | public $name; 23 | 24 | /** 25 | * @var string 26 | */ 27 | public $version; 28 | 29 | /** 30 | * @param string $name 31 | * @param string $version 32 | */ 33 | public function __construct($name, $version) 34 | { 35 | $this->name = $name; 36 | $this->version = $version; 37 | } 38 | 39 | /** 40 | * @param string $endpoint 41 | * @return PackageNameVersionExtractor 42 | */ 43 | public static function fromString($endpoint) 44 | { 45 | $map = explode('#', $endpoint); 46 | $name = isset($map[0]) ? $map[0] : $endpoint; 47 | $version = isset($map[1]) ? $map[1] : '*'; 48 | 49 | // Convert user/package shorthand to GitHub url 50 | if (preg_match('/^([-_a-z0-9]+)\/([-_a-z0-9]+)$/i', $name)) { 51 | $name = 'https://github.com/' . $name . '.git'; 52 | } 53 | 54 | return new self($name, $version); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Bowerphp/Util/Filesystem.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 Bowerphp\Util; 13 | 14 | use Symfony\Component\Filesystem\Exception\FileNotFoundException; 15 | use Symfony\Component\Filesystem\Filesystem as BaseFilesystem; 16 | 17 | /** 18 | * Filesystem 19 | */ 20 | class Filesystem extends BaseFilesystem 21 | { 22 | /** 23 | * Read a file 24 | * 25 | * @param string $filename 26 | * 27 | * @return string 28 | */ 29 | public function read($filename) 30 | { 31 | if (!is_readable($filename)) { 32 | throw new FileNotFoundException(sprintf('File "%s" does not exist.', $filename), 0, null, $filename); 33 | } 34 | 35 | return file_get_contents($filename); 36 | } 37 | 38 | /** 39 | * Write a file 40 | * 41 | * @param string $filename The file to be written to 42 | * @param string $content The data to write into the file 43 | * @param int $mode The file mode (octal) 44 | * 45 | * @throws \Symfony\Component\Filesystem\Exception\IOException If the file cannot be written to 46 | */ 47 | public function write($filename, $content, $mode = 0644) 48 | { 49 | $this->dumpFile($filename, $content); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Bowerphp/Repository/RepositoryInterface.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 Bowerphp\Util; 13 | 14 | use ErrorException; 15 | 16 | /** 17 | * Convert PHP errors into exceptions 18 | * Copied by Composer https://github.com/composer/composer 19 | */ 20 | class ErrorHandler 21 | { 22 | /** 23 | * Error handler 24 | * 25 | * @param int $level Level of the error raised 26 | * @param string $message Error message 27 | * @param string $file Filename that the error was raised in 28 | * @param int $line Line number the error was raised at 29 | * 30 | * @static 31 | * @throws \ErrorException 32 | */ 33 | public static function handle($level, $message, $file, $line) 34 | { 35 | // respect error_reporting being disabled 36 | if (0 === error_reporting()) { 37 | return; 38 | } 39 | 40 | if (ini_get('xdebug.scream')) { 41 | $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be" . 42 | "\na legitimately suppressed error that you were not supposed to see."; 43 | } 44 | 45 | throw new ErrorException($message, 0, $level, $file, $line); 46 | } 47 | 48 | /** 49 | * Register error handler 50 | * 51 | * @static 52 | */ 53 | public static function register() 54 | { 55 | set_error_handler([__CLASS__, 'handle']); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/LookupCommand.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 Bowerphp\Command; 13 | 14 | use Bowerphp\Util\PackageNameVersionExtractor; 15 | use Symfony\Component\Console\Input\InputArgument; 16 | use Symfony\Component\Console\Input\InputInterface; 17 | use Symfony\Component\Console\Output\OutputInterface; 18 | 19 | /** 20 | * Lookup 21 | */ 22 | class LookupCommand extends Command 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | protected function configure() 28 | { 29 | $this 30 | ->setName('lookup') 31 | ->setDescription('Look up a package URL by name') 32 | ->addArgument('package', InputArgument::REQUIRED, 'Choose a package.') 33 | ->setHelp(<<<'EOT' 34 | The %command.name% command is used for search with exact match the repository URL package 35 | 36 | php %command.full_name% packageName 37 | EOT 38 | ); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | protected function execute(InputInterface $input, OutputInterface $output) 45 | { 46 | $this->setGithubToken($output); 47 | 48 | $name = $input->getArgument('package'); 49 | 50 | $packageNameVersion = PackageNameVersionExtractor::fromString($name); 51 | 52 | $bowerphp = $this->getBowerphp($output); 53 | 54 | $package = $bowerphp->lookupPackage($packageNameVersion->name); 55 | 56 | $this->consoleOutput->writelnSearchOrLookup($package['name'], $package['url']); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/ListCommand.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 Bowerphp\Command; 13 | 14 | use Bowerphp\Installer\Installer; 15 | use Bowerphp\Util\ZipArchive; 16 | use Symfony\Component\Console\Input\InputInterface; 17 | use Symfony\Component\Console\Output\OutputInterface; 18 | use Symfony\Component\Finder\Finder; 19 | 20 | /** 21 | * This command shows a list of installed packages. 22 | * Not to be confused with original "list" command of Symfony, that has been 23 | * renamed to "list-commands" (see CommandListCommand.php) 24 | */ 25 | class ListCommand extends Command 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function configure() 31 | { 32 | $this 33 | ->setName('list') 34 | ->setDescription('Lists installed packages') 35 | ->setHelp(<<<'EOT' 36 | The %command.name% lists installed packages. 37 | 38 | %command.full_name% 39 | EOT 40 | ) 41 | ; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output) 48 | { 49 | $this->setGithubToken($output); 50 | 51 | $installer = new Installer($this->filesystem, new ZipArchive(), $this->config); 52 | $bowerphp = $this->getBowerphp($output); 53 | $packages = $bowerphp->getInstalledPackages($installer, new Finder()); 54 | 55 | foreach ($packages as $package) { 56 | $this->consoleOutput->writelnListPackage($package, $bowerphp); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beelab/bowerphp", 3 | "description": "An implementation of bower in PHP", 4 | "keywords": ["bower", "package", "dependency", "frontend"], 5 | "homepage": "https://bowerphp.github.io/", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Massimiliano Arione", 11 | "email": "massimiliano.arione@bee-lab.net" 12 | }, 13 | { 14 | "name": "Mauro D'Alatri", 15 | "email": "mauro.dalatri@bee-lab.net" 16 | }, 17 | { 18 | "name": "The Community", 19 | "homepage": "https://github.com/Bee-Lab/bowerphp/graphs/contributors" 20 | } 21 | ], 22 | "support": { 23 | "issues": "https://github.com/bee-lab/bowserphp/issues" 24 | }, 25 | "require": { 26 | "php": "^5.6|^7.0", 27 | "ext-fileinfo": "*", 28 | "ext-zip": "*", 29 | "knplabs/github-api": "^1.4", 30 | "samsonasik/package-versions": "^1.1", 31 | "symfony/console": "^3.4|^4.1", 32 | "symfony/filesystem": "^3.4|^4.1", 33 | "symfony/finder": "^3.4|^4.1", 34 | "symfony/process": "^3.4|^4.1", 35 | "vierbergenlars/php-semver": "^3.0" 36 | }, 37 | "require-dev": { 38 | "ext-phar": "*", 39 | "friendsofphp/php-cs-fixer": "^2.14", 40 | "mockery/mockery": "^1.0", 41 | "phpunit/phpunit": "^5.7|^6.0|^7.0" 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "Bowerphp\\": "src/Bowerphp" 46 | } 47 | }, 48 | "bin": ["bin/bowerphp"], 49 | "extra": { 50 | "branch-alias": { 51 | "dev-master": "1.0-dev" 52 | } 53 | }, 54 | "config": { 55 | "bin-dir": "bin", 56 | "sort-packages": true 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/SearchCommand.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 Bowerphp\Command; 13 | 14 | use Symfony\Component\Console\Input\InputArgument; 15 | use Symfony\Component\Console\Input\InputInterface; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | 18 | /** 19 | * Search 20 | */ 21 | class SearchCommand extends Command 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | protected function configure() 27 | { 28 | $this 29 | ->setName('search') 30 | ->setDescription('Search for a package by name') 31 | ->addArgument('name', InputArgument::REQUIRED, 'Name to search for.') 32 | ->setHelp(<<<'EOT' 33 | The %command.name% command searches for a package by name. 34 | 35 | php %command.full_name% name 36 | EOT 37 | ); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output) 44 | { 45 | $this->setGithubToken($output); 46 | 47 | $name = $input->getArgument('name'); 48 | 49 | $bowerphp = $this->getBowerphp($output); 50 | $packages = $bowerphp->searchPackages($name); 51 | 52 | if (empty($packages)) { 53 | $output->writeln('No results.'); 54 | } else { 55 | $output->writeln('Search results:' . PHP_EOL); 56 | $consoleOutput = $this->consoleOutput; 57 | array_walk($packages, function ($package) use ($consoleOutput) { 58 | $consoleOutput->writelnSearchOrLookup($package['name'], $package['url'], 4); 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/HelpCommand.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 Bowerphp\Command; 13 | 14 | use Symfony\Component\Console\Command\HelpCommand as SymfonyHelpCommand; 15 | use Symfony\Component\Console\Input\InputArgument; 16 | use Symfony\Component\Console\Input\InputOption; 17 | 18 | /** 19 | * This is just an extension of original HelpCommand, needed to remove the last line from help. 20 | * See also {@link https://github.com/Bee-Lab/bowerphp/issues/108}. 21 | */ 22 | class HelpCommand extends SymfonyHelpCommand 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | protected function configure() 28 | { 29 | $this->ignoreValidationErrors(); 30 | 31 | $this 32 | ->setName('help') 33 | ->setDefinition([ 34 | new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), 35 | new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), 36 | new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), 37 | new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), 38 | ]) 39 | ->setDescription('Displays help for a command') 40 | ->setHelp(<<<'EOF' 41 | The %command.name% command displays help for a given command: 42 | 43 | php %command.full_name% list 44 | 45 | You can also output the help in other formats by using the --format option: 46 | 47 | php %command.full_name% --format=xml list 48 | EOF 49 | ) 50 | ; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/UninstallCommand.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 Bowerphp\Command; 13 | 14 | use Bowerphp\Installer\Installer; 15 | use Bowerphp\Package\Package; 16 | use Bowerphp\Util\PackageNameVersionExtractor; 17 | use Bowerphp\Util\ZipArchive; 18 | use Symfony\Component\Console\Input\InputArgument; 19 | use Symfony\Component\Console\Input\InputInterface; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | 22 | /** 23 | * Uninstall 24 | */ 25 | class UninstallCommand extends Command 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function configure() 31 | { 32 | $this 33 | ->setName('uninstall') 34 | ->setDescription('Uninstalls a single specified package') 35 | ->addArgument('package', InputArgument::REQUIRED, 'Choose a package.') 36 | ->setHelp(<<<'EOT' 37 | The %command.name% command uninstall a package. 38 | 39 | php %command.full_name% packageName 40 | EOT 41 | ) 42 | ; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | protected function execute(InputInterface $input, OutputInterface $output) 49 | { 50 | $this->setGithubToken($output); 51 | 52 | $packageName = $input->getArgument('package'); 53 | 54 | $bowerphp = $this->getBowerphp($output); 55 | 56 | try { 57 | $installer = new Installer($this->filesystem, new ZipArchive(), $this->config); 58 | 59 | $packageNameVersion = PackageNameVersionExtractor::fromString($packageName); 60 | 61 | $package = new Package($packageNameVersion->name, $packageNameVersion->version); 62 | $bowerphp->uninstallPackage($package, $installer); 63 | } catch (\RuntimeException $e) { 64 | $output->writeln(sprintf('%s', $e->getMessage())); 65 | 66 | return 1; 67 | } 68 | 69 | $output->writeln(''); 70 | 71 | return 0; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/CommandListCommand.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 Bowerphp\Command; 13 | 14 | use Symfony\Component\Console\Command\ListCommand; 15 | use Symfony\Component\Console\Input\InputArgument; 16 | use Symfony\Component\Console\Input\InputDefinition; 17 | use Symfony\Component\Console\Input\InputOption; 18 | 19 | /** 20 | * CommandList: this command shows a list of available commands. 21 | * It's just an extension of original ListCommand, needed to change "list" name 22 | */ 23 | class CommandListCommand extends ListCommand 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function configure() 29 | { 30 | $this 31 | ->setName('list-commands') 32 | ->setDefinition($this->createDefinition()) 33 | ->setDescription('Lists commands') 34 | ->setHelp(<<<'EOF' 35 | The %command.name% command lists all commands: 36 | 37 | php %command.full_name% 38 | 39 | You can also display the commands for a specific namespace: 40 | 41 | php %command.full_name% test 42 | 43 | You can also output the information in other formats by using the --format option: 44 | 45 | php %command.full_name% --format=xml 46 | 47 | It's also possible to get raw list of commands (useful for embedding command runner): 48 | 49 | php %command.full_name% --raw 50 | EOF 51 | ) 52 | ; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | private function createDefinition() 59 | { 60 | return new InputDefinition([ 61 | new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), 62 | new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'), 63 | new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), 64 | new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output list in other formats', 'txt'), 65 | ]); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Bowerphp/Package/PackageInterface.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 Bowerphp\Command; 13 | 14 | use Bowerphp\Installer\Installer; 15 | use Bowerphp\Package\Package; 16 | use Bowerphp\Util\ZipArchive; 17 | use RuntimeException; 18 | use Symfony\Component\Console\Input\InputArgument; 19 | use Symfony\Component\Console\Input\InputInterface; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | 22 | /** 23 | * Inspired by Composer https://github.com/composer/composer 24 | */ 25 | class UpdateCommand extends Command 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function configure() 31 | { 32 | $this 33 | ->setName('update') 34 | ->setDescription('Update the project dependencies from the bower.json file or a single specified package') 35 | ->addArgument('package', InputArgument::OPTIONAL, 'Choose a package.') 36 | ->setHelp(<<<'EOT' 37 | The %command.name% command reads the bower.json file from 38 | the current directory, processes it, and downloads and installs all the 39 | libraries and dependencies outlined in that file. 40 | 41 | php %command.full_name% 42 | 43 | If an optional package name is passed, only that package is updated. 44 | 45 | php %command.full_name% packageName 46 | 47 | EOT 48 | ); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | protected function execute(InputInterface $input, OutputInterface $output) 55 | { 56 | $this->setGithubToken($output); 57 | 58 | $packageName = $input->getArgument('package'); 59 | $installer = new Installer($this->filesystem, new ZipArchive(), $this->config); 60 | 61 | try { 62 | $bowerphp = $this->getBowerphp($output); 63 | if (is_null($packageName)) { 64 | $bowerphp->updatePackages($installer); 65 | } else { 66 | $bowerphp->updatePackage(new Package($packageName), $installer); 67 | } 68 | } catch (RuntimeException $e) { 69 | throw new RuntimeException($e->getMessage()); 70 | } 71 | $output->writeln(''); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/InitCommand.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 Bowerphp\Command; 13 | 14 | use Symfony\Component\Console\Input\InputInterface; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | 17 | /** 18 | * Init 19 | */ 20 | class InitCommand extends Command 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | protected function configure() 26 | { 27 | $this 28 | ->setName('init') 29 | ->setDescription('Initializes a bower.json file') 30 | ->setHelp(<<<'EOT' 31 | The %command.name% command initializes a bower.json file in 32 | the current directory. 33 | 34 | php %command.full_name% 35 | EOT 36 | ) 37 | ; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output) 44 | { 45 | $this->setGithubToken($output); 46 | 47 | $author = sprintf('%s <%s>', $this->getGitInfo(), $this->getGitInfo('user.email')); 48 | 49 | $params = ['name' => get_current_user(), 'author' => $author]; 50 | 51 | // @codeCoverageIgnoreStart 52 | if ($input->isInteractive()) { 53 | $dialog = $this->getHelperSet()->get('question'); 54 | 55 | $params['name'] = $dialog->ask( 56 | $input, $output, $dialog->getQuestion('Please specify a name for project', $params['name']) 57 | ); 58 | 59 | $params['author'] = $dialog->ask( 60 | $input, $output, $dialog->getQuestion('Please specify an author', $params['author']) 61 | ); 62 | } 63 | // @codeCoverageIgnoreEnd 64 | $bowerphp = $this->getBowerphp($output); 65 | $bowerphp->init($params); 66 | 67 | $output->writeln(''); 68 | } 69 | 70 | /** 71 | * Get some info from local git 72 | * 73 | * @param string $info info type 74 | * @return string|null 75 | */ 76 | private function getGitInfo($info = 'user.name') 77 | { 78 | $output = []; 79 | $return = 0; 80 | $info = exec("git config --get $info", $output, $return); 81 | 82 | if (0 === $return) { 83 | return $info; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/InfoCommand.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 Bowerphp\Command; 13 | 14 | use Bowerphp\Package\Package; 15 | use Bowerphp\Util\PackageNameVersionExtractor; 16 | use Symfony\Component\Console\Input\InputArgument; 17 | use Symfony\Component\Console\Input\InputInterface; 18 | use Symfony\Component\Console\Output\OutputInterface; 19 | 20 | /** 21 | * Info 22 | */ 23 | class InfoCommand extends Command 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function configure() 29 | { 30 | $this 31 | ->setName('info') 32 | ->setDescription('Displays overall information of a package or of a particular version') 33 | ->addArgument('package', InputArgument::REQUIRED, 'Choose a package.') 34 | ->addArgument('property', InputArgument::OPTIONAL, 'A property present in bower.json.') 35 | ->setHelp(<<<'EOT' 36 | The %command.name% command displays overall information of a package or of a particular version. 37 | If you pass a property present in bower.json, you can get the correspondent value. 38 | 39 | php %command.full_name% package 40 | EOT 41 | ) 42 | ; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | protected function execute(InputInterface $input, OutputInterface $output) 49 | { 50 | $this->setGithubToken($output); 51 | 52 | $packageName = $input->getArgument('package'); 53 | $property = $input->getArgument('property'); 54 | 55 | $packageNameVersion = PackageNameVersionExtractor::fromString($packageName); 56 | 57 | $package = new Package($packageNameVersion->name, $packageNameVersion->version); 58 | $bowerphp = $this->getBowerphp($output); 59 | 60 | $bowerJsonFile = $bowerphp->getPackageBowerFile($package); 61 | if ('*' == $packageNameVersion->version) { 62 | $versions = $bowerphp->getPackageInfo($package, 'versions'); 63 | } 64 | if (!is_null($property)) { 65 | $bowerArray = json_decode($bowerJsonFile, true); 66 | $propertyValue = isset($bowerArray[$property]) ? $bowerArray[$property] : ''; 67 | $this->consoleOutput->writelnJsonText($propertyValue); 68 | 69 | return; 70 | } 71 | $this->consoleOutput->writelnJson($bowerJsonFile); 72 | if ('*' != $packageNameVersion->version) { 73 | return; 74 | } 75 | $output->writeln(''); 76 | if (empty($versions)) { 77 | $output->writeln('No versions available.'); 78 | } else { 79 | $output->writeln('Available versions:'); 80 | foreach ($versions as $vrs) { 81 | $output->writeln("- $vrs"); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/HomeCommand.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 Bowerphp\Command; 13 | 14 | use Bowerphp\Package\Package; 15 | use Bowerphp\Util\PackageNameVersionExtractor; 16 | use Symfony\Component\Console\Input\InputArgument; 17 | use Symfony\Component\Console\Input\InputInterface; 18 | use Symfony\Component\Console\Output\OutputInterface; 19 | use Symfony\Component\Process\Process; 20 | 21 | /** 22 | * Home 23 | */ 24 | class HomeCommand extends Command 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | protected function configure() 30 | { 31 | $this 32 | ->setName('home') 33 | ->setDescription('Opens a package homepage into your favorite browser') 34 | ->addArgument('package', InputArgument::REQUIRED, 'Choose a package.') 35 | ->setHelp(<<<'EOT' 36 | The %command.name% command opens a package homepage into your favorite browser. 37 | 38 | php %command.full_name% name 39 | EOT 40 | ) 41 | ; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output) 48 | { 49 | $this->setGithubToken($output); 50 | 51 | $packageName = $input->getArgument('package'); 52 | 53 | $packageNameVersion = PackageNameVersionExtractor::fromString($packageName); 54 | 55 | $package = new Package($packageNameVersion->name, $packageNameVersion->version); 56 | $bowerphp = $this->getBowerphp($output); 57 | 58 | $url = $bowerphp->getPackageInfo($package); 59 | 60 | $default = $this->getDefaultBrowser(); 61 | 62 | $arg = "$default \"$url\""; 63 | 64 | if (OutputInterface::VERBOSITY_DEBUG <= $output->getVerbosity()) { 65 | $output->writeln($arg); 66 | } else { 67 | $output->writeln(''); 68 | } 69 | // @codeCoverageIgnoreStart 70 | if (!defined('PHPUNIT_BOWER_TESTSUITE')) { 71 | $browser = new Process($arg); 72 | $browser->start(); 73 | while ($browser->isRunning()) { 74 | // do nothing... 75 | } 76 | } 77 | // @codeCoverageIgnoreEnd 78 | } 79 | 80 | /** 81 | * @return string 82 | * @codeCoverageIgnore 83 | */ 84 | private function getDefaultBrowser() 85 | { 86 | $xdgOpen = new Process('which xdg-open'); 87 | $xdgOpen->run(); 88 | if (!$xdgOpen->isSuccessful()) { 89 | $open = new Process('which open'); 90 | $open->run(); 91 | if (!$open->isSuccessful()) { 92 | throw new \RuntimeException('Could not open default browser.'); 93 | } 94 | 95 | return trim($open->getOutput()); 96 | } 97 | 98 | return trim($xdgOpen->getOutput()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/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 Bowerphp\Command; 13 | 14 | use Bowerphp\Bowerphp; 15 | use Bowerphp\Config\Config; 16 | use Bowerphp\Output\BowerphpConsoleOutput; 17 | use Bowerphp\Repository\GithubRepository; 18 | use Bowerphp\Util\Filesystem; 19 | use Github\Client; 20 | use Guzzle\Log\ClosureLogAdapter; 21 | use Guzzle\Log\MessageFormatter; 22 | use Guzzle\Plugin\Log\LogPlugin; 23 | use Symfony\Component\Console\Command\Command as BaseCommand; 24 | use Symfony\Component\Console\Output\OutputInterface; 25 | 26 | /** 27 | * Base class for Bowerphp commands 28 | * Inspired by Composer https://github.com/composer/composer 29 | */ 30 | abstract class Command extends BaseCommand 31 | { 32 | /** 33 | * @var Filesystem 34 | */ 35 | protected $filesystem; 36 | 37 | /** 38 | * @var Config 39 | */ 40 | protected $config; 41 | 42 | /** 43 | * @var Client 44 | */ 45 | protected $githubClient; 46 | 47 | /** 48 | * @var BowerphpConsoleOutput 49 | */ 50 | protected $consoleOutput; 51 | 52 | /** 53 | * Debug HTTP interactions 54 | * 55 | * @param Client $client 56 | * @param OutputInterface $output 57 | */ 58 | protected function logHttp(Client $client, OutputInterface $output) 59 | { 60 | $guzzle = $client->getHttpClient(); 61 | if (OutputInterface::VERBOSITY_DEBUG <= $output->getVerbosity()) { 62 | $logger = function ($message) use ($output) { 63 | $finfo = new \finfo(FILEINFO_MIME); 64 | $msg = ('text' == substr($finfo->buffer($message), 0, 4)) ? $message : '(binary string)'; 65 | $output->writeln('Guzzle ' . $msg); 66 | }; 67 | $logAdapter = new ClosureLogAdapter($logger); 68 | $logPlugin = new LogPlugin($logAdapter, MessageFormatter::DEBUG_FORMAT); 69 | $guzzle->addSubscriber($logPlugin); 70 | } 71 | } 72 | 73 | /** 74 | * Set oauth token (to increase API limit to 5000 per hour, instead of default 60) 75 | * 76 | * @param Client $client 77 | */ 78 | protected function setToken(Client $client) 79 | { 80 | $token = getenv('BOWERPHP_TOKEN'); 81 | if (!empty($token)) { 82 | $client->authenticate($token, null, Client::AUTH_HTTP_TOKEN); 83 | } 84 | } 85 | 86 | /** 87 | * @param OutputInterface $output 88 | */ 89 | protected function setGithubToken(OutputInterface $output) 90 | { 91 | $this->filesystem = new Filesystem(); 92 | $this->githubClient = new Client(); 93 | $this->config = new Config($this->filesystem); 94 | $this->logHttp($this->githubClient, $output); 95 | $this->setToken($this->githubClient); 96 | } 97 | 98 | /** 99 | * @param OutputInterface $output 100 | * @return Bowerphp 101 | */ 102 | protected function getBowerphp(OutputInterface $output) 103 | { 104 | $this->consoleOutput = new BowerphpConsoleOutput($output); 105 | 106 | return new Bowerphp($this->config, $this->filesystem, $this->githubClient, new GithubRepository(), $this->consoleOutput); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Bowerphp/Config/ConfigInterface.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 Bowerphp\Config; 13 | 14 | use Bowerphp\Package\PackageInterface; 15 | 16 | /** 17 | * ConfigInterface 18 | */ 19 | interface ConfigInterface 20 | { 21 | /** 22 | * @return string 23 | */ 24 | public function getBasePackagesUrl(); 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getCacheDir(); 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getInstallDir(); 35 | 36 | /** 37 | * @return bool 38 | */ 39 | public function isSaveToBowerJsonFile(); 40 | 41 | /** 42 | * Set true|false for decide if add package reference on bower.json file during install procedure 43 | * 44 | * @param bool $flag default true 45 | */ 46 | public function setSaveToBowerJsonFile($flag = true); 47 | 48 | /** 49 | * Init project's bower.json file 50 | * 51 | * @param array $params 52 | * 53 | * @return int 54 | */ 55 | public function initBowerJsonFile(array $params); 56 | 57 | /** 58 | * Update project's bower.json with a new added package 59 | * 60 | * @param PackageInterface $package 61 | * 62 | * @return int 63 | */ 64 | public function updateBowerJsonFile(PackageInterface $package); 65 | 66 | /** 67 | * Update project's bower.json from a previous existing one 68 | * 69 | * @param array $old values of previous bower.json 70 | * @param array $new new values 71 | * 72 | * @return int 73 | */ 74 | public function updateBowerJsonFile2(array $old, array $new); 75 | 76 | /** 77 | * Get content from project's bower.json file 78 | * 79 | * @return array 80 | * 81 | * @throws \Exception if bower.json does not exist 82 | */ 83 | public function getBowerFileContent(); 84 | 85 | /** 86 | * Retrieve the array of overrides optionally defined in the bower.json file. 87 | * Each element's key is a package name, and contains an array of other package names 88 | * and versions that should replace the dependencies found in that package's canonical bower.json 89 | * 90 | * @return array The overrides section from the bower.json file, or an empty array if no overrides section is defined 91 | */ 92 | public function getOverridesSection(); 93 | 94 | /** 95 | * Get the array of overrides defined for the specified package 96 | * 97 | * @param string $packageName The name of the package for which dependencies are being overridden 98 | * 99 | * @return array A list of dependency name => override versions to be used instead of the target package's normal dependencies. An empty array if none are defined 100 | */ 101 | public function getOverrideFor($packageName); 102 | 103 | /** 104 | * Get content from a packages' bower.json file 105 | * 106 | * @param PackageInterface $package 107 | * 108 | * @return array 109 | * 110 | * @throws \Exception if bower.json or package.json does not exist in a dir of installed package 111 | */ 112 | public function getPackageBowerFileContent(PackageInterface $package); 113 | 114 | /** 115 | * Check if project's bower.json file exists 116 | * 117 | * @return bool 118 | */ 119 | public function bowerFileExists(); 120 | } 121 | -------------------------------------------------------------------------------- /src/Bowerphp/Package/Package.php: -------------------------------------------------------------------------------- 1 | name = $name; 36 | $this->requiredVersion = 'master' === $requiredVersion ? '*' : $requiredVersion; 37 | $this->version = $version; 38 | if (!empty($requires)) { 39 | $this->requires = $requires; 40 | } 41 | if (!empty($info)) { 42 | $this->info = $info; 43 | } 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getName() 50 | { 51 | return $this->name; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getVersion() 58 | { 59 | return $this->version; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function setVersion($version) 66 | { 67 | return $this->version = $version; 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function getRequiredVersion() 74 | { 75 | return $this->requiredVersion; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function setRequiredVersion($version) 82 | { 83 | return $this->requiredVersion = $version; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function setRepository(RepositoryInterface $repository) 90 | { 91 | if ($this->repository && $repository !== $this->repository) { 92 | throw new \LogicException('A package can only be added to one repository'); 93 | } 94 | $this->repository = $repository; 95 | } 96 | 97 | /** 98 | * Returns package unique name, constructed from name, version and release type. 99 | * 100 | * @return string 101 | */ 102 | public function getUniqueName() 103 | { 104 | return $this->getName() . '-' . $this->getVersion(); 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function setRequires(array $requires = null) 111 | { 112 | $this->requires = $requires; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function getRequires() 119 | { 120 | // see if there is some inside $this->info (e.g. from bower.json) 121 | if (empty($this->requires) && isset($this->info['dependencies'])) { 122 | $this->requires = $this->info['dependencies']; 123 | } 124 | 125 | return $this->requires; 126 | } 127 | 128 | /** 129 | * {@inheritdoc} 130 | */ 131 | public function setInfo(array $info) 132 | { 133 | $this->info = $info; 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function getInfo() 140 | { 141 | return $this->info; 142 | } 143 | 144 | /** 145 | * Converts the package into a readable and unique string 146 | * 147 | * @return string 148 | */ 149 | public function __toString() 150 | { 151 | return $this->getUniqueName(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Bowerphp/Output/BowerphpConsoleOutput.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 Bowerphp\Output; 13 | 14 | use Bowerphp\Bowerphp; 15 | use Bowerphp\Package\PackageInterface; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | 18 | class BowerphpConsoleOutput 19 | { 20 | protected $output; 21 | 22 | /** 23 | * @param OutputInterface $output 24 | */ 25 | public function __construct(OutputInterface $output) 26 | { 27 | $this->output = $output; 28 | } 29 | 30 | /** 31 | * writelnInfoPackage 32 | * 33 | * @param PackageInterface $package 34 | * @param string $action 35 | * @param string $message 36 | */ 37 | public function writelnInfoPackage(PackageInterface $package, $action = '', $message = '') 38 | { 39 | $this->output->writeln(sprintf('bower %s %s %s', 40 | str_pad($package->getName() . '#' . $package->getRequiredVersion(), 21, ' ', STR_PAD_RIGHT), 41 | str_pad($action, 10, ' ', STR_PAD_LEFT), 42 | $message 43 | )); 44 | } 45 | 46 | /** 47 | * writelnInstalledPackage 48 | * 49 | * @param PackageInterface $package 50 | */ 51 | public function writelnInstalledPackage(PackageInterface $package) 52 | { 53 | $this->output->writeln(sprintf('bower %s %s', 54 | str_pad($package->getName() . '#' . $package->getVersion(), 21, ' ', STR_PAD_RIGHT), 55 | str_pad('install', 10, ' ', STR_PAD_LEFT) 56 | )); 57 | } 58 | 59 | /** 60 | * writelnNoBowerJsonFile 61 | */ 62 | public function writelnNoBowerJsonFile() 63 | { 64 | $this->output->writeln(sprintf( 65 | 'bower %s %s %s', 66 | str_pad('', 21, ' ', STR_PAD_RIGHT), 67 | str_pad('no-json', 10, ' ', STR_PAD_LEFT), 68 | 'No bower.json file to save to, use bower init to create one' 69 | )); 70 | } 71 | 72 | /** 73 | * Rewrite json with colors and unescaped slashes 74 | * 75 | * @param string $jsonString 76 | */ 77 | public function writelnJson($jsonString) 78 | { 79 | $keyColor = preg_replace('/"(\w+)": /', '$1: ', $jsonString); 80 | $valColor = preg_replace('/"([^"]+)"/', "'$1'", $keyColor); 81 | 82 | $this->output->writeln(stripslashes($valColor)); 83 | } 84 | 85 | /** 86 | * writelnJson 87 | * 88 | * @param mixed $jsonPart 89 | */ 90 | public function writelnJsonText($jsonPart) 91 | { 92 | $this->output->writeln(sprintf('%s', json_encode($jsonPart, JSON_PRETTY_PRINT))); 93 | } 94 | 95 | /** 96 | * writelnSearchOrLookup 97 | * 98 | * @param string $name 99 | * @param string $homepage 100 | * @param int $pad 101 | */ 102 | public function writelnSearchOrLookup($name, $homepage, $pad = 0) 103 | { 104 | $this->output->writeln(sprintf('%s%s %s', str_repeat(' ', $pad), $name, $homepage)); 105 | } 106 | 107 | /** 108 | * writelnListPackage 109 | * 110 | * @param PackageInterface $package 111 | * @param Bowerphp $bowerphp 112 | */ 113 | public function writelnListPackage(PackageInterface $package, Bowerphp $bowerphp) 114 | { 115 | $this->output->writeln(sprintf('%s#%s%s', 116 | $package->getName(), 117 | $package->getVersion(), 118 | $bowerphp->isPackageExtraneous($package) ? ' extraneous' : '' 119 | )); 120 | } 121 | 122 | public function writelnUpdatingPackage(PackageInterface $package) 123 | { 124 | $this->output->writeln(sprintf('bower %s %s', 125 | str_pad($package->getName() . '#' . $package->getRequiredVersion(), 21, ' ', STR_PAD_RIGHT), 126 | str_pad('install', 10, ' ', STR_PAD_LEFT) 127 | )); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Bowerphp/Command/InstallCommand.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 Bowerphp\Command; 13 | 14 | use Bowerphp\Installer\Installer; 15 | use Bowerphp\Package\Package; 16 | use Bowerphp\Repository\GithubRepository; 17 | use Bowerphp\Util\PackageNameVersionExtractor; 18 | use Bowerphp\Util\ZipArchive; 19 | use Symfony\Component\Console\Input\InputArgument; 20 | use Symfony\Component\Console\Input\InputInterface; 21 | use Symfony\Component\Console\Input\InputOption; 22 | use Symfony\Component\Console\Output\OutputInterface; 23 | 24 | /** 25 | * Inspired by Composer https://github.com/composer/composer 26 | */ 27 | class InstallCommand extends Command 28 | { 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | protected function configure() 33 | { 34 | $this 35 | ->setName('install') 36 | ->setDescription('Installs the project dependencies from the bower.json file or a single specified package') 37 | ->addOption('save', 'S', InputOption::VALUE_NONE, 'Add installed package to bower.json file.') 38 | ->addArgument('package', InputArgument::OPTIONAL, 'Choose a package.') 39 | ->setHelp(<<<'EOT' 40 | The %command.name% command reads the bower.json file from 41 | the current directory, processes it, and downloads and installs all the 42 | libraries and dependencies outlined in that file. 43 | 44 | php %command.full_name% 45 | 46 | If an optional package name is passed, that package is installed. 47 | 48 | php %command.full_name% packageName[#version] 49 | 50 | If an optional flag -S is passed, installed package is added 51 | to bower.json file (only if bower.json file already exists). 52 | 53 | EOT 54 | ) 55 | ; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | protected function execute(InputInterface $input, OutputInterface $output) 62 | { 63 | $this->setGithubToken($output); 64 | $this->config->setSaveToBowerJsonFile($input->getOption('save')); 65 | 66 | $packageName = $input->getArgument('package'); 67 | 68 | $bowerphp = $this->getBowerphp($output); 69 | 70 | try { 71 | $installer = new Installer($this->filesystem, new ZipArchive(), $this->config); 72 | 73 | if (is_null($packageName)) { 74 | $bowerphp->installDependencies($installer); 75 | } else { 76 | if ('bower.json' === substr($packageName, -10)) { 77 | if (!is_readable($packageName)) { 78 | $output->writeln(sprintf('Cannot read file %s', $packageName)); 79 | 80 | return 1; 81 | } 82 | $json = json_decode($this->filesystem->read($packageName), true); 83 | if (empty($json['dependencies'])) { 84 | $output->writeln(sprintf('Nothing to install in %s', $packageName)); 85 | 86 | return 1; 87 | } 88 | foreach ($json['dependencies'] as $name => $version) { 89 | $package = new Package($name, $version); 90 | $bowerphp->installPackage($package, $installer); 91 | } 92 | } else { 93 | $packageNameVersion = PackageNameVersionExtractor::fromString($packageName); 94 | $package = new Package($packageNameVersion->name, $packageNameVersion->version); 95 | $bowerphp->installPackage($package, $installer); 96 | } 97 | } 98 | } catch (\RuntimeException $e) { 99 | $output->writeln(sprintf('%s', $e->getMessage())); 100 | if (GithubRepository::VERSION_NOT_FOUND == $e->getCode() && !empty($package)) { 101 | $output->writeln(sprintf('Available versions: %s', implode(', ', $bowerphp->getPackageInfo($package, 'versions')))); 102 | } 103 | 104 | return 1; 105 | } 106 | 107 | $output->writeln(''); 108 | 109 | return 0; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Bowerphp/Console/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 Bowerphp\Console; 13 | 14 | use Bowerphp\Command; 15 | use Bowerphp\Command\Helper\QuestionHelper; 16 | use Bowerphp\Util\ErrorHandler; 17 | use PackageVersions\Versions; 18 | use Symfony\Component\Console\Application as BaseApplication; 19 | use Symfony\Component\Console\Input\ArrayInput; 20 | use Symfony\Component\Console\Input\InputInterface; 21 | use Symfony\Component\Console\Input\InputOption; 22 | use Symfony\Component\Console\Output\OutputInterface; 23 | 24 | /** 25 | * The console application that handles the commands 26 | * Inspired by Composer https://github.com/composer/composer 27 | */ 28 | class Application extends BaseApplication 29 | { 30 | /** 31 | * @var \Bowerphp\Bowerphp 32 | */ 33 | protected $bowerphp; 34 | 35 | private static $logo = ' ____ __ 36 | / __ )____ _ _____ _________ / /_ ____ 37 | / __ / __ \ | /| / / _ \/ ___/ __ \/ __ \/ __ \ 38 | / /_/ / /_/ / |/ |/ / __/ / / /_/ / / / / /_/ / 39 | /_____/\____/|__/|__/\___/_/ / .___/_/ /_/ .___/ 40 | /_/ /_/ 41 | '; 42 | 43 | /** 44 | * Constructor 45 | */ 46 | public function __construct() 47 | { 48 | ErrorHandler::register(); 49 | $version = Versions::getVersion('beelab/bowerphp'); 50 | parent::__construct('Bowerphp', $version . ' Powered by BeeLab (github.com/bee-lab)'); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function doRun(InputInterface $input, OutputInterface $output) 57 | { 58 | if ($input->hasParameterOption('--profile')) { 59 | $startTime = microtime(true); 60 | } 61 | 62 | if ($newWorkDir = $this->getNewWorkingDir($input)) { 63 | $oldWorkingDir = getcwd(); 64 | chdir($newWorkDir); 65 | } 66 | 67 | $result = $this->symfonyDoRun($input, $output); 68 | 69 | if (isset($oldWorkingDir)) { 70 | chdir($oldWorkingDir); 71 | } 72 | 73 | if (isset($startTime)) { 74 | $output->writeln('Memory usage: ' . round(memory_get_usage() / 1024 / 1024, 2) . 'MB (peak: ' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'MB), time: ' . round(microtime(true) - $startTime, 2) . 's'); 75 | } 76 | 77 | return $result; 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function getHelp() 84 | { 85 | return self::$logo . parent::getHelp(); 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | protected function getDefaultCommands() 92 | { 93 | return [ 94 | new Command\HelpCommand(), 95 | new Command\CommandListCommand(), 96 | new Command\HomeCommand(), 97 | new Command\InfoCommand(), 98 | new Command\InitCommand(), 99 | new Command\InstallCommand(), 100 | new Command\ListCommand(), 101 | new Command\LookupCommand(), 102 | new Command\SearchCommand(), 103 | new Command\UpdateCommand(), 104 | new Command\UninstallCommand(), 105 | ]; 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | protected function getDefaultInputDefinition() 112 | { 113 | $definition = parent::getDefaultInputDefinition(); 114 | $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); 115 | $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); 116 | 117 | return $definition; 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | protected function getDefaultHelperSet() 124 | { 125 | $helperSet = parent::getDefaultHelperSet(); 126 | $helperSet->set(new QuestionHelper()); 127 | 128 | return $helperSet; 129 | } 130 | 131 | /** 132 | * @param InputInterface $input 133 | * @return string 134 | * @throws \RuntimeException 135 | */ 136 | private function getNewWorkingDir(InputInterface $input) 137 | { 138 | $workingDir = $input->getParameterOption(['--working-dir', '-d']); 139 | if (false !== $workingDir && !is_dir($workingDir)) { 140 | throw new \RuntimeException('Invalid working directory specified.'); 141 | } 142 | 143 | return $workingDir; 144 | } 145 | 146 | /** 147 | * Copy of original Symfony doRun, to allow a default command 148 | * 149 | * @param InputInterface $input An Input instance 150 | * @param OutputInterface $output An Output instance 151 | * @param string $default Default command to execute 152 | * 153 | * @return int 0 if everything went fine, or an error code 154 | * @codeCoverageIgnore 155 | */ 156 | private function symfonyDoRun(InputInterface $input, OutputInterface $output, $default = 'list-commands') 157 | { 158 | if (true === $input->hasParameterOption(['--version', '-V'])) { 159 | $output->writeln($this->getLongVersion()); 160 | 161 | return 0; 162 | } 163 | 164 | $name = $this->getCommandName($input); 165 | 166 | if (true === $input->hasParameterOption(['--help', '-h'])) { 167 | if (!$name) { 168 | $name = 'help'; 169 | $input = new ArrayInput(['command' => 'help']); 170 | } else { 171 | // TODO $wantHelps is private in parent class 172 | $this->wantHelps = true; 173 | } 174 | } 175 | 176 | if (!$name) { 177 | $name = $default; 178 | $input = new ArrayInput(['command' => $default]); 179 | } 180 | 181 | // the command name MUST be the first element of the input 182 | $command = $this->find($name); 183 | 184 | $this->runningCommand = $command; 185 | $exitCode = $this->doRunCommand($command, $input, $output); 186 | $this->runningCommand = null; 187 | 188 | return $exitCode; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Bowerphp/Compiler.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) Mauro D'Alatri 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 | namespace Bowerphp; 14 | 15 | use Symfony\Component\Finder\Finder; 16 | 17 | /** 18 | * The Compiler class compiles Bower into a phar. 19 | */ 20 | class Compiler 21 | { 22 | /** 23 | * @var bool 24 | */ 25 | private $gz; 26 | 27 | /** 28 | * @param bool $gz 29 | */ 30 | public function __construct($gz = false) 31 | { 32 | $this->gz = $gz; 33 | } 34 | 35 | /** 36 | * Compiles bower into a single phar file 37 | * 38 | * @throws \RuntimeException 39 | * @param string $pharFile The full path to the file to create 40 | */ 41 | public function compile($pharFile = 'bowerphp.phar') 42 | { 43 | if (file_exists($pharFile)) { 44 | unlink($pharFile); 45 | } 46 | 47 | $phar = new \Phar($pharFile, 0, 'bowerphp.phar'); 48 | $phar->setSignatureAlgorithm(\Phar::SHA1); 49 | 50 | $phar->startBuffering(); 51 | 52 | $finder = new Finder(); 53 | $finder->files() 54 | ->ignoreVCS(true) 55 | ->name('*.php') 56 | ->notName('Compiler.php') 57 | ->notName('ClassLoader.php') 58 | ->in(__DIR__ . '/..') 59 | ; 60 | 61 | foreach ($finder as $file) { 62 | $this->addFile($phar, $file); 63 | } 64 | 65 | $finder = new Finder(); 66 | $finder->files() 67 | ->ignoreVCS(true) 68 | ->name('*.php') 69 | ->name('*.pem') 70 | ->name('*.pem.md5') 71 | ->exclude('Tests') 72 | ->in(__DIR__ . '/../../vendor/symfony/') 73 | ->in(__DIR__ . '/../../vendor/guzzle/guzzle/src/') 74 | ->in(__DIR__ . '/../../vendor/ircmaxell/password-compat/lib/') 75 | ->in(__DIR__ . '/../../vendor/paragonie/random_compat/lib/') 76 | ->in(__DIR__ . '/../../vendor/knplabs/github-api/lib/') 77 | ->in(__DIR__ . '/../../vendor/samsonasik/package-versions/src/PackageVersions/') 78 | ->in(__DIR__ . '/../../vendor/vierbergenlars/php-semver/src/vierbergenlars/LibJs/') 79 | ->in(__DIR__ . '/../../vendor/vierbergenlars/php-semver/src/vierbergenlars/SemVer/') 80 | ->in(__DIR__ . '/../../vendor/hamcrest/hamcrest-php/hamcrest/') 81 | ; 82 | 83 | foreach ($finder as $file) { 84 | $this->addFile($phar, $file); 85 | } 86 | 87 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/autoload.php')); 88 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_psr4.php')); 89 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_files.php')); 90 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_namespaces.php')); 91 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_classmap.php')); 92 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_real.php')); 93 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/autoload_static.php')); 94 | if (file_exists(__DIR__ . '/../../vendor/composer/include_paths.php')) { 95 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/include_paths.php')); 96 | } 97 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/composer/ClassLoader.php')); 98 | $this->addBin($phar); 99 | 100 | // Stubs 101 | $phar->setStub($this->getStub()); 102 | 103 | $phar->stopBuffering(); 104 | 105 | if ($this->gz) { 106 | $phar->compressFiles(\Phar::GZ); 107 | } 108 | 109 | $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../LICENSE'), false); 110 | 111 | unset($phar); 112 | chmod('bowerphp.phar', 0700); 113 | } 114 | 115 | /** 116 | * @param \Phar $phar 117 | * @param \SplFileInfo $file 118 | * @param bool $strip 119 | */ 120 | private function addFile(\Phar $phar, \SplFileInfo $file, $strip = true) 121 | { 122 | $path = strtr(str_replace(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR, '', $file->getRealPath()), '\\', '/'); 123 | 124 | $content = file_get_contents($file); 125 | if ($strip) { 126 | $content = $this->stripWhitespace($content); 127 | } elseif ('LICENSE' === basename($file)) { 128 | $content = "\n" . $content . "\n"; 129 | } 130 | 131 | $phar->addFromString($path, $content); 132 | } 133 | 134 | /** 135 | * @param \Phar $phar 136 | */ 137 | private function addBin(\Phar $phar) 138 | { 139 | $content = file_get_contents(__DIR__ . '/../../bin/bowerphp'); 140 | $content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content); 141 | $phar->addFromString('bin/bowerphp', $content); 142 | } 143 | 144 | /** 145 | * Removes whitespace from a PHP source string while preserving line numbers. 146 | * 147 | * @param string $source A PHP string 148 | * @return string The PHP string with the whitespace removed 149 | */ 150 | private function stripWhitespace($source) 151 | { 152 | if (!function_exists('token_get_all')) { 153 | return $source; 154 | } 155 | 156 | $output = ''; 157 | foreach (token_get_all($source) as $token) { 158 | if (is_string($token)) { 159 | $output .= $token; 160 | } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) { 161 | $output .= str_repeat("\n", substr_count($token[1], "\n")); 162 | } elseif (T_WHITESPACE === $token[0]) { 163 | // reduce wide spaces 164 | $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); 165 | // normalize newlines to \n 166 | $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); 167 | // trim leading spaces 168 | $whitespace = preg_replace('{\n +}', "\n", $whitespace); 169 | $output .= $whitespace; 170 | } else { 171 | $output .= $token[1]; 172 | } 173 | } 174 | 175 | return $output; 176 | } 177 | 178 | /** 179 | * @return string 180 | */ 181 | private function getStub() 182 | { 183 | $stub = <<<'EOF' 184 | #!/usr/bin/env php 185 | 190 | * Mauro D'Alatri 191 | * 192 | * For the full copyright and license information, please view 193 | * the license that is located at the bottom of this file. 194 | */ 195 | 196 | Phar::mapPhar('bowerphp.phar'); 197 | 198 | EOF; 199 | 200 | return $stub . <<<'EOF' 201 | require 'phar://bowerphp.phar/bin/bowerphp'; 202 | 203 | __HALT_COMPILER(); 204 | EOF; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Bowerphp/Config/Config.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 Bowerphp\Config; 13 | 14 | use Bowerphp\Package\PackageInterface; 15 | use Bowerphp\Util\Filesystem; 16 | use RuntimeException; 17 | 18 | /** 19 | * Config 20 | */ 21 | class Config implements ConfigInterface 22 | { 23 | protected $cacheDir; 24 | 25 | protected $installDir; 26 | 27 | protected $filesystem; 28 | 29 | protected $basePackagesUrl = 'http://registry.bower.io/packages/'; 30 | 31 | protected $saveToBowerJsonFile = false; 32 | 33 | protected $bowerFileNames = ['bower.json', 'package.json']; 34 | 35 | protected $stdBowerFileName = 'bower.json'; 36 | 37 | /** 38 | * @param Filesystem $filesystem 39 | */ 40 | public function __construct(Filesystem $filesystem) 41 | { 42 | $this->filesystem = $filesystem; 43 | $this->cacheDir = $this->getHomeDir() . '/.cache/bowerphp'; 44 | $this->installDir = getcwd() . '/bower_components'; 45 | $rc = getcwd() . '/.bowerrc'; 46 | 47 | if ($this->filesystem->exists($rc)) { 48 | $json = json_decode($this->filesystem->read($rc), true); 49 | if (is_null($json)) { 50 | throw new RuntimeException('Invalid .bowerrc file.'); 51 | } 52 | if (isset($json['directory'])) { 53 | $this->installDir = getcwd() . '/' . $json['directory']; 54 | } 55 | if (isset($json['storage']) && isset($json['storage']['packages'])) { 56 | $this->cacheDir = $json['storage']['packages']; 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function getBasePackagesUrl() 65 | { 66 | return $this->basePackagesUrl; 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function getCacheDir() 73 | { 74 | return $this->cacheDir; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function getInstallDir() 81 | { 82 | return $this->installDir; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function isSaveToBowerJsonFile() 89 | { 90 | return $this->saveToBowerJsonFile; 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function setSaveToBowerJsonFile($flag = true) 97 | { 98 | $this->saveToBowerJsonFile = $flag; 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function initBowerJsonFile(array $params) 105 | { 106 | $file = getcwd() . '/' . $this->stdBowerFileName; 107 | $json = json_encode($this->createAClearBowerFile($params), JSON_PRETTY_PRINT); 108 | 109 | return $this->filesystem->write($file, $json); 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function updateBowerJsonFile(PackageInterface $package) 116 | { 117 | if (!$this->isSaveToBowerJsonFile()) { 118 | return 0; 119 | } 120 | 121 | $decode = $this->getBowerFileContent(); 122 | $decode['dependencies'][$package->getName()] = $package->getRequiredVersion(); 123 | $file = getcwd() . '/' . $this->stdBowerFileName; 124 | $json = json_encode($decode, JSON_PRETTY_PRINT); 125 | 126 | return $this->filesystem->write($file, $json); 127 | } 128 | 129 | /** 130 | * {@inheritdoc} 131 | */ 132 | public function updateBowerJsonFile2(array $old, array $new) 133 | { 134 | $json = json_encode(array_merge($old, $new), JSON_PRETTY_PRINT); 135 | $file = getcwd() . '/' . $this->stdBowerFileName; 136 | 137 | return $this->filesystem->write($file, $json); 138 | } 139 | 140 | /** 141 | * {@inheritdoc} 142 | */ 143 | public function getBowerFileContent() 144 | { 145 | if (!$this->bowerFileExists()) { 146 | throw new RuntimeException('No ' . $this->stdBowerFileName . ' found. You can run "init" command to create it.'); 147 | } 148 | $bowerJson = $this->filesystem->read(getcwd() . '/' . $this->stdBowerFileName); 149 | if (empty($bowerJson) || !is_array($decode = json_decode($bowerJson, true))) { 150 | throw new RuntimeException(sprintf('Malformed JSON in %s: %s.', $this->stdBowerFileName, $bowerJson)); 151 | } 152 | 153 | return $decode; 154 | } 155 | 156 | /** 157 | * {@inheritdoc} 158 | */ 159 | public function getOverridesSection() 160 | { 161 | if ($this->bowerFileExists()) { 162 | $bowerData = $this->getBowerFileContent(); 163 | if ($bowerData && array_key_exists('overrides', $bowerData)) { 164 | return $bowerData['overrides']; 165 | } 166 | } 167 | 168 | return []; 169 | } 170 | 171 | /** 172 | * {@inheritdoc} 173 | */ 174 | public function getOverrideFor($packageName) 175 | { 176 | $overrides = $this->getOverridesSection(); 177 | if (array_key_exists($packageName, $overrides)) { 178 | return $overrides[$packageName]; 179 | } 180 | 181 | return []; 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function getPackageBowerFileContent(PackageInterface $package) 188 | { 189 | $file = $this->getInstallDir() . '/' . $package->getName() . '/.bower.json'; 190 | if (!$this->filesystem->exists($file)) { 191 | throw new RuntimeException(sprintf('Could not find .bower.json file for package %s.', $package->getName())); 192 | } 193 | $bowerJson = $this->filesystem->read($file); 194 | $bower = json_decode($bowerJson, true); 195 | if (is_null($bower)) { 196 | throw new RuntimeException(sprintf('Invalid content in .bower.json for package %s.', $package->getName())); 197 | } 198 | 199 | return $bower; 200 | } 201 | 202 | /** 203 | * {@inheritdoc} 204 | */ 205 | public function bowerFileExists() 206 | { 207 | return $this->filesystem->exists(getcwd() . '/' . $this->stdBowerFileName); 208 | } 209 | 210 | /** 211 | * @param array $params 212 | * @return array 213 | */ 214 | protected function createAClearBowerFile(array $params) 215 | { 216 | $structure = [ 217 | 'name' => $params['name'], 218 | 'authors' => [ 219 | 0 => 'Beelab ', 220 | 1 => $params['author'], 221 | ], 222 | 'private' => true, 223 | 'dependencies' => new \StdClass(), 224 | ]; 225 | 226 | return $structure; 227 | } 228 | 229 | /** 230 | * @return string 231 | */ 232 | protected function getHomeDir() 233 | { 234 | if (defined('PHP_WINDOWS_VERSION_MAJOR')) { 235 | $appData = getenv('APPDATA'); 236 | if (empty($appData)) { 237 | throw new \RuntimeException('The APPDATA environment variable must be set for bowerphp to run correctly'); 238 | } 239 | 240 | return strtr($appData, '\\', '/'); 241 | } 242 | $home = getenv('HOME'); 243 | if (empty($home)) { 244 | throw new \RuntimeException('The HOME environment variable must be set for bowerphp to run correctly'); 245 | } 246 | 247 | return rtrim($home, '/'); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/Bowerphp/Installer/Installer.php: -------------------------------------------------------------------------------- 1 | filesystem = $filesystem; 43 | $this->zipArchive = $zipArchive; 44 | $this->config = $config; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function install(PackageInterface $package) 51 | { 52 | $tmpFileName = $this->config->getCacheDir() . '/tmp/' . $package->getName(); 53 | if (true !== $this->zipArchive->open($tmpFileName)) { 54 | throw new RuntimeException(sprintf('Unable to open zip file %s.', $tmpFileName)); 55 | } 56 | $dirName = trim($this->zipArchive->getNameIndex(0), '/'); 57 | $info = $package->getInfo(); 58 | $files = $this->filterZipFiles($this->zipArchive, isset($info['ignore']) ? $info['ignore'] : [], isset($info['main']) ? (array) $info['main'] : []); 59 | foreach ($files as $i => $file) { 60 | $stat = $this->zipArchive->statIndex($i); 61 | $fileName = $this->config->getInstallDir() . '/' . str_replace($dirName, $package->getName(), $file); 62 | if ('/' != substr($fileName, -1)) { 63 | $fileContent = $this->zipArchive->getStream($file); 64 | $this->filesystem->write($fileName, $fileContent); 65 | $this->filesystem->touch($fileName, $stat['mtime']); 66 | } 67 | } 68 | // adjust timestamp for directories 69 | foreach ($files as $i => $file) { 70 | $stat = $this->zipArchive->statIndex($i); 71 | $fileName = $this->config->getInstallDir() . '/' . str_replace($dirName, $package->getName(), $file); 72 | if (is_dir($fileName) && '/' == substr($fileName, -1)) { 73 | $this->filesystem->touch($fileName, $stat['mtime']); 74 | } 75 | } 76 | $this->zipArchive->close(); 77 | 78 | // merge package info with bower.json contents 79 | $bowerJsonPath = $this->config->getInstallDir() . '/' . $package->getName() . '/bower.json'; 80 | if ($this->filesystem->exists($bowerJsonPath)) { 81 | $bowerJson = $this->filesystem->read($bowerJsonPath); 82 | $bower = json_decode($bowerJson, true); 83 | $package->setInfo(array_merge($bower, $package->getInfo())); 84 | } 85 | 86 | // create .bower.json metadata file 87 | // XXX we still need to add some other info 88 | $dotBowerContent = array_merge($package->getInfo(), ['version' => $package->getVersion()]); 89 | $dotBowerJson = str_replace('\/', '/', json_encode($dotBowerContent, JSON_PRETTY_PRINT)); 90 | $this->filesystem->write($this->config->getInstallDir() . '/' . $package->getName() . '/.bower.json', $dotBowerJson); 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function update(PackageInterface $package) 97 | { 98 | $this->install($package); 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function uninstall(PackageInterface $package) 105 | { 106 | $this->removeDir($this->config->getInstallDir() . '/' . $package->getName()); 107 | } 108 | 109 | /** 110 | * {@inheritdoc} 111 | */ 112 | public function getInstalled(Finder $finder) 113 | { 114 | $packages = []; 115 | if (!$this->filesystem->exists($this->config->getInstallDir())) { 116 | return $packages; 117 | } 118 | 119 | $directories = $finder->directories()->in($this->config->getInstallDir()); 120 | 121 | foreach ($directories as $packageDirectory) { 122 | if ($this->filesystem->exists($packageDirectory . '/.bower.json')) { 123 | $bowerJson = $this->filesystem->read($packageDirectory . '/.bower.json'); 124 | $bower = json_decode($bowerJson, true); 125 | if (is_null($bower)) { 126 | throw new RuntimeException(sprintf('Invalid content in .bower.json for package %s.', $packageDirectory)); 127 | } 128 | $packages[] = new Package($bower['name'], null, $bower['version'], isset($bower['dependencies']) ? $bower['dependencies'] : null, $bower); 129 | } 130 | } 131 | 132 | return $packages; 133 | } 134 | 135 | /** 136 | * {@inheritdoc} 137 | */ 138 | public function findDependentPackages(PackageInterface $package, Finder $finder) 139 | { 140 | $return = []; 141 | $packages = $this->getInstalled($finder); 142 | foreach ($packages as $installedPackage) { 143 | $requires = $installedPackage->getRequires(); 144 | if (isset($requires[$package->getName()])) { 145 | $return[$requires[$package->getName()]] = $installedPackage; 146 | } 147 | } 148 | 149 | return $return; 150 | } 151 | 152 | /** 153 | * Filter archive files based on an "ignore" list. 154 | * 155 | * @param ZipArchive $archive 156 | * @param array $ignore 157 | * @param array $force 158 | * @return array 159 | */ 160 | protected function filterZipFiles(ZipArchive $archive, array $ignore = [], array $force = []) 161 | { 162 | $dirName = $archive->getNameIndex(0); 163 | $return = []; 164 | $numFiles = $archive->getNumFiles(); 165 | for ($i = 0; $i < $numFiles; ++$i) { 166 | $stat = $archive->statIndex($i); 167 | $return[] = $stat['name']; 168 | } 169 | $that = $this; 170 | $filter = array_filter($return, function ($var) use ($ignore, $force, $dirName, $that) { 171 | return !$that->isIgnored($var, $ignore, $force, $dirName); 172 | }); 173 | 174 | return array_values($filter); 175 | } 176 | 177 | /** 178 | * @param string $dir 179 | */ 180 | protected function removeDir($dir) 181 | { 182 | $this->filesystem->remove($dir); 183 | } 184 | 185 | /** 186 | * Check if a file should be ignored 187 | * 188 | * @param string $name file's name 189 | * @param array $ignore list of ignores 190 | * @param array $force list of files to force (do not ignore) 191 | * @param string $dirName dir's name (to be removed from file's name) 192 | * @return bool 193 | */ 194 | public function isIgnored($name, array $ignore, array $force, $dirName) 195 | { 196 | $vName = substr($name, strlen($dirName)); 197 | if (in_array($vName, $force, true)) { 198 | return false; 199 | } 200 | // first check if there is line that overrides other lines 201 | foreach ($ignore as $pattern) { 202 | if (0 !== strpos($pattern, '!')) { 203 | continue; 204 | } 205 | $pattern = ltrim($pattern, '!'); 206 | // the ! negates the line, otherwise the syntax is the same 207 | if ($this->isIgnored($name, [$pattern], $force, $dirName)) { 208 | return false; 209 | } 210 | } 211 | foreach ($ignore as $pattern) { 212 | if (false !== strpos($pattern, '**')) { 213 | $pattern = str_replace('**', '*', $pattern); 214 | //$pattern = str_replace('*/*', '*', $pattern); 215 | if ('/' == substr($pattern, 0, 1)) { 216 | $vName = '/' . $vName; 217 | } 218 | if ('.' == substr($vName, 0, 1)) { 219 | $vName = '/' . $vName; 220 | } 221 | if (fnmatch($pattern, $vName, FNM_PATHNAME)) { 222 | return true; 223 | } elseif ('*/*' === $pattern && fnmatch('*', $vName, FNM_PATHNAME)) { 224 | // this a special case, where a double asterisk must match also files in the root dir 225 | return true; 226 | } 227 | } elseif ('/' == substr($pattern, -1)) { // trailing slash 228 | if ('/' == substr($pattern, 0, 1)) { 229 | $pattern = substr($pattern, 1); // remove possible starting slash 230 | } 231 | $escPattern = str_replace(['.', '*'], ['\.', '.*'], $pattern); 232 | if (preg_match('#^' . $escPattern . '#', $vName) > 0) { 233 | return true; 234 | } 235 | } elseif (false === strpos($pattern, '/')) { // no slash 236 | $escPattern = str_replace(['.', '*'], ['\.', '.*'], $pattern); 237 | if (preg_match('#^' . $escPattern . '#', $vName) > 0) { 238 | return true; 239 | } 240 | } elseif ('/' == substr($pattern, 0, 1)) { // starting slash 241 | $escPattern = str_replace(['.', '*'], ['\.', '.*'], $pattern); 242 | if (preg_match('#^' . $escPattern . '#', '/' . $vName) > 0) { 243 | return true; 244 | } 245 | } else { 246 | $escPattern = str_replace(['.', '*'], ['\.', '.*'], $pattern); 247 | if (preg_match('#^' . $escPattern . '#', $vName) > 0) { 248 | return true; 249 | } 250 | } 251 | } 252 | 253 | return false; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/Bowerphp/Repository/GithubRepository.php: -------------------------------------------------------------------------------- 1 | null]; 26 | 27 | /** 28 | * @var Client 29 | */ 30 | protected $githubClient; 31 | 32 | /** 33 | * {@inheritdoc} 34 | * 35 | * @return GithubRepository 36 | */ 37 | public function setUrl($url, $raw = true) 38 | { 39 | $url = preg_replace('/\.git$/', '', str_replace('git://', 'https://' . ($raw ? 'raw.' : ''), $url)); 40 | $this->url = str_replace('raw.github.com', 'raw.githubusercontent.com', $url); 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function getUrl() 49 | { 50 | return $this->url; 51 | } 52 | 53 | /** 54 | * @param Client $githubClient 55 | * @return GithubRepository 56 | */ 57 | public function setHttpClient(Client $githubClient) 58 | { 59 | $this->githubClient = $githubClient; 60 | // see https://developer.github.com/changes/2015-04-17-preview-repository-redirects/ 61 | $this->githubClient->getHttpClient()->setHeaders(['Accept' => 'application/vnd.github.quicksilver-preview+json']); 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function getBower($version = 'master', $includeHomepage = false, $url = '') 70 | { 71 | if ('*' == $version) { 72 | $version = 'master'; 73 | } 74 | if (!empty($url)) { 75 | // we need to save current $this->url 76 | $oldUrl = $this->url; 77 | // then, we call setUrl(), to get the http url 78 | $this->setUrl($url); 79 | } 80 | $json = $this->getDepBowerJson($version); 81 | if ($includeHomepage) { 82 | $array = json_decode($json, true); 83 | if (!empty($url)) { 84 | // here, we set again original $this->url, to pass it in bower.json 85 | $this->setUrl($oldUrl); 86 | } 87 | $array['homepage'] = $this->url; 88 | $json = json_encode($array, JSON_PRETTY_PRINT); 89 | } 90 | 91 | return $json; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function findPackage($rawCriteria = '*') 98 | { 99 | list($repoUser, $repoName) = explode('/', $this->clearGitURL($this->url)); 100 | $paginator = new ResultPager($this->githubClient); 101 | $tags = $paginator->fetchAll($this->githubClient->api('repo'), 'tags', [$repoUser, $repoName]); 102 | 103 | // edge case: package has no tags 104 | if (0 === count($tags)) { 105 | $this->tag['name'] = 'master'; 106 | 107 | return $this->tag['name']; 108 | } 109 | 110 | // edge case: user asked for latest package 111 | if ('latest' == $rawCriteria || '*' == $rawCriteria || empty($rawCriteria)) { 112 | $sortedTags = $this->sortTags($tags); 113 | $this->tag = end($sortedTags); 114 | 115 | return $this->tag['name']; 116 | } 117 | 118 | // edge case for versions with slash (like ckeditor). See also issue #120 119 | if (strpos($rawCriteria, '/') > 0) { 120 | $tagNames = array_column($tags, 'name'); 121 | if (false !== $tag = array_search($rawCriteria, $tagNames, true)) { 122 | $this->tag = $tag; 123 | 124 | return $rawCriteria; 125 | } 126 | } 127 | 128 | try { 129 | $criteria = new expression($rawCriteria); 130 | } catch (SemVerException $sve) { 131 | throw new RuntimeException(sprintf('Criteria %s is not valid.', $rawCriteria), self::INVALID_CRITERIA, $sve); 132 | } 133 | $sortedTags = $this->sortTags($tags); 134 | 135 | // Yes, the php-semver lib does offer a maxSatisfying() method similar the code below. 136 | // We're not using it because it will throw an exception on what it considers to be an 137 | // "invalid" candidate version, and not continue checking the rest of the candidates. 138 | // So, even if it's faster than this code, it's not a complete solution. 139 | $matches = array_filter($sortedTags, function ($tag) use ($criteria) { 140 | $candidate = $tag['parsed_version']; 141 | 142 | return $criteria->satisfiedBy($candidate) ? $tag : false; 143 | }); 144 | 145 | // If the array has elements, the LAST element is the best (highest numbered) version. 146 | if (count($matches) > 0) { 147 | // @todo Get rid of this side effect? 148 | $this->tag = array_pop($matches); 149 | 150 | return $this->tag['name']; 151 | } 152 | 153 | throw new RuntimeException(sprintf('%s: No suitable version for %s was found.', $repoName, $rawCriteria), self::VERSION_NOT_FOUND); 154 | } 155 | 156 | /** 157 | * {@inheritdoc} 158 | */ 159 | public function getRelease($type = 'zip') 160 | { 161 | list($repoUser, $repoName) = explode('/', $this->clearGitURL($this->url)); 162 | 163 | return $this->githubClient->api('repo')->contents()->archive($repoUser, $repoName, $type . 'ball', $this->tag['name']); 164 | } 165 | 166 | /** 167 | * {@inheritdoc} 168 | */ 169 | public function getTags() 170 | { 171 | list($repoUser, $repoName) = explode('/', $this->clearGitURL($this->url)); 172 | $paginator = new ResultPager($this->githubClient); 173 | $tags = $paginator->fetchAll($this->githubClient->api('repo'), 'tags', [$repoUser, $repoName]); 174 | // edge case: no tags 175 | if (0 === count($tags)) { 176 | return []; 177 | } 178 | 179 | $sortedTags = $this->sortTags($tags); // Filters out bad tag specs 180 | 181 | return array_keys($sortedTags); 182 | } 183 | 184 | /** 185 | * Get remote bower.json file (or package.json file) 186 | * 187 | * @param string $version 188 | * @return string 189 | */ 190 | private function getDepBowerJson($version) 191 | { 192 | list($repoUser, $repoName) = explode('/', $this->clearGitURL($this->url)); 193 | $contents = $this->githubClient->api('repo')->contents(); 194 | if ($contents->exists($repoUser, $repoName, 'bower.json', $version)) { 195 | $json = $contents->download($repoUser, $repoName, 'bower.json', $version); 196 | } else { 197 | $isPackageJson = true; 198 | if ($contents->exists($repoUser, $repoName, 'package.json', $version)) { 199 | $json = $contents->download($repoUser, $repoName, 'package.json', $version); 200 | } elseif ('master' != $version) { 201 | return $this->getDepBowerJson('master'); 202 | } 203 | // try anyway. E.g. exists() return false for Modernizr, but then it downloads :-| 204 | $json = $contents->download($repoUser, $repoName, 'package.json', $version); 205 | } 206 | 207 | if ("\xef\xbb\xbf" == substr($json, 0, 3)) { 208 | $json = substr($json, 3); 209 | } 210 | 211 | // for package.json, remove dependencies (see the case of Modernizr) 212 | if (isset($isPackageJson)) { 213 | $array = json_decode($json, true); 214 | if (isset($array['dependencies'])) { 215 | unset($array['dependencies']); 216 | } 217 | $json = json_encode($array, JSON_PRETTY_PRINT); 218 | } 219 | 220 | return $json; 221 | } 222 | 223 | /** 224 | * @param string $url 225 | * 226 | * @return string 227 | */ 228 | private function clearGitURL($url) 229 | { 230 | $partsToClean = [ 231 | 'git://', 232 | 'git@github.com:', 233 | 'https://', 234 | 'github.com/', 235 | 'raw.githubusercontent.com/', 236 | ]; 237 | foreach ($partsToClean as $part) { 238 | $url = str_replace($part, '', $url); 239 | } 240 | 241 | if ('.git' == substr($url, -4)) { 242 | $url = substr($url, 0, -4); 243 | } 244 | 245 | return $url; 246 | } 247 | 248 | /** 249 | * Why do we have to do this? Your guess is as good as mine. 250 | * The only flaw I've seen in the semver lib we're using, 251 | * and the regex's in there are too complicated to mess with. 252 | * 253 | * @param string $rawValue 254 | * 255 | * @return string 256 | */ 257 | private function fixupRawTag($rawValue) 258 | { 259 | if (0 === strpos($rawValue, 'v')) { 260 | $rawValue = substr($rawValue, 1); 261 | } 262 | // WHY NOT SCRUB OUT PLUS SIGNS, RIGHT? 263 | $foundIt = strpos($rawValue, '+'); 264 | if (false !== $foundIt) { 265 | $rawValue = substr($rawValue, 0, $foundIt); 266 | } 267 | $rawValue = strtr($rawValue, ['.alpha' => '-alpha', '.beta' => '-beta', '.dev' => '-dev']); 268 | $pieces = explode('.', $rawValue); 269 | $count = count($pieces); 270 | if (0 == $count) { 271 | $pieces[] = '0'; 272 | $count = 1; 273 | } 274 | for ($add = $count; $add < 3; ++$add) { 275 | $pieces[] = '0'; 276 | } 277 | $return = implode('.', array_slice($pieces, 0, 3)); 278 | 279 | return $return; 280 | } 281 | 282 | /** 283 | * @param array $tags 284 | * @param bool $excludeUnstables 285 | * 286 | * @return array 287 | */ 288 | private function sortTags(array $tags, $excludeUnstables = true) 289 | { 290 | $return = []; 291 | 292 | // Don't include invalid tags 293 | foreach ($tags as $tag) { 294 | try { 295 | $fixedName = $this->fixupRawTag($tag['name']); 296 | $v = new version($fixedName); 297 | if ($v->valid()) { 298 | $version = $v->getVersion(); 299 | if ($excludeUnstables && $this->isNotStable($v)) { 300 | continue; 301 | } 302 | $tag['parsed_version'] = $v; 303 | $return[$version] = $tag; 304 | } 305 | } catch (\Exception $ex) { 306 | // Skip 307 | } 308 | } 309 | 310 | uasort($return, function ($a, $b) { 311 | return version::compare($a['parsed_version'], $b['parsed_version']); 312 | }); 313 | 314 | return $return; 315 | } 316 | 317 | /** 318 | * @param version $version 319 | * 320 | * @return bool 321 | */ 322 | private function isNotStable(version $version) 323 | { 324 | return count($version->getPrerelease()) > 0; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/Bowerphp/Bowerphp.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 Bowerphp; 13 | 14 | use Bowerphp\Config\ConfigInterface; 15 | use Bowerphp\Installer\InstallerInterface; 16 | use Bowerphp\Output\BowerphpConsoleOutput; 17 | use Bowerphp\Package\Package; 18 | use Bowerphp\Package\PackageInterface; 19 | use Bowerphp\Repository\RepositoryInterface; 20 | use Bowerphp\Util\Filesystem; 21 | use Github\Client; 22 | use Guzzle\Http\Exception\RequestException; 23 | use InvalidArgumentException; 24 | use RuntimeException; 25 | use Symfony\Component\Finder\Finder; 26 | use vierbergenlars\SemVer\expression; 27 | use vierbergenlars\SemVer\version; 28 | 29 | /** 30 | * Main class 31 | */ 32 | class Bowerphp 33 | { 34 | protected $config; 35 | 36 | protected $filesystem; 37 | 38 | protected $githubClient; 39 | 40 | protected $repository; 41 | 42 | protected $output; 43 | 44 | /** 45 | * @param ConfigInterface $config 46 | * @param Filesystem $filesystem 47 | * @param Client $githubClient 48 | * @param RepositoryInterface $repository 49 | * @param BowerphpConsoleOutput $output 50 | */ 51 | public function __construct( 52 | ConfigInterface $config, 53 | Filesystem $filesystem, 54 | Client $githubClient, 55 | RepositoryInterface $repository, 56 | BowerphpConsoleOutput $output 57 | ) { 58 | $this->config = $config; 59 | $this->filesystem = $filesystem; 60 | $this->githubClient = $githubClient; 61 | $this->repository = $repository; 62 | $this->output = $output; 63 | } 64 | 65 | /** 66 | * Init bower.json 67 | * 68 | * @param array $params 69 | */ 70 | public function init(array $params) 71 | { 72 | if ($this->config->bowerFileExists()) { 73 | $bowerJson = $this->config->getBowerFileContent(); 74 | $this->config->setSaveToBowerJsonFile(); 75 | $this->config->updateBowerJsonFile2($bowerJson, $params); 76 | } else { 77 | $this->config->initBowerJsonFile($params); 78 | } 79 | } 80 | 81 | /** 82 | * Install a single package 83 | * 84 | * @param PackageInterface $package 85 | * @param InstallerInterface $installer 86 | * @param bool $isDependency 87 | */ 88 | public function installPackage(PackageInterface $package, InstallerInterface $installer, $isDependency = false) 89 | { 90 | if (false !== strpos($package->getName(), 'github')) { 91 | // install from a github endpoint 92 | $name = basename($package->getName(), '.git'); 93 | $repoUrl = $package->getName(); 94 | $package = new Package($name, $package->getRequiredVersion()); 95 | $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient); 96 | $package->setRepository($this->repository); 97 | $packageTag = $this->repository->findPackage($package->getRequiredVersion()); 98 | if (is_null($packageTag)) { 99 | throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion())); 100 | } 101 | } else { 102 | $packageTag = $this->getPackageTag($package, true); 103 | $package->setRepository($this->repository); 104 | } 105 | 106 | $package->setVersion($packageTag); 107 | 108 | $this->updateBowerFile($package, $isDependency); 109 | 110 | // if package is already installed, match current version with latest available version 111 | if ($this->isPackageInstalled($package)) { 112 | $this->output->writelnInfoPackage($package, 'validate', sprintf('%s against %s#%s', $packageTag, $package->getName(), $package->getRequiredVersion())); 113 | $packageBower = $this->config->getPackageBowerFileContent($package); 114 | if ($packageTag == $packageBower['version']) { 115 | // if version is fully matching, there's no need to install 116 | return; 117 | } 118 | } 119 | 120 | $this->cachePackage($package); 121 | 122 | $this->output->writelnInstalledPackage($package); 123 | $installer->install($package); 124 | 125 | $overrides = $this->config->getOverrideFor($package->getName()); 126 | if (array_key_exists('dependencies', $overrides)) { 127 | $dependencies = $overrides['dependencies']; 128 | } else { 129 | $dependencies = $package->getRequires(); 130 | } 131 | if (!empty($dependencies)) { 132 | foreach ($dependencies as $name => $version) { 133 | $depPackage = new Package($name, $version); 134 | if (!$this->isPackageInstalled($depPackage)) { 135 | $this->installPackage($depPackage, $installer, true); 136 | } elseif ($this->isNeedUpdate($depPackage)) { 137 | $this->updatePackage($depPackage, $installer); 138 | } 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * Install all dependencies 145 | * 146 | * @param InstallerInterface $installer 147 | */ 148 | public function installDependencies(InstallerInterface $installer) 149 | { 150 | $decode = $this->config->getBowerFileContent(); 151 | if (!empty($decode['dependencies'])) { 152 | foreach ($decode['dependencies'] as $name => $requiredVersion) { 153 | if (false !== strpos($requiredVersion, 'github')) { 154 | list($name, $requiredVersion) = explode('#', $requiredVersion); 155 | } 156 | $package = new Package($name, $requiredVersion); 157 | $this->installPackage($package, $installer, true); 158 | } 159 | } 160 | } 161 | 162 | /** 163 | * Update a single package 164 | * 165 | * @param PackageInterface $package 166 | * @param InstallerInterface $installer 167 | */ 168 | public function updatePackage(PackageInterface $package, InstallerInterface $installer) 169 | { 170 | if (!$this->isPackageInstalled($package)) { 171 | throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName())); 172 | } 173 | if (is_null($package->getRequiredVersion())) { 174 | $decode = $this->config->getBowerFileContent(); 175 | if (empty($decode['dependencies']) || empty($decode['dependencies'][$package->getName()])) { 176 | throw new InvalidArgumentException(sprintf('Package %s not found in bower.json', $package->getName())); 177 | } 178 | $package->setRequiredVersion($decode['dependencies'][$package->getName()]); 179 | } 180 | 181 | $bower = $this->config->getPackageBowerFileContent($package); 182 | $package->setInfo($bower); 183 | $package->setVersion($bower['version']); 184 | $package->setRequires(isset($bower['dependencies']) ? $bower['dependencies'] : null); 185 | 186 | $packageTag = $this->getPackageTag($package); 187 | $this->output->writelnInfoPackage($package, 'validate', sprintf('%s against %s#%s', $packageTag, $package->getName(), $package->getRequiredVersion())); 188 | 189 | $package->setRepository($this->repository); 190 | if ($packageTag == $package->getVersion()) { 191 | // if version is fully matching, there's no need to update 192 | return; 193 | } 194 | $package->setVersion($packageTag); 195 | 196 | $this->cachePackage($package); 197 | 198 | $this->output->writelnUpdatingPackage($package); 199 | $installer->update($package); 200 | 201 | $overrides = $this->config->getOverrideFor($package->getName()); 202 | if (array_key_exists('dependencies', $overrides)) { 203 | $dependencies = $overrides['dependencies']; 204 | } else { 205 | $dependencies = $package->getRequires(); 206 | } 207 | if (!empty($dependencies)) { 208 | foreach ($dependencies as $name => $requiredVersion) { 209 | $depPackage = new Package($name, $requiredVersion); 210 | if (!$this->isPackageInstalled($depPackage)) { 211 | $this->installPackage($depPackage, $installer, true); 212 | } elseif ($this->isNeedUpdate($depPackage)) { 213 | $this->updatePackage($depPackage, $installer); 214 | } 215 | } 216 | } 217 | } 218 | 219 | /** 220 | * Update all dependencies 221 | * 222 | * @param InstallerInterface $installer 223 | */ 224 | public function updatePackages(InstallerInterface $installer) 225 | { 226 | $decode = $this->config->getBowerFileContent(); 227 | if (!empty($decode['dependencies'])) { 228 | foreach ($decode['dependencies'] as $packageName => $requiredVersion) { 229 | $this->updatePackage(new Package($packageName, $requiredVersion), $installer); 230 | } 231 | } 232 | } 233 | 234 | /** 235 | * @param PackageInterface $package 236 | * @param string $info 237 | * @return mixed 238 | */ 239 | public function getPackageInfo(PackageInterface $package, $info = 'url') 240 | { 241 | $decode = $this->lookupPackage($package->getName()); 242 | 243 | $this->repository->setHttpClient($this->githubClient); 244 | 245 | if ('url' == $info) { 246 | $this->repository->setUrl($decode['url'], false); 247 | 248 | return $this->repository->getUrl(); 249 | } 250 | 251 | if ('versions' == $info) { 252 | $tags = $this->repository->getTags(); 253 | usort($tags, function ($a, $b) { 254 | return version_compare($b, $a); 255 | }); 256 | 257 | return $tags; 258 | } 259 | 260 | throw new RuntimeException(sprintf('Unsupported info option "%s".', $info)); 261 | } 262 | 263 | /** 264 | * @param string $name 265 | * @return array 266 | */ 267 | public function lookupPackage($name) 268 | { 269 | return $this->findPackage($name); 270 | } 271 | 272 | /** 273 | * @param PackageInterface $package 274 | * @return string 275 | */ 276 | public function getPackageBowerFile(PackageInterface $package) 277 | { 278 | $this->repository->setHttpClient($this->githubClient); 279 | $lookupPackage = $this->lookupPackage($package->getName()); 280 | $this->repository->setUrl($lookupPackage['url'], false); 281 | $tag = $this->repository->findPackage($package->getRequiredVersion()); 282 | 283 | return $this->repository->getBower($tag, true, $lookupPackage['url']); 284 | } 285 | 286 | /** 287 | * Uninstall a single package 288 | * 289 | * @param PackageInterface $package 290 | * @param InstallerInterface $installer 291 | */ 292 | public function uninstallPackage(PackageInterface $package, InstallerInterface $installer) 293 | { 294 | if (!$this->isPackageInstalled($package)) { 295 | throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName())); 296 | } 297 | $installer->uninstall($package); 298 | } 299 | 300 | /** 301 | * Search packages by name 302 | * 303 | * @param string $name 304 | * @return array 305 | */ 306 | public function searchPackages($name) 307 | { 308 | try { 309 | $url = $this->config->getBasePackagesUrl() . 'search/' . $name; 310 | $response = $this->githubClient->getHttpClient()->get($url); 311 | 312 | return json_decode($response->getBody(true), true); 313 | } catch (RequestException $e) { 314 | throw new RuntimeException(sprintf('Cannot get package list from %s.', str_replace('/packages/', '', $this->config->getBasePackagesUrl()))); 315 | } 316 | } 317 | 318 | /** 319 | * Get a list of installed packages 320 | * 321 | * @param InstallerInterface $installer 322 | * @param Finder $finder 323 | * @return array 324 | */ 325 | public function getInstalledPackages(InstallerInterface $installer, Finder $finder) 326 | { 327 | return $installer->getInstalled($finder); 328 | } 329 | 330 | /** 331 | * Check if package is installed 332 | * 333 | * @param PackageInterface $package 334 | * @return bool 335 | */ 336 | public function isPackageInstalled(PackageInterface $package) 337 | { 338 | return $this->filesystem->exists($this->config->getInstallDir() . '/' . $package->getName() . '/.bower.json'); 339 | } 340 | 341 | /** 342 | * @param PackageInterface $package 343 | * @param bool $checkInstall 344 | * 345 | * @return bool 346 | */ 347 | public function isPackageExtraneous(PackageInterface $package, $checkInstall = false) 348 | { 349 | if ($checkInstall && !$this->isPackageInstalled($package)) { 350 | return false; 351 | } 352 | 353 | try { 354 | $bower = $this->config->getBowerFileContent(); 355 | } catch (RuntimeException $e) { // no bower.json file, package is extraneous 356 | return true; 357 | } 358 | if (!isset($bower['dependencies'])) { 359 | return true; 360 | } 361 | // package is a direct dependencies 362 | if (isset($bower['dependencies'][$package->getName()])) { 363 | return false; 364 | } 365 | // look for dependencies of dependencies 366 | foreach ($bower['dependencies'] as $name => $version) { 367 | $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name . '/.bower.json'); 368 | $depBower = json_decode($dotBowerJson, true); 369 | if (isset($depBower['dependencies'][$package->getName()])) { 370 | return false; 371 | } 372 | // look for dependencies of dependencies of dependencies 373 | if (isset($depBower['dependencies'])) { 374 | foreach ($depBower['dependencies'] as $name1 => $version1) { 375 | $dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name1 . '/.bower.json'); 376 | $depDepBower = json_decode($dotBowerJson, true); 377 | if (isset($depDepBower['dependencies'][$package->getName()])) { 378 | return false; 379 | } 380 | } 381 | } 382 | } 383 | 384 | return true; 385 | } 386 | 387 | /** 388 | * @param array $params 389 | * @return array 390 | */ 391 | protected function createAClearBowerFile(array $params) 392 | { 393 | $authors = ['Beelab ']; 394 | if (!empty($params['author'])) { 395 | $authors[] = $params['author']; 396 | } 397 | $structure = [ 398 | 'name' => $params['name'], 399 | 'authors' => $authors, 400 | 'private' => true, 401 | 'dependencies' => new \StdClass(), 402 | ]; 403 | 404 | return $structure; 405 | } 406 | 407 | /** 408 | * @param PackageInterface $package 409 | * @param bool $setInfo 410 | * @return string 411 | */ 412 | protected function getPackageTag(PackageInterface $package, $setInfo = false) 413 | { 414 | $decode = $this->findPackage($package->getName()); 415 | // open package repository 416 | $repoUrl = $decode['url']; 417 | $this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient); 418 | $packageTag = $this->repository->findPackage($package->getRequiredVersion()); 419 | if (is_null($packageTag)) { 420 | throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion())); 421 | } 422 | $bowerJson = $this->repository->getBower($packageTag); 423 | $bower = json_decode($bowerJson, true); 424 | if (!is_array($bower)) { 425 | throw new RuntimeException(sprintf('Invalid bower.json found in package %s: %s.', $package->getName(), $bowerJson)); 426 | } 427 | if ($setInfo) { 428 | $package->setInfo($bower); 429 | } 430 | 431 | return $packageTag; 432 | } 433 | 434 | /** 435 | * @param string $name 436 | * @return array 437 | */ 438 | protected function findPackage($name) 439 | { 440 | try { 441 | $response = $this->githubClient->getHttpClient()->get($this->config->getBasePackagesUrl() . urlencode($name)); 442 | } catch (RuntimeException $e) { 443 | throw new RuntimeException(sprintf('Cannot fetch registry info for package %s from search registry (%s).', $name, $e->getMessage())); 444 | } 445 | $packageInfo = json_decode($response->getBody(true), true); 446 | if (!is_array($packageInfo) || empty($packageInfo['url'])) { 447 | throw new RuntimeException(sprintf('Registry info for package %s has malformed json or is missing "url".', $name)); 448 | } 449 | 450 | return $packageInfo; 451 | } 452 | 453 | /** 454 | * @param PackageInterface $package 455 | */ 456 | private function cachePackage(PackageInterface $package) 457 | { 458 | $this->output->writelnInfoPackage($package, 'download'); 459 | 460 | // get release archive from repository 461 | $file = $this->repository->getRelease(); 462 | 463 | $tmpFileName = $this->config->getCacheDir() . '/tmp/' . $package->getName(); 464 | $this->filesystem->write($tmpFileName, $file); 465 | } 466 | 467 | /** 468 | * @param PackageInterface $package 469 | * @param bool $isDependency 470 | */ 471 | private function updateBowerFile(PackageInterface $package, $isDependency = false) 472 | { 473 | if ($this->config->isSaveToBowerJsonFile() && !$isDependency) { 474 | try { 475 | $this->config->updateBowerJsonFile($package); 476 | } catch (RuntimeException $e) { 477 | $this->output->writelnNoBowerJsonFile(); 478 | } 479 | } 480 | } 481 | 482 | /** 483 | * Update only if needed is greater version 484 | * 485 | * @param PackageInterface $package 486 | * @return bool 487 | */ 488 | public function isNeedUpdate($package) 489 | { 490 | $packageBower = $this->config->getPackageBowerFileContent($package); 491 | $semver = new version($packageBower['version']); 492 | 493 | return !$semver->satisfies(new expression($package->getRequiredVersion())); 494 | } 495 | } 496 | --------------------------------------------------------------------------------