.github/workflows/phpstan.yml 0000644 00000001631 14264017271 0012346 0 ustar 00 name: "Static analysis"
on:
push:
branches:
- "main"
- "master"
pull_request: null
jobs:
static-analysis:
runs-on: "ubuntu-latest"
name: "PHPStan on PHP ${{ matrix.php }}"
strategy:
fail-fast: false
matrix:
php:
- "8.1"
steps:
- name: "Check out repository code"
uses: "actions/checkout@v2"
- name: "Setup PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php }}"
tools: "composer"
- name: "Install Composer dependencies"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"
- name: "Perform static analysis"
run: "make phpstan"
.github/workflows/tests.yaml 0000644 00000004570 14264017271 0012201 0 ustar 00 name: "Unit Tests"
on:
push:
branches:
- "main"
- "master"
pull_request: null
jobs:
unit-tests:
runs-on: "ubuntu-latest"
name: "Unit Tests on PHP ${{ matrix.php }} and ${{ matrix.tools }}"
strategy:
fail-fast: false
matrix:
php:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
tools: [ "composer" ]
include:
- php: "7.2"
tools: "composer:v2.0"
steps:
- name: "Check out repository code"
uses: "actions/checkout@v2"
- name: "Setup PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php }}"
tools: "${{ matrix.tools }}"
- name: "Install Composer dependencies"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"
- name: "Validate composer.json"
run: "composer validate --strict --no-check-lock"
- name: "Run tests"
run: "vendor/bin/phpunit --group default"
e2e-tests:
runs-on: "ubuntu-latest"
name: "E2E Tests on PHP ${{ matrix.php }}"
strategy:
fail-fast: false
matrix:
php:
- "8.1"
steps:
- name: "Check out repository code"
uses: "actions/checkout@v2"
- name: "Setup PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php }}"
tools: "composer"
- name: "Correct bin plugin version for e2e scenarios (PR-only)"
if: github.event_name == 'pull_request'
run: find e2e -maxdepth 1 -mindepth 1 -type d -exec bash -c "cd {} && composer require --dev bamarni/composer-bin-plugin:dev-${GITHUB_SHA} --no-update" \;
- name: "Install Composer dependencies"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"
- name: "Run tests"
run: "vendor/bin/phpunit --group e2e"
.makefile/touch.sh 0000755 00000002145 14264017271 0010066 0 ustar 00 #!/usr/bin/env bash
#
# Takes a given string, e.g. 'bin/console' or 'docker-compose exec php bin/console'
# and split it by words. For each words, if the target is a file, it is touched.
#
# This allows to implement a similar rule to:
#
# ```Makefile
# bin/php-cs-fixer: vendor
# touch $@
# ```
#
# Indeed when the rule `bin/php-cs-fixer` is replaced with a docker-compose
# equivalent, it will not play out as nicely.
#
# Arguments:
# $1 - {string} Command potentially containing a file
#
set -Eeuo pipefail;
readonly ERROR_COLOR="\e[41m";
readonly NO_COLOR="\e[0m";
if [ $# -ne 1 ]; then
printf "${ERROR_COLOR}Illegal number of parameters.${NO_COLOR}\n";
exit 1;
fi
readonly FILES="$1";
#######################################
# Touch the given file path if the target is a file and do not create the file
# if does not exist.
#
# Globals:
# None
#
# Arguments:
# $1 - {string} File path
#
# Returns:
# None
#######################################
touch_file() {
local file="$1";
if [ -e ${file} ]; then
touch -c ${file};
fi
}
for file in ${FILES}
do
touch_file ${file};
done
.phive/phars.xml 0000644 00000000654 14264017271 0007605 0 ustar 00
.php-cs-fixer.php 0000644 00000000602 14264017271 0007642 0 ustar 00 files()
->in(['src', 'tests']);
$config = new PhpCsFixer\Config();
return $config
->setRules([
'@PSR12' => true,
'strict_param' => true,
'array_syntax' => ['syntax' => 'short'],
'no_unused_imports' => true,
])
->setRiskyAllowed(true)
->setFinder($finder);
LICENSE 0000644 00000002040 14264017271 0005551 0 ustar 00 Copyright (c) 2016 Bilal Amarni
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Makefile 0000644 00000006206 14264017271 0006214 0 ustar 00 # See https://tech.davis-hansson.com/p/make/
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
# General variables
TOUCH = bash .makefile/touch.sh
# PHP variables
COMPOSER=composer
COVERAGE_DIR = dist/coverage
INFECTION_BIN = tools/infection
INFECTION = php -d zend.enable_gc=0 $(INFECTION_BIN) --skip-initial-tests --coverage=$(COVERAGE_DIR) --only-covered --threads=4 --min-msi=100 --min-covered-msi=100 --ansi
PHPUNIT_BIN = vendor/bin/phpunit
PHPUNIT = php -d zend.enable_gc=0 $(PHPUNIT_BIN)
PHPUNIT_COVERAGE = XDEBUG_MODE=coverage $(PHPUNIT) --group default --coverage-xml=$(COVERAGE_DIR)/coverage-xml --log-junit=$(COVERAGE_DIR)/phpunit.junit.xml
PHPSTAN_BIN = vendor/bin/phpstan
PHPSTAN = $(PHPSTAN_BIN) analyse --level=5 src tests
PHP_CS_FIXER_BIN = tools/php-cs-fixer
PHP_CS_FIXER = $(PHP_CS_FIXER_BIN) fix --ansi --verbose --config=.php-cs-fixer.php
COMPOSER_NORMALIZE_BIN=tools/composer-normalize
COMPOSER_NORMALIZE = ./$(COMPOSER_NORMALIZE_BIN)
.DEFAULT_GOAL := default
#
# Command
#---------------------------------------------------------------------------
.PHONY: help
help: ## Shows the help
help:
@printf "\033[33mUsage:\033[0m\n make TARGET\n\n\033[32m#\n# Commands\n#---------------------------------------------------------------------------\033[0m\n"
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' | awk 'BEGIN {FS = ":"}; {printf "\033[33m%s:\033[0m%s\n", $$1, $$2}'
.PHONY: default
default: ## Runs the default task: CS fix and all the tests
default: cs test
.PHONY: cs
cs: ## Runs PHP-CS-Fixer
cs: $(PHP_CS_FIXER_BIN) $(COMPOSER_NORMALIZE_BIN)
$(PHP_CS_FIXER)
$(COMPOSER_NORMALIZE)
.PHONY: phpstan
phpstan: ## Runs PHPStan
phpstan:
$(PHPSTAN)
.PHONY: infection
infection: ## Runs infection
infection: $(INFECTION_BIN) $(COVERAGE_DIR) vendor
if [ -d $(COVERAGE_DIR)/coverage-xml ]; then $(INFECTION); fi
.PHONY: test
test: ## Runs all the tests
test: validate-package phpstan $(COVERAGE_DIR) e2e #infection include infection later
.PHONY: validate-package
validate-package: ## Validates the Composer package
validate-package: vendor
$(COMPOSER) validate --strict
.PHONY: coverage
coverage: ## Runs PHPUnit with code coverage
coverage: $(PHPUNIT_BIN) vendor
$(PHPUNIT_COVERAGE)
.PHONY: unit-test
unit-test: ## Runs PHPUnit (default group)
unit-test: $(PHPUNIT_BIN) vendor
$(PHPUNIT) --group default
.PHONY: e2e
e2e: ## Runs PHPUnit end-to-end tests
e2e: $(PHPUNIT_BIN) vendor
$(PHPUNIT) --group e2e
#
# Rules
#---------------------------------------------------------------------------
# Vendor does not depend on the composer.lock since the later is not tracked
# or committed.
vendor: composer.json
$(COMPOSER) update
$(TOUCH) "$@"
$(PHPUNIT_BIN): vendor
$(TOUCH) "$@"
$(INFECTION_BIN): ./.phive/phars.xml
phive install infection
$(TOUCH) "$@"
$(COMPOSER_NORMALIZE_BIN): ./.phive/phars.xml
phive install composer-normalize
$(TOUCH) "$@"
$(COVERAGE_DIR): $(PHPUNIT_BIN) src tests phpunit.xml.dist
$(PHPUNIT_COVERAGE)
$(TOUCH) "$@"
$(PHP_CS_FIXER_BIN): vendor
phive install php-cs-fixer
$(TOUCH) "$@"
$(PHPSTAN_BIN): vendor
$(TOUCH) "$@"
README.md 0000644 00000021656 14264017271 0006041 0 ustar 00 # Composer bin plugin — Isolate your bin dependencies
[![Package version](http://img.shields.io/packagist/v/bamarni/composer-bin-plugin.svg?style=flat-square)](https://packagist.org/packages/bamarni/composer-bin-plugin)
[![Build Status](https://img.shields.io/travis/bamarni/composer-bin-plugin.svg?branch=master&style=flat-square)](https://travis-ci.org/bamarni/composer-bin-plugin?branch=master)
[![License](https://img.shields.io/badge/license-MIT-red.svg?style=flat-square)](LICENSE)
## Table of Contents
1. [Why?](#why)
1. [How does this plugin work?](#how-does-this-plugin-work)
1. [Installation](#installation)
1. [Usage](#usage)
1. [Example](#example)
1. [The `all` bin namespace](#the-all-bin-namespace)
1. [What happens when symlink conflicts?](#what-happens-when-symlink-conflicts)
1. [Tips](#tips)
1. [Auto-installation](#auto-installation)
1. [Disable links](#disable-links)
1. [Change directory](#change-directory)
1. [Forward mode](#forward-mode)
1. [Reduce clutter](#reduce-clutter)
1. [Related plugins](#related-plugins)
1. [Backward Compatibility Promise](#backward-compatibility-promise)
1. [Contributing](#contributing)
## Why?
In PHP, with Composer, your dependencies are flattened, which might result in conflicts. Most of the time those
conflicts are legitimate and should be properly resolved. However you may have dev tools that you want to manage
via Composer for convenience, but should not influence your project dependencies or for which conflicts don't make
sense. For example: [EtsyPhan][1] and [PhpMetrics][2]. Installing one of those static analysis tools should not change
your application dependencies, neither should it be a problem to install both of them at the same time.
## How does this plugin work?
It allows you to install your *bin vendors* in isolated locations, and still link them to your
[bin-dir][3] (if you want to).
This is done by registering a `bin` command, which can be used to run Composer commands inside a namespace.
## Installation
# Globally
$ composer global require bamarni/composer-bin-plugin
# In your project
$ composer require --dev bamarni/composer-bin-plugin
## Usage
$ composer bin [namespace] [composer_command]
$ composer global bin [namespace] [composer_command]
### Example
Let's install [Behat][4] and [PhpSpec][5] inside a `bdd` bin namespace, [EtsyPhan][1] in `etsy-phan` and [PhpMetrics][2]
in `phpmetrics`:
$ composer bin bdd require behat/behat phpspec/phpspec
$ composer bin etsy-phan require etsy/phan
$ composer bin phpmetrics require phpmetrics/phpmetrics
This command creates the following directory structure :
.
├── composer.json
├── composer.lock
├── vendor/
│ └── bin
│ ├── behat -> ../../vendor-bin/bdd/vendor/behat/behat/bin/behat
│ ├── phpspec -> ../../vendor-bin/bdd/vendor/phpspec/phpspec/bin/phpspec
│ ├── phan -> ../../vendor-bin/etsy-phan/vendor/etsy/phan/phan
│ └── phpmetrics -> ../../vendor-bin/phpmetrics/vendor/phpmetrics/phpmetrics/bin/phpmetrics
└── vendor-bin/
└── bdd
│ ├── composer.json
│ ├── composer.lock
│ └── vendor/
│ ├── behat/
│ ├── phpspec/
│ └── ...
└── etsy-phan
│ ├── composer.json
│ ├── composer.lock
│ └── vendor/
│ ├── etsy/
│ └── ...
└── phpmetrics
├── composer.json
├── composer.lock
└── vendor/
├── phpmetrics/
└── ...
You can continue to run `vendor/bin/behat`, `vendor/bin/phpspec` and co. as before but they will be properly isolated.
Also, `composer.json` and `composer.lock` files in each namespace will allow you to take advantage of automated dependency
management as normally provided by Composer.
### The `all` bin namespace
The `all` bin namespace has a special meaning. It runs a command for all existing bin namespaces. For instance, the
following command would update all your bins :
$ composer bin all update
Changed current directory to vendor-bin/phpspec
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
Changed current directory to vendor-bin/phpunit
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
### What happens when symlink conflicts?
If we take the case described in the [example section](#example), there might be more binaries linked due to
the dependencies. For example [PhpMetrics][2] depends on [Nikic PHP-Parser][6] and as such you will also have `php-parse`
in `.vendor/bin/`:
.
├── composer.json
├── composer.lock
├── vendor/
│ └── bin
│ ├── phpmetrics -> ../../vendor-bin/phpmetrics/vendor/phpmetrics/phpmetrics/bin/phpmetrics
│ └── php-parse -> ../../vendor-bin/phpmetrics/vendor/nikic/PHP-Parser/bin/php-parsee
└── vendor-bin/
└── phpmetrics
├── composer.json
├── composer.lock
└── vendor/
├── phpmetrics/
├── nikic/
└── ...
But what happens if another bin-namespace has a dependency using [Nikic PHP-Parser][6]? In that situation symlinks would
collides and are not created (only the colliding ones).
## Tips
### Auto-installation
For convenience, you can add the following script in your `composer.json` :
```json
{
"scripts": {
"bin": "echo 'bin not installed'",
"post-install-cmd": ["@composer bin all install --ansi"],
"post-update-cmd": ["@composer bin all update --ansi"]
}
}
```
This makes sure all your bins are installed during `composer install` and updated during `composer update`.
### Disable links
By default, binaries of the sub namespaces are linked to the root one like described in [example](#example). If you
wish to disable that behaviour, you can do so by adding a little setting in the extra config:
```json
{
"extra": {
"bamarni-bin": {
"bin-links": false
}
}
}
```
Note that otherwise, in case of conflicts (e.g. `phpstan` is present in two namespaces), only the
first one is linked and the second one is ignored.
### Change directory
By default, the packages are looked for in the `vendor-bin` directory. The location can be changed using `target-directory` value in the extra config:
```json
{
"extra": {
"bamarni-bin": {
"target-directory": "ci/vendor"
}
}
}
```
### Forward mode
There is a `forward mode` which is disabled by default. This can be activated by using the `forward-command` value in the extra config.
```json
{
"extra": {
"bamarni-bin": {
"forward-command": true
}
}
}
```
If this mode is activated, all your `composer install` and `composer update` commands are forwarded to all bin directories.
This is an replacement for the tasks shown in section [Auto-installation](#auto-installation).
### Reduce clutter
You can add following line to your `.gitignore` file in order to avoid committing dependencies of your tools.
```.gitignore
/vendor-bin/**/vendor
```
Updating each tool can create many not legible changes in `composer.lock` files. You can use `.gitattributes` file in order
to inform git that it shouldn't show diffs of `composer.lock` files.
```.gitattributes
vendor-bin/**/composer.lock binary
```
## Related plugins
* [theofidry/composer-inheritance-plugin][7]: Opinionated version of [Wikimedia composer-merge-plugin][8] to work in pair with this plugin.
## Backward Compatibility Promise
The backward compatibility promise only applies to the following API:
- The commands registered by the plugin
- The behaviour of the commands (but not their logging/output)
- The Composer configuration
The plugin implementation is considered to be strictly internal and its code may
change at any time in a non back-ward compatible way.
## Contributing
A makefile is available to help out:
```bash
$ make # Runs all checks
$ make help # List all available commands
```
**Note:** you do need to install [phive][phive] first.
[1]: https://github.com/etsy/phan
[2]: https://github.com/phpmetrics/PhpMetrics
[3]: https://getcomposer.org/doc/06-config.md#bin-dir
[4]: http://behat.org
[5]: http://phpspec.net
[6]: https://github.com/nikic/PHP-Parser
[7]: https://github.com/theofidry/composer-inheritance-plugin
[8]: https://github.com/wikimedia/composer-merge-plugin
[phive]: https://phar.io/
[symfony-bc-policy]: https://symfony.com/doc/current/contributing/code/bc.html
composer.json 0000644 00000002463 14264017271 0007277 0 ustar 00 {
"name": "bamarni/composer-bin-plugin",
"description": "No conflicts for your bin dependencies",
"license": "MIT",
"type": "composer-plugin",
"keywords": [
"composer",
"dependency",
"tool",
"isolation",
"conflict",
"executable"
],
"require": {
"php": "^7.2.5 || ^8.0",
"composer-plugin-api": "^2.0"
},
"require-dev": {
"ext-json": "*",
"composer/composer": "^2.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^8.5 || ^9.5",
"symfony/console": "^5.4.7 || ^6.0.7",
"symfony/finder": "^5.4.7 || ^6.0.7",
"symfony/process": "^5.4.7 || ^6.0.7"
},
"autoload": {
"psr-4": {
"Bamarni\\Composer\\Bin\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Bamarni\\Composer\\Bin\\Tests\\": "tests"
}
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"ergebnis/composer-normalize": true,
"infection/extension-installer": true
},
"sort-packages": true
},
"extra": {
"class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin"
}
}
e2e/scenario0/README.md 0000644 00000000074 14264017271 0010366 0 ustar 00 Regular installation on a project with multiple namespaces.
e2e/scenario0/composer.json 0000644 00000000450 14264017271 0011627 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require-dev": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
}
}
e2e/scenario0/expected.txt 0000644 00000001130 14264017271 0011443 0 ustar 00 [bamarni-bin] Checking namespace vendor-bin/ns1
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
[bamarni-bin] Checking namespace vendor-bin/ns2
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
e2e/scenario0/script.sh 0000755 00000001273 14264017271 0010754 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
composer update
# Actual command to execute the test itself
composer bin all update 2>&1 | tee > actual.txt
e2e/scenario0/vendor-bin/ns1/composer.json 0000644 00000000003 14264017271 0014365 0 ustar 00 {}
e2e/scenario0/vendor-bin/ns2/composer.json 0000644 00000000003 14264017271 0014366 0 ustar 00 {}
e2e/scenario1/README.md 0000644 00000000114 14264017271 0010362 0 ustar 00 Regular installation on a project with multiple namespaces in verbose mode.
e2e/scenario1/composer.json 0000644 00000000450 14264017271 0011630 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require-dev": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
}
}
e2e/scenario1/expected.txt 0000644 00000002474 14264017271 0011460 0 ustar 00 [bamarni-bin] Current working directory: /path/to/project/e2e/scenario1
[bamarni-bin] Configuring bin directory to /path/to/project/e2e/scenario1/vendor/bin.
[bamarni-bin] Checking namespace vendor-bin/ns1
[bamarni-bin] Changed current directory to vendor-bin/ns1.
[bamarni-bin] Running `@composer update --verbose --working-dir='.'`.
Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario1.
[bamarni-bin] Checking namespace vendor-bin/ns2
[bamarni-bin] Changed current directory to vendor-bin/ns2.
[bamarni-bin] Running `@composer update --verbose --working-dir='.'`.
Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario1.
e2e/scenario1/script.sh 0000755 00000001305 14264017271 0010751 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
composer update
# Actual command to execute the test itself
composer bin all update --verbose 2>&1 | tee > actual.txt
e2e/scenario1/vendor-bin/ns1/composer.json 0000644 00000000003 14264017271 0014366 0 ustar 00 {}
e2e/scenario1/vendor-bin/ns2/composer.json 0000644 00000000003 14264017271 0014367 0 ustar 00 {}
e2e/scenario10/README.md 0000644 00000000160 14264017271 0010443 0 ustar 00 Check that the composer environment variables are well respected when commands are forwarded to the namespaces.
e2e/scenario10/composer.json 0000644 00000000601 14264017271 0011706 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"forward-command": true
}
}
}
e2e/scenario10/expected.txt 0000644 00000000026 14264017271 0011527 0 ustar 00 ./.composer
.composer
e2e/scenario10/script.sh 0000755 00000001567 14264017271 0011043 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf .composer || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
rm -rf vendor-bin/*/.composer || true
readonly CUSTOM_COMPOSER_DIR=$(pwd)/.composer
COMPOSER_CACHE_DIR=$CUSTOM_COMPOSER_DIR composer update
# Actual command to execute the test itself
find . ".composer" -name ".composer" -type d 2>&1 | sort -n | tee > actual.txt || true
e2e/scenario10/vendor-bin/ns1/composer.json 0000644 00000000072 14264017271 0014454 0 ustar 00 {
"require": {
"nikic/iter": "v1.6.0"
}
}
e2e/scenario11/README.md 0000644 00000000067 14264017271 0010452 0 ustar 00 Check that the deprecation messages are well rendered.
e2e/scenario11/composer.json 0000644 00000000601 14264017271 0011707 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"forward-command": true
}
}
}
e2e/scenario11/expected.txt 0000644 00000001632 14264017271 0011534 0 ustar 00 Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking bamarni/composer-bin-plugin (dev-hash)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing bamarni/composer-bin-plugin (dev-hash): Symlinking from ../..
Generating autoload files
[bamarni-bin] The setting "bamarni-bin.bin-links" will be set to "false" from 2.x onwards. If you wish to keep it to "true", you need to set it explicitly.
[bamarni-bin] The command is being forwarded.
[bamarni-bin] Checking namespace vendor-bin/ns1
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
e2e/scenario11/script.sh 0000755 00000001341 14264017271 0011032 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf .composer || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
rm -rf vendor-bin/*/.composer || true
# Actual command to execute the test itself
composer update 2>&1 | tee > actual.txt
e2e/scenario11/vendor-bin/ns1/composer.json 0000644 00000000003 14264017271 0014447 0 ustar 00 {}
e2e/scenario2/README.md 0000644 00000000063 14264017271 0010366 0 ustar 00 Regular script (same name) in different namespaces
e2e/scenario2/composer.json 0000644 00000000450 14264017271 0011631 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require-dev": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
}
}
e2e/scenario2/expected.txt 0000644 00000000176 14264017271 0011456 0 ustar 00 [bamarni-bin] Checking namespace vendor-bin/ns1
> echo ns1
ns1
[bamarni-bin] Checking namespace vendor-bin/ns2
> echo ns2
ns2
e2e/scenario2/script.sh 0000755 00000001303 14264017271 0010750 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
composer update
# Actual command to execute the test itself
composer bin all run-script foo 2>&1 | tee > actual.txt
e2e/scenario2/vendor-bin/ns1/composer.json 0000644 00000000065 14264017271 0014377 0 ustar 00 {
"scripts": {
"foo": "echo ns1"
}
}
e2e/scenario2/vendor-bin/ns2/composer.json 0000644 00000000065 14264017271 0014400 0 ustar 00 {
"scripts": {
"foo": "echo ns2"
}
}
e2e/scenario3/README.md 0000644 00000000077 14264017271 0010374 0 ustar 00 Regular installation on a project with forwarded mode enabled.
e2e/scenario3/composer.json 0000644 00000000645 14264017271 0011640 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require-dev": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"bin-links": false,
"forward-command": true
}
}
}
e2e/scenario3/expected.txt 0000644 00000005430 14264017271 0011455 0 ustar 00 Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Lock file operations: 1 install, 0 updates, 0 removals
Installs: bamarni/composer-bin-plugin:dev-hash
- Locking bamarni/composer-bin-plugin (dev-hash)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
Installs: bamarni/composer-bin-plugin:dev-hash
- Installing bamarni/composer-bin-plugin (dev-hash): Symlinking from ../..
Generating autoload files
> post-autoload-dump: Bamarni\Composer\Bin\BamarniBinPlugin->onPostAutoloadDump
[bamarni-bin] Calling onPostAutoloadDump().
[bamarni-bin] The command is being forwarded.
[bamarni-bin] Original input: update --verbose.
[bamarni-bin] Current working directory: /path/to/project/e2e/scenario3
[bamarni-bin] Checking namespace vendor-bin/ns1
[bamarni-bin] Changed current directory to vendor-bin/ns1.
[bamarni-bin] Running `@composer update --verbose --working-dir='.'`.
Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario3.
–––––––––––––––––––––
[bamarni-bin] Calling onCommandEvent().
[bamarni-bin] The command is being forwarded.
[bamarni-bin] Original input: update --verbose.
[bamarni-bin] Current working directory: /path/to/project/e2e/scenario3
[bamarni-bin] Checking namespace vendor-bin/ns1
[bamarni-bin] Changed current directory to vendor-bin/ns1.
[bamarni-bin] Running `@composer update --verbose --working-dir='.'`.
Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Nothing to modify in lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario3.
Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Nothing to modify in lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
> post-autoload-dump: Bamarni\Composer\Bin\BamarniBinPlugin->onPostAutoloadDump
[bamarni-bin] Calling onPostAutoloadDump().
[bamarni-bin] Command already forwarded within the process: skipping.
e2e/scenario3/script.sh 0000755 00000001464 14264017271 0010761 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
# Actual command to execute the test itself
composer update --verbose 2>&1 | tee > actual.txt
echo "–––––––––––––––––––––" >> actual.txt
composer update --verbose 2>&1 | tee >> actual.txt
e2e/scenario3/vendor-bin/ns1/composer.json 0000644 00000000003 14264017271 0014370 0 ustar 00 {}
e2e/scenario5/README.md 0000644 00000000124 14264017271 0010367 0 ustar 00 Regular installation on a project with multiple namespaces and with links disabled.
e2e/scenario5/composer.json 0000644 00000000600 14264017271 0011631 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require-dev": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"bin-links": false
}
}
}
e2e/scenario5/expected.txt 0000644 00000000276 14264017271 0011462 0 ustar 00 find: vendor/bin: No such file or directory
vendor-bin/ns1/vendor/bin/phpstan
vendor-bin/ns1/vendor/bin/phpstan.phar
vendor-bin/ns2/vendor/bin/phpstan
vendor-bin/ns2/vendor/bin/phpstan.phar
e2e/scenario5/script.sh 0000755 00000001411 14264017271 0010753 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
composer update
composer bin all update
# Actual command to execute the test itself
find vendor/bin vendor-bin/*/vendor/bin -maxdepth 1 -type f 2>&1 | sort -n | tee > actual.txt || true
e2e/scenario5/vendor-bin/ns1/composer.json 0000644 00000000076 14264017271 0014404 0 ustar 00 {
"require": {
"phpstan/phpstan": "1.8.0"
}
}
e2e/scenario5/vendor-bin/ns2/composer.json 0000644 00000000076 14264017271 0014405 0 ustar 00 {
"require": {
"phpstan/phpstan": "1.8.0"
}
}
e2e/scenario6/README.md 0000644 00000000154 14264017271 0010373 0 ustar 00 Regular installation on a project with multiple namespaces and with links enabled and conflicting symlinks.
e2e/scenario6/composer.json 0000644 00000000577 14264017271 0011647 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require-dev": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"bin-links": true
}
}
}
e2e/scenario6/expected.txt 0000644 00000000215 14264017271 0011454 0 ustar 00 find: vendor-bin/*/vendor/bin: No such file or directory
vendor/bin/phpstan
vendor/bin/phpstan.phar
PHPStan - PHP Static Analysis Tool 1.8.0
e2e/scenario6/script.sh 0000755 00000001464 14264017271 0010764 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
composer update
composer bin all update
# Actual command to execute the test itself
find vendor/bin vendor-bin/*/vendor/bin -maxdepth 1 -type f 2>&1 | sort -n | tee > actual.txt || true
vendor/bin/phpstan --version >> actual.txt
e2e/scenario6/vendor-bin/ns1/composer.json 0000644 00000000076 14264017271 0014405 0 ustar 00 {
"require": {
"phpstan/phpstan": "1.8.0"
}
}
e2e/scenario6/vendor-bin/ns2/composer.json 0000644 00000000076 14264017271 0014406 0 ustar 00 {
"require": {
"phpstan/phpstan": "1.6.0"
}
}
e2e/scenario7/README.md 0000644 00000000103 14264017271 0010366 0 ustar 00 Tests that dev dependencies are not installed if no-dev is passed.
e2e/scenario7/composer.json 0000644 00000000444 14264017271 0011641 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
}
}
e2e/scenario7/expected.txt 0000644 00000001326 14264017271 0011461 0 ustar 00 [bamarni-bin] Current working directory: /path/to/project/e2e/scenario7
[bamarni-bin] Configuring bin directory to /path/to/project/e2e/scenario7/vendor/bin.
[bamarni-bin] Checking namespace vendor-bin/ns1
[bamarni-bin] Changed current directory to vendor-bin/ns1.
[bamarni-bin] Running `@composer update --no-dev --verbose --working-dir='.' -- foo`.
Cannot update only a partial set of packages without a lock file present. Run `composer update` to generate a lock file.
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario7.
–––––––––––––––––––––
[bamarni-bin] Checking namespace vendor-bin/ns1
No dependencies installed. Try running composer install or update.
e2e/scenario7/script.sh 0000755 00000001576 14264017271 0010771 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
composer update
# Actual command to execute the test itself
composer bin ns1 update --no-dev --verbose -- foo 2>&1 | tee > actual.txt || true
echo "–––––––––––––––––––––" >> actual.txt
composer bin ns1 show --direct --name-only 2>&1 | tee >> actual.txt || true
e2e/scenario7/vendor-bin/ns1/composer.json 0000644 00000000171 14264017271 0014402 0 ustar 00 {
"require": {
"nikic/iter": "v1.6.0"
},
"require-dev": {
"phpstan/phpstan": "1.8.0"
}
}
e2e/scenario8/README.md 0000644 00000000144 14264017271 0010374 0 ustar 00 Tests that extra arguments and options are not lost when forwarding the command to a bin namespace.
e2e/scenario8/composer.json 0000644 00000000641 14264017271 0011641 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"bin-links": false,
"forward-command": true
}
}
}
e2e/scenario8/expected.txt 0000644 00000002752 14264017271 0011466 0 ustar 00 Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Lock file operations: 1 install, 0 updates, 0 removals
Installs: bamarni/composer-bin-plugin:dev-hash
- Locking bamarni/composer-bin-plugin (dev-hash)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
Installs: bamarni/composer-bin-plugin:dev-hash
- Installing bamarni/composer-bin-plugin (dev-hash): Symlinking from ../..
Generating autoload files
> post-autoload-dump: Bamarni\Composer\Bin\BamarniBinPlugin->onPostAutoloadDump
[bamarni-bin] Calling onPostAutoloadDump().
[bamarni-bin] The command is being forwarded.
[bamarni-bin] Original input: update --prefer-lowest --verbose.
[bamarni-bin] Current working directory: /path/to/project/e2e/scenario8
[bamarni-bin] Checking namespace vendor-bin/ns1
[bamarni-bin] Changed current directory to vendor-bin/ns1.
[bamarni-bin] Running `@composer update --prefer-lowest --verbose --working-dir='.'`.
Loading composer repositories with package information
Updating dependencies
Analyzed 90 packages to resolve dependencies
Analyzed 90 rules to resolve dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
[bamarni-bin] Changed current directory to /path/to/project/e2e/scenario8.
e2e/scenario8/script.sh 0000755 00000001305 14264017271 0010760 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
# Actual command to execute the test itself
composer update --prefer-lowest --verbose 2>&1 | tee >> actual.txt || true
e2e/scenario8/vendor-bin/ns1/composer.json 0000644 00000000003 14264017271 0014375 0 ustar 00 {}
e2e/scenario9/README.md 0000644 00000000144 14264017271 0010375 0 ustar 00 Tests that plugins installed in a namespace are loaded when a command is executed in the namespace.
e2e/scenario9/composer.json 0000644 00000000601 14264017271 0011636 0 ustar 00 {
"repositories": [
{
"type": "path",
"url": "../../"
}
],
"require": {
"bamarni/composer-bin-plugin": "dev-master"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"forward-command": true
}
}
}
e2e/scenario9/expected.txt 0000644 00000000270 14264017271 0011460 0 ustar 00 [bamarni-bin] Checking namespace vendor-bin/composer-unused
Loading packages
----------------
! [NOTE] Found 0 package(s) to be checked.
[OK] Done. No required packages to scan.
e2e/scenario9/script.sh 0000755 00000001335 14264017271 0010764 0 ustar 00 #!/usr/bin/env bash
set -Eeuo pipefail
# Set env envariables in order to experience a behaviour closer to what happens
# in the CI locally. It should not hurt to set those in the CI as the CI should
# contain those values.
export CI=1
export COMPOSER_NO_INTERACTION=1
readonly ORIGINAL_WORKING_DIR=$(pwd)
trap "cd ${ORIGINAL_WORKING_DIR}" err exit
# Change to script directory
cd "$(dirname "$0")"
# Ensure we have a clean state
rm -rf actual.txt || true
rm -rf composer.lock || true
rm -rf vendor || true
rm -rf vendor-bin/*/composer.lock || true
rm -rf vendor-bin/*/vendor || true
composer update
# Actual command to execute the test itself
composer bin composer-unused unused --no-progress 2>&1 | tee > actual.txt || true
e2e/scenario9/vendor-bin/composer-unused/composer.json 0000644 00000000344 14264017271 0017035 0 ustar 00 {
"require": {
"composer-runtime-api": "^2.2",
"icanhazstring/composer-unused": "~0.7.12"
},
"config": {
"allow-plugins": {
"icanhazstring/composer-unused": true
}
}
}
infection.json 0000644 00000000376 14264017271 0007427 0 ustar 00 {
"$schema": "vendor/infection/infection/resources/schema.json",
"source": {
"directories": [
"src"
]
},
"logs": {
"text": "dist/infection.txt"
},
"mutators": {
"@default": true
}
}
src/ApplicationFactory/FreshInstanceApplicationFactory.php 0000644 00000000507 14264017271 0020115 0 ustar 00 composer = $composer;
$this->io = $io;
$this->logger = new Logger($io);
}
public function getCapabilities(): array
{
return [
ComposerPluginCommandProvider::class => BamarniCommandProvider::class,
];
}
public function deactivate(Composer $composer, IOInterface $io): void
{
}
public function uninstall(Composer $composer, IOInterface $io): void
{
}
public static function getSubscribedEvents(): array
{
return [
PluginEvents::COMMAND => 'onCommandEvent',
ScriptEvents::POST_AUTOLOAD_DUMP => 'onPostAutoloadDump',
];
}
public function onPostAutoloadDump(Event $event): void
{
$this->logger->logDebug('Calling onPostAutoloadDump().');
$eventIO = $event->getIO();
if (!($eventIO instanceof ConsoleIO)) {
return;
}
// This is a bit convoluted but Event does not expose the input unlike
// CommandEvent.
$publicIO = PublicIO::fromConsoleIO($eventIO);
$eventInput = $publicIO->getInput();
$this->onEvent(
$eventInput->getArgument('command'),
$eventInput,
$publicIO->getOutput()
);
}
public function onCommandEvent(CommandEvent $event): bool
{
$this->logger->logDebug('Calling onCommandEvent().');
return $this->onEvent(
$event->getCommandName(),
$event->getInput(),
$event->getOutput()
);
}
private function onEvent(
string $commandName,
InputInterface $input,
OutputInterface $output
): bool {
$config = Config::fromComposer($this->composer);
$deprecations = $config->getDeprecations();
if (count($deprecations) > 0) {
foreach ($deprecations as $deprecation) {
$this->logger->logStandard($deprecation);
}
}
if ($config->isCommandForwarded()
&& in_array($commandName, self::FORWARDED_COMMANDS, true)
) {
return $this->onForwardedCommand($input, $output);
}
return true;
}
protected function onForwardedCommand(
InputInterface $input,
OutputInterface $output
): bool {
if ($this->forwarded) {
$this->logger->logDebug('Command already forwarded within the process: skipping.');
return true;
}
$this->forwarded = true;
$this->logger->logStandard('The command is being forwarded.');
$this->logger->logDebug(
sprintf(
'Original input: %s.',
$input->__toString()
)
);
// Note that the input & output of $io should be the same as the event
// input & output.
$io = $this->io;
$application = new Application();
$command = new BinCommand();
$command->setComposer($this->composer);
$command->setApplication($application);
$command->setIO($io);
$forwardedCommandInput = BinInputFactory::createForwardedCommandInput($input);
try {
return Command::SUCCESS === $command->run(
$forwardedCommandInput,
$output
);
} catch (Throwable $throwable) {
return false;
}
}
}
src/Command/BinCommand.php 0000644 00000021204 14264017271 0011434 0 ustar 00 applicationFactory = $applicationFactory ?? new FreshInstanceApplicationFactory();
$this->logger = $logger ?? new Logger(new NullIO());
}
protected function configure(): void
{
$this
->setDescription('Run a command inside a bin namespace')
->addArgument(
self::NAMESPACE_ARG,
InputArgument::REQUIRED
)
->ignoreValidationErrors();
}
public function setIO(IOInterface $io): void
{
parent::setIO($io);
$this->logger = new Logger($io);
}
public function getIO(): IOInterface
{
$io = parent::getIO();
$this->logger = new Logger($io);
return $io;
}
public function isProxyCommand(): bool
{
return true;
}
public function execute(InputInterface $input, OutputInterface $output): int
{
// Switch to requireComposer() once Composer 2.3 is set as the minimum
$config = Config::fromComposer($this->getComposer());
$currentWorkingDir = getcwd();
$this->logger->logDebug(
sprintf(
'Current working directory: %s',
$currentWorkingDir
)
);
// Ensures Composer is reset – we are setting some environment variables
// & co. so a fresh Composer instance is required.
$this->resetComposers();
$this->configureBinLinksDir($config);
$vendorRoot = $config->getTargetDirectory();
$namespace = $input->getArgument(self::NAMESPACE_ARG);
$binInput = BinInputFactory::createInput(
$namespace,
$input
);
return (self::ALL_NAMESPACES !== $namespace)
? $this->executeInNamespace(
$currentWorkingDir,
$vendorRoot.'/'.$namespace,
$binInput,
$output
)
: $this->executeAllNamespaces(
$currentWorkingDir,
$vendorRoot,
$binInput,
$output
);
}
/**
* @return list
*/
private static function getBinNamespaces(string $binVendorRoot): array
{
return glob($binVendorRoot.'/*', GLOB_ONLYDIR);
}
private function executeAllNamespaces(
string $originalWorkingDir,
string $binVendorRoot,
InputInterface $input,
OutputInterface $output
): int {
$namespaces = self::getBinNamespaces($binVendorRoot);
if (count($namespaces) === 0) {
$this->logger->logStandard('Could not find any bin namespace.');
// Is a valid scenario: the user may not have set up any bin
// namespace yet
return self::SUCCESS;
}
$exitCode = self::SUCCESS;
foreach ($namespaces as $namespace) {
$exitCode += $this->executeInNamespace(
$originalWorkingDir,
$namespace,
$input,
$output
);
}
return min($exitCode, self::FAILURE);
}
private function executeInNamespace(
string $originalWorkingDir,
string $namespace,
InputInterface $input,
OutputInterface $output
): int {
$this->logger->logStandard(
sprintf(
'Checking namespace %s',
$namespace
)
);
try {
self::createNamespaceDirIfDoesNotExist($namespace);
} catch (CouldNotCreateNamespaceDir $exception) {
$this->logger->logStandard(
sprintf(
'%s',
$exception->getMessage()
)
);
return self::FAILURE;
}
// Use a new application: this avoids a variety of issues:
// - A command may be added in a namespace which may cause side effects
// when executed in another namespace afterwards (since it is the same
// process).
// - Different plugins may be registered in the namespace in which case
// an already executed application will not pick that up.
$namespaceApplication = $this->applicationFactory->create(
$this->getApplication()
);
// It is important to clean up the state either for follow-up plugins
// or for example the execution in the next namespace.
$cleanUp = function () use ($originalWorkingDir): void {
$this->chdir($originalWorkingDir);
$this->resetComposers();
};
$this->chdir($namespace);
$this->ensureComposerFileExists();
$namespaceInput = BinInputFactory::createNamespaceInput($input);
$this->logger->logDebug(
sprintf(
'Running `@composer %s`.',
$namespaceInput->__toString()
)
);
try {
$exitCode = $namespaceApplication->doRun($namespaceInput, $output);
} catch (Throwable $executionFailed) {
// Ensure we do the cleanup even in case of failure
$cleanUp();
throw $executionFailed;
}
$cleanUp();
return $exitCode;
}
/**
* @throws CouldNotCreateNamespaceDir
*/
private static function createNamespaceDirIfDoesNotExist(string $namespace): void
{
if (file_exists($namespace)) {
return;
}
$mkdirResult = mkdir($namespace, 0777, true);
if (!$mkdirResult && !is_dir($namespace)) {
throw CouldNotCreateNamespaceDir::forNamespace($namespace);
}
}
private function configureBinLinksDir(Config $config): void
{
if (!$config->binLinksAreEnabled()) {
return;
}
$binDir = ConfigFactory::createConfig()->get('bin-dir');
putenv(
sprintf(
'COMPOSER_BIN_DIR=%s',
$binDir
)
);
$this->logger->logDebug(
sprintf(
'Configuring bin directory to %s.',
$binDir
)
);
}
private function ensureComposerFileExists(): void
{
// Some plugins require access to the Composer file e.g. Symfony Flex
$namespaceComposerFile = Factory::getComposerFile();
if (file_exists($namespaceComposerFile)) {
return;
}
file_put_contents($namespaceComposerFile, '{}');
$this->logger->logDebug(
sprintf(
'Created the file %s.',
$namespaceComposerFile
)
);
}
private function resetComposers(): void
{
$this->getApplication()->resetComposer();
foreach ($this->getApplication()->all() as $command) {
if ($command instanceof BaseCommand) {
$command->resetComposer();
}
}
}
private function chdir(string $dir): void
{
chdir($dir);
$this->logger->logDebug(
sprintf(
'Changed current directory to %s.',
$dir
)
);
}
}
src/Command/CouldNotCreateNamespaceDir.php 0000644 00000000642 14264017271 0014557 0 ustar 00 true,
self::TARGET_DIRECTORY => 'vendor-bin',
self::FORWARD_COMMAND => false,
];
/**
* @var bool
*/
private $binLinks;
/**
* @var string
*/
private $targetDirectory;
/**
* @var bool
*/
private $forwardCommand;
/**
* @var list
*/
private $deprecations = [];
/**
* @throws InvalidBamarniComposerExtraConfig
*/
public static function fromComposer(Composer $composer): self
{
return new self($composer->getPackage()->getExtra());
}
/**
* @param mixed[] $extra
*
* @throws InvalidBamarniComposerExtraConfig
*/
public function __construct(array $extra)
{
$userExtra = $extra[self::EXTRA_CONFIG_KEY] ?? [];
$config = array_merge(self::DEFAULT_CONFIG, $userExtra);
$getType = function_exists('get_debug_type') ? 'get_debug_type' : 'gettype';
$binLinks = $config[self::BIN_LINKS_ENABLED];
if (!is_bool($binLinks)) {
throw new InvalidBamarniComposerExtraConfig(
sprintf(
'Expected setting "%s.%s" to be a boolean value. Got "%s".',
self::EXTRA_CONFIG_KEY,
self::BIN_LINKS_ENABLED,
$getType($binLinks)
)
);
}
$binLinksSetExplicitly = array_key_exists(self::BIN_LINKS_ENABLED, $userExtra);
if ($binLinks && !$binLinksSetExplicitly) {
$this->deprecations[] = sprintf(
'The setting "%s.%s" will be set to "false" from 2.x onwards. If you wish to keep it to "true", you need to set it explicitly.',
self::EXTRA_CONFIG_KEY,
self::BIN_LINKS_ENABLED
);
}
$targetDirectory = $config[self::TARGET_DIRECTORY];
if (!is_string($targetDirectory)) {
throw new InvalidBamarniComposerExtraConfig(
sprintf(
'Expected setting "%s.%s" to be a string. Got "%s".',
self::EXTRA_CONFIG_KEY,
self::TARGET_DIRECTORY,
$getType($targetDirectory)
)
);
}
$forwardCommand = $config[self::FORWARD_COMMAND];
if (!is_bool($forwardCommand)) {
throw new InvalidBamarniComposerExtraConfig(
sprintf(
'Expected setting "%s.%s" to be a boolean value. Got "%s".',
self::EXTRA_CONFIG_KEY,
self::FORWARD_COMMAND,
gettype($forwardCommand)
)
);
}
$forwardCommandSetExplicitly = array_key_exists(self::FORWARD_COMMAND, $userExtra);
if (!$forwardCommand && !$forwardCommandSetExplicitly) {
$this->deprecations[] = sprintf(
'The setting "%s.%s" will be set to "true" from 2.x onwards. If you wish to keep it to "false", you need to set it explicitly.',
self::EXTRA_CONFIG_KEY,
self::FORWARD_COMMAND
);
}
$this->binLinks = $binLinks;
$this->targetDirectory = $targetDirectory;
$this->forwardCommand = $forwardCommand;
}
public function binLinksAreEnabled(): bool
{
return $this->binLinks;
}
public function getTargetDirectory(): string
{
return $this->targetDirectory;
}
public function isCommandForwarded(): bool
{
return $this->forwardCommand;
}
/**
* @return list
*/
public function getDeprecations(): array
{
return $this->deprecations;
}
}
src/Config/ConfigFactory.php 0000644 00000001401 14264017271 0012006 0 ustar 00 exists()) {
return $config;
}
$file->validateSchema(JsonFile::LAX_SCHEMA);
$config->merge($file->read());
return $config;
}
private function __construct()
{
}
}
src/Config/InvalidBamarniComposerExtraConfig.php 0000644 00000000273 14264017271 0016001 0 ustar 00 `update --prefer-lowest`
*
* Note that no input definition is bound in the resulting input.
*/
public static function createInput(
string $namespace,
InputInterface $previousInput
): InputInterface {
$matchResult = preg_match(
sprintf(
'/^(?.+)?bin (?:(?.+?) )?(?:%1$s|\'%1$s\') (?.+?)(? -- .*)?$/',
preg_quote($namespace, '/')
),
$previousInput->__toString(),
$matches
);
if (1 !== $matchResult) {
throw InvalidBinInput::forBinInput($previousInput);
}
$inputParts = array_filter(
array_map(
'trim',
[
$matches['binCommand'],
$matches['preBinOptions2'] ?? '',
$matches['preBinOptions'] ?? '',
$matches['extraInput'] ?? '',
]
)
);
// Move the options present _before_ bin namespaceName to after, but
// before the end of option marker (--) if present.
$reorderedInput = implode(' ', $inputParts);
return new StringInput($reorderedInput);
}
public static function createNamespaceInput(InputInterface $previousInput): InputInterface
{
$matchResult = preg_match(
'/^(.+?\s?)(--(?: .+)?)?$/',
$previousInput->__toString(),
$matches
);
if (1 !== $matchResult) {
throw InvalidBinInput::forNamespaceInput($previousInput);
}
$inputParts = array_filter(
array_map(
'trim',
[
$matches[1],
'--working-dir=.',
$matches[2] ?? '',
]
)
);
$newInput = implode(' ', $inputParts);
return new StringInput($newInput);
}
public static function createForwardedCommandInput(InputInterface $input): InputInterface
{
return new StringInput(
sprintf(
'bin all %s',
$input->__toString()
)
);
}
private function __construct()
{
}
}
src/Input/InvalidBinInput.php 0000644 00000001564 14264017271 0012214 0 ustar 00 ", for example "bin all update --prefer-lowest".',
$input->__toString()
)
);
}
public static function forNamespaceInput(InputInterface $input): self
{
return new self(
sprintf(
'Could not parse the input (executed within the namespace) "%s".',
$input->__toString()
)
);
}
}
src/Logger.php 0000644 00000001316 14264017271 0007270 0 ustar 00 io = $io;
}
public function logStandard(string $message): void
{
$this->log($message, false);
}
public function logDebug(string $message): void
{
$this->log($message, true);
}
private function log(string $message, bool $debug): void
{
$verbosity = $debug
? IOInterface::VERBOSE
: IOInterface::NORMAL;
$this->io->writeError('[bamarni-bin] '.$message, true, $verbosity);
}
}
src/PublicIO.php 0000644 00000001142 14264017271 0007514 0 ustar 00 input,
$io->output,
$io->helperSet
);
}
public function getInput(): InputInterface
{
return $this->input;
}
public function getOutput(): OutputInterface
{
return $this->output;
}
}