├── .php-cs-fixer.dist.php ├── .scrutinizer.yml ├── LICENSE ├── README.md ├── bin ├── devkit.php └── toolbox.php ├── box-devkit.json.dist ├── deptrac.yaml ├── resources ├── architecture.json ├── checkstyle.json ├── compatibility.json ├── composer.json ├── deprecation.json ├── documentation.json ├── linting.json ├── metrics.json ├── phpcs.json ├── phpstan.json ├── pre-installation.json ├── psalm.json ├── refactoring.json ├── security.json ├── test.json └── tools.json └── src ├── Cli ├── Application.php ├── Command │ ├── DefaultTag.php │ ├── DefaultTargetDir.php │ ├── InstallCommand.php │ ├── ListCommand.php │ └── TestCommand.php ├── Runner │ └── DryRunner.php ├── ServiceContainer.php └── ServiceContainer │ ├── LazyRunner.php │ └── RunnerFactory.php ├── Json ├── Factory │ ├── Assert.php │ ├── BoxBuildCommandFactory.php │ ├── ComposerBinPluginCommandFactory.php │ ├── ComposerGlobalInstallCommandFactory.php │ ├── ComposerInstallCommandFactory.php │ ├── FileDownloadCommandFactory.php │ ├── PharDownloadCommandFactory.php │ ├── PhiveInstallCommandFactory.php │ ├── ShCommandFactory.php │ └── ToolFactory.php └── JsonTools.php ├── Runner ├── ParametrisedRunner.php ├── PassthruRunner.php └── Runner.php ├── Tool ├── Collection.php ├── Command.php ├── Command │ ├── BoxBuildCommand.php │ ├── ComposerBinPluginCommand.php │ ├── ComposerBinPluginLinkCommand.php │ ├── ComposerGlobalInstallCommand.php │ ├── ComposerGlobalMultiInstallCommand.php │ ├── ComposerInstallCommand.php │ ├── FileDownloadCommand.php │ ├── MultiStepCommand.php │ ├── OptimisedComposerBinPluginCommand.php │ ├── PharDownloadCommand.php │ ├── PhiveInstallCommand.php │ ├── ShCommand.php │ └── TestCommand.php ├── Filter.php ├── Tool.php └── Tools.php └── UseCase ├── InstallTools.php ├── ListTools.php └── TestTools.php /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in(['src', 'tests']) 5 | ; 6 | 7 | return (new PhpCsFixer\Config()) 8 | ->setRules([ 9 | '@PSR2' => true, 10 | 'array_syntax' => ['syntax' => 'short'], 11 | 'blank_line_before_statement' => true, 12 | 'concat_space' => ['spacing' => 'none'], 13 | 'declare_strict_types' => true, 14 | 'native_function_invocation' => ['include' => ['@internal']], 15 | 'no_empty_comment' => true, 16 | 'no_empty_phpdoc' => true, 17 | 'no_empty_statement' => true, 18 | 'no_extra_blank_lines' => true, 19 | 'no_leading_import_slash' => true, 20 | 'no_leading_namespace_whitespace' => true, 21 | 'no_unused_imports' => true, 22 | 'no_useless_else' => true, 23 | 'ordered_class_elements' => true, 24 | 'ordered_imports' => true, 25 | 'phpdoc_add_missing_param_annotation' => ['only_untyped' => true], 26 | 'protected_to_private' => true, 27 | 'strict_comparison' => true, 28 | 'ternary_operator_spaces' => true, 29 | 'ternary_to_null_coalescing' => true, 30 | 'yoda_style' => true, 31 | ]) 32 | ->setFinder($finder) 33 | ; 34 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | inherit: true 2 | 3 | build: 4 | environment: 5 | php: 6 | version: 8.2 7 | variables: 8 | XDEBUG_MODE: coverage 9 | tests: 10 | override: 11 | - make phpunit 12 | project_setup: 13 | nodes: 14 | coverage: 15 | tests: 16 | override: 17 | - command: make phpunit 18 | coverage: 19 | file: build/coverage.xml 20 | format: clover 21 | filter: 22 | paths: [src/*] 23 | 24 | build_failure_conditions: 25 | - 'elements.rating(<= B).new.exists' 26 | - 'issues.label("coding-style").new.exists' 27 | - 'issues.severity(>= MAJOR).new.exists' 28 | 29 | checks: 30 | php: true 31 | 32 | tools: 33 | php_code_sniffer: false 34 | php_cs_fixer: { config: { level: psr2 } } 35 | external_code_coverage: false 36 | php_code_coverage: true 37 | php_changetracking: true 38 | php_sim: true 39 | php_mess_detector: true 40 | php_pdepend: true 41 | php_analyzer: true 42 | sensiolabs_security_checker: true 43 | 44 | coding_style: 45 | php: 46 | spaces: 47 | within: 48 | brackets: false 49 | before_parentheses: 50 | closure_definition: true 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Jakub Zalas 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toolbox 2 | 3 | [![Build Status](https://github.com/jakzal/toolbox/workflows/Build/badge.svg)](https://github.com/jakzal/toolbox/actions) 4 | [![Build Status](https://scrutinizer-ci.com/g/jakzal/toolbox/badges/build.png?b=master)](https://scrutinizer-ci.com/g/jakzal/toolbox/build-status/master) 5 | 6 | Helps to discover and install tools. 7 | 8 | ## Use cases 9 | 10 | Toolbox [started its life](https://github.com/jakzal/phpqa/blob/49482ae447d4b6341cf77aac9d51390fe1176e8c/tools.php) 11 | as a simple script in the [phpqa docker image](https://github.com/jakzal/phpqa). 12 | Its purpose was to install set of tools while building the docker image and it's still its main goal. 13 | It has been extracted as a separate project to make maintenance easier and enable new use cases. 14 | 15 | ## Available tools 16 | 17 | | Name | Description | PHP 8.2 | PHP 8.3 | PHP 8.4 | 18 | | :--- | :---------- | :------ | :------ | :------ | 19 | | behat | [Helps to test business expectations](http://behat.org/) | ✅ | ✅ | ✅ | 20 | | box | [Fast, zero config application bundler with PHARs](https://github.com/humbug/box) | ✅ | ✅ | ✅ | 21 | | box-3 | [Fast, zero config application bundler with PHARs](https://github.com/humbug/box) | ❌ | ✅ | ✅ | 22 | | churn | [Discovers good candidates for refactoring](https://github.com/bmitch/churn-php) | ✅ | ✅ | ✅ | 23 | | codeception | [Codeception is a BDD-styled PHP testing framework](https://codeception.com/) | ✅ | ✅ | ✅ | 24 | | composer | [Dependency Manager for PHP](https://getcomposer.org/) | ✅ | ✅ | ✅ | 25 | | composer-bin-plugin | [Composer plugin to install bin vendors in isolated locations](https://github.com/bamarni/composer-bin-plugin) | ✅ | ✅ | ✅ | 26 | | composer-lock-diff | [Composer plugin to check what has changed after a composer update](https://github.com/davidrjonas/composer-lock-diff) | ✅ | ✅ | ✅ | 27 | | composer-normalize | [Composer plugin to normalize composer.json files](https://github.com/ergebnis/composer-normalize) | ✅ | ✅ | ✅ | 28 | | composer-require-checker | [Verify that no unknown symbols are used in the sources of a package.](https://github.com/maglnet/ComposerRequireChecker) | ✅ | ✅ | ✅ | 29 | | composer-require-checker-3 | [Verify that no unknown symbols are used in the sources of a package.](https://github.com/maglnet/ComposerRequireChecker) | ✅ | ✅ | ✅ | 30 | | composer-unused | [Show unused packages by scanning your code](https://github.com/icanhazstring/composer-unused) | ✅ | ✅ | ✅ | 31 | | cyclonedx-php-composer | [Composer plugin to create Software-Bill-of-Materials (SBOM) in CycloneDX format](https://github.com/CycloneDX/cyclonedx-php-composer) | ✅ | ✅ | ✅ | 32 | | dephpend | [Detect flaws in your architecture](https://dephpend.com/) | ✅ | ✅ | ✅ | 33 | | deprecation-detector | [Finds usages of deprecated code](https://github.com/sensiolabs-de/deprecation-detector) | ✅ | ✅ | ✅ | 34 | | deptrac | [Enforces dependency rules between software layers](https://github.com/deptrac/deptrac) | ✅ | ✅ | ✅ | 35 | | diffFilter | [Applies QA tools to run on a single pull request](https://github.com/exussum12/coverageChecker) | ✅ | ✅ | ✅ | 36 | | ecs | [Sets up and runs coding standard checks](https://github.com/Symplify/EasyCodingStandard) | ✅ | ✅ | ✅ | 37 | | gherkin-lint-php | [Gherkin linter for PHP](https://github.com/dantleech/gherkin-lint-php) | ✅ | ✅ | ✅ | 38 | | infection | [AST based PHP Mutation Testing Framework](https://infection.github.io/) | ✅ | ✅ | ✅ | 39 | | kahlan | [Kahlan is a full-featured Unit & BDD test framework a la RSpec/JSpec](https://kahlan.github.io/docs/) | ✅ | ✅ | ✅ | 40 | | larastan | [PHPStan extension for Laravel](https://github.com/nunomaduro/larastan) | ✅ | ✅ | ✅ | 41 | | lines | [CLI tool for quick metrics of PHP projects](https://github.com/tomasVotruba/lines) | ✅ | ✅ | ✅ | 42 | | local-php-security-checker | [Checks composer dependencies for known security vulnerabilities](https://github.com/fabpot/local-php-security-checker) | ✅ | ✅ | ✅ | 43 | | parallel-lint | [Checks PHP file syntax](https://github.com/php-parallel-lint/PHP-Parallel-Lint) | ✅ | ✅ | ✅ | 44 | | paratest | [Parallel testing for PHPUnit](https://github.com/paratestphp/paratest) | ✅ | ✅ | ✅ | 45 | | pdepend | [Static Analysis Tool](https://pdepend.org/) | ✅ | ✅ | ✅ | 46 | | pest | [The elegant PHP Testing Framework](https://github.com/pestphp/pest) | ✅ | ✅ | ✅ | 47 | | phan | [Static Analysis Tool](https://github.com/phan/phan) | ✅ | ✅ | ✅ | 48 | | phive | [PHAR Installation and Verification Environment](https://phar.io/) | ✅ | ✅ | ✅ | 49 | | php-cs-fixer | [PHP Coding Standards Fixer](http://cs.symfony.com/) | ✅ | ✅ | ❌ | 50 | | php-fuzzer | [A fuzzer for PHP, which can be used to find bugs in libraries by feeding them 'random' inputs](https://github.com/nikic/PHP-Fuzzer) | ✅ | ✅ | ✅ | 51 | | php-semver-checker | [Suggests a next version according to semantic versioning](https://github.com/tomzx/php-semver-checker) | ❌ | ❌ | ❌ | 52 | | phpa | [Checks for weak assumptions](https://github.com/rskuipers/php-assumptions) | ✅ | ✅ | ✅ | 53 | | phparkitect | [Helps to put architectural constraints in a PHP code base](https://github.com/phparkitect/arkitect) | ✅ | ✅ | ✅ | 54 | | phpat | [Easy to use architecture testing tool](https://github.com/carlosas/phpat) | ✅ | ✅ | ✅ | 55 | | phpbench | [PHP Benchmarking framework](https://github.com/phpbench/phpbench) | ✅ | ✅ | ✅ | 56 | | phpca | [Finds usage of non-built-in extensions](https://github.com/wapmorgan/PhpCodeAnalyzer) | ✅ | ✅ | ✅ | 57 | | phpcb | [PHP Code Browser](https://github.com/mayflower/PHP_CodeBrowser) | ✅ | ✅ | ✅ | 58 | | phpcbf | [Automatically corrects coding standard violations](https://github.com/squizlabs/PHP_CodeSniffer) | ✅ | ✅ | ✅ | 59 | | phpcodesniffer-composer-install | [Easy installation of PHP_CodeSniffer coding standards (rulesets).](https://github.com/Dealerdirect/phpcodesniffer-composer-installer) | ✅ | ✅ | ✅ | 60 | | phpcov | [a command-line frontend for the PHP_CodeCoverage library](https://github.com/sebastianbergmann/phpcov) | ❌ | ✅ | ✅ | 61 | | phpcpd | [Copy/Paste Detector](https://github.com/sebastianbergmann/phpcpd) | ✅ | ✅ | ✅ | 62 | | phpcs | [Detects coding standard violations](https://github.com/squizlabs/PHP_CodeSniffer) | ✅ | ✅ | ✅ | 63 | | phpcs-security-audit | [Finds vulnerabilities and weaknesses related to security in PHP code](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | ✅ | ✅ | ✅ | 64 | | phpdd | [Finds usage of deprecated features](http://wapmorgan.github.io/PhpDeprecationDetector) | ✅ | ✅ | ✅ | 65 | | phpDocumentor | [Documentation generator](https://www.phpdoc.org/) | ✅ | ✅ | ✅ | 66 | | phpinsights | [Analyses code quality, style, architecture and complexity](https://phpinsights.com/) | ✅ | ✅ | ✅ | 67 | | phplint | [Lints php files in parallel](https://github.com/overtrue/phplint) | ✅ | ✅ | ✅ | 68 | | phploc | [A tool for quickly measuring the size of a PHP project](https://github.com/sebastianbergmann/phploc) | ✅ | ✅ | ✅ | 69 | | phpmd | [A tool for finding problems in PHP code](https://phpmd.org/) | ✅ | ✅ | ✅ | 70 | | phpmetrics | [Static Analysis Tool](http://www.phpmetrics.org/) | ✅ | ✅ | ✅ | 71 | | phpmnd | [Helps to detect magic numbers](https://github.com/povils/phpmnd) | ✅ | ✅ | ✅ | 72 | | phpspec | [SpecBDD Framework](http://www.phpspec.net/) | ✅ | ✅ | ❌ | 73 | | phpstan | [Static Analysis Tool](https://github.com/phpstan/phpstan) | ✅ | ✅ | ✅ | 74 | | phpstan-banned-code | [PHPStan rules for detecting calls to specific functions you don't want in your project](https://github.com/ekino/phpstan-banned-code) | ✅ | ✅ | ✅ | 75 | | phpstan-beberlei-assert | [PHPStan extension for beberlei/assert](https://github.com/phpstan/phpstan-beberlei-assert) | ✅ | ✅ | ✅ | 76 | | phpstan-deprecation-rules | [PHPStan rules for detecting deprecated code](https://github.com/phpstan/phpstan-deprecation-rules) | ✅ | ✅ | ✅ | 77 | | phpstan-doctrine | [Doctrine extensions for PHPStan](https://github.com/phpstan/phpstan-doctrine) | ✅ | ✅ | ✅ | 78 | | phpstan-ergebnis-rules | [Additional rules for PHPstan](https://github.com/ergebnis/phpstan-rules) | ✅ | ✅ | ✅ | 79 | | phpstan-larastan | [Separate installation of phpstan for larastan](https://github.com/phpstan/phpstan) | ✅ | ✅ | ✅ | 80 | | phpstan-phpunit | [PHPUnit extensions and rules for PHPStan](https://github.com/phpstan/phpstan-phpunit) | ✅ | ✅ | ✅ | 81 | | phpstan-strict-rules | [Extra strict and opinionated rules for PHPStan](https://github.com/phpstan/phpstan-strict-rules) | ✅ | ✅ | ✅ | 82 | | phpstan-symfony | [Symfony extension for PHPStan](https://github.com/phpstan/phpstan-symfony) | ✅ | ✅ | ✅ | 83 | | phpstan-webmozart-assert | [PHPStan extension for webmozart/assert](https://github.com/phpstan/phpstan-webmozart-assert) | ✅ | ✅ | ✅ | 84 | | phpunit | [The PHP testing framework](https://phpunit.de/) | ❌ | ✅ | ✅ | 85 | | phpunit-10 | [The PHP testing framework (10.x version)](https://phpunit.de/) | ✅ | ✅ | ✅ | 86 | | phpunit-11 | [The PHP testing framework (11.x version)](https://phpunit.de/) | ✅ | ✅ | ✅ | 87 | | phpunit-8 | [The PHP testing framework (8.x version)](https://phpunit.de/) | ✅ | ✅ | ✅ | 88 | | phpunit-9 | [The PHP testing framework (9.x version)](https://phpunit.de/) | ✅ | ✅ | ✅ | 89 | | pint | [Opinionated PHP code style fixer for Laravel](https://github.com/laravel/pint) | ✅ | ✅ | ✅ | 90 | | psalm | [Finds errors in PHP applications](https://psalm.dev/) | ✅ | ✅ | ✅ | 91 | | psalm-plugin-doctrine | [Stubs to let Psalm understand Doctrine better](https://github.com/weirdan/doctrine-psalm-plugin) | ✅ | ✅ | ✅ | 92 | | psalm-plugin-phpunit | [Psalm plugin for PHPUnit](https://github.com/psalm/psalm-plugin-phpunit) | ✅ | ✅ | ✅ | 93 | | psalm-plugin-symfony | [Psalm Plugin for Symfony](https://github.com/psalm/psalm-plugin-symfony) | ✅ | ✅ | ✅ | 94 | | psecio-parse | [Scans code for potential security-related issues](https://github.com/psecio/parse) | ✅ | ✅ | ✅ | 95 | | rector | [Tool for instant code upgrades and refactoring](https://github.com/rectorphp/rector) | ✅ | ✅ | ✅ | 96 | | roave-backward-compatibility-check | [Tool to compare two revisions of a class API to check for BC breaks](https://github.com/Roave/BackwardCompatibilityCheck) | ✅ | ✅ | ❌ | 97 | | simple-phpunit | [Provides utilities to report legacy tests and usage of deprecated code](https://symfony.com/doc/current/components/phpunit_bridge.html) | ✅ | ✅ | ✅ | 98 | | twig-cs-fixer | [Automatically corrects twig files following the official coding standard rules](https://github.com/VincentLanglet/Twig-CS-Fixer) | ✅ | ✅ | ✅ | 99 | | twig-lint | [Standalone cli twig 1.X linter](https://github.com/asm89/twig-lint) | ✅ | ✅ | ✅ | 100 | | twig-linter | [Standalone cli twig 3.X linter](https://github.com/sserbin/twig-linter) | ✅ | ✅ | ✅ | 101 | | twigcs | [The missing checkstyle for twig!](https://github.com/friendsoftwig/twigcs) | ✅ | ✅ | ✅ | 102 | | yaml-lint | [Compact command line utility for checking YAML file syntax](https://github.com/j13k/yaml-lint) | ✅ | ✅ | ✅ | 103 | 104 | ### Removed tools 105 | 106 | | Name | Summary | 107 | | :--- | :------ | 108 | | analyze | [Visualizes metrics and source code](https://github.com/Qafoo/QualityAnalyzer) | 109 | | box-legacy | [Legacy version of box](https://box-project.github.io/box2/) | 110 | | design-pattern | [Detects design patterns](https://github.com/Halleck45/DesignPatternDetector) | 111 | | parallel-lint | [Checks PHP file syntax](https://github.com/JakubOnderka/PHP-Parallel-Lint) | 112 | | php-coupling-detector | [Detects code coupling issues](https://akeneo.github.io/php-coupling-detector/) | 113 | | php-formatter | [Custom coding standards fixer](https://github.com/mmoreram/php-formatter) | 114 | | phpcf | [Finds usage of deprecated features](http://wapmorgan.github.io/PhpCodeFixer/) | 115 | | phpda | [Generates dependency graphs](https://mamuz.github.io/PhpDependencyAnalysis/) | 116 | | phpdoc-to-typehint | [Automatically adds type hints and return types based on PHPDocs](https://github.com/dunglas/phpdoc-to-typehint) | 117 | | phpstan-exception-rules | [PHPStan rules for checked and unchecked exceptions](https://github.com/pepakriz/phpstan-exception-rules) | 118 | | phpstan-localheinz-rules | [Additional rules for PHPstan](https://github.com/localheinz/phpstan-rules) | 119 | | phpunit-5 | [The PHP testing framework (5.x version)](https://phpunit.de/) | 120 | | phpunit-7 | [The PHP testing framework (7.x version)](https://phpunit.de/) | 121 | | security-checker | [Checks composer dependencies for known security vulnerabilities](https://github.com/sensiolabs/security-checker) | 122 | | testability | [Analyses and reports testability issues of a php codebase](https://github.com/edsonmedina/php_testability) | 123 | 124 | ## Installation 125 | 126 | Get the `toolbox.phar` from the [latest release](https://github.com/jakzal/toolbox/releases/latest). 127 | The command below should do the job: 128 | 129 | ```bash 130 | curl -Ls https://github.com/jakzal/toolbox/releases/latest/download/toolbox.phar -o toolbox && chmod +x toolbox 131 | ``` 132 | 133 | ## Usage 134 | 135 | ### List available tools 136 | 137 | ``` 138 | ./toolbox list-tools 139 | ``` 140 | 141 | #### Filter tools by tags 142 | 143 | To exclude some tools from the listing multiple `--exclude-tag` options can be added. 144 | The `--tag` option can be used to filter tools by tags. 145 | 146 | ``` 147 | ./toolbox list-tools --exclude-tag exclude-php:8.2 --exclude-tag foo --tag bar 148 | ``` 149 | 150 | ### Install tools 151 | 152 | ``` 153 | ./toolbox install 154 | ``` 155 | 156 | #### Install tools in a custom directory 157 | 158 | By default tools are installed in the `/usr/local/bin` directory. To perform an installation in another location, 159 | pass the `--target-dir` option to the `install` command. Also, to change the location composer packages are installed in, 160 | export the `COMPOSER_HOME` environment variable. 161 | 162 | ``` 163 | mkdir /tools 164 | export COMPOSER_HOME=/tools/.composer 165 | export PATH="/tools:$COMPOSER_HOME/vendor/bin:$PATH" 166 | ./toolbox install --target-dir /tools 167 | ``` 168 | 169 | The target dir can also be configured with the `TOOLBOX_TARGET_DIR` environment variable. 170 | 171 | #### Dry run 172 | 173 | To only see what commands would be executed, use the dry run mode: 174 | 175 | ``` 176 | ./toolbox install --dry-run 177 | ``` 178 | 179 | #### Filter tools by tags 180 | 181 | To exclude some tools from the installation multiple `--exclude-tag` options can be added. 182 | The `--tag` option can be used to filter tools by tags. 183 | 184 | ``` 185 | ./toolbox install --exclude-tag exclude-php:8.2 --exclude-tag foo --tag bar 186 | ``` 187 | 188 | ### Test if installed tools are usable 189 | 190 | ``` 191 | ./toolbox test 192 | ``` 193 | 194 | #### Dry run 195 | 196 | To only see what commands would be executed, use the dry run mode: 197 | 198 | ``` 199 | ./toolbox test --dry-run 200 | ``` 201 | 202 | #### Filter tools by tags 203 | 204 | To exclude some tools from the generated test command multiple `--exclude-tag` options can be added. 205 | The `--tag` option can be used to filter tools by tags. 206 | 207 | ``` 208 | ./toolbox test --exclude-tag exclude-php:8.2 --exclude-tag foo --tag bar 209 | ``` 210 | 211 | ### Tools definitions 212 | 213 | By default the following files are used to load tool definitions: 214 | 215 | * `resources/pre-installation.json` 216 | * `resources/architecture.json` 217 | * `resources/checkstyle.json` 218 | * `resources/compatibility.json` 219 | * `resources/composer.json` 220 | * `resources/deprecation.json` 221 | * `resources/documentation.json` 222 | * `resources/linting.json` 223 | * `resources/metrics.json` 224 | * `resources/phpstan.json` 225 | * `resources/psalm.json` 226 | * `resources/refactoring.json` 227 | * `resources/security.json` 228 | * `resources/test.json` 229 | * `resources/tools.json` 230 | 231 | Definitions can be loaded from customised files by passing the `--tools` option(s): 232 | 233 | ``` 234 | ./toolbox list-tools --tools path/to/file1.json --tools path/to/file2.json 235 | ``` 236 | 237 | Tool definition location(s) can be also specified with the `TOOLBOX_JSON` environment variable: 238 | 239 | ``` 240 | TOOLBOX_JSON='path/to/file1.json,path/to/file2.json' ./toolbox list-tools 241 | ``` 242 | 243 | ### Tool tags 244 | 245 | Tools can be tagged in order to enable grouping and filtering them. 246 | 247 | The tags below have a special meaning: 248 | 249 | * `pre-installation` - these tools will be installed before any other tools. 250 | * `exclude-php:8.2`, `exclude-php:8.3` etc - used to exclude installation on the specified php version. 251 | 252 | ## Contributing 253 | 254 | Please read the [Contributing guide](CONTRIBUTING.md) to learn about contributing to this project. 255 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). 256 | By participating in this project you agree to abide by its terms. 257 | -------------------------------------------------------------------------------- /bin/devkit.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | all($filter ?? new Filter([], [])); 50 | } 51 | } 52 | 53 | $application = new Application('Toolbox DevKit', 'dev'); 54 | $application->add( 55 | new class extends CliCommand 56 | { 57 | use Tools; 58 | 59 | protected function configure(): void 60 | { 61 | $this->setName('update:readme'); 62 | $this->setDescription('Updates README.md with latest list of available tools'); 63 | $this->addOption('tools', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to the list of tools. Can also be set with TOOLBOX_JSON environment variable.', $this->toolsJsonDefault()); 64 | $this->addOption('readme', null, InputOption::VALUE_REQUIRED, 'Path to the readme file', __DIR__ . '/../README.md'); 65 | } 66 | 67 | protected function execute(InputInterface $input, OutputInterface $output): int 68 | { 69 | $jsonPath = $input->getOption('tools'); 70 | $readmePath = $input->getOption('readme'); 71 | $tools = $this->loadTools($jsonPath); 72 | 73 | $toolsList = '| Name | Description | PHP 8.2 | PHP 8.3 | PHP 8.4 |' . PHP_EOL; 74 | $toolsList .= '| :--- | :---------- | :------ | :------ | :------ |' . PHP_EOL; 75 | $toolsList .= $tools->sort(function (Tool $left, Tool $right) { 76 | return strcasecmp($left->name(), $right->name()); 77 | })->reduce('', function ($acc, Tool $tool) { 78 | 79 | return $acc . sprintf('| %s | [%s](%s) | %s | %s | %s |', 80 | $tool->name(), 81 | $tool->summary(), 82 | $tool->website(), 83 | in_array('exclude-php:8.2', $tool->tags(), true) ? '❌' : '✅', 84 | in_array('exclude-php:8.3', $tool->tags(), true) ? '❌' : '✅', 85 | in_array('exclude-php:8.4', $tool->tags(), true) ? '❌' : '✅', 86 | ) . PHP_EOL; 87 | }); 88 | 89 | $readme = file_get_contents($readmePath); 90 | $readme = preg_replace('/(## Available tools\n\n).*?(\n#+ )/smi', '$1' . $toolsList . '$2', $readme); 91 | 92 | file_put_contents($readmePath, $readme); 93 | 94 | $output->writeln(sprintf('The %s was updated with latest tools found in %s.', $readmePath, implode(', ', $jsonPath))); 95 | 96 | return 0; 97 | } 98 | } 99 | ); 100 | $application->add( 101 | new class extends CliCommand 102 | { 103 | use Tools; 104 | 105 | protected function configure(): void 106 | { 107 | $this->setName('update:phars'); 108 | $this->setDescription('Attempts to update phar links to latest versions'); 109 | $this->addOption('tools', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to the list of tools. Can also be set with TOOLBOX_JSON environment variable.', $this->toolsJsonDefault()); 110 | } 111 | 112 | protected function execute(InputInterface $input, OutputInterface $output): int 113 | { 114 | foreach ($input->getOption('tools') as $jsonPath) { 115 | $result = $this->updatePhars($jsonPath, $output); 116 | 117 | if ($result !== 0) { 118 | return $result; 119 | } 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | private function updatePhars(string $jsonPath, OutputInterface $output): int 126 | { 127 | $phars = $this->findLatestPhars($jsonPath); 128 | 129 | if (empty($phars)) { 130 | return 0; 131 | } 132 | 133 | $output->writeln('Found phars:'); 134 | 135 | foreach ($phars as $phar) { 136 | $output->writeln(sprintf('* %s', $phar)); 137 | } 138 | 139 | $output->writeln(sprintf('Updated %s.', $jsonPath)); 140 | 141 | return (new PassthruRunner())->run($this->updatePharsCommand($jsonPath, $phars)); 142 | } 143 | 144 | private function findLatestPharsCommand(string $jsonPath): Command 145 | { 146 | $command = <<<'CMD' 147 | grep -e 'github\.com.*releases.*\.phar"' %TOOLBOX_JSON% | 148 | grep -v -e '/latest/' | 149 | sed -e 's@.*github.com/\(.*\)/releases.*@\1@' | 150 | xargs -I"{}" sh -c "curl -s -XGET 'https://api.github.com/repos/{}/releases/latest' -H 'Accept:application/json' | grep browser_download_url | grep .phar | head -n 1" | 151 | sed -e 's/^[^:]*: "\([^"]*\)"/\1/' 152 | CMD; 153 | $command = strtr($command, ['%TOOLBOX_JSON%' => $jsonPath]); 154 | 155 | return new ShCommand($command); 156 | } 157 | 158 | private function findLatestPhars(string $jsonPath): array 159 | { 160 | $phars = []; 161 | 162 | exec((string)$this->findLatestPharsCommand($jsonPath), $phars); 163 | 164 | return $phars; 165 | } 166 | 167 | private function updatePharsCommand(string $jsonPath, array $phars): Command 168 | { 169 | $replacements = implode(' ', array_map( 170 | function (string $phar) { 171 | $project = preg_replace('@https://[^/]*/([^/]*/[^/]*).*@', '$1', $phar); 172 | 173 | return strtr( 174 | '-e "s@\"phar\": \"([^\"]*%PROJECT%[^\"]*)\"@\"phar\": \"%PHAR%\"@g"' . 175 | ' ' . 176 | '-e "s@\"url\": \"([^\"]*%PROJECT%[^\"]*\.phar(\.asc|\.pubkey))\"@\"url\": \"%PHAR%\\2\"@g"', 177 | ['%PROJECT%' => $project, '%PHAR%' => $phar] 178 | ); 179 | }, 180 | $phars 181 | )); 182 | 183 | return new ShCommand(sprintf('sed -i.bak -E %s %s', $replacements, $jsonPath)); 184 | } 185 | } 186 | ); 187 | $application->add( 188 | new class extends CliCommand 189 | { 190 | use Tools; 191 | 192 | protected function configure(): void 193 | { 194 | $this->setName('generate:html'); 195 | $this->setDescription('Generates an html list of available tools'); 196 | $this->addOption('tools', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to the list of tools. Can also be set with TOOLBOX_JSON environment variable.', $this->toolsJsonDefault()); 197 | } 198 | 199 | protected function execute(InputInterface $input, OutputInterface $output): int 200 | { 201 | $tools = $this->loadTools($input->getOption('tools'), new Filter(['pre-installation'], [])); 202 | 203 | $output->writeln($this->renderPage($tools->map($this->toolToHtml()))); 204 | 205 | return 0; 206 | } 207 | 208 | private function toolToHtml(): \Closure 209 | { 210 | $tagTemplate = '
  • %TAG%
  • '; 211 | $toolTemplate = <<<'TEMPLATE' 212 |
    213 |
    214 |
    %NAME%
    215 |

    %SUMMARY%

    216 | %WEBSITE_NAME% 217 |
    218 | 223 |
    224 | TEMPLATE; 225 | 226 | return function (Tool $tool) use ($toolTemplate, $tagTemplate) { 227 | return strtr( 228 | $toolTemplate, 229 | [ 230 | '%NAME%' => $tool->name(), 231 | '%SUMMARY%' => $tool->summary(), 232 | '%WEBSITE%' => $tool->website(), 233 | '%WEBSITE_NAME%' => preg_replace('#^(https?://(github.com/)?)(.*?)/?$#', '$3', $tool->website()), 234 | '%TAGS%' => \implode(\array_map(function (string $tag) use ($tagTemplate) { 235 | return strtr($tagTemplate, ['%TAG%' => $tag]); 236 | }, $tool->tags())) 237 | ]); 238 | }; 239 | } 240 | 241 | private function renderPage(Collection $toolsHtml): string 242 | { 243 | $template = <<<'TEMPLATE' 244 | 245 | 246 | 247 | 248 | 249 | 251 | Quality Assurance Tools for PHP | Toolbox | PHPQA 252 | 261 | 262 | 263 | 264 |
    265 | 266 |
    267 |

    Quality Assurance Tools for PHP

    268 |

    269 | The below list of tools is provided by the phpqa docker image. 270 | Toolbox is used to install them in the image. 271 |

    272 |
    273 | toolbox repository 274 | phpqa docker image 275 | phpqa repository 276 |
    277 | 278 | %TOOLS% 279 | 280 |
    281 | 282 |
    Generated on %GENERATED_ON%.
    283 | 284 | 285 | TEMPLATE; 286 | 287 | return strtr($template, [ 288 | '%TOOLS%' => \implode(PHP_EOL, \array_map( 289 | function ($htmls) { 290 | return PHP_EOL . '
    ' . implode($htmls) . '
    ' . PHP_EOL; 291 | }, 292 | \array_chunk($toolsHtml->toArray(), 4) 293 | )), 294 | '%GENERATED_ON%' => (new \DateTime('now', new \DateTimeZone('UTC')))->format('r'), 295 | ]); 296 | } 297 | } 298 | ); 299 | $application->run(); 300 | -------------------------------------------------------------------------------- /bin/toolbox.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 7 | -------------------------------------------------------------------------------- /box-devkit.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "base-path": "build/devkit-phar", 3 | "output": "../devkit.phar", 4 | "compression": "GZ", 5 | "directories": ["."], 6 | "check-requirements": false, 7 | "main": "bin/devkit.php", 8 | "compactors": [ 9 | "KevinGH\\Box\\Compactor\\PhpScoper" 10 | ], 11 | "banner": [ 12 | "This file is part of the zalas/toolbox project.", 13 | "", 14 | "(c) Jakub Zalas " 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /deptrac.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | paths: 3 | - ./src 4 | exclude_files: [] 5 | layers: 6 | - name: Cli 7 | collectors: 8 | - type: classLike 9 | value: ^Zalas\\Toolbox\\Cli\\.* 10 | - name: Json 11 | collectors: 12 | - type: classLike 13 | value: ^Zalas\\Toolbox\\Json\\.* 14 | - name: Runner 15 | collectors: 16 | - type: classLike 17 | value: ^Zalas\\Toolbox\\Runner\\.* 18 | - name: Tool 19 | collectors: 20 | - type: classLike 21 | value: ^Zalas\\Toolbox\\Tool\\.* 22 | - name: UseCase 23 | collectors: 24 | - type: classLike 25 | value: ^Zalas\\Toolbox\\UseCase\\.* 26 | - name: Psr Container 27 | collectors: 28 | - type: classLike 29 | value: ^Psr\\Container\\.* 30 | - name: Symfony Console 31 | collectors: 32 | - type: classLike 33 | value: ^Symfony\\Component\\Console\\.* 34 | - name: Other Vendors 35 | collectors: 36 | - type: bool 37 | must: 38 | # must be outside of global namespace 39 | - type: classLike 40 | value: '[\\]+' 41 | must_not: 42 | # must not be one of the known vendors 43 | - type: classLike 44 | value: ^Zalas\\Toolbox\\(Cli|Json|Runner|Tool|UseCase)\\.* 45 | - type: classLike 46 | value: ^Psr\\Container\\.* 47 | - type: classLike 48 | value: ^Symfony\\Component\\Console\\.* 49 | ruleset: 50 | Cli: 51 | - Tool 52 | - Json 53 | - Runner 54 | - UseCase 55 | - Symfony Console 56 | - Psr Container 57 | Json: 58 | - Tool 59 | Runner: 60 | - Tool 61 | Tool: 62 | UseCase: 63 | - Tool 64 | -------------------------------------------------------------------------------- /resources/architecture.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "dephpend", 5 | "summary": "Detect flaws in your architecture", 6 | "website": "https://dephpend.com/", 7 | "command": { 8 | "phive-install": { 9 | "alias": "dephpend", 10 | "bin": "%target-dir%/dephpend", 11 | "sig": "76835C9464877BDD" 12 | } 13 | }, 14 | "test": "dephpend list", 15 | "tags": ["architecture"] 16 | }, 17 | { 18 | "name": "deptrac", 19 | "summary": "Enforces dependency rules between software layers", 20 | "website": "https://github.com/deptrac/deptrac", 21 | "command": { 22 | "composer-global-install": { 23 | "package": "deptrac/deptrac" 24 | } 25 | }, 26 | "test": "deptrac list", 27 | "tags": ["featured", "architecture"] 28 | }, 29 | { 30 | "name": "pdepend", 31 | "summary": "Static Analysis Tool", 32 | "website": "https://pdepend.org/", 33 | "command": { 34 | "phive-install": { 35 | "alias": "pdepend/pdepend", 36 | "bin": "%target-dir%/pdepend", 37 | "sig": "508003DAED98C308" 38 | } 39 | }, 40 | "test": "pdepend --version", 41 | "tags": ["featured", "architecture"] 42 | }, 43 | { 44 | "name": "phparkitect", 45 | "summary": "Helps to put architectural constraints in a PHP code base", 46 | "website": "https://github.com/phparkitect/arkitect", 47 | "command": { 48 | "phar-download": { 49 | "phar": "https://github.com/phparkitect/arkitect/releases/latest/download/phparkitect.phar", 50 | "bin": "%target-dir%/phparkitect" 51 | } 52 | }, 53 | "test": "phparkitect --version", 54 | "tags": ["architecture"] 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /resources/checkstyle.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "ecs", 5 | "summary": "Sets up and runs coding standard checks", 6 | "website": "https://github.com/Symplify/EasyCodingStandard", 7 | "command": { 8 | "composer-bin-plugin": { 9 | "package": "symplify/easy-coding-standard", 10 | "namespace": "ecs", 11 | "links": {"%target-dir%/ecs": "ecs"} 12 | } 13 | }, 14 | "test": "ecs -h", 15 | "tags": ["checkstyle"] 16 | }, 17 | { 18 | "name": "pint", 19 | "summary": "Opinionated PHP code style fixer for Laravel", 20 | "website": "https://github.com/laravel/pint", 21 | "command": { 22 | "composer-bin-plugin": { 23 | "package": "laravel/pint", 24 | "namespace": "pint", 25 | "links": {"%target-dir%/pint": "pint"} 26 | } 27 | }, 28 | "test": "pint --version", 29 | "tags": ["checkstyle"] 30 | }, 31 | { 32 | "name": "php-cs-fixer", 33 | "summary": "PHP Coding Standards Fixer", 34 | "website": "http://cs.symfony.com/", 35 | "command": { 36 | "phive-install": { 37 | "alias": "php-cs-fixer", 38 | "bin": "%target-dir%/php-cs-fixer", 39 | "sig": "E82B2FB314E9906E" 40 | } 41 | }, 42 | "test": "php-cs-fixer list", 43 | "tags": ["featured", "checkstyle", "exclude-php:8.4"] 44 | }, 45 | { 46 | "name": "phpcbf", 47 | "summary": "Automatically corrects coding standard violations", 48 | "website": "https://github.com/squizlabs/PHP_CodeSniffer", 49 | "command": { 50 | "phive-install": { 51 | "alias": "phpcbf", 52 | "bin": "%target-dir%/phpcbf", 53 | "sig": "A978220305CD5C32" 54 | } 55 | }, 56 | "test": "phpcbf --help", 57 | "tags": ["checkstyle"] 58 | }, 59 | { 60 | "name": "twigcs", 61 | "summary": "The missing checkstyle for twig!", 62 | "website": "https://github.com/friendsoftwig/twigcs", 63 | "command": { 64 | "phive-install": { 65 | "alias": "friendsoftwig/twigcs", 66 | "bin": "%target-dir%/twigcs", 67 | "sig": "C00543248C87FB13" 68 | } 69 | }, 70 | "test": "twigcs --help", 71 | "tags": ["checkstyle"] 72 | }, 73 | { 74 | "name": "twig-cs-fixer", 75 | "summary": "Automatically corrects twig files following the official coding standard rules", 76 | "website": "https://github.com/VincentLanglet/Twig-CS-Fixer", 77 | "command": { 78 | "composer-bin-plugin": { 79 | "package": "vincentlanglet/twig-cs-fixer", 80 | "namespace": "twig-cs-fixer", 81 | "links": {"%target-dir%/twig-cs-fixer": "twig-cs-fixer"} 82 | } 83 | }, 84 | "test": "twig-cs-fixer --help", 85 | "tags": ["checkstyle"] 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /resources/compatibility.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "php-semver-checker", 5 | "summary": "Suggests a next version according to semantic versioning", 6 | "website": "https://github.com/tomzx/php-semver-checker", 7 | "command": { 8 | "phar-download": { 9 | "phar": "http://psvc.coreteks.org/php-semver-checker.phar", 10 | "bin": "%target-dir%/php-semver-checker" 11 | } 12 | }, 13 | "test": "php-semver-checker list", 14 | "tags": ["compatibility", "exclude-php:8.2", "exclude-php:8.3", "exclude-php:8.4"] 15 | }, 16 | { 17 | "name": "roave-backward-compatibility-check", 18 | "summary": "Tool to compare two revisions of a class API to check for BC breaks", 19 | "website": "https://github.com/Roave/BackwardCompatibilityCheck", 20 | "command": { 21 | "sh": { 22 | "command": "composer global bin roavebackwardcompatibilitycheck config allow-plugins.ocramius/package-versions true" 23 | }, 24 | "composer-bin-plugin": { 25 | "package": "roave/backward-compatibility-check", 26 | "namespace": "roavebackwardcompatibilitycheck", 27 | "links": {"%target-dir%/roave-backward-compatibility-check": "roave-backward-compatibility-check"} 28 | } 29 | }, 30 | "test": "roave-backward-compatibility-check --version", 31 | "tags": ["compatibility", "exclude-php:8.4"] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /resources/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "composer-normalize", 5 | "summary": "Composer plugin to normalize composer.json files", 6 | "website": "https://github.com/ergebnis/composer-normalize", 7 | "command": { 8 | "sh": { 9 | "command": "composer config --global --json allow-plugins.ergebnis/composer-normalize true" 10 | }, 11 | "composer-global-install": { 12 | "package": "ergebnis/composer-normalize" 13 | } 14 | }, 15 | "test": "composer global show ergebnis/composer-normalize", 16 | "tags": ["composer"] 17 | }, 18 | { 19 | "name": "composer-unused", 20 | "summary": "Show unused packages by scanning your code", 21 | "website": "https://github.com/icanhazstring/composer-unused", 22 | "command": { 23 | "phive-install": { 24 | "alias": "composer-unused", 25 | "bin": "%target-dir%/composer-unused", 26 | "sig": "3135AA4CB4F1AB0B" 27 | } 28 | }, 29 | "test": "composer-unused -V", 30 | "tags": ["composer"] 31 | }, 32 | { 33 | "name": "composer-require-checker", 34 | "summary": "Verify that no unknown symbols are used in the sources of a package.", 35 | "website": "https://github.com/maglnet/ComposerRequireChecker", 36 | "command": { 37 | "phive-install": { 38 | "alias": "composer-require-checker", 39 | "bin": "%target-dir%/composer-require-checker", 40 | "sig": "033E5F8D801A2F8D" 41 | } 42 | }, 43 | "test": "composer-require-checker -V", 44 | "tags": ["composer"] 45 | }, 46 | { 47 | "name": "composer-require-checker-3", 48 | "summary": "Verify that no unknown symbols are used in the sources of a package.", 49 | "website": "https://github.com/maglnet/ComposerRequireChecker", 50 | "command": { 51 | "phive-install": { 52 | "alias": "composer-require-checker@^3.8", 53 | "bin": "%target-dir%/composer-require-checker-3", 54 | "sig": "033E5F8D801A2F8D" 55 | } 56 | }, 57 | "test": "composer-require-checker-3 -V", 58 | "tags": ["composer"] 59 | }, 60 | { 61 | "name": "cyclonedx-php-composer", 62 | "summary": "Composer plugin to create Software-Bill-of-Materials (SBOM) in CycloneDX format", 63 | "website": "https://github.com/CycloneDX/cyclonedx-php-composer", 64 | "command": { 65 | "sh": { 66 | "command": "composer global config --no-plugins allow-plugins.cyclonedx/cyclonedx-php-composer true" 67 | }, 68 | "composer-global-install": { 69 | "package": "cyclonedx/cyclonedx-php-composer" 70 | } 71 | }, 72 | "test": "composer global show cyclonedx/cyclonedx-php-composer", 73 | "tags": ["composer"] 74 | }, 75 | { 76 | "name": "composer-lock-diff", 77 | "summary": "Composer plugin to check what has changed after a composer update", 78 | "website": "https://github.com/davidrjonas/composer-lock-diff", 79 | "command": { 80 | "composer-bin-plugin": { 81 | "package": "davidrjonas/composer-lock-diff", 82 | "namespace": "composer-lock-diff", 83 | "links": {"%target-dir%/composer-lock-diff": "composer-lock-diff"} 84 | } 85 | }, 86 | "test": "composer-lock-diff --help", 87 | "tags": ["composer"] 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /resources/deprecation.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "deprecation-detector", 5 | "summary": "Finds usages of deprecated code", 6 | "website": "https://github.com/sensiolabs-de/deprecation-detector", 7 | "command": { 8 | "phive-install": { 9 | "alias": "sensiolabs-de/deprecation-detector", 10 | "bin": "%target-dir%/deprecation-detector" 11 | } 12 | }, 13 | "test": "deprecation-detector list", 14 | "tags": ["deprecation"] 15 | }, 16 | { 17 | "name": "phpdd", 18 | "summary": "Finds usage of deprecated features", 19 | "website": "http://wapmorgan.github.io/PhpDeprecationDetector", 20 | "command": { 21 | "phive-install": { 22 | "alias": "wapmorgan/phpdeprecationdetector", 23 | "bin": "%target-dir%/phpdd" 24 | } 25 | }, 26 | "test": "phpdd -h", 27 | "tags": ["deprecation"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /resources/documentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "phpDocumentor", 5 | "summary": "Documentation generator", 6 | "website": "https://www.phpdoc.org/", 7 | "command": { 8 | "phive-install": { 9 | "alias": "phpDocumentor", 10 | "bin": "%target-dir%/phpDocumentor", 11 | "sig": "6DA3ACC4991FFAE5" 12 | } 13 | }, 14 | "test": "phpDocumentor --help", 15 | "tags": ["featured", "documentation"] 16 | }, 17 | { 18 | "name": "phpcb", 19 | "summary": "PHP Code Browser", 20 | "website": "https://github.com/mayflower/PHP_CodeBrowser", 21 | "command": { 22 | "phar-download": { 23 | "phar": "https://github.com/bytepark/php-phar-qatools/raw/master/phpcb.phar", 24 | "bin": "%target-dir%/phpcb" 25 | } 26 | }, 27 | "test": "phpcb -V", 28 | "tags": ["documentation"] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /resources/linting.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "parallel-lint", 5 | "summary": "Checks PHP file syntax", 6 | "website": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", 7 | "command": { 8 | "phive-install": { 9 | "alias": "php-parallel-lint/PHP-Parallel-Lint", 10 | "bin": "%target-dir%/parallel-lint" 11 | } 12 | }, 13 | "test": "parallel-lint -h", 14 | "tags": ["linting"] 15 | }, 16 | { 17 | "name": "phplint", 18 | "summary": "Lints php files in parallel", 19 | "website": "https://github.com/overtrue/phplint", 20 | "command": { 21 | "composer-bin-plugin": { 22 | "package": "overtrue/phplint", 23 | "namespace": "phplint", 24 | "links": {"%target-dir%/phplint": "phplint"} 25 | } 26 | }, 27 | "test": "phplint -V", 28 | "tags": ["linting"] 29 | }, 30 | { 31 | "name": "twig-lint", 32 | "summary": "Standalone cli twig 1.X linter", 33 | "website": "https://github.com/asm89/twig-lint", 34 | "command": { 35 | "phar-download": { 36 | "phar": "https://asm89.github.io/d/twig-lint.phar", 37 | "bin": "%target-dir%/twig-lint" 38 | } 39 | }, 40 | "test": "twig-lint --version", 41 | "tags": ["linting"] 42 | }, 43 | { 44 | "name": "yaml-lint", 45 | "summary": "Compact command line utility for checking YAML file syntax", 46 | "website": "https://github.com/j13k/yaml-lint", 47 | "command": { 48 | "phive-install": { 49 | "alias": "j13k/yaml-lint", 50 | "bin": "%target-dir%/yaml-lint", 51 | "sig": "985E1E22802973B2" 52 | } 53 | }, 54 | "test": "yaml-lint --version", 55 | "tags": ["linting"] 56 | }, 57 | { 58 | "name": "twig-linter", 59 | "summary": "Standalone cli twig 3.X linter", 60 | "website": "https://github.com/sserbin/twig-linter", 61 | "command": { 62 | "composer-bin-plugin": { 63 | "package": "sserbin/twig-linter:@dev", 64 | "namespace": "twig-linter", 65 | "links": {"%target-dir%/twig-linter": "twig-linter"} 66 | } 67 | }, 68 | "test": "twig-linter --help", 69 | "tags": ["linting"] 70 | }, 71 | { 72 | "name": "gherkin-lint-php", 73 | "summary": "Gherkin linter for PHP", 74 | "website": "https://github.com/dantleech/gherkin-lint-php", 75 | "command": { 76 | "composer-bin-plugin": { 77 | "package": "dantleech/gherkin-lint", 78 | "namespace": "gherkin-lint-php", 79 | "links": {"%target-dir%/gherkinlint": "gherkinlint"} 80 | } 81 | }, 82 | "test": "gherkinlint --help", 83 | "tags": ["linting"] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /resources/metrics.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "phpinsights", 5 | "summary": "Analyses code quality, style, architecture and complexity", 6 | "website": "https://phpinsights.com/", 7 | "command": { 8 | "sh": { 9 | "command": "composer global bin phpinsights config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true" 10 | }, 11 | "composer-bin-plugin": { 12 | "package": "nunomaduro/phpinsights", 13 | "namespace": "phpinsights", 14 | "links": {"%target-dir%/phpinsights": "phpinsights"} 15 | } 16 | }, 17 | "test": "phpinsights --version", 18 | "tags": ["metrics"] 19 | }, 20 | { 21 | "name": "phploc", 22 | "summary": "A tool for quickly measuring the size of a PHP project", 23 | "website": "https://github.com/sebastianbergmann/phploc", 24 | "command": { 25 | "phive-install": { 26 | "alias": "phploc", 27 | "bin": "%target-dir%/phploc", 28 | "sig": "4AA394086372C20A" 29 | } 30 | }, 31 | "test": "phploc -v", 32 | "tags": ["metrics"] 33 | }, 34 | { 35 | "name": "phpmetrics", 36 | "summary": "Static Analysis Tool", 37 | "website": "http://www.phpmetrics.org/", 38 | "command": { 39 | "phive-install": { 40 | "alias": "phpmetrics/PhpMetrics", 41 | "bin": "%target-dir%/phpmetrics" 42 | } 43 | }, 44 | "test": "phpmetrics --version", 45 | "tags": ["featured", "metrics"] 46 | }, 47 | { 48 | "name": "lines", 49 | "summary": "CLI tool for quick metrics of PHP projects", 50 | "website": "https://github.com/tomasVotruba/lines", 51 | "command": { 52 | "composer-bin-plugin": { 53 | "package": "tomasvotruba/lines", 54 | "namespace": "lines", 55 | "links": {"%target-dir%/lines": "lines"} 56 | } 57 | }, 58 | "test": "lines list -q", 59 | "tags": ["metrics"] 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /resources/phpcs.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "phpcs", 5 | "summary": "Detects coding standard violations", 6 | "website": "https://github.com/squizlabs/PHP_CodeSniffer", 7 | "command": { 8 | "composer-bin-plugin": { 9 | "package": "squizlabs/php_codesniffer", 10 | "namespace": "phpcs", 11 | "links": {"%target-dir%/phpcs": "phpcs"} 12 | } 13 | }, 14 | "test": "phpcs --help", 15 | "tags": ["checkstyle"] 16 | }, 17 | { 18 | "name": "phpcodesniffer-composer-install", 19 | "summary": "Easy installation of PHP_CodeSniffer coding standards (rulesets).", 20 | "website": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer", 21 | "command": { 22 | "sh": { 23 | "command": "composer global bin phpcs config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true" 24 | }, 25 | "composer-bin-plugin": { 26 | "package": "dealerdirect/phpcodesniffer-composer-installer", 27 | "namespace": "phpcs" 28 | } 29 | }, 30 | "test": "composer global bin phpcs show dealerdirect/phpcodesniffer-composer-installer", 31 | "tags": ["pre-installation"] 32 | }, 33 | { 34 | "name": "phpcs-security-audit", 35 | "summary": "Finds vulnerabilities and weaknesses related to security in PHP code", 36 | "website": "https://github.com/FloeDesignTechnologies/phpcs-security-audit", 37 | "command": { 38 | "composer-bin-plugin": { 39 | "package": "pheromone/phpcs-security-audit", 40 | "namespace": "phpcs" 41 | } 42 | }, 43 | "test": "phpcs -i | grep Security", 44 | "tags": ["security"] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /resources/phpstan.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "phpstan", 5 | "summary": "Static Analysis Tool", 6 | "website": "https://github.com/phpstan/phpstan", 7 | "command": { 8 | "composer-bin-plugin": { 9 | "package": "phpstan/phpstan", 10 | "namespace": "phpstan", 11 | "links": {"%target-dir%/phpstan": "phpstan"} 12 | } 13 | }, 14 | "test": "phpstan --version", 15 | "tags": ["featured", "phpstan"] 16 | }, 17 | { 18 | "name": "phpstan-deprecation-rules", 19 | "summary": "PHPStan rules for detecting deprecated code", 20 | "website": "https://github.com/phpstan/phpstan-deprecation-rules", 21 | "command": { 22 | "composer-bin-plugin": { 23 | "package": "phpstan/phpstan-deprecation-rules", 24 | "namespace": "phpstan" 25 | } 26 | }, 27 | "test": "composer global bin phpstan show phpstan/phpstan-deprecation-rules", 28 | "tags": ["phpstan"] 29 | }, 30 | { 31 | "name": "phpstan-ergebnis-rules", 32 | "summary": "Additional rules for PHPstan", 33 | "website": "https://github.com/ergebnis/phpstan-rules", 34 | "command": { 35 | "composer-bin-plugin": { 36 | "package": "ergebnis/phpstan-rules", 37 | "namespace": "phpstan" 38 | } 39 | }, 40 | "test": "composer global bin phpstan show ergebnis/phpstan-rules", 41 | "tags": ["phpstan"] 42 | }, 43 | { 44 | "name": "phpstan-strict-rules", 45 | "summary": "Extra strict and opinionated rules for PHPStan", 46 | "website": "https://github.com/phpstan/phpstan-strict-rules", 47 | "command": { 48 | "composer-bin-plugin": { 49 | "package": "phpstan/phpstan-strict-rules", 50 | "namespace": "phpstan" 51 | } 52 | }, 53 | "test": "composer global bin phpstan show phpstan/phpstan-strict-rules", 54 | "tags": ["phpstan"] 55 | }, 56 | { 57 | "name": "phpstan-doctrine", 58 | "summary": "Doctrine extensions for PHPStan", 59 | "website": "https://github.com/phpstan/phpstan-doctrine", 60 | "command": { 61 | "composer-bin-plugin": { 62 | "package": "phpstan/phpstan-doctrine", 63 | "namespace": "phpstan" 64 | } 65 | }, 66 | "test": "composer global bin phpstan show phpstan/phpstan-doctrine", 67 | "tags": ["phpstan"] 68 | }, 69 | { 70 | "name": "phpstan-phpunit", 71 | "summary": "PHPUnit extensions and rules for PHPStan", 72 | "website": "https://github.com/phpstan/phpstan-phpunit", 73 | "command": { 74 | "composer-bin-plugin": { 75 | "package": "phpstan/phpstan-phpunit", 76 | "namespace": "phpstan" 77 | } 78 | }, 79 | "test": "composer global bin phpstan show phpstan/phpstan-phpunit", 80 | "tags": ["phpstan"] 81 | }, 82 | { 83 | "name": "phpstan-symfony", 84 | "summary": "Symfony extension for PHPStan", 85 | "website": "https://github.com/phpstan/phpstan-symfony", 86 | "command": { 87 | "composer-bin-plugin": { 88 | "package": "phpstan/phpstan-symfony", 89 | "namespace": "phpstan" 90 | } 91 | }, 92 | "test": "composer global bin phpstan show phpstan/phpstan-symfony", 93 | "tags": ["phpstan"] 94 | }, 95 | { 96 | "name": "phpstan-beberlei-assert", 97 | "summary": "PHPStan extension for beberlei/assert", 98 | "website": "https://github.com/phpstan/phpstan-beberlei-assert", 99 | "command": { 100 | "composer-bin-plugin": { 101 | "package": "phpstan/phpstan-beberlei-assert", 102 | "namespace": "phpstan" 103 | } 104 | }, 105 | "test": "composer global bin phpstan show phpstan/phpstan-beberlei-assert", 106 | "tags": ["phpstan"] 107 | }, 108 | { 109 | "name": "phpstan-webmozart-assert", 110 | "summary": "PHPStan extension for webmozart/assert", 111 | "website": "https://github.com/phpstan/phpstan-webmozart-assert", 112 | "command": { 113 | "composer-bin-plugin": { 114 | "package": "phpstan/phpstan-webmozart-assert", 115 | "namespace": "phpstan" 116 | } 117 | }, 118 | "test": "composer global bin phpstan show phpstan/phpstan-webmozart-assert", 119 | "tags": ["phpstan"] 120 | }, 121 | { 122 | "name": "phpat", 123 | "summary": "Easy to use architecture testing tool", 124 | "website": "https://github.com/carlosas/phpat", 125 | "command": { 126 | "composer-bin-plugin": { 127 | "package": "phpat/phpat", 128 | "namespace": "phpstan" 129 | } 130 | }, 131 | "test": "composer global bin phpstan show phpat/phpat", 132 | "tags": ["phpstan", "architecture"] 133 | }, 134 | { 135 | "name": "phpstan-larastan", 136 | "summary": "Separate installation of phpstan for larastan", 137 | "website": "https://github.com/phpstan/phpstan", 138 | "command": { 139 | "composer-bin-plugin": { 140 | "package": "phpstan/phpstan", 141 | "namespace": "larastan", 142 | "links": {"%target-dir%/phpstan-larastan": "phpstan"} 143 | } 144 | }, 145 | "test": "phpstan-larastan --version", 146 | "tags": ["phpstan"] 147 | }, 148 | { 149 | "name": "larastan", 150 | "summary": "PHPStan extension for Laravel", 151 | "website": "https://github.com/nunomaduro/larastan", 152 | "command": { 153 | "composer-bin-plugin": { 154 | "package": "nunomaduro/larastan", 155 | "namespace": "larastan" 156 | } 157 | }, 158 | "test": "composer global bin phpstan show ergebnis/phpstan-rules", 159 | "tags": ["phpstan"] 160 | }, 161 | { 162 | "name": "phpstan-banned-code", 163 | "summary": "PHPStan rules for detecting calls to specific functions you don't want in your project", 164 | "website": "https://github.com/ekino/phpstan-banned-code", 165 | "command": { 166 | "composer-bin-plugin": { 167 | "package": "ekino/phpstan-banned-code", 168 | "namespace": "phpstan" 169 | } 170 | }, 171 | "test": "composer global bin phpstan show ekino/phpstan-banned-code", 172 | "tags": [ 173 | "phpstan" 174 | ] 175 | } 176 | ] 177 | } 178 | -------------------------------------------------------------------------------- /resources/pre-installation.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "composer", 5 | "summary": "Dependency Manager for PHP", 6 | "website": "https://getcomposer.org/", 7 | "command": { 8 | "sh": { 9 | "command": "composer self-update" 10 | } 11 | }, 12 | "test": "composer list", 13 | "tags": ["pre-installation"] 14 | }, 15 | { 16 | "name": "phive", 17 | "summary": "PHAR Installation and Verification Environment", 18 | "website": "https://phar.io/", 19 | "command": { 20 | "file-download": { 21 | "url": "https://github.com/phar-io/phive/releases/download/0.16.0/phive-0.16.0.phar.asc", 22 | "file": "%target-dir%/phive.asc" 23 | }, 24 | "phar-download": { 25 | "phar": "https://github.com/phar-io/phive/releases/download/0.16.0/phive-0.16.0.phar", 26 | "bin": "%target-dir%/phive" 27 | }, 28 | "sh": { 29 | "command": "gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 && gpg --verify %target-dir%/phive.asc %target-dir%/phive" 30 | } 31 | }, 32 | "test": "phive --version", 33 | "tags": ["pre-installation"] 34 | }, 35 | { 36 | "name": "composer-bin-plugin", 37 | "summary": "Composer plugin to install bin vendors in isolated locations", 38 | "website": "https://github.com/bamarni/composer-bin-plugin", 39 | "command": { 40 | "sh": { 41 | "command": "composer global config --json extra.bamarni-bin.bin-links false && composer config --global --json allow-plugins.bamarni/composer-bin-plugin true" 42 | }, 43 | "composer-global-install": { 44 | "package": "bamarni/composer-bin-plugin" 45 | } 46 | }, 47 | "test": "composer global show bamarni/composer-bin-plugin", 48 | "tags": ["pre-installation"] 49 | }, 50 | { 51 | "name": "box", 52 | "summary": "Fast, zero config application bundler with PHARs", 53 | "website": "https://github.com/humbug/box", 54 | "command": { 55 | "phive-install": { 56 | "alias": "humbug/box", 57 | "bin": "%target-dir%/box", 58 | "sig": "2DF45277AEF09A2F" 59 | } 60 | }, 61 | "test": "box list", 62 | "tags": ["pre-installation"] 63 | }, 64 | { 65 | "name": "box-3", 66 | "summary": "Fast, zero config application bundler with PHARs", 67 | "website": "https://github.com/humbug/box", 68 | "command": { 69 | "phive-install": { 70 | "alias": "humbug/box:^3.16", 71 | "bin": "%target-dir%/box-3", 72 | "sig": "2DF45277AEF09A2F" 73 | } 74 | }, 75 | "test": "box-3 list", 76 | "tags": ["exclude-php:8.2", "pre-installation"] 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /resources/psalm.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "psalm", 5 | "summary": "Finds errors in PHP applications", 6 | "website": "https://psalm.dev/", 7 | "command": { 8 | "composer-bin-plugin": { 9 | "package": "vimeo/psalm", 10 | "namespace": "psalm", 11 | "links": { 12 | "%target-dir%/psalm": "psalm", 13 | "%target-dir%/psalm-language-server": "psalm-language-server", 14 | "%target-dir%/psalm-plugin": "psalm-plugin", 15 | "%target-dir%/psalm-refactor": "psalm-refactor", 16 | "%target-dir%/psalter": "psalter" 17 | } 18 | } 19 | }, 20 | "test": "psalm -h", 21 | "tags": ["featured", "psalm"] 22 | }, 23 | { 24 | "name": "psalm-plugin-doctrine", 25 | "summary": "Stubs to let Psalm understand Doctrine better", 26 | "website": "https://github.com/weirdan/doctrine-psalm-plugin", 27 | "command": { 28 | "composer-bin-plugin": { 29 | "package": "weirdan/doctrine-psalm-plugin", 30 | "namespace": "psalm" 31 | } 32 | }, 33 | "test": "cd / && psalm-plugin show | grep weirdan/doctrine-psalm-plugin", 34 | "tags": ["psalm"] 35 | }, 36 | { 37 | "name": "psalm-plugin-phpunit", 38 | "summary": "Psalm plugin for PHPUnit", 39 | "website": "https://github.com/psalm/psalm-plugin-phpunit", 40 | "command": { 41 | "composer-bin-plugin": { 42 | "package": "psalm/plugin-phpunit", 43 | "namespace": "psalm" 44 | } 45 | }, 46 | "test": "cd / && psalm-plugin show | grep psalm/plugin-phpunit", 47 | "tags": ["psalm"] 48 | }, 49 | { 50 | "name": "psalm-plugin-symfony", 51 | "summary": "Psalm Plugin for Symfony", 52 | "website": "https://github.com/psalm/psalm-plugin-symfony", 53 | "command": { 54 | "composer-bin-plugin": { 55 | "package": "psalm/plugin-symfony", 56 | "namespace": "psalm" 57 | } 58 | }, 59 | "test": "cd / && psalm-plugin show | grep psalm/plugin-symfony", 60 | "tags": ["psalm"] 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /resources/refactoring.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "churn", 5 | "summary": "Discovers good candidates for refactoring", 6 | "website": "https://github.com/bmitch/churn-php", 7 | "command": { 8 | "phive-install": { 9 | "alias": "churn", 10 | "bin": "%target-dir%/churn", 11 | "sig": "96141E4421A9B0D5" 12 | } 13 | }, 14 | "test": "churn --version", 15 | "tags": ["featured", "refactoring"] 16 | }, 17 | { 18 | "name": "rector", 19 | "summary": "Tool for instant code upgrades and refactoring", 20 | "website": "https://github.com/rectorphp/rector", 21 | "command": { 22 | "composer-bin-plugin": { 23 | "package": "rector/rector", 24 | "namespace": "rector", 25 | "links": {"%target-dir%/rector": "rector"} 26 | } 27 | }, 28 | "test": "rector --version", 29 | "tags": ["refactoring"] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /resources/security.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "psecio-parse", 5 | "summary": "Scans code for potential security-related issues", 6 | "website": "https://github.com/psecio/parse", 7 | "command": { 8 | "composer-bin-plugin": { 9 | "package": "psecio/parse:dev-master", 10 | "namespace": "legacy-php-parser", 11 | "links": {"%target-dir%/psecio-parse": "psecio-parse"} 12 | } 13 | }, 14 | "test": "psecio-parse --version", 15 | "tags": ["security"] 16 | }, 17 | { 18 | "name": "local-php-security-checker", 19 | "summary": "Checks composer dependencies for known security vulnerabilities", 20 | "website": "https://github.com/fabpot/local-php-security-checker", 21 | "command": { 22 | "file-download": { 23 | "url": "https://github.com/fabpot/local-php-security-checker/releases/download/v2.0.6/local-php-security-checker_2.0.6_linux_amd64", 24 | "file": "%target-dir%/local-php-security-checker" 25 | }, 26 | "sh": { 27 | "command": "chmod +x %target-dir%/local-php-security-checker" 28 | } 29 | }, 30 | "test": "local-php-security-checker --help", 31 | "tags": ["featured", "security"] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /resources/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "behat", 5 | "summary": "Helps to test business expectations", 6 | "website": "http://behat.org/", 7 | "command": { 8 | "composer-bin-plugin": { 9 | "package": "behat/behat", 10 | "namespace": "behat", 11 | "links": {"%target-dir%/behat": "behat"} 12 | } 13 | }, 14 | "test": "behat --version", 15 | "tags": ["featured", "test"] 16 | }, 17 | { 18 | "name": "codeception", 19 | "summary": "Codeception is a BDD-styled PHP testing framework", 20 | "website": "https://codeception.com/", 21 | "command": { 22 | "phar-download": { 23 | "phar": "https://codeception.com/codecept.phar", 24 | "bin": "%target-dir%/codeception" 25 | } 26 | }, 27 | "test": "codeception --version", 28 | "tags": ["test"] 29 | }, 30 | { 31 | "name": "infection", 32 | "summary": "AST based PHP Mutation Testing Framework", 33 | "website": "https://infection.github.io/", 34 | "command": { 35 | "phive-install": { 36 | "alias": "infection", 37 | "bin": "%target-dir%/infection", 38 | "sig": "C5095986493B4AA0" 39 | } 40 | }, 41 | "test": "infection --version", 42 | "tags": ["featured", "test"] 43 | }, 44 | { 45 | "name": "paratest", 46 | "summary": "Parallel testing for PHPUnit", 47 | "website": "https://github.com/paratestphp/paratest", 48 | "command": { 49 | "composer-bin-plugin": { 50 | "package": "brianium/paratest", 51 | "namespace": "paratest", 52 | "links": {"%target-dir%/paratest": "paratest"} 53 | } 54 | }, 55 | "test": "paratest --version", 56 | "tags": ["test"] 57 | }, 58 | { 59 | "name": "pest", 60 | "summary": "The elegant PHP Testing Framework", 61 | "website": "https://github.com/pestphp/pest", 62 | "command": { 63 | "sh": { 64 | "command": "composer global bin pest config allow-plugins.pestphp/pest-plugin true" 65 | }, 66 | "composer-bin-plugin": { 67 | "package": "pestphp/pest", 68 | "namespace": "pest", 69 | "links": {"%target-dir%/pest": "pest"} 70 | } 71 | }, 72 | "test": "pest --version", 73 | "tags": ["test"] 74 | }, 75 | { 76 | "name": "phpcov", 77 | "summary": "a command-line frontend for the PHP_CodeCoverage library", 78 | "website": "https://github.com/sebastianbergmann/phpcov", 79 | "command": { 80 | "phive-install": { 81 | "alias": "phpcov", 82 | "bin": "%target-dir%/phpcov", 83 | "sig": "4AA394086372C20A" 84 | } 85 | }, 86 | "test": "phpcov -v", 87 | "tags": ["test", "exclude-php:8.2"] 88 | }, 89 | { 90 | "name": "php-fuzzer", 91 | "summary": "A fuzzer for PHP, which can be used to find bugs in libraries by feeding them 'random' inputs", 92 | "website": "https://github.com/nikic/PHP-Fuzzer", 93 | "command": { 94 | "phive-install": { 95 | "alias": "nikic/php-fuzzer", 96 | "bin": "%target-dir%/php-fuzzer" 97 | } 98 | }, 99 | "test": "php-fuzzer --help | grep 'Usage:'", 100 | "tags": ["test"] 101 | }, 102 | { 103 | "name": "phpspec", 104 | "summary": "SpecBDD Framework", 105 | "website": "http://www.phpspec.net/", 106 | "command": { 107 | "phive-install": { 108 | "alias": "phpspec/phpspec", 109 | "bin": "%target-dir%/phpspec" 110 | } 111 | }, 112 | "test": "phpspec --version", 113 | "tags": ["exclude-php:8.4", "featured", "test"] 114 | }, 115 | { 116 | "name": "phpunit", 117 | "summary": "The PHP testing framework", 118 | "website": "https://phpunit.de/", 119 | "command": { 120 | "phive-install": { 121 | "alias": "phpunit", 122 | "bin": "%target-dir%/phpunit", 123 | "sig": "4AA394086372C20A" 124 | } 125 | }, 126 | "test": "phpunit --version", 127 | "tags": ["featured", "test", "exclude-php:8.2"] 128 | }, 129 | { 130 | "name": "phpunit-11", 131 | "summary": "The PHP testing framework (11.x version)", 132 | "website": "https://phpunit.de/", 133 | "command": { 134 | "phive-install": { 135 | "alias": "phpunit@^11.0", 136 | "bin": "%target-dir%/phpunit-11", 137 | "sig": "4AA394086372C20A" 138 | } 139 | }, 140 | "test": "phpunit-11 --version", 141 | "tags": ["test"] 142 | }, 143 | { 144 | "name": "phpunit-10", 145 | "summary": "The PHP testing framework (10.x version)", 146 | "website": "https://phpunit.de/", 147 | "command": { 148 | "phive-install": { 149 | "alias": "phpunit@^10.0", 150 | "bin": "%target-dir%/phpunit-10", 151 | "sig": "4AA394086372C20A" 152 | } 153 | }, 154 | "test": "phpunit-10 --version", 155 | "tags": ["test"] 156 | }, 157 | { 158 | "name": "phpunit-9", 159 | "summary": "The PHP testing framework (9.x version)", 160 | "website": "https://phpunit.de/", 161 | "command": { 162 | "phive-install": { 163 | "alias": "phpunit@^9.0", 164 | "bin": "%target-dir%/phpunit-9", 165 | "sig": "4AA394086372C20A" 166 | } 167 | }, 168 | "test": "phpunit-9 --version", 169 | "tags": ["test"] 170 | }, 171 | { 172 | "name": "phpunit-8", 173 | "summary": "The PHP testing framework (8.x version)", 174 | "website": "https://phpunit.de/", 175 | "command": { 176 | "phive-install": { 177 | "alias": "phpunit@^8.0", 178 | "bin": "%target-dir%/phpunit-8", 179 | "sig": "4AA394086372C20A" 180 | } 181 | }, 182 | "test": "phpunit-8 --version", 183 | "tags": ["test"] 184 | }, 185 | { 186 | "name": "simple-phpunit", 187 | "summary": "Provides utilities to report legacy tests and usage of deprecated code", 188 | "website": "https://symfony.com/doc/current/components/phpunit_bridge.html", 189 | "command": { 190 | "composer-bin-plugin": { 191 | "package": "symfony/phpunit-bridge", 192 | "namespace": "symfony", 193 | "links": {"%target-dir%/simple-phpunit": "simple-phpunit"} 194 | }, 195 | "sh": { 196 | "command": "simple-phpunit install && SYMFONY_PHPUNIT_VERSION=9 simple-phpunit install" 197 | } 198 | }, 199 | "test": "simple-phpunit --version", 200 | "tags": ["test"] 201 | }, 202 | { 203 | "name": "kahlan", 204 | "summary": "Kahlan is a full-featured Unit & BDD test framework a la RSpec/JSpec", 205 | "website": "https://kahlan.github.io/docs/", 206 | "command": { 207 | "composer-bin-plugin": { 208 | "package": "kahlan/kahlan", 209 | "namespace": "kahlan", 210 | "links": {"%target-dir%/kahlan": "kahlan"} 211 | } 212 | }, 213 | "test": "kahlan --version", 214 | "tags": ["test"] 215 | } 216 | ] 217 | } 218 | -------------------------------------------------------------------------------- /resources/tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "diffFilter", 5 | "summary": "Applies QA tools to run on a single pull request", 6 | "website": "https://github.com/exussum12/coverageChecker", 7 | "command": { 8 | "composer-bin-plugin": { 9 | "package": "exussum12/coverage-checker", 10 | "namespace": "tools", 11 | "links": {"%target-dir%/diffFilter": "diffFilter"} 12 | } 13 | }, 14 | "test": "diffFilter -v", 15 | "tags": [] 16 | }, 17 | { 18 | "name": "phan", 19 | "summary": "Static Analysis Tool", 20 | "website": "https://github.com/phan/phan", 21 | "command": { 22 | "phar-download": { 23 | "phar": "https://github.com/phan/phan/releases/latest/download/phan.phar", 24 | "bin": "%target-dir%/phan" 25 | } 26 | }, 27 | "test": "phan -v", 28 | "tags": ["featured"] 29 | }, 30 | { 31 | "name": "phpbench", 32 | "summary": "PHP Benchmarking framework", 33 | "website": "https://github.com/phpbench/phpbench", 34 | "command": { 35 | "phive-install": { 36 | "alias": "phpbench", 37 | "bin": "%target-dir%/phpbench", 38 | "sig": "6FC579F5F0FCC966" 39 | } 40 | }, 41 | "test": "phpbench -V", 42 | "tags": [] 43 | }, 44 | { 45 | "name": "phpa", 46 | "summary": "Checks for weak assumptions", 47 | "website": "https://github.com/rskuipers/php-assumptions", 48 | "command": { 49 | "composer-bin-plugin": { 50 | "package": "rskuipers/php-assumptions", 51 | "namespace": "tools", 52 | "links": {"%target-dir%/phpa": "phpa"} 53 | } 54 | }, 55 | "test": "phpa --version", 56 | "tags": ["not-maintained"] 57 | }, 58 | { 59 | "name": "phpca", 60 | "summary": "Finds usage of non-built-in extensions", 61 | "website": "https://github.com/wapmorgan/PhpCodeAnalyzer", 62 | "command": { 63 | "composer-bin-plugin": { 64 | "package": "wapmorgan/php-code-analyzer", 65 | "namespace": "tools", 66 | "links": {"%target-dir%/phpca": "phpca"} 67 | } 68 | }, 69 | "test": "phpca -h" 70 | }, 71 | { 72 | "name": "phpcpd", 73 | "summary": "Copy/Paste Detector", 74 | "website": "https://github.com/sebastianbergmann/phpcpd", 75 | "command": { 76 | "phive-install": { 77 | "alias": "phpcpd", 78 | "bin": "%target-dir%/phpcpd", 79 | "sig": "4AA394086372C20A" 80 | } 81 | }, 82 | "test": "phpcpd -v", 83 | "tags": ["featured"] 84 | }, 85 | { 86 | "name": "phpmd", 87 | "summary": "A tool for finding problems in PHP code", 88 | "website": "https://phpmd.org/", 89 | "command": { 90 | "phive-install": { 91 | "alias": "phpmd", 92 | "bin": "%target-dir%/phpmd", 93 | "sig": "9093F8B32E4815AA" 94 | } 95 | }, 96 | "test": "phpmd --version" 97 | }, 98 | { 99 | "name": "phpmnd", 100 | "summary": "Helps to detect magic numbers", 101 | "website": "https://github.com/povils/phpmnd", 102 | "command": { 103 | "composer-bin-plugin": { 104 | "package": "povils/phpmnd", 105 | "namespace": "phpmnd", 106 | "links": {"%target-dir%/phpmnd": "phpmnd"} 107 | } 108 | }, 109 | "test": "phpmnd -V" 110 | } 111 | ] 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/Cli/Application.php: -------------------------------------------------------------------------------- 1 | serviceContainer = $serviceContainer; 26 | 27 | $this->setCommandLoader($this->createCommandLoader($serviceContainer)); 28 | } 29 | 30 | /** 31 | * @throws \Throwable 32 | */ 33 | public function doRun(InputInterface $input, OutputInterface $output): int 34 | { 35 | $this->serviceContainer->set(InputInterface::class, $input); 36 | $this->serviceContainer->set(OutputInterface::class, $output); 37 | 38 | return parent::doRun($input, $output); 39 | } 40 | 41 | protected function getDefaultInputDefinition(): InputDefinition 42 | { 43 | $definition = parent::getDefaultInputDefinition(); 44 | $definition->addOption(new InputOption('tools', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to the list of tools. Can also be set with TOOLBOX_JSON environment variable.', $this->toolsJsonDefault())); 45 | 46 | return $definition; 47 | } 48 | 49 | private function toolsJsonDefault(): array 50 | { 51 | return \getenv('TOOLBOX_JSON') 52 | ? \array_map('trim', \explode(',', \getenv('TOOLBOX_JSON'))) 53 | : [ 54 | __DIR__.'/../../resources/pre-installation.json', 55 | __DIR__.'/../../resources/architecture.json', 56 | __DIR__.'/../../resources/checkstyle.json', 57 | __DIR__.'/../../resources/compatibility.json', 58 | __DIR__.'/../../resources/composer.json', 59 | __DIR__.'/../../resources/deprecation.json', 60 | __DIR__.'/../../resources/documentation.json', 61 | __DIR__.'/../../resources/linting.json', 62 | __DIR__.'/../../resources/metrics.json', 63 | __DIR__.'/../../resources/phpcs.json', 64 | __DIR__.'/../../resources/phpstan.json', 65 | __DIR__.'/../../resources/psalm.json', 66 | __DIR__.'/../../resources/refactoring.json', 67 | __DIR__.'/../../resources/security.json', 68 | __DIR__.'/../../resources/test.json', 69 | __DIR__.'/../../resources/tools.json', 70 | ]; 71 | } 72 | 73 | private function createCommandLoader(ContainerInterface $container): CommandLoaderInterface 74 | { 75 | return new ContainerCommandLoader( 76 | $container, 77 | [ 78 | InstallCommand::NAME => InstallCommand::class, 79 | ListCommand::NAME => ListCommand::class, 80 | TestCommand::NAME => TestCommand::class, 81 | ] 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Cli/Command/DefaultTag.php: -------------------------------------------------------------------------------- 1 | useCase = $useCase; 28 | $this->runner = $runner; 29 | } 30 | 31 | protected function configure(): void 32 | { 33 | $this->setDescription('Installs tools'); 34 | $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the command without executing it'); 35 | $this->addOption('target-dir', null, InputOption::VALUE_REQUIRED, 'The target installation directory', $this->defaultTargetDir()); 36 | $this->addOption('exclude-tag', 'e', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Tool tags to exclude', $this->defaultExcludeTag()); 37 | $this->addOption('tag', 't', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Tool tags to filter by', $this->defaultTag()); 38 | } 39 | 40 | protected function execute(InputInterface $input, OutputInterface $output): int 41 | { 42 | return $this->runner->run(\call_user_func($this->useCase, new Filter($input->getOption('exclude-tag'), $input->getOption('tag')))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Cli/Command/ListCommand.php: -------------------------------------------------------------------------------- 1 | listTools = $listTools; 28 | } 29 | 30 | protected function configure(): void 31 | { 32 | $this->setDescription('Lists available tools'); 33 | $this->addOption('exclude-tag', 'e', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Tool tags to exclude', $this->defaultExcludeTag()); 34 | $this->addOption('tag', 't', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Tool tags to filter by', $this->defaultTag()); 35 | } 36 | 37 | protected function execute(InputInterface $input, OutputInterface $output): int 38 | { 39 | $tools = \call_user_func($this->listTools, new Filter($input->getOption('exclude-tag'), $input->getOption('tag'))); 40 | 41 | $style = $this->createStyle($input, $output); 42 | $style->title('Available tools'); 43 | $style->table( 44 | ['Name', 'Summary'], 45 | $tools->map(function (Tool $tool) { 46 | return [\sprintf('%s', $tool->name()), $tool->summary().PHP_EOL.$tool->website().PHP_EOL]; 47 | })->toArray() 48 | ); 49 | 50 | return 0; 51 | } 52 | 53 | private function createStyle(InputInterface $input, OutputInterface $output): StyleInterface 54 | { 55 | return new SymfonyStyle($input, $output); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Cli/Command/TestCommand.php: -------------------------------------------------------------------------------- 1 | useCase = $useCase; 28 | $this->runner = $runner; 29 | } 30 | 31 | protected function configure(): void 32 | { 33 | $this->setDescription('Runs basic tests to verify tools are installed'); 34 | $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Output the command without executing it'); 35 | $this->addOption('target-dir', null, InputOption::VALUE_REQUIRED, 'The target installation directory', $this->defaultTargetDir()); 36 | $this->addOption('exclude-tag', 'e', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Tool tags to exclude', $this->defaultExcludeTag()); 37 | $this->addOption('tag', 't', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'Tool tags to filter by', $this->defaultTag()); 38 | } 39 | 40 | protected function execute(InputInterface $input, OutputInterface $output): int 41 | { 42 | return $this->runner->run(\call_user_func($this->useCase, new Filter($input->getOption('exclude-tag'), $input->getOption('tag')))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Cli/Runner/DryRunner.php: -------------------------------------------------------------------------------- 1 | output = $output; 16 | } 17 | 18 | public function run(Command $command): int 19 | { 20 | $this->output->writeln((string) $command); 21 | 22 | return 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Cli/ServiceContainer.php: -------------------------------------------------------------------------------- 1 | 'createInstallCommand', 27 | ListCommand::class => 'createListCommand', 28 | TestCommand::class => 'createTestCommand', 29 | Runner::class => 'createRunner', 30 | InstallTools::class => 'createInstallToolsUseCase', 31 | ListTools::class => 'createListToolsUseCase', 32 | TestTools::class => 'createTestToolsUseCase', 33 | Tools::class => 'createTools', 34 | ]; 35 | 36 | private array $runtimeServices = [ 37 | InputInterface::class => null, 38 | OutputInterface::class => null, 39 | ]; 40 | 41 | public function set(string $id, /*object */$service): void 42 | { 43 | if (!\array_key_exists($id, $this->runtimeServices)) { 44 | throw new class(\sprintf('The "%s" runtime service is not expected.', $id)) extends RuntimeException implements ContainerExceptionInterface { 45 | }; 46 | } 47 | 48 | $this->runtimeServices[$id] = $service; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function get(string $id) 55 | { 56 | if (isset($this->runtimeServices[$id])) { 57 | return $this->runtimeServices[$id]; 58 | } 59 | 60 | if (isset($this->services[$id])) { 61 | return \call_user_func([$this, $this->services[$id]]); 62 | } 63 | 64 | throw new class(\sprintf('The "%s" service is not registered in the service container.', $id)) extends RuntimeException implements NotFoundExceptionInterface { 65 | }; 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function has(string $id): bool 72 | { 73 | return isset($this->services[$id]) || isset($this->runtimeServices[$id]); 74 | } 75 | 76 | /** 77 | * @throws ContainerExceptionInterface 78 | * @throws NotFoundExceptionInterface 79 | */ 80 | private function createInstallCommand(): InstallCommand 81 | { 82 | return new InstallCommand($this->get(InstallTools::class), $this->get(Runner::class)); 83 | } 84 | 85 | /** 86 | * @throws ContainerExceptionInterface 87 | * @throws NotFoundExceptionInterface 88 | */ 89 | private function createListCommand(): ListCommand 90 | { 91 | return new ListCommand($this->get(ListTools::class)); 92 | } 93 | 94 | /** 95 | * @throws ContainerExceptionInterface 96 | * @throws NotFoundExceptionInterface 97 | */ 98 | private function createTestCommand(): TestCommand 99 | { 100 | return new TestCommand($this->get(TestTools::class), $this->get(Runner::class)); 101 | } 102 | 103 | private function createRunner(): Runner 104 | { 105 | return new LazyRunner(new RunnerFactory($this)); 106 | } 107 | 108 | /** 109 | * @throws ContainerExceptionInterface 110 | * @throws NotFoundExceptionInterface 111 | */ 112 | private function createInstallToolsUseCase(): InstallTools 113 | { 114 | return new InstallTools($this->get(Tools::class)); 115 | } 116 | 117 | /** 118 | * @throws ContainerExceptionInterface 119 | * @throws NotFoundExceptionInterface 120 | */ 121 | private function createListToolsUseCase(): ListTools 122 | { 123 | return new ListTools($this->get(Tools::class)); 124 | } 125 | 126 | /** 127 | * @throws ContainerExceptionInterface 128 | * @throws NotFoundExceptionInterface 129 | */ 130 | private function createTestToolsUseCase(): TestTools 131 | { 132 | return new TestTools($this->get(Tools::class)); 133 | } 134 | 135 | private function createTools(): Tools 136 | { 137 | return new JsonTools(function (): array { 138 | return $this->get(InputInterface::class)->getOption('tools'); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Cli/ServiceContainer/LazyRunner.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 19 | } 20 | 21 | /** 22 | * @throws ContainerExceptionInterface 23 | * @throws NotFoundExceptionInterface 24 | */ 25 | public function run(Command $command): int 26 | { 27 | return $this->runner()->run($command); 28 | } 29 | 30 | /** 31 | * @throws ContainerExceptionInterface 32 | * @throws NotFoundExceptionInterface 33 | */ 34 | private function runner(): Runner 35 | { 36 | if (null === $this->runner) { 37 | $this->runner = $this->factory->createRunner(); 38 | } 39 | 40 | return $this->runner; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Cli/ServiceContainer/RunnerFactory.php: -------------------------------------------------------------------------------- 1 | container = $container; 22 | } 23 | 24 | /** 25 | * @throws ContainerExceptionInterface 26 | * @throws NotFoundExceptionInterface 27 | */ 28 | public function createRunner(): Runner 29 | { 30 | $runner = $this->createRealRunner(); 31 | 32 | if ($parameters = $this->parameters()) { 33 | return new ParametrisedRunner($runner, $parameters); 34 | } 35 | 36 | return $runner; 37 | } 38 | 39 | /** 40 | * @throws ContainerExceptionInterface 41 | * @throws NotFoundExceptionInterface 42 | */ 43 | private function createRealRunner(): DryRunner|PassthruRunner 44 | { 45 | if ($this->container->get(InputInterface::class)->getOption('dry-run')) { 46 | return new DryRunner($this->container->get(OutputInterface::class)); 47 | } 48 | 49 | return new PassthruRunner(); 50 | } 51 | 52 | /** 53 | * @throws ContainerExceptionInterface 54 | * @throws NotFoundExceptionInterface 55 | */ 56 | private function parameters(): array 57 | { 58 | if ($targetDir = $this->targetDir()) { 59 | return ['%target-dir%' => $targetDir]; 60 | } 61 | 62 | return []; 63 | } 64 | 65 | /** 66 | * @throws ContainerExceptionInterface 67 | * @throws NotFoundExceptionInterface 68 | */ 69 | private function targetDir(): ?string 70 | { 71 | if (!$this->container->get(InputInterface::class)->hasOption('target-dir')) { 72 | return null; 73 | } 74 | 75 | $targetDir = $this->container->get(InputInterface::class)->getOption('target-dir'); 76 | 77 | if (!\is_dir($targetDir)) { 78 | throw new class(\sprintf('The target dir does not exist: "%s".', $targetDir)) extends \RuntimeException implements ContainerExceptionInterface { 79 | }; 80 | } 81 | 82 | return \realpath($targetDir); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Json/Factory/Assert.php: -------------------------------------------------------------------------------- 1 | $command) { 32 | $commands = $commands->merge(self::createCommands($type, $command)); 33 | } 34 | 35 | if (0 === $commands->count()) { 36 | throw new \RuntimeException(\sprintf('No valid command defined for the tool: %s', \json_encode($tool))); 37 | } 38 | 39 | return 1 === $commands->count() ? $commands->toArray()[0] : new MultiStepCommand($commands); 40 | } 41 | 42 | private static function createCommands($type, $command): Collection 43 | { 44 | $factories = [ 45 | 'phar-download' => \sprintf('%s::import', PharDownloadCommandFactory::class), 46 | 'file-download' => \sprintf('%s::import', FileDownloadCommandFactory::class), 47 | 'box-build' => \sprintf('%s::import', BoxBuildCommandFactory::class), 48 | 'composer-install' => \sprintf('%s::import', ComposerInstallCommandFactory::class), 49 | 'phive-install' => \sprintf('%s::import', PhiveInstallCommandFactory::class), 50 | 'composer-global-install' => \sprintf('%s::import', ComposerGlobalInstallCommandFactory::class), 51 | 'composer-bin-plugin' => \sprintf('%s::import', ComposerBinPluginCommandFactory::class), 52 | 'sh' => \sprintf('%s::import', ShCommandFactory::class), 53 | ]; 54 | 55 | if (!isset($factories[$type])) { 56 | throw new \RuntimeException(\sprintf('Unrecognised command: "%s". Supported commands are: "%s".', $type, \implode(', ', \array_keys($factories)))); 57 | } 58 | 59 | $command = !\is_numeric(\key($command)) ? [$command] : $command; 60 | 61 | return Collection::create(\array_map(function ($c) use ($type, $factories) { 62 | return $factories[$type]($c); 63 | }, $command)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Json/JsonTools.php: -------------------------------------------------------------------------------- 1 | resourceLocator = $resourceLocator; 22 | } 23 | 24 | /** 25 | * @param Filter $filter 26 | * @return Collection 27 | */ 28 | public function all(Filter $filter): Collection 29 | { 30 | return $this->loadTools()->filter($filter); 31 | } 32 | 33 | private function loadTools(): Collection 34 | { 35 | return \array_reduce($this->resources(), function (Collection $tools, string $resource): Collection { 36 | return $tools->merge(Collection::create( 37 | \array_map(\sprintf('%s::import', ToolFactory::class), $this->loadJson($resource)) 38 | )); 39 | }, Collection::create([])); 40 | } 41 | 42 | private function loadJson(string $resource): array 43 | { 44 | $json = \json_decode(\file_get_contents($resource), true); 45 | 46 | if (!$json) { 47 | throw new RuntimeException(\sprintf('Failed to parse json: "%s"', $resource)); 48 | } 49 | 50 | if (!isset($json['tools']) || !\is_array($json['tools'])) { 51 | throw new RuntimeException(\sprintf('Failed to find any tools in: "%s".', $resource)); 52 | } 53 | 54 | return $json['tools']; 55 | } 56 | 57 | private function resources(): array 58 | { 59 | $resources = \call_user_func($this->resourceLocator); 60 | 61 | return \array_map(function (string $resource) { 62 | if (!\is_readable($resource)) { 63 | throw new InvalidArgumentException(\sprintf('Could not read the file: "%s".', $resource)); 64 | } 65 | 66 | return $resource; 67 | }, $resources); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Runner/ParametrisedRunner.php: -------------------------------------------------------------------------------- 1 | decoratedRunner = $decoratedRunner; 15 | $this->parameters = $parameters; 16 | } 17 | 18 | public function run(Command $command): int 19 | { 20 | return $this->decoratedRunner->run(new class($command, $this->parameters) implements Command { 21 | private Command $command; 22 | private array $parameters; 23 | 24 | public function __construct(Command $command, array $parameters) 25 | { 26 | $this->command = $command; 27 | $this->parameters = $parameters; 28 | } 29 | 30 | public function __toString(): string 31 | { 32 | return \strtr((string) $this->command, $this->parameters); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Runner/PassthruRunner.php: -------------------------------------------------------------------------------- 1 | elements = $elements; 16 | } 17 | 18 | public static function create(array $elements): Collection 19 | { 20 | return new self($elements); 21 | } 22 | 23 | public function getIterator(): Traversable 24 | { 25 | yield from $this->elements; 26 | } 27 | 28 | public function merge(Collection $other): Collection 29 | { 30 | return self::create(\array_merge($this->elements, $other->elements)); 31 | } 32 | 33 | public function filter(callable $f): Collection 34 | { 35 | return self::create(\array_values(\array_filter($this->elements, $f))); 36 | } 37 | 38 | public function map(callable $f): Collection 39 | { 40 | return self::create(\array_map($f, $this->elements)); 41 | } 42 | 43 | public function reduce($initial, callable $param) 44 | { 45 | return \array_reduce($this->elements, $param, $initial); 46 | } 47 | 48 | public function sort(callable $f): Collection 49 | { 50 | $elements = $this->elements; 51 | \usort($elements, $f); 52 | 53 | return self::create($elements); 54 | } 55 | 56 | public function toArray(): array 57 | { 58 | return $this->elements; 59 | } 60 | 61 | public function count(): int 62 | { 63 | return \count($this->elements); 64 | } 65 | 66 | public function empty(): bool 67 | { 68 | return empty($this->elements); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Tool/Command.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 18 | $this->phar = $phar; 19 | $this->bin = $bin; 20 | $this->workDir = $workDir; 21 | $this->version = $version; 22 | } 23 | 24 | public function __toString(): string 25 | { 26 | return \sprintf( 27 | 'git clone %s %s&& cd %s && git checkout %s && composer install --no-dev --prefer-dist -n && box compile && mv %s %s && chmod +x %s && cd && rm -rf %s', 28 | $this->repository, 29 | $this->targetDir(), 30 | $this->targetDir(), 31 | $this->version ?? '$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null)', 32 | $this->phar, 33 | $this->bin, 34 | $this->bin, 35 | $this->targetDir() 36 | ); 37 | } 38 | 39 | private function targetDir(): string 40 | { 41 | $targetDir = \preg_replace('#^.*/(.*?)(.git)?$#', '$1', $this->repository); 42 | 43 | return \sprintf('%s/%s', $this->workDir, $targetDir !== $this->repository ? $targetDir : 'tmp'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Tool/Command/ComposerBinPluginCommand.php: -------------------------------------------------------------------------------- 1 | package = $package; 19 | $this->namespace = $namespace; 20 | $this->links = $links; 21 | } 22 | 23 | public function __toString(): string 24 | { 25 | return \sprintf('composer global bin %s require --prefer-dist --update-no-dev -n %s%s', $this->namespace, $this->package, $this->linkCommand()); 26 | } 27 | 28 | public function package(): string 29 | { 30 | return $this->package; 31 | } 32 | 33 | public function namespace(): string 34 | { 35 | return $this->namespace; 36 | } 37 | 38 | public function links(): Collection 39 | { 40 | return $this->links; 41 | } 42 | 43 | private function linkCommand(): string 44 | { 45 | return $this->links->reduce('', function (string $command, ComposerBinPluginLinkCommand $link) { 46 | return $command.' && '.$link; 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Tool/Command/ComposerBinPluginLinkCommand.php: -------------------------------------------------------------------------------- 1 | source = $source; 16 | $this->target = $target; 17 | $this->namespace = $namespace; 18 | } 19 | 20 | public function __toString(): string 21 | { 22 | return \sprintf( 23 | 'ln -sf ${COMPOSER_HOME:-"~/.composer"}/vendor-bin/%s/vendor/bin/%s %s', 24 | $this->namespace, 25 | $this->source, 26 | $this->target 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Tool/Command/ComposerGlobalInstallCommand.php: -------------------------------------------------------------------------------- 1 | package = $package; 14 | } 15 | 16 | public function __toString(): string 17 | { 18 | return \sprintf('composer global require --prefer-dist --update-no-dev -n %s', $this->package); 19 | } 20 | 21 | public function package(): string 22 | { 23 | return $this->package; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Tool/Command/ComposerGlobalMultiInstallCommand.php: -------------------------------------------------------------------------------- 1 | empty()) { 16 | throw new InvalidArgumentException('Collection of composer global install commands cannot be empty.'); 17 | } 18 | 19 | $this->commands = $commands->filter(function (ComposerGlobalInstallCommand $c) { 20 | return $c; 21 | }); 22 | } 23 | 24 | public function __toString(): string 25 | { 26 | $packages = \implode(' ', \array_map(function (ComposerGlobalInstallCommand $command) { 27 | return $command->package(); 28 | }, $this->commands->toArray())); 29 | 30 | return \sprintf('composer global require --prefer-dist --update-no-dev -n %s', $packages); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Tool/Command/ComposerInstallCommand.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 16 | $this->targetDir = $targetDir; 17 | $this->version = $version; 18 | } 19 | 20 | public function __toString(): string 21 | { 22 | return \sprintf( 23 | 'git clone %s %s && cd %s && git checkout %s && composer install --no-dev --prefer-dist -n', 24 | $this->repository, 25 | $this->targetDir, 26 | $this->targetDir, 27 | $this->version ?? '$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null)' 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Tool/Command/FileDownloadCommand.php: -------------------------------------------------------------------------------- 1 | url = $url; 15 | $this->file = $file; 16 | } 17 | 18 | public function __toString(): string 19 | { 20 | return \sprintf('curl -Ls -w %%{filename_effective}\'\n\' %s -o %s', $this->url, $this->file); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tool/Command/MultiStepCommand.php: -------------------------------------------------------------------------------- 1 | empty()) { 17 | throw new InvalidArgumentException('Collection of commands cannot be empty.'); 18 | } 19 | 20 | $this->commands = $commands->filter(function (Command $c) { 21 | return $c; 22 | }); 23 | $this->glue = $glue; 24 | } 25 | 26 | public function __toString(): string 27 | { 28 | return \implode($this->glue, $this->commands->toArray()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Tool/Command/OptimisedComposerBinPluginCommand.php: -------------------------------------------------------------------------------- 1 | empty()) { 16 | throw new InvalidArgumentException('Collection of composer bin plugin commands cannot be empty.'); 17 | } 18 | 19 | $this->commands = $commands->filter(function (ComposerBinPluginCommand $command) { 20 | return $command; 21 | }); 22 | } 23 | 24 | public function __toString(): string 25 | { 26 | return \implode(' && ', \array_merge($this->commandsToRun($this->packagesGroupedByNamespace()), $this->linksToCreate())); 27 | } 28 | 29 | private function packagesGroupedByNamespace(): array 30 | { 31 | return $this->commands->reduce([], function (array $packages, ComposerBinPluginCommand $command) { 32 | $packages[$command->namespace()][] = $command->package(); 33 | 34 | return $packages; 35 | }); 36 | } 37 | 38 | private function commandToRun(string $namespace, array $packages): string 39 | { 40 | return \sprintf('composer global bin %s require --prefer-dist --update-no-dev -n %s', $namespace, \implode(' ', $packages)); 41 | } 42 | 43 | private function commandsToRun(array $packagesGrouped): array 44 | { 45 | return \array_map([$this, 'commandToRun'], \array_keys($packagesGrouped), $packagesGrouped); 46 | } 47 | 48 | private function linksToCreate(): array 49 | { 50 | return $this->commands 51 | ->filter(function (ComposerBinPluginCommand $command) { 52 | return !$command->links()->empty(); 53 | }) 54 | ->map(function (ComposerBinPluginCommand $command) { 55 | return $command->links()->reduce('', function (string $command, ComposerBinPluginLinkCommand $link) { 56 | return !empty($command) ? $command.' && '.$link : $link; 57 | }); 58 | }) 59 | ->toArray(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Tool/Command/PharDownloadCommand.php: -------------------------------------------------------------------------------- 1 | phar = $phar; 15 | $this->bin = $bin; 16 | } 17 | 18 | public function __toString(): string 19 | { 20 | return \sprintf('curl -Ls -w %%{filename_effective}\'\n\' %s -o %s && chmod +x %s', $this->phar, $this->bin, $this->bin); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tool/Command/PhiveInstallCommand.php: -------------------------------------------------------------------------------- 1 | alias = $alias; 16 | $this->bin = $bin; 17 | $this->sig = $sig; 18 | } 19 | 20 | public function __toString(): string 21 | { 22 | $home = \sprintf('%s/.phive', \dirname($this->bin)); 23 | $tmp = \sprintf('%s/tmp/%s', $home, \md5($this->alias)); 24 | 25 | return \sprintf( 26 | 'phive --no-progress --home %s install %s %s -t %s && mv %s/* %s', 27 | $home, 28 | $this->sig ? '--trust-gpg-keys '.$this->sig : '--force-accept-unsigned', 29 | $this->alias, 30 | $tmp, 31 | $tmp, 32 | $this->bin 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Tool/Command/ShCommand.php: -------------------------------------------------------------------------------- 1 | command = $command; 14 | } 15 | 16 | public function __toString(): string 17 | { 18 | return $this->command; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Tool/Command/TestCommand.php: -------------------------------------------------------------------------------- 1 | command = $command; 15 | $this->name = $name; 16 | } 17 | 18 | public function __toString(): string 19 | { 20 | return \sprintf('((%s > /dev/null && echo -e "\e[0;32m✔\e[0m︎%s") || (echo -e "\e[1;31m✘\e[0m%s" && false))', $this->command, $this->name, $this->name); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tool/Filter.php: -------------------------------------------------------------------------------- 1 | excludedTags = $excludedTags; 24 | $this->tags = $tags; 25 | } 26 | 27 | public function __invoke(Tool $tool): bool 28 | { 29 | return $this->excludedTags === \array_diff($this->excludedTags, $tool->tags()) 30 | && (empty($this->tags) || \array_intersect($this->tags, $tool->tags())); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Tool/Tool.php: -------------------------------------------------------------------------------- 1 | name = $name; 17 | $this->summary = $summary; 18 | $this->website = $website; 19 | $this->tags = \array_map(function (string $tag) { 20 | return $tag; 21 | }, $tags); 22 | $this->command = $command; 23 | $this->testCommand = $testCommand; 24 | } 25 | 26 | public function name(): string 27 | { 28 | return $this->name; 29 | } 30 | 31 | public function summary(): string 32 | { 33 | return $this->summary; 34 | } 35 | 36 | public function website(): string 37 | { 38 | return $this->website; 39 | } 40 | 41 | public function command(): Command 42 | { 43 | return $this->command; 44 | } 45 | 46 | public function testCommand(): Command 47 | { 48 | return $this->testCommand; 49 | } 50 | 51 | /** 52 | * @return array|string[] 53 | */ 54 | public function tags(): array 55 | { 56 | return $this->tags; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Tool/Tools.php: -------------------------------------------------------------------------------- 1 | tools = $tools; 32 | } 33 | 34 | public function __invoke(Filter $filter): Command 35 | { 36 | $tools = $this->tools->all($filter); 37 | $installationCommands = $this->installationCommands($tools); 38 | $commandFilter = $this->commandFilter($this->toolCommands($tools)); 39 | 40 | return new MultiStepCommand( 41 | $installationCommands 42 | ->merge($commandFilter(ShCommand::class)) 43 | ->merge($commandFilter(FileDownloadCommand::class)) 44 | ->merge($commandFilter(PharDownloadCommand::class)) 45 | ->merge($commandFilter(PhiveInstallCommand::class)) 46 | ->merge($commandFilter(MultiStepCommand::class)) 47 | ->merge($this->groupComposerGlobalInstallCommands($commandFilter(ComposerGlobalInstallCommand::class))) 48 | ->merge($this->groupComposerBinPluginCommands($commandFilter(ComposerBinPluginCommand::class))) 49 | ->merge($commandFilter(ComposerInstallCommand::class)) 50 | ->merge($commandFilter(BoxBuildCommand::class)) 51 | ); 52 | } 53 | 54 | private function commandFilter(Collection $commands): Closure 55 | { 56 | return function ($type) use ($commands) { 57 | return $commands->filter(function (Command $command) use ($type) { 58 | return $command instanceof $type; 59 | }); 60 | }; 61 | } 62 | 63 | private function installationCommands(Collection $tools): Collection 64 | { 65 | return $tools->filter(function (Tool $tool) { 66 | return \in_array(self::PRE_INSTALLATION_TAG, $tool->tags()); 67 | })->map(function (Tool $tool) { 68 | return $tool->command(); 69 | }); 70 | } 71 | 72 | private function toolCommands(Collection $tools): Collection 73 | { 74 | return $tools->filter(function (Tool $tool) { 75 | return !\in_array(self::PRE_INSTALLATION_TAG, $tool->tags()); 76 | })->map(function (Tool $tool) { 77 | return $tool->command(); 78 | }); 79 | } 80 | 81 | private function groupComposerGlobalInstallCommands(Collection $commands): Collection 82 | { 83 | $commands = $commands->empty() ? [] : [new ComposerGlobalMultiInstallCommand($commands)]; 84 | 85 | return Collection::create($commands); 86 | } 87 | 88 | private function groupComposerBinPluginCommands(Collection $commands): Collection 89 | { 90 | $commands = $commands->empty() ? [] : [new OptimisedComposerBinPluginCommand($commands)]; 91 | 92 | return Collection::create($commands); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/UseCase/ListTools.php: -------------------------------------------------------------------------------- 1 | tools = $tools; 16 | } 17 | 18 | public function __invoke(Filter $filter): Collection 19 | { 20 | return $this->tools->all($filter); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/UseCase/TestTools.php: -------------------------------------------------------------------------------- 1 | tools = $tools; 18 | } 19 | 20 | public function __invoke(Filter $filter): Command 21 | { 22 | return new MultiStepCommand( 23 | $this->tools->all($filter)->map(function (Tool $tool) { 24 | return $tool->testCommand(); 25 | }) 26 | ); 27 | } 28 | } 29 | --------------------------------------------------------------------------------