├── .editorconfig ├── .github ├── FUNDING.yml ├── donate.png └── workflows │ └── test.yml ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README-zh_CN.md ├── README.md ├── composer.json ├── crm.default.json ├── phpunit.xml.dist ├── src ├── Command │ ├── AddCommand.php │ ├── Command.php │ ├── ListCommand.php │ ├── RemoveCommand.php │ ├── RepoCommand.php │ ├── ResetCommand.php │ └── UseCommand.php ├── ProxyApplication.php ├── Repository.php ├── RepositoryCollection.php ├── RepositoryManager.php └── Utils.php └── tests ├── ApplicationTest.php ├── Command ├── AddCommandTest.php ├── CommandTestCase.php ├── ListCommandTest.php ├── RemoveCommandTest.php ├── RepoCommandTest.php ├── ResetCommandTest.php └── UseCommandTest.php ├── RepositoryCollectionTest.php ├── RepositoryManagerTest.php ├── RepositoryTest.php └── Stub └── RepositoryManagerStub.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; top-most EditorConfig file 2 | root = true 3 | 4 | ; Unix-style newlines 5 | [*] 6 | end_of_line = LF 7 | 8 | [*.php] 9 | indent_style = space 10 | indent_size = 4 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://raw.githubusercontent.com/slince/composer-registry-manager/master/.github/donate.png 13 | -------------------------------------------------------------------------------- /.github/donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slince/composer-registry-manager/217654df0e0f8a9b5494e0f7d55d46e9b024ecd3/.github/donate.png -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Validate composer.json and composer.lock 21 | run: composer validate --strict 22 | 23 | - name: Cache Composer packages 24 | id: composer-cache 25 | uses: actions/cache@v3 26 | with: 27 | path: vendor 28 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-php- 31 | 32 | - name: Install dependencies 33 | run: composer install --prefer-dist --no-progress 34 | 35 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 36 | # Docs: https://getcomposer.org/doc/articles/scripts.md 37 | 38 | 39 | - name: Run test suite 40 | run: ./vendor/bin/phpunit --coverage-clover coverage.xml 41 | 42 | - name: Upload coverage to Codecov 43 | uses: codecov/codecov-action@v1 44 | with: 45 | token: ${{ secrets.CODECOV_TOKEN }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .idea/ 3 | composer.lock 4 | /logs/ 5 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - "tests/*" 4 | - "vendor/*" 5 | 6 | tools: 7 | external_code_coverage: 8 | timeout: 600 9 | 10 | php_sim: true 11 | 12 | php_changetracking: true 13 | 14 | php_cs_fixer: 15 | enabled: true 16 | config: 17 | level: all 18 | filter: 19 | excluded_paths: 20 | - "tests/*" 21 | - "vendor/*" 22 | 23 | php_mess_detector: 24 | enabled: true 25 | filter: 26 | excluded_paths: 27 | - "tests/*" 28 | - "vendor/*" 29 | 30 | php_pdepend: 31 | enabled: true 32 | filter: 33 | excluded_paths: 34 | - "tests/*" 35 | - "vendor/*" 36 | 37 | php_analyzer: 38 | enabled: true 39 | filter: 40 | excluded_paths: 41 | - "tests/*" 42 | - "vendor/*" 43 | 44 | 45 | php_cpd: 46 | enabled: true 47 | excluded_dirs: 48 | - "tests/*" 49 | - "vendor/*" 50 | 51 | php_loc: 52 | enabled: true 53 | excluded_dirs: 54 | - "tests/*" 55 | - "vendor/*" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 8.1 5 | 6 | before_script: 7 | - composer install 8 | 9 | script: ./vendor/bin/phpunit --coverage-clover=coverage.xml 10 | 11 | after_success: 12 | - bash <(curl -s https://codecov.io/bash) 13 | 14 | notifications: 15 | email: false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2017 Slince https://www.phpdish.com 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. -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 | # CRM - Composer源管理工具 2 | 3 | [![Build Status](https://img.shields.io/github/workflow/status/slince/composer-registry-manager/test?style=flat-square)](https://github.com/slince/composer-registry-manager/actions) 4 | [![Coverage Status](https://img.shields.io/codecov/c/github/slince/composer-registry-manager.svg?style=flat-square)](https://codecov.io/github/slince/composer-registry-manager) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/slince/composer-registry-manager.svg?style=flat-square)](https://packagist.org/packages/slince/composer-registry-manager) 6 | [![Latest Stable Version](https://img.shields.io/packagist/v/slince/composer-registry-manager.svg?style=flat-square&label=stable)](https://packagist.org/packages/slince/composer-registry-manager) 7 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/slince/composer-registry-manager.svg?style=flat-square)](https://scrutinizer-ci.com/g/slince/composer-registry-manager/?branch=master) 8 | 9 | 由于composer在国内下载速度非常慢,大家都习惯使用中国镜像,如果正在使用的镜像没有更新或者出现故障你可以使用Composer Registry Manager帮助你轻松地切换到另外一个镜像。 10 | 默认带了一些镜像,当然你也可以添加新的镜像。 11 | 12 | ## 安装 13 | 14 | 使用composer安装,执行下面命令 15 | 16 | ```bash 17 | $ composer global require slince/composer-registry-manager 18 | ``` 19 | 20 | ## 基本用法 21 | 22 | ### 列出所有可使用的镜像 23 | 24 | ```bash 25 | $ composer repo:ls 26 | 27 | --- ------------- ------------------------------------------------ ------------------------------ 28 | composer https://packagist.org Europe, Canada and Singapore 29 | aliyun https://mirrors.aliyun.com/composer China 30 | tencent https://mirrors.cloud.tencent.com/composer China 31 | * huawei https://mirrors.huaweicloud.com/repository/php China 32 | cnpkg https://php.cnpkg.org China 33 | sjtug https://packagist.mirrors.sjtug.sjtu.edu.cn China 34 | phpcomposer https://packagist.phpcomposer.com China 35 | kkame https://packagist.kr South Korea 36 | hiraku https://packagist.jp Japan 37 | webysther https://packagist.com.br Brazil 38 | solidworx https://packagist.co.za South Africa 39 | indra https://packagist.phpindonesia.id Indonesia 40 | varun https://packagist.in India 41 | --- ------------- ------------------------------------------------ ------------------------------ 42 | ``` 43 | 标“*”表示当前正在使用的源; 44 | 45 | 你可以使用 `--location xx` 按地区过滤 46 | 47 | ```bash 48 | $ composer repo:ls --location China 49 | ``` 50 | 51 | ### 切换镜像 52 | 53 | ```bash 54 | $ composer repo:use 55 | 56 | Please select your favorite repository (defaults to composer) [composer]: 57 | [0 ] composer 58 | [1 ] aliyun 59 | [2 ] tencent 60 | [3 ] huawei 61 | [4 ] cnpkg 62 | [5 ] sjtug 63 | [6 ] phpcomposer 64 | [7 ] kkame 65 | [8 ] hiraku 66 | [9 ] webysther 67 | [10] solidworx 68 | [11] indra 69 | [12] varun 70 | > 71 | ``` 72 | 你也可以直接追加镜像名称来跳过选择 73 | 74 | ```bash 75 | $ composer repo:use aliyun 76 | ``` 77 | 78 | 添加选项 `--current/-c` 为当前项目切换源,默认是修改全局的源。 79 | 80 | ### 重置命令 81 | 82 | 如果你想丢弃所有自定义的镜像源,你可以使用下面命令: 83 | 84 | ```bash 85 | $ composer repo:reset 86 | ``` 87 | 88 | ### 所有命令 89 | 90 | 执行下面命令查看 91 | 92 | ```bash 93 | $ composer repo 94 | _____ _____ ___ ___ 95 | / ___| | _ \ / |/ | 96 | | | | |_| | / /| /| | 97 | | | | _ / / / |__/ | | 98 | | |___ | | \ \ / / | | 99 | \_____| |_| \_\ /_/ |_| 100 | 101 | Composer Repository Manager version 2.0.0 102 | 103 | Usage: 104 | command [options] [arguments] 105 | 106 | Options: 107 | -h, --help Display this help message 108 | -q, --quiet Do not output any message 109 | -V, --version Display this application version 110 | --ansi Force ANSI output 111 | --no-ansi Disable ANSI output 112 | -n, --no-interaction Do not ask any interactive question 113 | -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug 114 | 115 | Available commands for the "repo" namespace: 116 | repo:add Creates a repository 117 | repo:ls List all available repositories 118 | repo:remove Remove a repository 119 | repo:use Change current repository 120 | ``` 121 | 122 | ## LICENSE 123 | 124 | The MIT license. See [MIT](https://opensource.org/licenses/MIT) 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRM - Composer Registry Manager 2 | 3 | [![Build Status](https://img.shields.io/github/actions/workflow/status/slince/composer-registry-manager/test.yml?style=flat-square)](https://github.com/slince/composer-registry-manager/actions) 4 | [![Coverage Status](https://img.shields.io/codecov/c/github/slince/composer-registry-manager.svg?style=flat-square)](https://codecov.io/github/slince/composer-registry-manager) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/slince/composer-registry-manager.svg?style=flat-square)](https://packagist.org/packages/slince/composer-registry-manager) 6 | [![Latest Stable Version](https://img.shields.io/packagist/v/slince/composer-registry-manager.svg?style=flat-square&label=stable)](https://packagist.org/packages/slince/composer-registry-manager) 7 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/slince/composer-registry-manager.svg?style=flat-square)](https://scrutinizer-ci.com/g/slince/composer-registry-manager/?branch=master) 8 | 9 | Composer Registry Manager can help you easily and quickly switch between different composer repositories. 10 | 11 | [简体中文](./README-zh_CN.md) 12 | 13 | ## Installation 14 | 15 | Install via composer 16 | 17 | ```bash 18 | $ composer global require slince/composer-registry-manager ^2.0 19 | ``` 20 | 21 | ## Example 22 | 23 | ### List all available repositories 24 | 25 | ```bash 26 | $ composer repo:ls 27 | 28 | --- ------------- ------------------------------------------------ ------------------------------ 29 | composer https://packagist.org Europe, Canada and Singapore 30 | aliyun https://mirrors.aliyun.com/composer China 31 | tencent https://mirrors.cloud.tencent.com/composer China 32 | * huawei https://mirrors.huaweicloud.com/repository/php China 33 | cnpkg https://php.cnpkg.org China 34 | sjtug https://packagist.mirrors.sjtug.sjtu.edu.cn China 35 | phpcomposer https://packagist.phpcomposer.com China 36 | kkame https://packagist.kr South Korea 37 | hiraku https://packagist.jp Japan 38 | webysther https://packagist.com.br Brazil 39 | solidworx https://packagist.co.za South Africa 40 | indra https://packagist.phpindonesia.id Indonesia 41 | varun https://packagist.in India 42 | --- ------------- ------------------------------------------------ ------------------------------ 43 | ``` 44 | 45 | You can filter by location using `--location xx` 46 | 47 | ```bash 48 | $ composer repo:ls --location China 49 | ``` 50 | 51 | ### Switch repository 52 | 53 | ```bash 54 | $ composer repo:use 55 | 56 | Please select your favorite repository (defaults to composer) [composer]: 57 | [0 ] composer 58 | [1 ] aliyun 59 | [2 ] tencent 60 | [3 ] huawei 61 | [4 ] cnpkg 62 | [5 ] sjtug 63 | [6 ] phpcomposer 64 | [7 ] kkame 65 | [8 ] hiraku 66 | [9 ] webysther 67 | [10] solidworx 68 | [11] indra 69 | [12] varun 70 | > 71 | ``` 72 | You can also skip selection by giving repository name. 73 | 74 | ```bash 75 | $ composer repo:use aliyun 76 | ``` 77 | Add the option `--current/-c` for the current project. 78 | 79 | ### Reset command 80 | 81 | If you want to discard all custom mirrors, you can use the following command: 82 | 83 | ```bash 84 | $ composer repo:reset 85 | ``` 86 | 87 | ### Available commands 88 | 89 | Use the following command for help. 90 | 91 | ```bash 92 | $ composer repo 93 | _____ _____ ___ ___ 94 | / ___| | _ \ / |/ | 95 | | | | |_| | / /| /| | 96 | | | | _ / / / |__/ | | 97 | | |___ | | \ \ / / | | 98 | \_____| |_| \_\ /_/ |_| 99 | 100 | Composer Repository Manager version 2.0.0 101 | 102 | Usage: 103 | command [options] [arguments] 104 | 105 | Options: 106 | -h, --help Display this help message 107 | -q, --quiet Do not output any message 108 | -V, --version Display this application version 109 | --ansi Force ANSI output 110 | --no-ansi Disable ANSI output 111 | -n, --no-interaction Do not ask any interactive question 112 | -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug 113 | 114 | Available commands for the "repo" namespace: 115 | repo:add Creates a repository 116 | repo:ls List all available repositories 117 | repo:remove Remove a repository 118 | repo:use Change current repository 119 | ``` 120 | 121 | ## LICENSE 122 | 123 | The MIT license. See [MIT](https://opensource.org/licenses/MIT) 124 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slince/composer-registry-manager", 3 | "description": "Composer mirrors manager", 4 | "keywords": ["composer repository", "composer mirror", "switch composer mirror", "composer", "mirror"], 5 | "type": "composer-plugin", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Slince", 10 | "email": "taosikai@yeah.net" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=8.1", 15 | "ext-json": "*", 16 | "composer-plugin-api": "^1.1|^2.0" 17 | }, 18 | "require-dev": { 19 | "composer/composer": "^1.5|^2.0", 20 | "phpunit/phpunit": "^8.0|^9.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Slince\\Crm\\": "./src" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Slince\\Crm\\Tests\\": "./tests" 30 | } 31 | }, 32 | "extra":{ 33 | "class": "Slince\\Crm\\RepositoryManager" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crm.default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "composer", 4 | "location": "Europe, Canada and Singapore", 5 | "url": "https://packagist.org" 6 | }, 7 | { 8 | "name": "aliyun", 9 | "location": "China", 10 | "url": "https://mirrors.aliyun.com/composer" 11 | }, 12 | { 13 | "name": "tencent", 14 | "location": "China", 15 | "url": "https://mirrors.cloud.tencent.com/composer" 16 | }, 17 | { 18 | "name": "huawei", 19 | "location": "China", 20 | "url": "https://mirrors.huaweicloud.com/repository/php" 21 | }, 22 | { 23 | "name": "cnpkg", 24 | "location": "China", 25 | "url": "https://php.cnpkg.org" 26 | }, 27 | { 28 | "name": "sjtug", 29 | "location": "China", 30 | "url": "https://packagist.mirrors.sjtug.sjtu.edu.cn" 31 | }, 32 | { 33 | "name": "phpcomposer", 34 | "location": "China", 35 | "url": "https://packagist.phpcomposer.com" 36 | }, 37 | { 38 | "name": "kkame", 39 | "location": "South Korea", 40 | "url": "https://packagist.kr" 41 | }, 42 | { 43 | "name": "hiraku", 44 | "location": "Japan", 45 | "url": "https://packagist.jp" 46 | }, 47 | { 48 | "name": "webysther", 49 | "location": "Brazil", 50 | "url": "https://packagist.com.br" 51 | }, 52 | { 53 | "name": "solidworx", 54 | "location": "South Africa", 55 | "url": "https://packagist.co.za" 56 | }, 57 | { 58 | "name": "indra", 59 | "location": "Indonesia", 60 | "url": "https://packagist.phpindonesia.id" 61 | }, 62 | { 63 | "name": "varun", 64 | "location": "India", 65 | "url": "https://packagist.in" 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | ./tests/ 11 | 12 | 13 | 14 | 15 | 16 | ./ 17 | 18 | ./tests 19 | ./vendor 20 | ./src/RepositoryManager.php 21 | ./src/ConfigPath.php 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Command/AddCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm\Command; 15 | 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\Console\Style\SymfonyStyle; 20 | 21 | class AddCommand extends Command 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function configure() 27 | { 28 | $this->setName('repo:add') 29 | ->setDescription('Creates a repository') 30 | ->addArgument('repository-name', InputArgument::REQUIRED, 'The repository name') 31 | ->addArgument('repository-url', InputArgument::REQUIRED, 'The repository url') 32 | ->addArgument('repository-location', InputArgument::OPTIONAL, 'The repository location'); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function execute(InputInterface $input, OutputInterface $output): int 39 | { 40 | $repositoryName = $input->getArgument('repository-name'); 41 | $repositoryUrl = $input->getArgument('repository-url'); 42 | $repositoryLocation = $input->getArgument('repository-location'); 43 | //Add repository & dump to config file 44 | $this->repositoryManager->addRepository($repositoryName, $repositoryUrl, $repositoryLocation); 45 | 46 | $style = new SymfonyStyle($input, $output); 47 | $style->success("Add the repository [{$repositoryName}] success"); 48 | 49 | return 0; 50 | } 51 | } -------------------------------------------------------------------------------- /src/Command/Command.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm\Command; 15 | 16 | use Composer\Command\BaseCommand; 17 | use Slince\Crm\RepositoryManager; 18 | 19 | class Command extends BaseCommand 20 | { 21 | /** 22 | * @var RepositoryManager 23 | */ 24 | protected RepositoryManager $repositoryManager; 25 | 26 | public function __construct(RepositoryManager $repositoryManager, ?string $name = null) 27 | { 28 | $this->repositoryManager = $repositoryManager; 29 | parent::__construct($name); 30 | } 31 | 32 | /** 33 | * Return the repo manager. 34 | * 35 | * @return RepositoryManager 36 | */ 37 | public function getRepositoryManager(): RepositoryManager 38 | { 39 | return $this->repositoryManager; 40 | } 41 | } -------------------------------------------------------------------------------- /src/Command/ListCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm\Command; 15 | 16 | use Slince\Crm\Repository; 17 | use Symfony\Component\Console\Input\InputInterface; 18 | use Symfony\Component\Console\Input\InputOption; 19 | use Symfony\Component\Console\Output\OutputInterface; 20 | use Symfony\Component\Console\Style\SymfonyStyle; 21 | 22 | class ListCommand extends Command 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function configure() 28 | { 29 | $this->setName('repo:ls') 30 | ->addOption('location', 'l', InputOption::VALUE_OPTIONAL, 'The location of the repository') 31 | ->setDescription('List all available repositories'); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function execute(InputInterface $input, OutputInterface $output): int 38 | { 39 | // filter by location 40 | $location = $input->getOption('location'); 41 | $repositories = $location 42 | ? $this->filterRepositoriesByLocation($location) 43 | : iterator_to_array($this->repositoryManager->getRepositories()); 44 | 45 | //find all repository records 46 | $currentRepository = $this->repositoryManager->getCurrentComposerRepository(); 47 | $rows = array_map(function (Repository $repository) use ($currentRepository) { 48 | if ($currentRepository === $repository) { 49 | return [ 50 | '*', 51 | "{$repository->getName()}", 52 | "{$repository->getUrl()}", 53 | "{$repository->getLocation()}", 54 | ]; 55 | } else { 56 | return [ 57 | '', 58 | $repository->getName(), 59 | $repository->getUrl(), 60 | $repository->getLocation() 61 | ]; 62 | } 63 | }, $repositories); 64 | 65 | $style = new SymfonyStyle($input, $output); 66 | $style->table([], $rows); 67 | 68 | return 0; 69 | } 70 | 71 | protected function filterRepositoriesByLocation(string $location): array 72 | { 73 | return array_filter(iterator_to_array($this->repositoryManager->getRepositories()), 74 | fn(Repository $repository) => stripos($repository->getLocation(), $location) !== false 75 | ); 76 | } 77 | } -------------------------------------------------------------------------------- /src/Command/RemoveCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm\Command; 15 | 16 | use Slince\Crm\Repository; 17 | use Symfony\Component\Console\Input\InputArgument; 18 | use Symfony\Component\Console\Input\InputInterface; 19 | use Symfony\Component\Console\Output\OutputInterface; 20 | use Symfony\Component\Console\Question\ChoiceQuestion; 21 | use Symfony\Component\Console\Style\SymfonyStyle; 22 | 23 | class RemoveCommand extends Command 24 | { 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function configure() 29 | { 30 | $this->setName('repo:remove') 31 | ->setDescription('Remove a repository') 32 | ->addArgument('repository-name', InputArgument::OPTIONAL, 'The repository name you want to remove'); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function execute(InputInterface $input, OutputInterface $output): int 39 | { 40 | $style = new SymfonyStyle($input, $output); 41 | $repositoryName = $input->getArgument('repository-name'); 42 | //auto select 43 | if (is_null($repositoryName)) { 44 | $question = new ChoiceQuestion( 45 | 'Please select repository your want to remove', 46 | array_map(function (Repository $repository) { 47 | return $repository->getName(); 48 | }, $this->repositoryManager->getRepositories()->all()) 49 | ); 50 | $question->setErrorMessage('repository %s is invalid.'); 51 | $repositoryName = $style->askQuestion($question); 52 | } 53 | //Remove repository & dump to config file 54 | $this->repositoryManager->removeRepository($repositoryName); 55 | 56 | $style->success("Remove the repository [{$repositoryName}] success"); 57 | 58 | return 0; 59 | } 60 | } -------------------------------------------------------------------------------- /src/Command/RepoCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm\Command; 15 | 16 | use Slince\Crm\ProxyApplication; 17 | use Symfony\Component\Console\Input\InputArgument; 18 | use Symfony\Component\Console\Input\InputDefinition; 19 | use Symfony\Component\Console\Input\InputInterface; 20 | use Symfony\Component\Console\Input\InputOption; 21 | use Symfony\Component\Console\Output\OutputInterface; 22 | use Symfony\Component\Console\Helper\DescriptorHelper; 23 | 24 | class RepoCommand extends Command 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function configure() 30 | { 31 | $this->setName('repo'); 32 | $this->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 | * {@inheritdoc} 56 | */ 57 | public function execute(InputInterface $input, OutputInterface $output): int 58 | { 59 | $helper = new DescriptorHelper(); 60 | $helper->describe($output, new ProxyApplication($this->repositoryManager->getCommands()), array( 61 | 'format' => $input->getOption('format'), 62 | 'raw_text' => $input->getOption('raw'), 63 | 'namespace' => 'repo', 64 | )); 65 | 66 | return 0; 67 | } 68 | 69 | private function createDefinition(): InputDefinition 70 | { 71 | return new InputDefinition(array( 72 | new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), 73 | new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), 74 | new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), 75 | )); 76 | } 77 | } -------------------------------------------------------------------------------- /src/Command/ResetCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm\Command; 15 | 16 | use Symfony\Component\Console\Input\InputInterface; 17 | use Symfony\Component\Console\Output\OutputInterface; 18 | use Symfony\Component\Console\Style\SymfonyStyle; 19 | 20 | class ResetCommand extends Command 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function configure() 26 | { 27 | $this->setName('repo:reset') 28 | ->setDescription('Remove custom repositories and reset to default'); 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | protected function execute(InputInterface $input, OutputInterface $output): int 35 | { 36 | $style = new SymfonyStyle($input, $output); 37 | if ($style->ask('This command will remove custom repositories. Are you sure to do this?')) { 38 | $this->repositoryManager->resetRepositories(); 39 | $style->success('Successfully reset'); 40 | } 41 | 42 | return 0; 43 | } 44 | } -------------------------------------------------------------------------------- /src/Command/UseCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm\Command; 15 | 16 | use Slince\Crm\Repository; 17 | use Symfony\Component\Console\Input\InputArgument; 18 | use Symfony\Component\Console\Input\InputInterface; 19 | use Symfony\Component\Console\Input\InputOption; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | use Symfony\Component\Console\Question\ChoiceQuestion; 22 | use Symfony\Component\Console\Style\SymfonyStyle; 23 | 24 | class UseCommand extends Command 25 | { 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function configure() 30 | { 31 | parent::configure(); 32 | $this->setName('repo:use') 33 | ->setDescription('Change current repository') 34 | ->addArgument('repository-name', InputArgument::OPTIONAL, 'The repository name you want to use') 35 | ->addOption('current', 'c', InputOption::VALUE_NONE, 'Change repository for current project'); 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function execute(InputInterface $input, OutputInterface $output): int 42 | { 43 | $style = new SymfonyStyle($input, $output); 44 | $repositoryName = $input->getArgument('repository-name'); 45 | //auto select 46 | if (is_null($repositoryName)) { 47 | $question = new ChoiceQuestion( 48 | 'Please select your favorite repository (defaults to composer)', 49 | array_map(function (Repository $repository) { 50 | return $repository->getName(); 51 | }, $this->repositoryManager->getRepositories()->all()), 52 | 0 53 | ); 54 | $question->setErrorMessage('The repository %s is invalid.'); 55 | $repositoryName = $style->askQuestion($question); 56 | } 57 | 58 | $repository = $this->repositoryManager->getRepositories()->search($repositoryName); 59 | 60 | $this->repositoryManager->useRepository($repository, $input->getOption('current')); 61 | 62 | $style->success("Use the repository [{$repositoryName}] successfully"); 63 | 64 | return 0; 65 | } 66 | } -------------------------------------------------------------------------------- /src/ProxyApplication.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm; 15 | 16 | use Symfony\Component\Console\Application; 17 | 18 | class ProxyApplication extends Application 19 | { 20 | /** 21 | * @var string 22 | */ 23 | const VERSION = '2.4.0'; 24 | 25 | protected static string $logo = <<commands = $commands; 41 | parent::__construct('Composer Repository Manager', static::VERSION); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function getHelp(): string 48 | { 49 | return static::$logo.parent::getHelp(); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function getDefaultCommands(): array 56 | { 57 | return array_merge(parent::getDefaultCommands(), $this->commands); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Repository.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm; 15 | 16 | final class Repository 17 | { 18 | /** 19 | * Repository name. 20 | * 21 | * @var string 22 | */ 23 | protected string $name; 24 | 25 | /** 26 | * Repository url. 27 | * 28 | * @var string 29 | */ 30 | protected string $url; 31 | 32 | /** 33 | * Repository location. 34 | * 35 | * @var string|null 36 | */ 37 | protected ?string $location; 38 | 39 | public function __construct(string $name, string $url, ?string $location = null) 40 | { 41 | $this->name = $name; 42 | $this->url = $url; 43 | $this->location = $location; 44 | } 45 | 46 | /** 47 | * @param string $name 48 | */ 49 | public function setName(string $name) 50 | { 51 | $this->name = $name; 52 | } 53 | 54 | /** 55 | * @param string $url 56 | */ 57 | public function setUrl(string $url) 58 | { 59 | $this->url = $url; 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function getName(): string 66 | { 67 | return $this->name; 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | public function getUrl(): string 74 | { 75 | return $this->url; 76 | } 77 | 78 | /** 79 | * @return string|null 80 | */ 81 | public function getLocation(): ?string 82 | { 83 | return $this->location; 84 | } 85 | 86 | /** 87 | * @param string $location 88 | */ 89 | public function setLocation(string $location) 90 | { 91 | $this->location = $location; 92 | } 93 | 94 | /** 95 | * convert to array. 96 | * 97 | * @return array 98 | */ 99 | public function toArray(): array 100 | { 101 | return [ 102 | 'name' => $this->name, 103 | 'url' => $this->url, 104 | 'location' => $this->location 105 | ]; 106 | } 107 | 108 | /** 109 | * Create a repository instance. 110 | * 111 | * @param array $data 112 | * 113 | * @return static 114 | * @throws \InvalidArgumentException 115 | * 116 | */ 117 | public static function create(array $data): Repository 118 | { 119 | if (empty($data['name']) || empty($data['url'])) { 120 | throw new \InvalidArgumentException('Repository data must contain key [name] and [url]'); 121 | } 122 | $data['location'] = $data['location'] ?? null; 123 | return new static(...$data); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/RepositoryCollection.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm; 15 | 16 | final class RepositoryCollection implements \IteratorAggregate, \Countable 17 | { 18 | /** 19 | * @var Repository[] 20 | */ 21 | protected array $repositories = []; 22 | 23 | /** 24 | * @param array $repositories 25 | */ 26 | public function __construct(array $repositories = []) 27 | { 28 | $this->repositories = $repositories; 29 | } 30 | 31 | /** 32 | * Search a repository by the name. 33 | * 34 | * @param string $name 35 | * 36 | * @return Repository|null 37 | */ 38 | public function search(string $name): ?Repository 39 | { 40 | $filtered = array_filter($this->repositories, function(Repository $repository) use ($name){ 41 | return $repository->getName() === $name; 42 | }); 43 | 44 | return $filtered ? reset($filtered) : null; 45 | } 46 | 47 | /** 48 | * Add a repository. 49 | * 50 | * @param Repository $repository 51 | */ 52 | public function add(Repository $repository) 53 | { 54 | $this->repositories[] = $repository; 55 | } 56 | 57 | /** 58 | * Remove a repository. 59 | * 60 | * @param Repository $repository 61 | */ 62 | public function remove(Repository $repository) 63 | { 64 | $key = array_search($repository, $this->repositories); 65 | if ($key !== false) { 66 | unset($this->repositories[$key]); 67 | } 68 | } 69 | 70 | /** 71 | * 72 | * Checks whether the repository exists. 73 | * 74 | * @param Repository $repository 75 | * 76 | * @return boolean 77 | */ 78 | public function has(Repository $repository): bool 79 | { 80 | return in_array($repository, $this->repositories); 81 | } 82 | 83 | /** 84 | * Get all registries. 85 | * 86 | * @return Repository[] 87 | */ 88 | public function all(): array 89 | { 90 | return $this->repositories; 91 | } 92 | 93 | /** 94 | * Convert to array. 95 | * 96 | * @return array 97 | */ 98 | public function toArray(): array 99 | { 100 | return array_map(function (Repository $repository) { 101 | return $repository->toArray(); 102 | }, $this->repositories); 103 | } 104 | 105 | /** 106 | * Creates a collection from array data. 107 | * 108 | * @param array $data 109 | * 110 | * @return static 111 | */ 112 | public static function fromArray(array $data): RepositoryCollection 113 | { 114 | return new RepositoryCollection(array_map(fn($repositoryData) => Repository::create($repositoryData), $data)); 115 | } 116 | 117 | /** 118 | * {@inheritdoc} 119 | */ 120 | public function getIterator(): \Traversable 121 | { 122 | return new \ArrayIterator($this->repositories); 123 | } 124 | 125 | /** 126 | * {@inheritdoc} 127 | */ 128 | public function count(): int 129 | { 130 | return count($this->repositories); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/RepositoryManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Slince\Crm; 15 | 16 | use Composer\Composer; 17 | use Composer\Config\JsonConfigSource; 18 | use Composer\Factory; 19 | use Composer\IO\IOInterface; 20 | use Composer\Json\JsonFile; 21 | use Composer\Plugin\Capability\CommandProvider; 22 | use Composer\Plugin\Capable; 23 | use Composer\Plugin\PluginInterface; 24 | use Composer\Util\Silencer; 25 | use Seld\JsonLint\ParsingException; 26 | 27 | class RepositoryManager implements PluginInterface, Capable, CommandProvider 28 | { 29 | /** 30 | * @var Composer 31 | */ 32 | protected static Composer $composer; 33 | 34 | /** 35 | * @var JsonFile 36 | */ 37 | protected static JsonFile $configFile; 38 | 39 | /** 40 | * Global config file source. 41 | * 42 | * @var JsonConfigSource 43 | */ 44 | protected static JsonConfigSource $configSource; 45 | 46 | /** 47 | * Config file source of the project. 48 | * 49 | * @var JsonConfigSource|null 50 | */ 51 | protected static ?JsonConfigSource $currentConfigSource = null; 52 | 53 | /** 54 | * @var RepositoryCollection 55 | */ 56 | protected static RepositoryCollection $repositories; 57 | 58 | /** 59 | * @var bool 60 | */ 61 | protected static bool $repositoriesLoaded = false; 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function activate(Composer $composer, IOInterface $io) 67 | { 68 | static::$composer = $composer; 69 | $file = static::$composer->getConfig()->get('home').'/config.json'; 70 | $this->prepareConfigSource($file); 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function deactivate(Composer $composer, IOInterface $io) 77 | { 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function uninstall(Composer $composer, IOInterface $io) 84 | { 85 | } 86 | 87 | /** 88 | * Initialize the config file. 89 | * 90 | * @param string $file 91 | * @throws \Exception 92 | */ 93 | protected function prepareConfigSource(string $file) 94 | { 95 | $configFile = new JsonFile($file); 96 | if (!$configFile->exists()) { 97 | touch($configFile->getPath()); 98 | $configFile->write(['config' => new \ArrayObject()]); 99 | Silencer::call('chmod', $configFile->getPath(), 0600); 100 | } 101 | static::$configFile = $configFile; 102 | static::$configSource = new JsonConfigSource($configFile); 103 | } 104 | 105 | /** 106 | * Gets all repositories. 107 | * 108 | * @return RepositoryCollection 109 | * @throws ParsingException 110 | */ 111 | public function getRepositories(): RepositoryCollection 112 | { 113 | if (static::$repositoriesLoaded) { 114 | return static::$repositories; 115 | } 116 | static::$repositories = $this->loadRepositories(); 117 | static::$repositoriesLoaded = true; 118 | return static::$repositories; 119 | } 120 | 121 | /** 122 | * Load repository from config file. 123 | * 124 | * @return RepositoryCollection 125 | * @throws ParsingException 126 | */ 127 | protected function loadRepositories(): RepositoryCollection 128 | { 129 | $config = static::$configFile->read(); 130 | $repositories = []; 131 | if (isset($config['config']['_repositories']) && is_array($config['config']['_repositories'])) { 132 | $repositories = $config['config']['_repositories']; 133 | } 134 | if (count($repositories) === 0) { 135 | $repositories = Utils::readJsonFile(Utils::getDefaultConfigFile()); 136 | } 137 | return RepositoryCollection::fromArray($repositories); 138 | } 139 | 140 | 141 | /** 142 | * Adds a repository. 143 | * 144 | * @param string $name 145 | * @param string $url 146 | * @param string|null $location 147 | * @return Repository 148 | * @throws ParsingException 149 | */ 150 | public function addRepository(string $name, string $url, string $location = null): Repository 151 | { 152 | $repository = Repository::create([ 153 | 'name' => $name, 154 | 'url' => $url, 155 | 'location' => $location 156 | ]); 157 | $this->getRepositories()->add($repository); 158 | static::$configSource->addConfigSetting('_repositories', $this->getRepositories()->toArray()); 159 | return $repository; 160 | } 161 | 162 | /** 163 | * Remove a repository by the name. 164 | * 165 | * @param string $name 166 | * @throws ParsingException 167 | */ 168 | public function removeRepository(string $name) 169 | { 170 | $repository = $this->getRepositories()->search($name); 171 | if (null === $repository) { 172 | throw new \InvalidArgumentException(sprintf('Cannot find the repository %s', $name)); 173 | } 174 | $this->getRepositories()->remove($repository); 175 | static::$configSource->addConfigSetting('_repositories', $this->getRepositories()->toArray()); 176 | } 177 | 178 | /** 179 | * Use the repository. 180 | * 181 | * @param Repository $repository 182 | * @param bool $modifyCurrent 183 | */ 184 | public function useRepository(Repository $repository, bool $modifyCurrent = false) 185 | { 186 | $configSource = $modifyCurrent ? $this->getCurrentConfigSource() : static::$configSource; 187 | $configSource->addRepository('packagist', [ 188 | 'type' => 'composer', 189 | 'url' => $repository->getUrl(), 190 | ]); 191 | } 192 | 193 | /** 194 | * Remove custom repositories and reset to default. 195 | */ 196 | public function resetRepositories() 197 | { 198 | static::$configSource->removeConfigSetting('_repositories'); 199 | } 200 | 201 | /** 202 | * Get the project config source. 203 | * 204 | * @return JsonConfigSource 205 | */ 206 | protected function getCurrentConfigSource(): JsonConfigSource 207 | { 208 | if (static::$currentConfigSource) { 209 | return static::$currentConfigSource; 210 | } 211 | $file = Factory::getComposerFile(); 212 | $configFile = new JsonFile($file); 213 | if (!$configFile->exists()) { 214 | throw new \RuntimeException('Composer.json is not exists in current dir'); 215 | } 216 | return static::$currentConfigSource = new JsonConfigSource($configFile); 217 | } 218 | 219 | /** 220 | * Gets current composer repository. 221 | * 222 | * @return Repository 223 | * @throws ParsingException 224 | */ 225 | public function getCurrentComposerRepository(): Repository 226 | { 227 | $composerRepos = array_filter(static::$composer->getConfig()->getRepositories(), fn($repo) => 'composer' === $repo['type']); 228 | $packagistRepoUrl = reset($composerRepos)['url']; 229 | foreach ($this->getRepositories() as $repository) { 230 | if (0 === strcasecmp($repository->getUrl(), $packagistRepoUrl)) { 231 | return $repository; 232 | } 233 | } 234 | 235 | return Repository::create([ 236 | 'url' => $packagistRepoUrl, 237 | 'name' => 'composer', 238 | ]); 239 | } 240 | 241 | /** 242 | * {@inheritdoc} 243 | */ 244 | public function getCapabilities(): array 245 | { 246 | return [ 247 | CommandProvider::class => __CLASS__, 248 | ]; 249 | } 250 | 251 | /** 252 | * {@inheritdoc} 253 | */ 254 | public function getCommands(): array 255 | { 256 | return [ 257 | new Command\RepoCommand($this), 258 | new Command\ListCommand($this), 259 | new Command\AddCommand($this), 260 | new Command\RemoveCommand($this), 261 | new Command\UseCommand($this), 262 | new Command\ResetCommand($this), 263 | ]; 264 | } 265 | } -------------------------------------------------------------------------------- /src/Utils.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Crm; 14 | 15 | final class Utils 16 | { 17 | /** 18 | * Read json file data. 19 | * 20 | * @param string $file 21 | * 22 | * @return array 23 | */ 24 | public static function readJsonFile(string $file): array 25 | { 26 | if (!is_file($file)) { 27 | throw new \InvalidArgumentException(sprintf('File [%s] does not exists', $file)); 28 | } 29 | $rawContent = @file_get_contents($file); 30 | $data = json_decode($rawContent, true); 31 | if (json_last_error()) { 32 | throw new \InvalidArgumentException(sprintf('File [%s] must contain valid json, error: %s', $file, json_last_error_msg())); 33 | } 34 | return $data; 35 | } 36 | 37 | /** 38 | * Get default config json file. 39 | * 40 | * @return string 41 | */ 42 | public static function getDefaultConfigFile(): string 43 | { 44 | return __DIR__.'/../crm.default.json'; 45 | } 46 | } -------------------------------------------------------------------------------- /tests/ApplicationTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(\Symfony\Component\Console\Application::class, $application); 13 | $this->assertStringContainsString('Composer Repository Manager', $application->getName()); 14 | $this->assertStringContainsString('Composer Repository Manager', $application->getHelp()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Command/AddCommandTest.php: -------------------------------------------------------------------------------- 1 | assertMatchesRegularExpression('#success#', $this->runCommandTest(AddCommand::class, [ 14 | 'repository-name' => 'foo', 15 | 'repository-url' => 'http://foo.com', 16 | ])); 17 | $this->assertStringContainsString('http://foo.com', $this->runCommandTest(ListCommand::class, [])); 18 | } 19 | 20 | public function testExecuteWithoutArgument() 21 | { 22 | $this->expectException(RuntimeException::class); 23 | $this->runCommandTest(AddCommand::class, [ 24 | 'repository-name' => 'foo', 25 | ]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Command/CommandTestCase.php: -------------------------------------------------------------------------------- 1 | resolveCommand($command); 17 | $commandTester = new CommandTester($command); 18 | $commandTester->execute($arguments, $options); 19 | return $commandTester->getDisplay(); 20 | } 21 | 22 | protected function runCommandTest(string $commandClass, $arguments, $options = []): string 23 | { 24 | $command = $this->createMockCommand($commandClass); 25 | $commandTester = new CommandTester($command); 26 | $commandTester->execute($arguments, $options); 27 | return $commandTester->getDisplay(); 28 | } 29 | 30 | protected function createMockCommand(string $commandClass, bool $defaultRepos = false): Command 31 | { 32 | $manager = new RepositoryManagerStub(); 33 | $defaultRepos && $manager->readRegistriesFromFile(Utils::getDefaultConfigFile()); 34 | 35 | $command = $this->getMockBuilder($commandClass) 36 | ->onlyMethods(['getApplication']) 37 | ->setConstructorArgs([$manager]) 38 | ->getMock(); 39 | $this->resolveCommand($command); 40 | 41 | return $command; 42 | } 43 | 44 | protected function resolveCommand(MockObject $command): MockObject 45 | { 46 | $application = new Application(); 47 | $command->method('getApplication')->willReturn($application); 48 | return $command; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Command/ListCommandTest.php: -------------------------------------------------------------------------------- 1 | assertStringContainsString('packagist.org', $this->runCommandTest(ListCommand::class, [])); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Command/RemoveCommandTest.php: -------------------------------------------------------------------------------- 1 | readRegistriesFromFile(Utils::getDefaultConfigFile()); 19 | 20 | 21 | $this->runCommandTester(new AddCommand($manager), [ 22 | 'repository-name' => 'foo', 23 | 'repository-url' => 'http://foo.com', 24 | ]); 25 | 26 | $this->assertRegExp('#foo\.com#', $this->runCommandTester(new ListCommand($manager), [])); 27 | 28 | $this->runCommandTester(new RemoveCommand($manager), [ 29 | 'repository-name' => 'foo', 30 | ]); 31 | 32 | $this->assertNotRegExp('#foo\.com#', $this->runCommandTester(new ListCommand($manager), [])); 33 | } 34 | 35 | public function testExecuteWithoutArgument() 36 | { 37 | $commandTester = $this->createCommandTester(); 38 | 39 | //If the symfony/console version is less than 3.2, the test is not performed 40 | if (!method_exists($commandTester, 'setInputs')) { 41 | return; 42 | } 43 | $commandTester->setInputs([1]); 44 | $commandTester->execute([]); 45 | $display = $commandTester->getDisplay(); 46 | $this->assertStringContainsString('Please select repository your want to remove', $display); 47 | $this->assertStringContainsString('Remove the repository [aliyun] success', $display); 48 | } 49 | 50 | 51 | protected function createCommandTester() 52 | { 53 | $manager = new RepositoryManagerStub(); 54 | $manager->readRegistriesFromFile(Utils::getDefaultConfigFile()); 55 | $command = $this->getMockBuilder(RemoveCommand::class)->onlyMethods(['getApplication']) 56 | ->setConstructorArgs([$manager]) 57 | ->getMock(); 58 | $command->setApplication(new ProxyApplication([])); 59 | return new CommandTester($command); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Command/RepoCommandTest.php: -------------------------------------------------------------------------------- 1 | assertStringContainsString(static::$logo, $this->runCommandTest(RepoCommand::class, [])); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Command/ResetCommandTest.php: -------------------------------------------------------------------------------- 1 | readRegistriesFromFile(Utils::getDefaultConfigFile()); 17 | $command = $this->getMockBuilder(ResetCommand::class)->onlyMethods(['getApplication']) 18 | ->setConstructorArgs([$manager]) 19 | ->getMock(); 20 | $command->setApplication(new ProxyApplication([])); 21 | $commandTester = new CommandTester($command); 22 | //If the symfony/console version is less than 3.2, the test is not performed 23 | if (!method_exists($commandTester, 'setInputs')) { 24 | return; 25 | } 26 | $commandTester->setInputs(['y']); 27 | $commandTester->execute([]); 28 | $display = $commandTester->getDisplay(); 29 | $this->assertStringContainsString('This command will remove custom repositories. Are you sure to do this', $display); 30 | $this->assertStringContainsString('Successfully reset', $display); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Command/UseCommandTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(AddCommand::class)->onlyMethods(['getApplication']) 17 | ->setConstructorArgs([$manager]) 18 | ->getMock(); 19 | $this->runCommandTester($addCommand, [ 20 | 'repository-name' => 'foo', 21 | 'repository-url' => 'http://foo.com', 22 | ]); 23 | $useCommand = $this->getMockBuilder(UseCommand::class)->onlyMethods(['getApplication']) 24 | ->setConstructorArgs([$manager]) 25 | ->getMock(); 26 | $this->runCommandTester($useCommand, [ 27 | 'repository-name' => 'foo', 28 | ]); 29 | $this->assertEquals('http://foo.com', $manager->getCurrentRepository()->getUrl()); 30 | } 31 | 32 | public function testExecuteForCurrent() 33 | { 34 | $manager = new RepositoryManagerStub(); 35 | $manager->readRegistriesFromFile(Utils::getDefaultConfigFile()); 36 | 37 | $addCommand = $this->getMockBuilder(AddCommand::class)->onlyMethods(['getApplication']) 38 | ->setConstructorArgs([$manager]) 39 | ->getMock(); 40 | 41 | $this->runCommandTester($addCommand, [ 42 | 'repository-name' => 'bar', 43 | 'repository-url' => 'http://bar.com', 44 | ]); 45 | 46 | $useCommand = $this->getMockBuilder(UseCommand::class)->onlyMethods(['getApplication']) 47 | ->setConstructorArgs([$manager]) 48 | ->getMock(); 49 | $this->runCommandTester($useCommand, [ 50 | 'repository-name' => 'composer', 51 | ]); 52 | $this->runCommandTester($useCommand, [ 53 | 'repository-name' => 'bar', 54 | '--current' => true 55 | ]); 56 | $this->assertEquals('https://packagist.org', $manager->getCurrentRepository()->getUrl()); 57 | $this->assertEquals('http://bar.com', $manager->getCurrentRepository(true)->getUrl()); 58 | } 59 | 60 | public function testExecuteWithoutArgumentsAndInput() 61 | { 62 | $commandTester = $this->createCommandTester(); 63 | 64 | //If the symfony/console version is less than 3.2, the test is not performed 65 | if (!method_exists($commandTester, 'setInputs')) { 66 | return; 67 | } 68 | 69 | $commandTester->setInputs([PHP_EOL]); 70 | $commandTester->execute([]); 71 | $display = $commandTester->getDisplay(); 72 | $this->assertStringContainsString('Please select your favorite repository', $display); 73 | $this->assertStringContainsString('Use the repository [composer] success', $display); 74 | } 75 | 76 | public function testExecuteWithoutArguments() 77 | { 78 | $commandTester = $this->createCommandTester(); 79 | 80 | //If the symfony/console version is less than 3.2, the test is not performed 81 | if (!method_exists($commandTester, 'setInputs')) { 82 | return; 83 | } 84 | $commandTester->setInputs([1]); 85 | $commandTester->execute([]); 86 | $display = $commandTester->getDisplay(); 87 | $this->assertStringContainsString('Please select your favorite repository', $display); 88 | $this->assertStringContainsString('Use the repository [aliyun] success', $display); 89 | } 90 | 91 | protected function createCommandTester() 92 | { 93 | $manager = new RepositoryManagerStub(); 94 | $manager->readRegistriesFromFile(Utils::getDefaultConfigFile()); 95 | $command = $this->getMockBuilder(UseCommand::class)->onlyMethods(['getApplication']) 96 | ->setConstructorArgs([$manager]) 97 | ->getMock(); 98 | $command->setApplication(new ProxyApplication([])); 99 | return new CommandTester($command); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/RepositoryCollectionTest.php: -------------------------------------------------------------------------------- 1 | assertCount(2, $registries->all()); 17 | } 18 | 19 | public function testCreateFromArray() 20 | { 21 | $registries = RepositoryCollection::fromArray([ 22 | ['name' => 'foo', 'url' => 'http://foo.com'], 23 | ['name' => 'bar', 'url' => 'http://bar.com'] 24 | ]); 25 | $this->assertCount(2, $registries->all()); 26 | } 27 | 28 | public function testAdd() 29 | { 30 | $registries = RepositoryCollection::fromArray([ 31 | ['name' => 'foo', 'url' => 'http://foo.com'], 32 | ['name' => 'bar', 'url' => 'http://bar.com'] 33 | ]); 34 | $registries->add(new Repository('baz', 'http://baz.com')); 35 | $this->assertCount(3, $registries->all()); 36 | } 37 | 38 | public function testRemove() 39 | { 40 | $registries = new RepositoryCollection([ 41 | $foo = new Repository('foo', 'http://foo.com'), 42 | new Repository('bar', 'http://bar.com'), 43 | ]); 44 | $this->assertCount(2, $registries->all()); 45 | $registries->remove($foo); 46 | $this->assertCount(1, $registries->all()); 47 | } 48 | 49 | public function testToArray() 50 | { 51 | $registries = new RepositoryCollection([ 52 | $foo = new Repository('foo', 'http://foo.com'), 53 | new Repository('bar', 'http://bar.com'), 54 | ]); 55 | $registriesData = $registries->toArray(); 56 | $this->assertTrue(is_array($registriesData)); 57 | $this->assertTrue(is_array($registriesData[0])); 58 | $this->assertEquals('bar', $registriesData[1]['name']); 59 | $this->assertEquals('http://bar.com', $registriesData[1]['url']); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/RepositoryManagerTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(RepositoryCollection::class, (new RepositoryManagerStub())->getRepositories()); 14 | } 15 | 16 | public function testAddRepository() 17 | { 18 | $manager = new RepositoryManagerStub(); 19 | $this->assertCount(0, $manager->getRepositories()->all()); 20 | $manager->addRepository('foo', 'http://foo.com'); 21 | $this->assertCount(1, $manager->getRepositories()->all()); 22 | $this->assertEquals('foo', $manager->getRepositories()->all()[0]->getName()); 23 | $this->assertEquals('http://foo.com', $manager->getRepositories()->all()[0]->getUrl()); 24 | $manager->addRepository('foo', 'http://foo.com'); 25 | } 26 | 27 | public function testFindRepository() 28 | { 29 | $manager = new RepositoryManagerStub(); 30 | $repository = $manager->addRepository('foo', 'http://foo.com'); 31 | $this->assertTrue($repository === $manager->getRepositories()->search('foo')); 32 | } 33 | 34 | public function testRemoveRepository() 35 | { 36 | $manager = new RepositoryManagerStub(); 37 | $repository = $manager->addRepository('foo', 'http://foo.com'); 38 | $manager->removeRepository('foo'); 39 | $this->assertNull($manager->getRepositories()->search('foo')); 40 | } 41 | 42 | public function testUseRepository() 43 | { 44 | $manager = new RepositoryManagerStub(); 45 | $repository = new Repository('foo', 'http://foo.com'); 46 | $manager->useRepository($repository); 47 | $this->assertRegExp('#foo\.com#', $manager->getCurrentRepository()->getUrl()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/RepositoryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('foo', $repository->getName()); 14 | $this->assertEquals('http://bar.com', $repository->getUrl()); 15 | } 16 | 17 | public function testFactoryCreate() 18 | { 19 | $repository = Repository::create([ 20 | 'name' => 'foo', 21 | 'url' => 'http://bar.com' 22 | ]); 23 | $this->assertEquals('foo', $repository->getName()); 24 | $this->assertEquals('http://bar.com', $repository->getUrl()); 25 | 26 | $this->expectException(\InvalidArgumentException::class); 27 | Repository::create([ 28 | 'withoutName' => 'foo', 29 | 'withoutUrl' => 'http://bar.com', 30 | ]); 31 | } 32 | 33 | public function testSet() 34 | { 35 | $repository = Repository::create([ 36 | 'name' => 'foo', 37 | 'url' => 'http://bar.com' 38 | ]); 39 | $this->assertEquals('foo', $repository->getName()); 40 | $repository->setName('bar'); 41 | $this->assertEquals('bar', $repository->getName()); 42 | $repository->setUrl('http://foo.com'); 43 | $this->assertEquals('http://foo.com', $repository->getUrl()); 44 | } 45 | 46 | public function testReservedAttribute() 47 | { 48 | $repository = Repository::create([ 49 | 'name' => 'foo', 50 | 'url' => 'http://bar.com', 51 | 'location' => 'China' 52 | ]); 53 | $this->assertEquals('China', $repository->getLocation()); 54 | $this->assertEquals('http://bar.com', $repository->getUrl()); 55 | 56 | $repository->setLocation('Russia'); 57 | $this->assertEquals('Russia', $repository->getLocation()); 58 | } 59 | 60 | public function testToArray() 61 | { 62 | $repository = Repository::create([ 63 | 'name' => 'foo', 64 | 'url' => 'http://bar.com' 65 | ]); 66 | $repositoryData = $repository->toArray(); 67 | $this->assertArrayHasKey('name', $repositoryData); 68 | $this->assertArrayHasKey('url', $repositoryData); 69 | $this->assertArrayHasKey('location', $repositoryData); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Stub/RepositoryManagerStub.php: -------------------------------------------------------------------------------- 1 | currentRepository = $repository; 26 | } else { 27 | $this->repository = $repository; 28 | } 29 | } 30 | 31 | public function addRepository(string $name, string $url, string $location = null): Repository 32 | { 33 | $repository = Repository::create([ 34 | 'name' => $name, 35 | 'url' => $url, 36 | 'location' => $location 37 | ]); 38 | static::$repositories->add($repository); 39 | return $repository; 40 | } 41 | 42 | public function getRepositories(): RepositoryCollection 43 | { 44 | return static::$repositories; 45 | } 46 | 47 | public function getCurrentRepository($isCurrent = false) 48 | { 49 | return $isCurrent ? $this->currentRepository : $this->repository; 50 | } 51 | 52 | public function readRegistriesFromFile($file) 53 | { 54 | static::$repositories = RepositoryCollection::fromArray(Utils::readJsonFile($file)); 55 | } 56 | 57 | public function removeRepository(string $name) 58 | { 59 | static::$repositories->remove(static::$repositories->search($name)); 60 | } 61 | 62 | public function resetRepositories() 63 | { 64 | static::$repositories = new RepositoryCollection(); 65 | } 66 | 67 | public function getCurrentComposerRepository(): Repository 68 | { 69 | return $this->repository; 70 | } 71 | } --------------------------------------------------------------------------------