Merge branch 'composer:main' into add-tests-for-GlobalCommand
commit
50c2a969d4
|
@ -8,16 +8,16 @@
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "composer/ca-bundle",
|
"name": "composer/ca-bundle",
|
||||||
"version": "1.3.7",
|
"version": "1.4.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/ca-bundle.git",
|
"url": "https://github.com/composer/ca-bundle.git",
|
||||||
"reference": "76e46335014860eec1aa5a724799a00a2e47cc85"
|
"reference": "b66d11b7479109ab547f9405b97205640b17d385"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85",
|
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385",
|
||||||
"reference": "76e46335014860eec1aa5a724799a00a2e47cc85",
|
"reference": "b66d11b7479109ab547f9405b97205640b17d385",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
"phpstan/phpstan": "^0.12.55",
|
"phpstan/phpstan": "^0.12.55",
|
||||||
"psr/log": "^1.0",
|
"psr/log": "^1.0",
|
||||||
"symfony/phpunit-bridge": "^4.2 || ^5",
|
"symfony/phpunit-bridge": "^4.2 || ^5",
|
||||||
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0"
|
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"support": {
|
"support": {
|
||||||
"irc": "irc://irc.freenode.org/composer",
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
"issues": "https://github.com/composer/ca-bundle/issues",
|
"issues": "https://github.com/composer/ca-bundle/issues",
|
||||||
"source": "https://github.com/composer/ca-bundle/tree/1.3.7"
|
"source": "https://github.com/composer/ca-bundle/tree/1.4.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-08-30T09:31:38+00:00"
|
"time": "2023-12-18T12:05:55+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/class-map-generator",
|
"name": "composer/class-map-generator",
|
||||||
|
@ -765,16 +765,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "seld/jsonlint",
|
"name": "seld/jsonlint",
|
||||||
"version": "1.10.0",
|
"version": "1.10.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Seldaek/jsonlint.git",
|
"url": "https://github.com/Seldaek/jsonlint.git",
|
||||||
"reference": "594fd6462aad8ecee0b45ca5045acea4776667f1"
|
"reference": "76d449a358ece77d6f1d6331c68453e657172202"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/594fd6462aad8ecee0b45ca5045acea4776667f1",
|
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/76d449a358ece77d6f1d6331c68453e657172202",
|
||||||
"reference": "594fd6462aad8ecee0b45ca5045acea4776667f1",
|
"reference": "76d449a358ece77d6f1d6331c68453e657172202",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -801,7 +801,7 @@
|
||||||
{
|
{
|
||||||
"name": "Jordi Boggiano",
|
"name": "Jordi Boggiano",
|
||||||
"email": "j.boggiano@seld.be",
|
"email": "j.boggiano@seld.be",
|
||||||
"homepage": "http://seld.be"
|
"homepage": "https://seld.be"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "JSON Linter",
|
"description": "JSON Linter",
|
||||||
|
@ -813,7 +813,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/Seldaek/jsonlint/issues",
|
"issues": "https://github.com/Seldaek/jsonlint/issues",
|
||||||
"source": "https://github.com/Seldaek/jsonlint/tree/1.10.0"
|
"source": "https://github.com/Seldaek/jsonlint/tree/1.10.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -825,7 +825,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-05-11T13:16:46+00:00"
|
"time": "2023-12-18T13:03:25+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "seld/phar-utils",
|
"name": "seld/phar-utils",
|
||||||
|
@ -938,16 +938,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v5.4.32",
|
"version": "v5.4.34",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/console.git",
|
"url": "https://github.com/symfony/console.git",
|
||||||
"reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7"
|
"reference": "4b4d8cd118484aa604ec519062113dd87abde18c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/console/zipball/c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
|
"url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c",
|
||||||
"reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
|
"reference": "4b4d8cd118484aa604ec519062113dd87abde18c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1017,7 +1017,7 @@
|
||||||
"terminal"
|
"terminal"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/console/tree/v5.4.32"
|
"source": "https://github.com/symfony/console/tree/v5.4.34"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -1033,7 +1033,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-11-18T18:23:04+00:00"
|
"time": "2023-12-08T13:33:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
|
@ -1802,16 +1802,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/process",
|
"name": "symfony/process",
|
||||||
"version": "v5.4.28",
|
"version": "v5.4.34",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/process.git",
|
"url": "https://github.com/symfony/process.git",
|
||||||
"reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b"
|
"reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b",
|
"url": "https://api.github.com/repos/symfony/process/zipball/8fa22178dfc368911dbd513b431cd9b06f9afe7a",
|
||||||
"reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b",
|
"reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1844,7 +1844,7 @@
|
||||||
"description": "Executes commands in sub-processes",
|
"description": "Executes commands in sub-processes",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/process/tree/v5.4.28"
|
"source": "https://github.com/symfony/process/tree/v5.4.34"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -1860,7 +1860,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-08-07T10:36:04+00:00"
|
"time": "2023-12-02T08:41:43+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/service-contracts",
|
"name": "symfony/service-contracts",
|
||||||
|
@ -1947,16 +1947,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/string",
|
"name": "symfony/string",
|
||||||
"version": "v5.4.32",
|
"version": "v5.4.34",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/string.git",
|
"url": "https://github.com/symfony/string.git",
|
||||||
"reference": "91bf4453d65d8231688a04376c3a40efe0770f04"
|
"reference": "e3f98bfc7885c957488f443df82d97814a3ce061"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/string/zipball/91bf4453d65d8231688a04376c3a40efe0770f04",
|
"url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061",
|
||||||
"reference": "91bf4453d65d8231688a04376c3a40efe0770f04",
|
"reference": "e3f98bfc7885c957488f443df82d97814a3ce061",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2013,7 +2013,7 @@
|
||||||
"utf8"
|
"utf8"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/string/tree/v5.4.32"
|
"source": "https://github.com/symfony/string/tree/v5.4.34"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -2029,22 +2029,22 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-11-26T13:43:46+00:00"
|
"time": "2023-12-09T13:20:28+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "1.10.50",
|
"version": "1.10.55",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
|
"reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
|
||||||
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
|
"reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2093,7 +2093,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-12-13T10:59:42+00:00"
|
"time": "2024-01-08T12:32:40+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-deprecation-rules",
|
"name": "phpstan/phpstan-deprecation-rules",
|
||||||
|
@ -2246,16 +2246,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-symfony",
|
"name": "phpstan/phpstan-symfony",
|
||||||
"version": "1.3.5",
|
"version": "1.3.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
||||||
"reference": "27ff6339f83796a7e0dd963cf445cd3c456fc620"
|
"reference": "34b3c43684834f6a20aa51af8d455480d9de8b88"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/27ff6339f83796a7e0dd963cf445cd3c456fc620",
|
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/34b3c43684834f6a20aa51af8d455480d9de8b88",
|
||||||
"reference": "27ff6339f83796a7e0dd963cf445cd3c456fc620",
|
"reference": "34b3c43684834f6a20aa51af8d455480d9de8b88",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2312,22 +2312,22 @@
|
||||||
"description": "Symfony Framework extensions and rules for PHPStan",
|
"description": "Symfony Framework extensions and rules for PHPStan",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.5"
|
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.6"
|
||||||
},
|
},
|
||||||
"time": "2023-10-30T14:52:15+00:00"
|
"time": "2023-12-22T11:22:34+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/phpunit-bridge",
|
"name": "symfony/phpunit-bridge",
|
||||||
"version": "v7.0.1",
|
"version": "v7.0.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/phpunit-bridge.git",
|
"url": "https://github.com/symfony/phpunit-bridge.git",
|
||||||
"reference": "c2d059b25e31274157dd7727131cd1cf33650207"
|
"reference": "92df075808c9437beca9540e25ae0c40eea1c061"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c2d059b25e31274157dd7727131cd1cf33650207",
|
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/92df075808c9437beca9540e25ae0c40eea1c061",
|
||||||
"reference": "c2d059b25e31274157dd7727131cd1cf33650207",
|
"reference": "92df075808c9437beca9540e25ae0c40eea1c061",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2379,7 +2379,7 @@
|
||||||
"description": "Provides utilities for PHPUnit, especially user deprecation notices management",
|
"description": "Provides utilities for PHPUnit, especially user deprecation notices management",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.1"
|
"source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -2395,7 +2395,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-12-01T09:26:31+00:00"
|
"time": "2023-12-19T11:23:03+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|
|
@ -122,8 +122,8 @@ versions of the dependencies that you are using. Your CI server, production
|
||||||
machines, other developers in your team, everything and everyone runs on the
|
machines, other developers in your team, everything and everyone runs on the
|
||||||
same dependencies, which mitigates the potential for bugs affecting only some
|
same dependencies, which mitigates the potential for bugs affecting only some
|
||||||
parts of the deployments. Even if you develop alone, in six months when
|
parts of the deployments. Even if you develop alone, in six months when
|
||||||
reinstalling the project you can feel confident the dependencies installed are
|
reinstalling the project you can feel confident that the dependencies installed are
|
||||||
still working even if your dependencies released many new versions since then.
|
still working, even if the dependencies have released many new versions since then.
|
||||||
(See note below about using the `update` command.)
|
(See note below about using the `update` command.)
|
||||||
|
|
||||||
> **Note:** For libraries it is not necessary to commit the lock
|
> **Note:** For libraries it is not necessary to commit the lock
|
||||||
|
@ -141,7 +141,7 @@ in `composer.lock` to ensure that the package versions are consistent for everyo
|
||||||
working on your project. As a result you will have all dependencies requested by your
|
working on your project. As a result you will have all dependencies requested by your
|
||||||
`composer.json` file, but they may not all be at the very latest available versions
|
`composer.json` file, but they may not all be at the very latest available versions
|
||||||
(some of the dependencies listed in the `composer.lock` file may have released newer versions since
|
(some of the dependencies listed in the `composer.lock` file may have released newer versions since
|
||||||
the file was created). This is by design, it ensures that your project does not break because of
|
the file was created). This is by design, ensuring that your project does not break because of
|
||||||
unexpected changes in dependencies.
|
unexpected changes in dependencies.
|
||||||
|
|
||||||
So after fetching new changes from your VCS repository it is recommended to run
|
So after fetching new changes from your VCS repository it is recommended to run
|
||||||
|
|
|
@ -198,8 +198,9 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.*
|
||||||
* **--no-install:** Does not run the install step after updating the composer.lock file.
|
* **--no-install:** Does not run the install step after updating the composer.lock file.
|
||||||
* **--no-audit:** Does not run the audit steps after updating the composer.lock file. Also see [COMPOSER_NO_AUDIT](#composer-no-audit).
|
* **--no-audit:** Does not run the audit steps after updating the composer.lock file. Also see [COMPOSER_NO_AUDIT](#composer-no-audit).
|
||||||
* **--audit-format:** Audit output format. Must be "table", "plain", "json", or "summary" (default).
|
* **--audit-format:** Audit output format. Must be "table", "plain", "json", or "summary" (default).
|
||||||
* **--lock:** Only updates the lock file hash to suppress warning about the
|
* **--lock:** Overwrites the lock file hash to suppress warning about the lock file being out of
|
||||||
lock file being out of date.
|
date without updating package versions. Package metadata like mirrors and URLs are updated if
|
||||||
|
they changed.
|
||||||
* **--with:** Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0
|
* **--with:** Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0
|
||||||
* **--no-autoloader:** Skips autoloader generation.
|
* **--no-autoloader:** Skips autoloader generation.
|
||||||
* **--no-progress:** Removes the progress display that can mess with some
|
* **--no-progress:** Removes the progress display that can mess with some
|
||||||
|
@ -556,6 +557,7 @@ php composer.phar show monolog/monolog 1.0.2
|
||||||
* **--major-only (-M):** Use with --latest or --outdated. Only shows packages that have major SemVer-compatible updates.
|
* **--major-only (-M):** Use with --latest or --outdated. Only shows packages that have major SemVer-compatible updates.
|
||||||
* **--minor-only (-m):** Use with --latest or --outdated. Only shows packages that have minor SemVer-compatible updates.
|
* **--minor-only (-m):** Use with --latest or --outdated. Only shows packages that have minor SemVer-compatible updates.
|
||||||
* **--patch-only:** Use with --latest or --outdated. Only shows packages that have patch-level SemVer-compatible updates.
|
* **--patch-only:** Use with --latest or --outdated. Only shows packages that have patch-level SemVer-compatible updates.
|
||||||
|
* **--sort-by-age (-A):** Displays the installed version's age, and sorts packages oldest first. Use with the --latest or --outdated option.
|
||||||
* **--direct (-D):** Restricts the list of packages to your direct dependencies.
|
* **--direct (-D):** Restricts the list of packages to your direct dependencies.
|
||||||
* **--strict:** Return a non-zero exit code when there are outdated packages.
|
* **--strict:** Return a non-zero exit code when there are outdated packages.
|
||||||
* **--format (-f):** Lets you pick between text (default) or json output format.
|
* **--format (-f):** Lets you pick between text (default) or json output format.
|
||||||
|
@ -589,6 +591,7 @@ The color coding is as such:
|
||||||
* **--major-only (-M):** Only shows packages that have major SemVer-compatible updates.
|
* **--major-only (-M):** Only shows packages that have major SemVer-compatible updates.
|
||||||
* **--minor-only (-m):** Only shows packages that have minor SemVer-compatible updates.
|
* **--minor-only (-m):** Only shows packages that have minor SemVer-compatible updates.
|
||||||
* **--patch-only (-p):** Only shows packages that have patch-level SemVer-compatible updates.
|
* **--patch-only (-p):** Only shows packages that have patch-level SemVer-compatible updates.
|
||||||
|
* **--sort-by-age (-A):** Displays the installed version's age, and sorts packages oldest first.
|
||||||
* **--format (-f):** Lets you pick between text (default) or json output format.
|
* **--format (-f):** Lets you pick between text (default) or json output format.
|
||||||
* **--no-dev:** Do not show outdated dev dependencies.
|
* **--no-dev:** Do not show outdated dev dependencies.
|
||||||
* **--locked:** Shows updates for packages from the lock file, regardless of what is currently in vendor dir.
|
* **--locked:** Shows updates for packages from the lock file, regardless of what is currently in vendor dir.
|
||||||
|
@ -1143,6 +1146,10 @@ If set to 1, this env suppresses a warning when Composer is running with the Xde
|
||||||
|
|
||||||
This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
|
This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
|
||||||
|
|
||||||
|
### COMPOSER_FUND
|
||||||
|
|
||||||
|
If set to 0, this env suppresses funding notices when installing.
|
||||||
|
|
||||||
### COMPOSER_HOME
|
### COMPOSER_HOME
|
||||||
|
|
||||||
The `COMPOSER_HOME` var allows you to change the Composer home directory. This
|
The `COMPOSER_HOME` var allows you to change the Composer home directory. This
|
||||||
|
@ -1237,6 +1244,11 @@ defaults to 12 and must be between 1 and 50. If your proxy has issues with
|
||||||
concurrency maybe you want to lower this. Increasing it should generally not result
|
concurrency maybe you want to lower this. Increasing it should generally not result
|
||||||
in performance gains.
|
in performance gains.
|
||||||
|
|
||||||
|
### COMPOSER_IPRESOLVE
|
||||||
|
|
||||||
|
Set to `4` or `6` to force IPv4 or IPv6 DNS resolution. This only works when the
|
||||||
|
curl extension is used for downloads.
|
||||||
|
|
||||||
### HTTP_PROXY_REQUEST_FULLURI
|
### HTTP_PROXY_REQUEST_FULLURI
|
||||||
|
|
||||||
If you use a proxy, but it does not support the request_fulluri flag, then you
|
If you use a proxy, but it does not support the request_fulluri flag, then you
|
||||||
|
|
|
@ -738,7 +738,7 @@ monolithic repository.
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
"type": "path",
|
"type": "path",
|
||||||
"url": "../../packages/my-package",
|
"url": "../../packages/*",
|
||||||
"options": {
|
"options": {
|
||||||
"symlink": false
|
"symlink": false
|
||||||
}
|
}
|
||||||
|
@ -772,7 +772,7 @@ The following modes exist:
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
"type": "path",
|
"type": "path",
|
||||||
"url": "../../packages/my-package",
|
"url": "../../packages/*",
|
||||||
"options": {
|
"options": {
|
||||||
"reference": "config"
|
"reference": "config"
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ autoloader are considered the application "runtime".
|
||||||
Starting with version 2.0, Composer makes [additional features](../07-runtime.md) (besides registering the class autoloader) available to the application runtime environment.
|
Starting with version 2.0, Composer makes [additional features](../07-runtime.md) (besides registering the class autoloader) available to the application runtime environment.
|
||||||
|
|
||||||
Similar to `composer-plugin-api`, not every Composer release adds new runtime features,
|
Similar to `composer-plugin-api`, not every Composer release adds new runtime features,
|
||||||
thus the version of `composer-runtimeapi` is also increased independently from Composer's version.
|
thus the version of `composer-runtime-api` is also increased independently from Composer's version.
|
||||||
|
|
||||||
## Composer package `composer`
|
## Composer package `composer`
|
||||||
|
|
||||||
|
|
|
@ -304,6 +304,11 @@ open stream: Operation timed out
|
||||||
We recommend you fix your IPv6 setup. If that is not possible, you can try the
|
We recommend you fix your IPv6 setup. If that is not possible, you can try the
|
||||||
following workarounds:
|
following workarounds:
|
||||||
|
|
||||||
|
**Generic Workaround:**
|
||||||
|
|
||||||
|
Set the [`COMPOSER_IPRESOLVE=4`](../03-cli.md#composer-ipresolve) environment variable which will force curl to resolve
|
||||||
|
domains using IPv4. This only works when the curl extension is used for downloads.
|
||||||
|
|
||||||
**Workaround Linux:**
|
**Workaround Linux:**
|
||||||
|
|
||||||
On linux, it seems that running this command helps to make ipv4 traffic have a
|
On linux, it seems that running this command helps to make ipv4 traffic have a
|
||||||
|
|
|
@ -55,6 +55,11 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Config/JsonConfigSource.php
|
path: ../src/Composer/Config/JsonConfigSource.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Call to function method_exists\\(\\) with \\$this\\(Composer\\\\Console\\\\Application\\) and 'setCatchErrors' will always evaluate to true\\.$#"
|
||||||
|
count: 2
|
||||||
|
path: ../src/Composer/Console/Application.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(string, string\\)\\: int, 'version_compare' given\\.$#"
|
message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(string, string\\)\\: int, 'version_compare' given\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
|
@ -85,6 +90,11 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Downloader/GzipDownloader.php
|
path: ../src/Composer/Downloader/GzipDownloader.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Call to function method_exists\\(\\) with Symfony\\\\Component\\\\Console\\\\Application and 'setCatchErrors' will always evaluate to true\\.$#"
|
||||||
|
count: 1
|
||||||
|
path: ../src/Composer/EventDispatcher/EventDispatcher.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
|
message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -270,11 +280,21 @@ parameters:
|
||||||
count: 2
|
count: 2
|
||||||
path: ../tests/Composer/Test/ConfigTest.php
|
path: ../tests/Composer/Test/ConfigTest.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Call to function method_exists\\(\\) with Composer\\\\Console\\\\Application and 'setCatchErrors' will always evaluate to true\\.$#"
|
||||||
|
count: 1
|
||||||
|
path: ../tests/Composer/Test/DocumentationTest.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{Composer\\\\Repository\\\\CompositeRepository, string\\} given\\.$#"
|
message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{Composer\\\\Repository\\\\CompositeRepository, string\\} given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: ../tests/Composer/Test/Repository/CompositeRepositoryTest.php
|
path: ../tests/Composer/Test/Repository/CompositeRepositoryTest.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Call to function method_exists\\(\\) with Composer\\\\Console\\\\Application and 'setCatchErrors' will always evaluate to true\\.$#"
|
||||||
|
count: 1
|
||||||
|
path: ../tests/Composer/Test/TestCase.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$object of method ReflectionProperty\\:\\:getValue\\(\\) expects object\\|null, object\\|string given\\.$#"
|
message: "#^Parameter \\#1 \\$object of method ReflectionProperty\\:\\:getValue\\(\\) expects object\\|null, object\\|string given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|
|
@ -765,16 +765,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Command/ShowCommand.php
|
path: ../src/Composer/Command/ShowCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in \\|\\|, array\\<string, Composer\\\\Package\\\\Link\\> given on the left side\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Command/ShowCommand.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in \\|\\|, array\\<string, Composer\\\\Package\\\\Link\\> given on the right side\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Command/ShowCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$arrayTree of method Composer\\\\Command\\\\ShowCommand\\:\\:displayPackageTree\\(\\) expects array\\<int, array\\<string, array\\|string\\>\\>, array\\<int, array\\<string, array\\<int, array\\<string, array\\|string\\>\\>\\|string\\|null\\>\\> given\\.$#"
|
message: "#^Parameter \\#1 \\$arrayTree of method Composer\\\\Command\\\\ShowCommand\\:\\:displayPackageTree\\(\\) expects array\\<int, array\\<string, array\\|string\\>\\>, array\\<int, array\\<string, array\\<int, array\\<string, array\\|string\\>\\>\\|string\\|null\\>\\> given\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
|
@ -4314,7 +4304,7 @@ parameters:
|
||||||
path: ../src/Composer/Util/Http/CurlDownloader.php
|
path: ../src/Composer/Util/Http/CurlDownloader.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#"
|
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Util/Http/CurlDownloader.php
|
path: ../src/Composer/Util/Http/CurlDownloader.php
|
||||||
|
|
||||||
|
|
|
@ -247,6 +247,7 @@ class Auditor
|
||||||
foreach ($packageAdvisories as $advisory) {
|
foreach ($packageAdvisories as $advisory) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'Package',
|
'Package',
|
||||||
|
'Severity',
|
||||||
'CVE',
|
'CVE',
|
||||||
'Title',
|
'Title',
|
||||||
'URL',
|
'URL',
|
||||||
|
@ -255,6 +256,7 @@ class Auditor
|
||||||
];
|
];
|
||||||
$row = [
|
$row = [
|
||||||
$advisory->packageName,
|
$advisory->packageName,
|
||||||
|
$this->getSeverity($advisory),
|
||||||
$this->getCVE($advisory),
|
$this->getCVE($advisory),
|
||||||
$advisory->title,
|
$advisory->title,
|
||||||
$this->getURL($advisory),
|
$this->getURL($advisory),
|
||||||
|
@ -289,6 +291,7 @@ class Auditor
|
||||||
$error[] = '--------';
|
$error[] = '--------';
|
||||||
}
|
}
|
||||||
$error[] = "Package: ".$advisory->packageName;
|
$error[] = "Package: ".$advisory->packageName;
|
||||||
|
$error[] = "Severity: ".$this->getSeverity($advisory);
|
||||||
$error[] = "CVE: ".$this->getCVE($advisory);
|
$error[] = "CVE: ".$this->getCVE($advisory);
|
||||||
$error[] = "Title: ".OutputFormatter::escape($advisory->title);
|
$error[] = "Title: ".OutputFormatter::escape($advisory->title);
|
||||||
$error[] = "URL: ".$this->getURL($advisory);
|
$error[] = "URL: ".$this->getURL($advisory);
|
||||||
|
@ -350,6 +353,15 @@ class Auditor
|
||||||
return $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
|
return $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getSeverity(SecurityAdvisory $advisory): string
|
||||||
|
{
|
||||||
|
if ($advisory->severity === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $advisory->severity;
|
||||||
|
}
|
||||||
|
|
||||||
private function getCVE(SecurityAdvisory $advisory): string
|
private function getCVE(SecurityAdvisory $advisory): string
|
||||||
{
|
{
|
||||||
if ($advisory->cve === null) {
|
if ($advisory->cve === null) {
|
||||||
|
|
|
@ -26,9 +26,9 @@ class IgnoredSecurityAdvisory extends SecurityAdvisory
|
||||||
/**
|
/**
|
||||||
* @param non-empty-array<array{name: string, remoteId: string}> $sources
|
* @param non-empty-array<array{name: string, remoteId: string}> $sources
|
||||||
*/
|
*/
|
||||||
public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $ignoreReason = null)
|
public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $ignoreReason = null, ?string $severity = null)
|
||||||
{
|
{
|
||||||
parent::__construct($packageName, $advisoryId, $affectedVersions, $title, $sources, $reportedAt, $cve, $link);
|
parent::__construct($packageName, $advisoryId, $affectedVersions, $title, $sources, $reportedAt, $cve, $link, $severity);
|
||||||
|
|
||||||
$this->ignoreReason = $ignoreReason;
|
$this->ignoreReason = $ignoreReason;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class PartialSecurityAdvisory implements JsonSerializable
|
||||||
{
|
{
|
||||||
$constraint = $parser->parseConstraints($data['affectedVersions']);
|
$constraint = $parser->parseConstraints($data['affectedVersions']);
|
||||||
if (isset($data['title'], $data['sources'], $data['reportedAt'])) {
|
if (isset($data['title'], $data['sources'], $data['reportedAt'])) {
|
||||||
return new SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null);
|
return new SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null, $data['severity'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new self($packageName, $data['advisoryId'], $constraint);
|
return new self($packageName, $data['advisoryId'], $constraint);
|
||||||
|
|
|
@ -47,10 +47,16 @@ class SecurityAdvisory extends PartialSecurityAdvisory
|
||||||
*/
|
*/
|
||||||
public $sources;
|
public $sources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
public $severity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param non-empty-array<array{name: string, remoteId: string}> $sources
|
* @param non-empty-array<array{name: string, remoteId: string}> $sources
|
||||||
*/
|
*/
|
||||||
public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null)
|
public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $severity = null)
|
||||||
{
|
{
|
||||||
parent::__construct($packageName, $advisoryId, $affectedVersions);
|
parent::__construct($packageName, $advisoryId, $affectedVersions);
|
||||||
|
|
||||||
|
@ -59,6 +65,7 @@ class SecurityAdvisory extends PartialSecurityAdvisory
|
||||||
$this->reportedAt = $reportedAt;
|
$this->reportedAt = $reportedAt;
|
||||||
$this->cve = $cve;
|
$this->cve = $cve;
|
||||||
$this->link = $link;
|
$this->link = $link;
|
||||||
|
$this->severity = $severity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +82,8 @@ class SecurityAdvisory extends PartialSecurityAdvisory
|
||||||
$this->reportedAt,
|
$this->reportedAt,
|
||||||
$this->cve,
|
$this->cve,
|
||||||
$this->link,
|
$this->link,
|
||||||
$ignoreReason
|
$ignoreReason,
|
||||||
|
$this->severity
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,23 @@
|
||||||
|
|
||||||
namespace Composer\Command;
|
namespace Composer\Command;
|
||||||
|
|
||||||
|
use Composer\Advisory\Auditor;
|
||||||
use Composer\Composer;
|
use Composer\Composer;
|
||||||
use Composer\Factory;
|
use Composer\Factory;
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
use Composer\Downloader\TransportException;
|
use Composer\Downloader\TransportException;
|
||||||
|
use Composer\IO\BufferIO;
|
||||||
|
use Composer\Json\JsonFile;
|
||||||
|
use Composer\Package\RootPackage;
|
||||||
|
use Composer\Package\Version\VersionParser;
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
|
use Composer\Repository\ComposerRepository;
|
||||||
|
use Composer\Repository\FilesystemRepository;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
use Composer\Repository\RootPackageRepository;
|
||||||
use Composer\Util\ConfigValidator;
|
use Composer\Util\ConfigValidator;
|
||||||
use Composer\Util\Git;
|
use Composer\Util\Git;
|
||||||
use Composer\Util\IniHelper;
|
use Composer\Util\IniHelper;
|
||||||
|
@ -153,10 +162,13 @@ EOT
|
||||||
$io->write('Checking pubkeys: ', false);
|
$io->write('Checking pubkeys: ', false);
|
||||||
$this->outputResult($this->checkPubKeys($config));
|
$this->outputResult($this->checkPubKeys($config));
|
||||||
|
|
||||||
$io->write('Checking composer version: ', false);
|
$io->write('Checking Composer version: ', false);
|
||||||
$this->outputResult($this->checkVersion($config));
|
$this->outputResult($this->checkVersion($config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$io->write('Checking Composer and its dependencies for vulnerabilities: ', false);
|
||||||
|
$this->outputResult($this->checkComposerAudit($config));
|
||||||
|
|
||||||
$io->write(sprintf('Composer version: <comment>%s</comment>', Composer::getVersion()));
|
$io->write(sprintf('Composer version: <comment>%s</comment>', Composer::getVersion()));
|
||||||
|
|
||||||
$platformOverrides = $config->get('platform') ?: [];
|
$platformOverrides = $config->get('platform') ?: [];
|
||||||
|
@ -438,6 +450,48 @@ EOT
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|true
|
||||||
|
*/
|
||||||
|
private function checkComposerAudit(Config $config)
|
||||||
|
{
|
||||||
|
$result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
|
||||||
|
if ($result !== true) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$auditor = new Auditor();
|
||||||
|
$repoSet = new RepositorySet();
|
||||||
|
$installedJson = new JsonFile(__DIR__ . '/../../../vendor/composer/installed.json');
|
||||||
|
if (!$installedJson->exists()) {
|
||||||
|
return '<warning>Could not find Composer\'s installed.json, this must be a non-standard Composer installation.</>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$localRepo = new FilesystemRepository($installedJson);
|
||||||
|
$version = Composer::getVersion();
|
||||||
|
$packages = $localRepo->getCanonicalPackages();
|
||||||
|
if ($version !== '@package_version@') {
|
||||||
|
$versionParser = new VersionParser();
|
||||||
|
$normalizedVersion = $versionParser->normalize($version);
|
||||||
|
$rootPkg = new RootPackage('composer/composer', $normalizedVersion, $version);
|
||||||
|
$packages[] = $rootPkg;
|
||||||
|
}
|
||||||
|
$repoSet->addRepository(new ComposerRepository(['type' => 'composer', 'url' => 'https://packagist.org'], new NullIO(), $config, $this->httpDownloader));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$io = new BufferIO();
|
||||||
|
$result = $auditor->audit($io, $repoSet, $packages, Auditor::FORMAT_TABLE, true, [], Auditor::ABANDONED_IGNORE);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return '<warning>Failed performing audit: '.$e->getMessage().'</>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result > 0) {
|
||||||
|
return '<error>Audit found some issues:</>' . PHP_EOL . $io->getOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private function getCurlVersion(): string
|
private function getCurlVersion(): string
|
||||||
{
|
{
|
||||||
if (extension_loaded('curl')) {
|
if (extension_loaded('curl')) {
|
||||||
|
@ -499,7 +553,7 @@ EOT
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
foreach ($result as $message) {
|
foreach ($result as $message) {
|
||||||
$io->write($message);
|
$io->write(trim($message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -722,6 +776,11 @@ EOT
|
||||||
$out($iniMessage, 'comment');
|
$out($iniMessage, 'comment');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) {
|
||||||
|
$warnings['ipresolve'] = true;
|
||||||
|
$out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment');
|
||||||
|
}
|
||||||
|
|
||||||
return count($warnings) === 0 && count($errors) === 0 ? true : $output;
|
return count($warnings) === 0 && count($errors) === 0 ? true : $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ class OutdatedCommand extends BaseCommand
|
||||||
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'),
|
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'),
|
||||||
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'),
|
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'),
|
||||||
new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'),
|
new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'),
|
||||||
|
new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first.'),
|
||||||
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
|
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
|
||||||
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)),
|
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)),
|
||||||
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
|
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
|
||||||
|
@ -97,6 +98,9 @@ EOT
|
||||||
if ($input->getOption('no-dev')) {
|
if ($input->getOption('no-dev')) {
|
||||||
$args['--no-dev'] = true;
|
$args['--no-dev'] = true;
|
||||||
}
|
}
|
||||||
|
if ($input->getOption('sort-by-age')) {
|
||||||
|
$args['--sort-by-age'] = true;
|
||||||
|
}
|
||||||
$args['--ignore-platform-req'] = $input->getOption('ignore-platform-req');
|
$args['--ignore-platform-req'] = $input->getOption('ignore-platform-req');
|
||||||
if ($input->getOption('ignore-platform-reqs')) {
|
if ($input->getOption('ignore-platform-reqs')) {
|
||||||
$args['--ignore-platform-reqs'] = true;
|
$args['--ignore-platform-reqs'] = true;
|
||||||
|
|
|
@ -349,6 +349,10 @@ EOT
|
||||||
}
|
}
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
if ($input->getOption('dry-run') && $this->newlyCreated) {
|
||||||
|
@unlink($this->json->getPath());
|
||||||
|
}
|
||||||
|
|
||||||
$signalHandler->unregister();
|
$signalHandler->unregister();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,10 @@ EOT
|
||||||
return $this->rollback($output, $rollbackDir, $localFilename);
|
return $this->rollback($output, $rollbackDir, $localFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') {
|
||||||
|
$input->setArgument('version', null);
|
||||||
|
}
|
||||||
|
|
||||||
$latest = $versionsUtil->getLatest();
|
$latest = $versionsUtil->getLatest();
|
||||||
$latestStable = $versionsUtil->getLatest('stable');
|
$latestStable = $versionsUtil->getLatest('stable');
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -27,6 +27,7 @@ use Composer\Package\Version\VersionSelector;
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
|
use Composer\Repository\ArrayRepository;
|
||||||
use Composer\Repository\InstalledArrayRepository;
|
use Composer\Repository\InstalledArrayRepository;
|
||||||
use Composer\Repository\ComposerRepository;
|
use Composer\Repository\ComposerRepository;
|
||||||
use Composer\Repository\CompositeRepository;
|
use Composer\Repository\CompositeRepository;
|
||||||
|
@ -98,6 +99,7 @@ class ShowCommand extends BaseCommand
|
||||||
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'),
|
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'),
|
||||||
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'),
|
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'),
|
||||||
new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'),
|
new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'),
|
||||||
|
new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first. Use with the --latest or --outdated option.'),
|
||||||
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
|
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
|
||||||
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
|
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
|
||||||
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
|
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
|
||||||
|
@ -141,7 +143,7 @@ EOT
|
||||||
$composer = $this->tryComposer();
|
$composer = $this->tryComposer();
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
|
|
||||||
if ($input->getOption('installed')) {
|
if ($input->getOption('installed') && !$input->getOption('self')) {
|
||||||
$io->writeError('<warning>You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>');
|
$io->writeError('<warning>You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +200,7 @@ EOT
|
||||||
$platformRepo = new PlatformRepository([], $platformOverrides);
|
$platformRepo = new PlatformRepository([], $platformOverrides);
|
||||||
$lockedRepo = null;
|
$lockedRepo = null;
|
||||||
|
|
||||||
if ($input->getOption('self')) {
|
if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) {
|
||||||
$package = clone $this->requireComposer()->getPackage();
|
$package = clone $this->requireComposer()->getPackage();
|
||||||
if ($input->getOption('name-only')) {
|
if ($input->getOption('name-only')) {
|
||||||
$io->write($package->getName());
|
$io->write($package->getName());
|
||||||
|
@ -242,6 +244,9 @@ EOT
|
||||||
}
|
}
|
||||||
$locker = $composer->getLocker();
|
$locker = $composer->getLocker();
|
||||||
$lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev'));
|
$lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev'));
|
||||||
|
if ($input->getOption('self')) {
|
||||||
|
$lockedRepo->addPackage(clone $composer->getPackage());
|
||||||
|
}
|
||||||
$repos = $installedRepo = new InstalledRepository([$lockedRepo]);
|
$repos = $installedRepo = new InstalledRepository([$lockedRepo]);
|
||||||
} else {
|
} else {
|
||||||
// --installed / default case
|
// --installed / default case
|
||||||
|
@ -249,17 +254,30 @@ EOT
|
||||||
$composer = $this->requireComposer();
|
$composer = $this->requireComposer();
|
||||||
}
|
}
|
||||||
$rootPkg = $composer->getPackage();
|
$rootPkg = $composer->getPackage();
|
||||||
$repos = $installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]);
|
|
||||||
|
|
||||||
|
$rootRepo = new InstalledArrayRepository();
|
||||||
|
if ($input->getOption('self')) {
|
||||||
|
$rootRepo = new RootPackageRepository(clone $rootPkg);
|
||||||
|
}
|
||||||
if ($input->getOption('no-dev')) {
|
if ($input->getOption('no-dev')) {
|
||||||
$packages = RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg);
|
$packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg);
|
||||||
$repos = $installedRepo = new InstalledRepository([new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface {
|
$repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface {
|
||||||
return clone $pkg;
|
return clone $pkg;
|
||||||
}, $packages))]);
|
}, $packages))]);
|
||||||
|
} else {
|
||||||
|
$repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$installedRepo->getPackages() && ($rootPkg->getRequires() || $rootPkg->getDevRequires())) {
|
if (!$installedRepo->getPackages()) {
|
||||||
$io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
|
$hasNonPlatformReqs = static function (array $reqs): bool {
|
||||||
|
return (bool) array_filter(array_keys($reqs), function (string $name) {
|
||||||
|
return !PlatformRepository::isPlatformPackage($name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($hasNonPlatformReqs($rootPkg->getRequires()) || $hasNonPlatformReqs($rootPkg->getDevRequires())) {
|
||||||
|
$io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,6 +299,12 @@ EOT
|
||||||
} elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) {
|
} elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) {
|
||||||
[$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version'));
|
[$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version'));
|
||||||
|
|
||||||
|
if (isset($package) && $input->getOption('direct')) {
|
||||||
|
if (!in_array($package->getName(), $this->getRootRequires(), true)) {
|
||||||
|
throw new \InvalidArgumentException('Package "' . $package->getName() . '" is installed but not a direct dependent of the root package.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isset($package)) {
|
if (!isset($package)) {
|
||||||
$options = $input->getOptions();
|
$options = $input->getOptions();
|
||||||
$hint = '';
|
$hint = '';
|
||||||
|
@ -293,7 +317,7 @@ EOT
|
||||||
if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) {
|
if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) {
|
||||||
$hint .= ', try using --platform (-p) to show platform packages';
|
$hint .= ', try using --platform (-p) to show platform packages';
|
||||||
}
|
}
|
||||||
if (!$input->getOption('all')) {
|
if (!$input->getOption('all') && !$input->getOption('available')) {
|
||||||
$hint .= ', try using --available (-a) to show all available packages';
|
$hint .= ', try using --available (-a) to show all available packages';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,7 +474,7 @@ EOT
|
||||||
if (isset($packages[$type])) {
|
if (isset($packages[$type])) {
|
||||||
ksort($packages[$type]);
|
ksort($packages[$type]);
|
||||||
|
|
||||||
$nameLength = $versionLength = $latestLength = 0;
|
$nameLength = $versionLength = $latestLength = $releaseDateLength = 0;
|
||||||
|
|
||||||
if ($showLatest && $showVersion) {
|
if ($showLatest && $showVersion) {
|
||||||
foreach ($packages[$type] as $package) {
|
foreach ($packages[$type] as $package) {
|
||||||
|
@ -469,9 +493,20 @@ EOT
|
||||||
$writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
|
$writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
|
||||||
$writeLatest = $writeVersion && $showLatest;
|
$writeLatest = $writeVersion && $showLatest;
|
||||||
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
|
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
|
||||||
|
$writeReleaseDate = $writeLatest && $input->getOption('sort-by-age');
|
||||||
|
|
||||||
$hasOutdatedPackages = false;
|
$hasOutdatedPackages = false;
|
||||||
|
|
||||||
|
if ($input->getOption('sort-by-age')) {
|
||||||
|
usort($packages[$type], function ($a, $b) {
|
||||||
|
if (is_object($a) && is_object($b)) {
|
||||||
|
return $a->getReleaseDate() <=> $b->getReleaseDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$viewData[$type] = [];
|
$viewData[$type] = [];
|
||||||
foreach ($packages[$type] as $package) {
|
foreach ($packages[$type] as $package) {
|
||||||
$packageViewData = [];
|
$packageViewData = [];
|
||||||
|
@ -505,6 +540,17 @@ EOT
|
||||||
$packageViewData['version'] = $package->getFullPrettyVersion();
|
$packageViewData['version'] = $package->getFullPrettyVersion();
|
||||||
$versionLength = max($versionLength, strlen($package->getFullPrettyVersion()));
|
$versionLength = max($versionLength, strlen($package->getFullPrettyVersion()));
|
||||||
}
|
}
|
||||||
|
if ($writeReleaseDate) {
|
||||||
|
if ($package->getReleaseDate() !== null) {
|
||||||
|
$packageViewData['release-age'] = str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate()));
|
||||||
|
if (!str_contains($packageViewData['release-age'], ' old')) {
|
||||||
|
$packageViewData['release-age'] = 'from '.$packageViewData['release-age'];
|
||||||
|
}
|
||||||
|
$releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age']));
|
||||||
|
} else {
|
||||||
|
$packageViewData['release-age'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($writeLatest && $latestPackage) {
|
if ($writeLatest && $latestPackage) {
|
||||||
$packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
|
$packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
|
||||||
$packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
|
$packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
|
||||||
|
@ -552,7 +598,9 @@ EOT
|
||||||
'nameLength' => $nameLength,
|
'nameLength' => $nameLength,
|
||||||
'versionLength' => $versionLength,
|
'versionLength' => $versionLength,
|
||||||
'latestLength' => $latestLength,
|
'latestLength' => $latestLength,
|
||||||
|
'releaseDateLength' => $releaseDateLength,
|
||||||
'writeLatest' => $writeLatest,
|
'writeLatest' => $writeLatest,
|
||||||
|
'writeReleaseDate' => $writeReleaseDate,
|
||||||
];
|
];
|
||||||
if ($input->getOption('strict') && $hasOutdatedPackages) {
|
if ($input->getOption('strict') && $hasOutdatedPackages) {
|
||||||
$exitCode = 1;
|
$exitCode = 1;
|
||||||
|
@ -588,11 +636,14 @@ EOT
|
||||||
$nameLength = $viewMetaData[$type]['nameLength'];
|
$nameLength = $viewMetaData[$type]['nameLength'];
|
||||||
$versionLength = $viewMetaData[$type]['versionLength'];
|
$versionLength = $viewMetaData[$type]['versionLength'];
|
||||||
$latestLength = $viewMetaData[$type]['latestLength'];
|
$latestLength = $viewMetaData[$type]['latestLength'];
|
||||||
|
$releaseDateLength = $viewMetaData[$type]['releaseDateLength'];
|
||||||
$writeLatest = $viewMetaData[$type]['writeLatest'];
|
$writeLatest = $viewMetaData[$type]['writeLatest'];
|
||||||
|
$writeReleaseDate = $viewMetaData[$type]['writeReleaseDate'];
|
||||||
|
|
||||||
$versionFits = $nameLength + $versionLength + 3 <= $width;
|
$versionFits = $nameLength + $versionLength + 3 <= $width;
|
||||||
$latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width;
|
$latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width;
|
||||||
$descriptionFits = $nameLength + $versionLength + $latestLength + 24 <= $width;
|
$releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width;
|
||||||
|
$descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width;
|
||||||
|
|
||||||
if ($latestFits && !$io->isDecorated()) {
|
if ($latestFits && !$io->isDecorated()) {
|
||||||
$latestLength += 2;
|
$latestLength += 2;
|
||||||
|
@ -620,14 +671,14 @@ EOT
|
||||||
$io->writeError('');
|
$io->writeError('');
|
||||||
$io->writeError('<info>Direct dependencies required in composer.json:</>');
|
$io->writeError('<info>Direct dependencies required in composer.json:</>');
|
||||||
if (\count($directDeps) > 0) {
|
if (\count($directDeps) > 0) {
|
||||||
$this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
$this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
|
||||||
} else {
|
} else {
|
||||||
$io->writeError('Everything up to date');
|
$io->writeError('Everything up to date');
|
||||||
}
|
}
|
||||||
$io->writeError('');
|
$io->writeError('');
|
||||||
$io->writeError('<info>Transitive dependencies not required in composer.json:</>');
|
$io->writeError('<info>Transitive dependencies not required in composer.json:</>');
|
||||||
if (\count($transitiveDeps) > 0) {
|
if (\count($transitiveDeps) > 0) {
|
||||||
$this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
$this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
|
||||||
} else {
|
} else {
|
||||||
$io->writeError('Everything up to date');
|
$io->writeError('Everything up to date');
|
||||||
}
|
}
|
||||||
|
@ -635,7 +686,7 @@ EOT
|
||||||
if ($writeLatest && \count($packages) === 0) {
|
if ($writeLatest && \count($packages) === 0) {
|
||||||
$io->writeError('All your direct dependencies are up to date');
|
$io->writeError('All your direct dependencies are up to date');
|
||||||
} else {
|
} else {
|
||||||
$this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
$this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,11 +702,12 @@ EOT
|
||||||
/**
|
/**
|
||||||
* @param array<array{name: string, direct-dependency?: bool, version?: string, latest?: string, latest-status?: string, description?: string|null, path?: string|null, source?: string|null, homepage?: string|null, warning?: string, abandoned?: bool|string}> $packages
|
* @param array<array{name: string, direct-dependency?: bool, version?: string, latest?: string, latest-status?: string, description?: string|null, path?: string|null, source?: string|null, homepage?: string|null, warning?: string, abandoned?: bool|string}> $packages
|
||||||
*/
|
*/
|
||||||
private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength): void
|
private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength): void
|
||||||
{
|
{
|
||||||
$padName = $writeVersion || $writeLatest || $writeDescription;
|
$padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription;
|
||||||
$padVersion = $writeLatest || $writeDescription;
|
$padVersion = $writeLatest || $writeReleaseDate || $writeDescription;
|
||||||
$padLatest = $writeDescription;
|
$padLatest = $writeDescription || $writeReleaseDate;
|
||||||
|
$padReleaseDate = $writeDescription;
|
||||||
foreach ($packages as $package) {
|
foreach ($packages as $package) {
|
||||||
$link = $package['source'] ?? $package['homepage'] ?? '';
|
$link = $package['source'] ?? $package['homepage'] ?? '';
|
||||||
if ($link !== '') {
|
if ($link !== '') {
|
||||||
|
@ -674,10 +726,13 @@ EOT
|
||||||
$latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion;
|
$latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion;
|
||||||
}
|
}
|
||||||
$io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '</' . $style . '>', false);
|
$io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '</' . $style . '>', false);
|
||||||
|
if ($writeReleaseDate && isset($package['release-age'])) {
|
||||||
|
$io->write(' '.str_pad($package['release-age'], ($padReleaseDate ? $releaseDateLength : 0), ' '), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isset($package['description']) && $writeDescription) {
|
if (isset($package['description']) && $writeDescription) {
|
||||||
$description = strtok($package['description'], "\r\n");
|
$description = strtok($package['description'], "\r\n");
|
||||||
$remaining = $width - $nameLength - $versionLength - 4;
|
$remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4;
|
||||||
if ($writeLatest) {
|
if ($writeLatest) {
|
||||||
$remaining -= $latestLength;
|
$remaining -= $latestLength;
|
||||||
}
|
}
|
||||||
|
@ -806,14 +861,20 @@ EOT
|
||||||
*/
|
*/
|
||||||
protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
|
protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
|
||||||
{
|
{
|
||||||
|
$isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package);
|
||||||
|
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
$io->write('<info>name</info> : ' . $package->getPrettyName());
|
$io->write('<info>name</info> : ' . $package->getPrettyName());
|
||||||
$io->write('<info>descrip.</info> : ' . $package->getDescription());
|
$io->write('<info>descrip.</info> : ' . $package->getDescription());
|
||||||
$io->write('<info>keywords</info> : ' . implode(', ', $package->getKeywords() ?: []));
|
$io->write('<info>keywords</info> : ' . implode(', ', $package->getKeywords() ?: []));
|
||||||
$this->printVersions($package, $versions, $installedRepo);
|
$this->printVersions($package, $versions, $installedRepo);
|
||||||
|
if ($isInstalledPackage && $package->getReleaseDate() !== null) {
|
||||||
|
$io->write('<info>released</info> : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate()));
|
||||||
|
}
|
||||||
if ($latestPackage) {
|
if ($latestPackage) {
|
||||||
$style = $this->getVersionStyle($latestPackage, $package);
|
$style = $this->getVersionStyle($latestPackage, $package);
|
||||||
$io->write('<info>latest</info> : <'.$style.'>' . $latestPackage->getPrettyVersion() . '</'.$style.'>');
|
$releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate());
|
||||||
|
$io->write('<info>latest</info> : <'.$style.'>' . $latestPackage->getPrettyVersion() . '</'.$style.'>' . $releasedTime);
|
||||||
} else {
|
} else {
|
||||||
$latestPackage = $package;
|
$latestPackage = $package;
|
||||||
}
|
}
|
||||||
|
@ -822,7 +883,7 @@ EOT
|
||||||
$io->write('<info>homepage</info> : ' . $package->getHomepage());
|
$io->write('<info>homepage</info> : ' . $package->getHomepage());
|
||||||
$io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
|
$io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
|
||||||
$io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
|
$io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
|
||||||
if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) {
|
if ($isInstalledPackage) {
|
||||||
$path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
|
$path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
|
||||||
if (is_string($path)) {
|
if (is_string($path)) {
|
||||||
$io->write('<info>path</info> : ' . realpath($path));
|
$io->write('<info>path</info> : ' . realpath($path));
|
||||||
|
@ -993,6 +1054,10 @@ EOT
|
||||||
} else {
|
} else {
|
||||||
$json['path'] = null;
|
$json['path'] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($package->getReleaseDate() !== null) {
|
||||||
|
$json['released'] = $package->getReleaseDate()->format(DATE_ATOM);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
|
if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
|
||||||
|
@ -1447,4 +1512,30 @@ EOT
|
||||||
|
|
||||||
return $this->repositorySet;
|
return $this->repositorySet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getRelativeTime(\DateTimeInterface $releaseDate): string
|
||||||
|
{
|
||||||
|
if ($releaseDate->format('Ymd') === date('Ymd')) {
|
||||||
|
return 'today';
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $releaseDate->diff(new \DateTimeImmutable());
|
||||||
|
if ($diff->days < 7) {
|
||||||
|
return 'this week';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diff->days < 14) {
|
||||||
|
return 'last week';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diff->m < 1 && $diff->days < 31) {
|
||||||
|
return floor($diff->days / 7) . ' weeks ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diff->y < 1) {
|
||||||
|
return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
|
||||||
use Composer\Package\AliasPackage;
|
use Composer\Package\AliasPackage;
|
||||||
use Composer\Package\BasePackage;
|
use Composer\Package\BasePackage;
|
||||||
use Composer\Package\Package;
|
use Composer\Package\Package;
|
||||||
|
use Composer\Pcre\Preg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
@ -111,6 +112,14 @@ class LockTransaction extends Transaction
|
||||||
if ($package->getName() === $presentPackage->getName() && $package->getVersion() === $presentPackage->getVersion()) {
|
if ($package->getName() === $presentPackage->getName() && $package->getVersion() === $presentPackage->getVersion()) {
|
||||||
if ($presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) {
|
if ($presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) {
|
||||||
$package->setSourceDistReferences($presentPackage->getSourceReference());
|
$package->setSourceDistReferences($presentPackage->getSourceReference());
|
||||||
|
// if the dist url is not one of those handled gracefully by setSourceDistReferences then we should overwrite it with the old one
|
||||||
|
if ($package->getDistUrl() !== null && !Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) {
|
||||||
|
$package->setDistUrl($presentPackage->getDistUrl());
|
||||||
|
}
|
||||||
|
$package->setDistType($presentPackage->getDistType());
|
||||||
|
if ($package instanceof Package) {
|
||||||
|
$package->setDistSha1Checksum($presentPackage->getDistSha1Checksum());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($presentPackage->getReleaseDate() !== null && $package instanceof Package) {
|
if ($presentPackage->getReleaseDate() !== null && $package instanceof Package) {
|
||||||
$package->setReleaseDate($presentPackage->getReleaseDate());
|
$package->setReleaseDate($presentPackage->getReleaseDate());
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface DvcsDownloaderInterface
|
||||||
/**
|
/**
|
||||||
* Checks for unpushed changes to a current branch
|
* Checks for unpushed changes to a current branch
|
||||||
*
|
*
|
||||||
* @param PackageInterface $package package directory
|
* @param PackageInterface $package package instance
|
||||||
* @param string $path package directory
|
* @param string $path package directory
|
||||||
* @return string|null changes or null
|
* @return string|null changes or null
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -458,7 +458,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
/**
|
/**
|
||||||
* Process the download url
|
* Process the download url
|
||||||
*
|
*
|
||||||
* @param PackageInterface $package package the url is coming from
|
* @param PackageInterface $package package instance
|
||||||
* @param non-empty-string $url download url
|
* @param non-empty-string $url download url
|
||||||
* @throws \RuntimeException If any problem with the url
|
* @throws \RuntimeException If any problem with the url
|
||||||
* @return non-empty-string url
|
* @return non-empty-string url
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface VcsCapableDownloaderInterface
|
||||||
/**
|
/**
|
||||||
* Gets the VCS Reference for the package at path
|
* Gets the VCS Reference for the package at path
|
||||||
*
|
*
|
||||||
* @param PackageInterface $package package directory
|
* @param PackageInterface $package package instance
|
||||||
* @param string $path package directory
|
* @param string $path package directory
|
||||||
* @return string|null reference or null
|
* @return string|null reference or null
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,4 +20,9 @@ final class IgnoreAllPlatformRequirementFilter implements PlatformRequirementFil
|
||||||
{
|
{
|
||||||
return PlatformRepository::isPlatformPackage($req);
|
return PlatformRepository::isPlatformPackage($req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isUpperBoundIgnored(string $req): bool
|
||||||
|
{
|
||||||
|
return $this->isIgnored($req);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,15 @@ final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFi
|
||||||
return Preg::isMatch($this->ignoreRegex, $req);
|
return Preg::isMatch($this->ignoreRegex, $req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isUpperBoundIgnored(string $req): bool
|
||||||
|
{
|
||||||
|
if (!PlatformRepository::isPlatformPackage($req)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->isIgnored($req) || Preg::isMatch($this->ignoreUpperBoundRegex, $req);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $allowUpperBoundOverride For conflicts we do not want the upper bound to be skipped
|
* @param bool $allowUpperBoundOverride For conflicts we do not want the upper bound to be skipped
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,4 +21,12 @@ final class IgnoreNothingPlatformRequirementFilter implements PlatformRequiremen
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
public function isUpperBoundIgnored(string $req): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,6 @@ namespace Composer\Filter\PlatformRequirementFilter;
|
||||||
interface PlatformRequirementFilterInterface
|
interface PlatformRequirementFilterInterface
|
||||||
{
|
{
|
||||||
public function isIgnored(string $req): bool;
|
public function isIgnored(string $req): bool;
|
||||||
|
|
||||||
|
public function isUpperBoundIgnored(string $req): bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,22 +372,30 @@ class Installer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$fundingCount = 0;
|
$fundEnv = Platform::getEnv('COMPOSER_FUND');
|
||||||
foreach ($localRepo->getPackages() as $package) {
|
$showFunding = true;
|
||||||
if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
|
if (is_numeric($fundEnv)) {
|
||||||
$fundingCount++;
|
$showFunding = intval($fundEnv) !== 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ($fundingCount > 0) {
|
|
||||||
$this->io->writeError([
|
if ($showFunding) {
|
||||||
sprintf(
|
$fundingCount = 0;
|
||||||
"<info>%d package%s you are using %s looking for funding.</info>",
|
foreach ($localRepo->getPackages() as $package) {
|
||||||
$fundingCount,
|
if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
|
||||||
1 === $fundingCount ? '' : 's',
|
$fundingCount++;
|
||||||
1 === $fundingCount ? 'is' : 'are'
|
}
|
||||||
),
|
}
|
||||||
'<info>Use the `composer fund` command to find out more!</info>',
|
if ($fundingCount > 0) {
|
||||||
]);
|
$this->io->writeError([
|
||||||
|
sprintf(
|
||||||
|
"<info>%d package%s you are using %s looking for funding.</info>",
|
||||||
|
$fundingCount,
|
||||||
|
1 === $fundingCount ? '' : 's',
|
||||||
|
1 === $fundingCount ? 'is' : 'are'
|
||||||
|
),
|
||||||
|
'<info>Use the `composer fund` command to find out more!</info>',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->runScripts) {
|
if ($this->runScripts) {
|
||||||
|
@ -605,7 +613,14 @@ class Installer
|
||||||
|
|
||||||
// output op if lock file is enabled, but alias op only in debug verbosity
|
// output op if lock file is enabled, but alias op only in debug verbosity
|
||||||
if ($this->config->get('lock') && (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) {
|
if ($this->config->get('lock') && (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) {
|
||||||
$this->io->writeError(' - ' . $operation->show(true));
|
$sourceRepo = '';
|
||||||
|
if ($this->io->isVeryVerbose() && false === strpos($operation->getOperationType(), 'Alias')) {
|
||||||
|
$operationPkg = ($operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage());
|
||||||
|
if ($operationPkg->getRepository() !== null) {
|
||||||
|
$sourceRepo = ' from ' . $operationPkg->getRepository()->getRepoName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->io->writeError(' - ' . $operation->show(true) . $sourceRepo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,10 +83,10 @@ class VersionBumper
|
||||||
$pattern = '{
|
$pattern = '{
|
||||||
(?<=,|\ |\||^) # leading separator
|
(?<=,|\ |\||^) # leading separator
|
||||||
(?P<constraint>
|
(?P<constraint>
|
||||||
\^'.$major.'(?:\.\d+)* # e.g. ^2.anything
|
\^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything
|
||||||
| ~'.$major.'(?:\.\d+){0,2} # e.g. ~2 or ~2.2 or ~2.2.2 but no more
|
| ~v?'.$major.'(?:\.\d+){0,2} # e.g. ~2 or ~2.2 or ~2.2.2 but no more
|
||||||
| '.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc
|
| v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc
|
||||||
| >=\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc
|
| >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc
|
||||||
| \* # full wildcard
|
| \* # full wildcard
|
||||||
)
|
)
|
||||||
(?=,|$|\ |\||@) # trailing separator
|
(?=,|$|\ |\||@) # trailing separator
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
namespace Composer\Package\Version;
|
namespace Composer\Package\Version;
|
||||||
|
|
||||||
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
|
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
|
||||||
|
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
|
||||||
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
|
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
|
||||||
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
|
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
@ -130,6 +131,13 @@ class VersionSelector
|
||||||
// constraint satisfied, go to next require
|
// constraint satisfied, go to next require
|
||||||
continue 2;
|
continue 2;
|
||||||
}
|
}
|
||||||
|
if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) {
|
||||||
|
$filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint());
|
||||||
|
if ($filteredConstraint->matches($providedConstraint)) {
|
||||||
|
// constraint satisfied with the upper bound ignored, go to next require
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// constraint not satisfied
|
// constraint not satisfied
|
||||||
|
@ -182,6 +190,7 @@ class VersionSelector
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* * 1.2.1 -> ^1.2
|
* * 1.2.1 -> ^1.2
|
||||||
|
* * 1.2.1.2 -> ^1.2
|
||||||
* * 1.2 -> ^1.2
|
* * 1.2 -> ^1.2
|
||||||
* * v3.2.1 -> ^3.2
|
* * v3.2.1 -> ^3.2
|
||||||
* * 2.0-beta.1 -> ^2.0@beta
|
* * 2.0-beta.1 -> ^2.0@beta
|
||||||
|
@ -227,7 +236,7 @@ class VersionSelector
|
||||||
$semanticVersionParts = explode('.', $version);
|
$semanticVersionParts = explode('.', $version);
|
||||||
|
|
||||||
// check to see if we have a semver-looking version
|
// check to see if we have a semver-looking version
|
||||||
if (count($semanticVersionParts) === 4 && Preg::isMatch('{^0\D?}', $semanticVersionParts[3])) {
|
if (count($semanticVersionParts) === 4 && Preg::isMatch('{^\d+\D?}', $semanticVersionParts[3])) {
|
||||||
// remove the last parts (i.e. the patch version number and any extra)
|
// remove the last parts (i.e. the patch version number and any extra)
|
||||||
if ($semanticVersionParts[0] === '0') {
|
if ($semanticVersionParts[0] === '0') {
|
||||||
unset($semanticVersionParts[3]);
|
unset($semanticVersionParts[3]);
|
||||||
|
|
|
@ -109,7 +109,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
private $partialPackagesByName = null;
|
private $partialPackagesByName = null;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $displayedWarningAboutNonMatchingPackageIndex = false;
|
private $displayedWarningAboutNonMatchingPackageIndex = false;
|
||||||
/** @var array{metadata: bool, query-all: bool, api-url: string|null}|null */
|
/** @var array{metadata: bool, api-url: string|null}|null */
|
||||||
private $securityAdvisoryConfig = null;
|
private $securityAdvisoryConfig = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -637,6 +637,15 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
|
|
||||||
$apiUrl = $this->securityAdvisoryConfig['api-url'];
|
$apiUrl = $this->securityAdvisoryConfig['api-url'];
|
||||||
|
|
||||||
|
// respect available-package-patterns / available-packages directives from the repo
|
||||||
|
if ($this->hasAvailablePackageList) {
|
||||||
|
foreach ($packageConstraintMap as $name => $constraint) {
|
||||||
|
if (!$this->lazyProvidersRepoContains(strtolower($name))) {
|
||||||
|
unset($packageConstraintMap[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$parser = new VersionParser();
|
$parser = new VersionParser();
|
||||||
/**
|
/**
|
||||||
* @param array<mixed> $data
|
* @param array<mixed> $data
|
||||||
|
@ -700,8 +709,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
$options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]);
|
$options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]);
|
||||||
|
|
||||||
$response = $this->httpDownloader->get($apiUrl, $options);
|
$response = $this->httpDownloader->get($apiUrl, $options);
|
||||||
|
$warned = false;
|
||||||
/** @var string $name */
|
/** @var string $name */
|
||||||
foreach ($response->decodeJson()['advisories'] as $name => $list) {
|
foreach ($response->decodeJson()['advisories'] as $name => $list) {
|
||||||
|
if (!isset($packageConstraintMap[$name])) {
|
||||||
|
if (!$warned) {
|
||||||
|
$this->io->writeError('<warning>'.$this->getRepoName().' returned names which were not requested in response to the security-advisories API. '.$name.' was not requested but is present in the response. Requested names were: '.implode(', ', array_keys($packageConstraintMap)).'</warning>');
|
||||||
|
$warned = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (count($list) > 0) {
|
if (count($list) > 0) {
|
||||||
$advisories[$name] = array_filter(array_map(
|
$advisories[$name] = array_filter(array_map(
|
||||||
static function ($data) use ($name, $create) {
|
static function ($data) use ($name, $create) {
|
||||||
|
@ -1248,9 +1265,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
if (isset($data['security-advisories']) && is_array($data['security-advisories'])) {
|
if (isset($data['security-advisories']) && is_array($data['security-advisories'])) {
|
||||||
$this->securityAdvisoryConfig = [
|
$this->securityAdvisoryConfig = [
|
||||||
'metadata' => $data['security-advisories']['metadata'] ?? false,
|
'metadata' => $data['security-advisories']['metadata'] ?? false,
|
||||||
'api-url' => $data['security-advisories']['api-url'] ?? null,
|
'api-url' => isset($data['security-advisories']['api-url']) && is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null,
|
||||||
'query-all' => $data['security-advisories']['query-all'] ?? false,
|
|
||||||
];
|
];
|
||||||
|
if ($this->securityAdvisoryConfig['api-url'] === null && !$this->hasAvailablePackageList) {
|
||||||
|
throw new \UnexpectedValueException('Invalid security advisory configuration on '.$this->getRepoName().': If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1280,12 +1299,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param non-empty-string $url
|
* @param string $url
|
||||||
* @return non-empty-string
|
* @return non-empty-string
|
||||||
*/
|
*/
|
||||||
private function canonicalizeUrl(string $url): string
|
private function canonicalizeUrl(string $url): string
|
||||||
{
|
{
|
||||||
if ('/' === $url[0]) {
|
if (strlen($url) === 0) {
|
||||||
|
throw new \InvalidArgumentException('Expected a string with a value and not an empty string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($url, '/')) {
|
||||||
if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) {
|
if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) {
|
||||||
return $matches[0] . $url;
|
return $matches[0] . $url;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class GitLabDriver extends VcsDriver
|
||||||
/**
|
/**
|
||||||
* @var mixed[] Project data returned by GitLab API
|
* @var mixed[] Project data returned by GitLab API
|
||||||
*/
|
*/
|
||||||
private $project;
|
private $project = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string|int, mixed[]> Keeps commits returned by GitLab API as commit id => info
|
* @var array<string|int, mixed[]> Keeps commits returned by GitLab API as commit id => info
|
||||||
|
@ -381,6 +381,10 @@ class GitLabDriver extends VcsDriver
|
||||||
|
|
||||||
protected function fetchProject(): void
|
protected function fetchProject(): void
|
||||||
{
|
{
|
||||||
|
if (!is_null($this->project)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// we need to fetch the default branch from the api
|
// we need to fetch the default branch from the api
|
||||||
$resource = $this->getApiUrl();
|
$resource = $this->getApiUrl();
|
||||||
$this->project = $this->getContents($resource, true)->decodeJson();
|
$this->project = $this->getContents($resource, true)->decodeJson();
|
||||||
|
@ -581,6 +585,18 @@ class GitLabDriver extends VcsDriver
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives back the loaded <gitlab-api>/projects/<owner>/<repo> result
|
||||||
|
*
|
||||||
|
* @return mixed[]|null
|
||||||
|
*/
|
||||||
|
public function getRepoData(): ?array
|
||||||
|
{
|
||||||
|
$this->fetchProject();
|
||||||
|
|
||||||
|
return $this->project;
|
||||||
|
}
|
||||||
|
|
||||||
protected function getNextPage(Response $response): ?string
|
protected function getNextPage(Response $response): ?string
|
||||||
{
|
{
|
||||||
$header = $response->getHeader('link');
|
$header = $response->getHeader('link');
|
||||||
|
|
|
@ -28,7 +28,7 @@ use React\Promise\Promise;
|
||||||
* @internal
|
* @internal
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
* @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool}
|
* @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null}
|
||||||
* @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable}
|
* @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable}
|
||||||
*/
|
*/
|
||||||
class CurlDownloader
|
class CurlDownloader
|
||||||
|
@ -143,7 +143,7 @@ class CurlDownloader
|
||||||
/**
|
/**
|
||||||
* @param mixed[] $options
|
* @param mixed[] $options
|
||||||
*
|
*
|
||||||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool} $attributes
|
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes
|
||||||
* @param non-empty-string $url
|
* @param non-empty-string $url
|
||||||
*
|
*
|
||||||
* @return int internal job id
|
* @return int internal job id
|
||||||
|
@ -155,8 +155,15 @@ class CurlDownloader
|
||||||
'redirects' => 0,
|
'redirects' => 0,
|
||||||
'retries' => 0,
|
'retries' => 0,
|
||||||
'storeAuth' => false,
|
'storeAuth' => false,
|
||||||
|
'ipResolve' => null,
|
||||||
], $attributes);
|
], $attributes);
|
||||||
|
|
||||||
|
if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') {
|
||||||
|
$attributes['ipResolve'] = 4;
|
||||||
|
} elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') {
|
||||||
|
$attributes['ipResolve'] = 6;
|
||||||
|
}
|
||||||
|
|
||||||
$originalOptions = $options;
|
$originalOptions = $options;
|
||||||
|
|
||||||
// check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
|
// check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
|
||||||
|
@ -199,6 +206,12 @@ class CurlDownloader
|
||||||
curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports
|
curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports
|
||||||
curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||||
|
|
||||||
|
if ($attributes['ipResolve'] === 4) {
|
||||||
|
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||||
|
} elseif ($attributes['ipResolve'] === 6) {
|
||||||
|
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
|
||||||
|
}
|
||||||
|
|
||||||
if (function_exists('curl_share_init')) {
|
if (function_exists('curl_share_init')) {
|
||||||
curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
|
curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
|
||||||
}
|
}
|
||||||
|
@ -352,8 +365,12 @@ class CurlDownloader
|
||||||
|| (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer'))
|
|| (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer'))
|
||||||
) && $job['attributes']['retries'] < $this->maxRetries
|
) && $job['attributes']['retries'] < $this->maxRetries
|
||||||
) {
|
) {
|
||||||
|
$attributes = ['retries' => $job['attributes']['retries'] + 1];
|
||||||
|
if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected
|
||||||
|
$attributes['ipResolve'] = 4;
|
||||||
|
}
|
||||||
$this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
|
$this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
|
||||||
$this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
|
$this->restartJobWithDelay($job, $job['url'], $attributes);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,7 +599,7 @@ class CurlDownloader
|
||||||
* @param Job $job
|
* @param Job $job
|
||||||
* @param non-empty-string $url
|
* @param non-empty-string $url
|
||||||
*
|
*
|
||||||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>} $attributes
|
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes
|
||||||
*/
|
*/
|
||||||
private function restartJob(array $job, string $url, array $attributes = []): void
|
private function restartJob(array $job, string $url, array $attributes = []): void
|
||||||
{
|
{
|
||||||
|
@ -600,7 +617,7 @@ class CurlDownloader
|
||||||
* @param Job $job
|
* @param Job $job
|
||||||
* @param non-empty-string $url
|
* @param non-empty-string $url
|
||||||
*
|
*
|
||||||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>} $attributes
|
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes
|
||||||
*/
|
*/
|
||||||
private function restartJobWithDelay(array $job, string $url, array $attributes): void
|
private function restartJobWithDelay(array $job, string $url, array $attributes): void
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,6 +54,7 @@ class AuditorTest extends TestCase
|
||||||
'expected' => 1,
|
'expected' => 1,
|
||||||
'output' => '<warning>Found 2 security vulnerability advisories affecting 1 package:</warning>
|
'output' => '<warning>Found 2 security vulnerability advisories affecting 1 package:</warning>
|
||||||
Package: vendor1/package1
|
Package: vendor1/package1
|
||||||
|
Severity: high
|
||||||
CVE: CVE3
|
CVE: CVE3
|
||||||
Title: advisory4
|
Title: advisory4
|
||||||
URL: https://advisory.example.com/advisory4
|
URL: https://advisory.example.com/advisory4
|
||||||
|
@ -61,6 +62,7 @@ Affected versions: >=8,<8.2.2|>=1,<2.5.6
|
||||||
Reported at: 2022-05-25T13:21:00+00:00
|
Reported at: 2022-05-25T13:21:00+00:00
|
||||||
--------
|
--------
|
||||||
Package: vendor1/package1
|
Package: vendor1/package1
|
||||||
|
Severity: medium
|
||||||
CVE: '.'
|
CVE: '.'
|
||||||
Title: advisory5
|
Title: advisory5
|
||||||
URL: https://advisory.example.com/advisory5
|
URL: https://advisory.example.com/advisory5
|
||||||
|
@ -169,6 +171,7 @@ Found 2 abandoned packages:
|
||||||
[
|
[
|
||||||
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
||||||
['text' => 'Package: vendor1/package1'],
|
['text' => 'Package: vendor1/package1'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE1'],
|
['text' => 'CVE: CVE1'],
|
||||||
['text' => 'Title: advisory1'],
|
['text' => 'Title: advisory1'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory1'],
|
['text' => 'URL: https://advisory.example.com/advisory1'],
|
||||||
|
@ -185,6 +188,7 @@ Found 2 abandoned packages:
|
||||||
[
|
[
|
||||||
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
||||||
['text' => 'Package: vendor1/package1'],
|
['text' => 'Package: vendor1/package1'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE1'],
|
['text' => 'CVE: CVE1'],
|
||||||
['text' => 'Title: advisory1'],
|
['text' => 'Title: advisory1'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory1'],
|
['text' => 'URL: https://advisory.example.com/advisory1'],
|
||||||
|
@ -202,6 +206,7 @@ Found 2 abandoned packages:
|
||||||
[
|
[
|
||||||
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
||||||
['text' => 'Package: vendor1/package2'],
|
['text' => 'Package: vendor1/package2'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: '],
|
['text' => 'CVE: '],
|
||||||
['text' => 'Title: advisory2'],
|
['text' => 'Title: advisory2'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory2'],
|
['text' => 'URL: https://advisory.example.com/advisory2'],
|
||||||
|
@ -218,6 +223,7 @@ Found 2 abandoned packages:
|
||||||
[
|
[
|
||||||
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
['text' => 'Found 1 ignored security vulnerability advisory affecting 1 package:'],
|
||||||
['text' => 'Package: vendorx/packagex'],
|
['text' => 'Package: vendorx/packagex'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE5'],
|
['text' => 'CVE: CVE5'],
|
||||||
['text' => 'Title: advisory17'],
|
['text' => 'Title: advisory17'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory17'],
|
['text' => 'URL: https://advisory.example.com/advisory17'],
|
||||||
|
@ -234,6 +240,7 @@ Found 2 abandoned packages:
|
||||||
[
|
[
|
||||||
['text' => 'Found 1 security vulnerability advisory affecting 1 package:'],
|
['text' => 'Found 1 security vulnerability advisory affecting 1 package:'],
|
||||||
['text' => 'Package: vendor1/package1'],
|
['text' => 'Package: vendor1/package1'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE1'],
|
['text' => 'CVE: CVE1'],
|
||||||
['text' => 'Title: advisory1'],
|
['text' => 'Title: advisory1'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory1'],
|
['text' => 'URL: https://advisory.example.com/advisory1'],
|
||||||
|
@ -254,6 +261,7 @@ Found 2 abandoned packages:
|
||||||
[
|
[
|
||||||
['text' => 'Found 3 ignored security vulnerability advisories affecting 2 packages:'],
|
['text' => 'Found 3 ignored security vulnerability advisories affecting 2 packages:'],
|
||||||
['text' => 'Package: vendor2/package1'],
|
['text' => 'Package: vendor2/package1'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE2'],
|
['text' => 'CVE: CVE2'],
|
||||||
['text' => 'Title: advisory3'],
|
['text' => 'Title: advisory3'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory3'],
|
['text' => 'URL: https://advisory.example.com/advisory3'],
|
||||||
|
@ -262,6 +270,7 @@ Found 2 abandoned packages:
|
||||||
['text' => 'Ignore reason: None specified'],
|
['text' => 'Ignore reason: None specified'],
|
||||||
['text' => '--------'],
|
['text' => '--------'],
|
||||||
['text' => 'Package: vendor2/package1'],
|
['text' => 'Package: vendor2/package1'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE4'],
|
['text' => 'CVE: CVE4'],
|
||||||
['text' => 'Title: advisory6'],
|
['text' => 'Title: advisory6'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory6'],
|
['text' => 'URL: https://advisory.example.com/advisory6'],
|
||||||
|
@ -270,6 +279,7 @@ Found 2 abandoned packages:
|
||||||
['text' => 'Ignore reason: None specified'],
|
['text' => 'Ignore reason: None specified'],
|
||||||
['text' => '--------'],
|
['text' => '--------'],
|
||||||
['text' => 'Package: vendorx/packagex'],
|
['text' => 'Package: vendorx/packagex'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE5'],
|
['text' => 'CVE: CVE5'],
|
||||||
['text' => 'Title: advisory17'],
|
['text' => 'Title: advisory17'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory17'],
|
['text' => 'URL: https://advisory.example.com/advisory17'],
|
||||||
|
@ -278,6 +288,7 @@ Found 2 abandoned packages:
|
||||||
['text' => 'Ignore reason: None specified'],
|
['text' => 'Ignore reason: None specified'],
|
||||||
['text' => 'Found 1 security vulnerability advisory affecting 1 package:'],
|
['text' => 'Found 1 security vulnerability advisory affecting 1 package:'],
|
||||||
['text' => 'Package: vendor3/package1'],
|
['text' => 'Package: vendor3/package1'],
|
||||||
|
['text' => 'Severity: medium'],
|
||||||
['text' => 'CVE: CVE5'],
|
['text' => 'CVE: CVE5'],
|
||||||
['text' => 'Title: advisory7'],
|
['text' => 'Title: advisory7'],
|
||||||
['text' => 'URL: https://advisory.example.com/advisory7'],
|
['text' => 'URL: https://advisory.example.com/advisory7'],
|
||||||
|
@ -380,6 +391,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2022-05-25 13:21:00',
|
'reportedAt' => '2022-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'advisoryId' => 'ID4',
|
'advisoryId' => 'ID4',
|
||||||
|
@ -396,6 +408,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2022-05-25 13:21:00',
|
'reportedAt' => '2022-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'high',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'advisoryId' => 'ID5',
|
'advisoryId' => 'ID5',
|
||||||
|
@ -412,6 +425,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2022-05-25 13:21:00',
|
'reportedAt' => '2022-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'vendor1/package2' => [
|
'vendor1/package2' => [
|
||||||
|
@ -430,6 +444,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2022-05-25 13:21:00',
|
'reportedAt' => '2022-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'vendorx/packagex' => [
|
'vendorx/packagex' => [
|
||||||
|
@ -448,6 +463,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2015-05-25 13:21:00',
|
'reportedAt' => '2015-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'vendor2/package1' => [
|
'vendor2/package1' => [
|
||||||
|
@ -466,6 +482,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2022-05-25 13:21:00',
|
'reportedAt' => '2022-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'advisoryId' => 'ID6',
|
'advisoryId' => 'ID6',
|
||||||
|
@ -482,6 +499,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2015-05-25 13:21:00',
|
'reportedAt' => '2015-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'vendory/packagey' => [
|
'vendory/packagey' => [
|
||||||
|
@ -500,6 +518,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2015-05-25 13:21:00',
|
'reportedAt' => '2015-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'vendor3/package1' => [
|
'vendor3/package1' => [
|
||||||
|
@ -518,6 +537,7 @@ Found 2 abandoned packages:
|
||||||
],
|
],
|
||||||
'reportedAt' => '2015-05-25 13:21:00',
|
'reportedAt' => '2015-05-25 13:21:00',
|
||||||
'composerRepository' => 'https://packagist.org',
|
'composerRepository' => 'https://packagist.org',
|
||||||
|
'severity' => 'medium',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -76,7 +76,7 @@ class BumpCommandTest extends TestCase
|
||||||
yield 'bump all by default' => [
|
yield 'bump all by default' => [
|
||||||
[
|
[
|
||||||
'require' => [
|
'require' => [
|
||||||
'first/pkg' => '^2.0',
|
'first/pkg' => '^v2.0',
|
||||||
'second/pkg' => '3.*',
|
'second/pkg' => '3.*',
|
||||||
],
|
],
|
||||||
'require-dev' => [
|
'require-dev' => [
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
namespace Composer\Test\Command;
|
namespace Composer\Test\Command;
|
||||||
|
|
||||||
use Composer\Test\TestCase;
|
use Composer\Test\TestCase;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
class ConfigCommandTest extends TestCase
|
class ConfigCommandTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -139,4 +140,13 @@ class ConfigCommandTest extends TestCase
|
||||||
'{"foo":{"type":"vcs","url":"https://example.org"}}',
|
'{"foo":{"type":"vcs","url":"https://example.org"}}',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testConfigThrowsForInvalidArgCombination(): void
|
||||||
|
{
|
||||||
|
$this->expectException(RuntimeException::class);
|
||||||
|
$this->expectExceptionMessage('--file and --global can not be combined');
|
||||||
|
|
||||||
|
$appTester = $this->getApplicationTester();
|
||||||
|
$appTester->run(['command' => 'config', '--file' => 'alt.composer.json', '--global' => true]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ use Composer\Pcre\Preg;
|
||||||
use Composer\Pcre\Regex;
|
use Composer\Pcre\Regex;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
use Composer\Test\TestCase;
|
use Composer\Test\TestCase;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class ShowCommandTest extends TestCase
|
class ShowCommandTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -28,6 +30,8 @@ class ShowCommandTest extends TestCase
|
||||||
public function testShow(array $command, string $expected, array $requires = []): void
|
public function testShow(array $command, string $expected, array $requires = []): void
|
||||||
{
|
{
|
||||||
$this->initTempComposer([
|
$this->initTempComposer([
|
||||||
|
'name' => 'root/pkg',
|
||||||
|
'version' => '1.2.3',
|
||||||
'repositories' => [
|
'repositories' => [
|
||||||
'packages' => [
|
'packages' => [
|
||||||
'type' => 'package',
|
'type' => 'package',
|
||||||
|
@ -55,12 +59,19 @@ class ShowCommandTest extends TestCase
|
||||||
|
|
||||||
$pkg = self::getPackage('vendor/package', '1.0.0');
|
$pkg = self::getPackage('vendor/package', '1.0.0');
|
||||||
$pkg->setDescription('description of installed package');
|
$pkg->setDescription('description of installed package');
|
||||||
|
$major = self::getPackage('outdated/major', '1.0.0');
|
||||||
|
$major->setReleaseDate(new DateTimeImmutable());
|
||||||
|
$minor = self::getPackage('outdated/minor', '1.0.0');
|
||||||
|
$minor->setReleaseDate(new DateTimeImmutable('-2 years'));
|
||||||
|
$patch = self::getPackage('outdated/patch', '1.0.0');
|
||||||
|
$patch->setReleaseDate(new DateTimeImmutable('-2 weeks'));
|
||||||
|
|
||||||
$this->createInstalledJson([
|
$this->createInstalledJson([$pkg, $major, $minor, $patch]);
|
||||||
|
|
||||||
|
$pkg = self::getPackage('vendor/locked', '3.0.0');
|
||||||
|
$pkg->setDescription('description of locked package');
|
||||||
|
$this->createComposerLock([
|
||||||
$pkg,
|
$pkg,
|
||||||
self::getPackage('outdated/major', '1.0.0'),
|
|
||||||
self::getPackage('outdated/minor', '1.0.0'),
|
|
||||||
self::getPackage('outdated/patch', '1.0.0'),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$appTester = $this->getApplicationTester();
|
$appTester = $this->getApplicationTester();
|
||||||
|
@ -78,6 +89,21 @@ outdated/patch 1.0.0
|
||||||
vendor/package 1.0.0 description of installed package',
|
vendor/package 1.0.0 description of installed package',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
yield 'with -s and --installed shows list of installed + self package' => [
|
||||||
|
['--installed' => true, '--self' => true],
|
||||||
|
'outdated/major 1.0.0
|
||||||
|
outdated/minor 1.0.0
|
||||||
|
outdated/patch 1.0.0
|
||||||
|
root/pkg 1.2.3
|
||||||
|
vendor/package 1.0.0 description of installed package',
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'with -s and --locked shows list of installed + self package' => [
|
||||||
|
['--locked' => true, '--self' => true],
|
||||||
|
'root/pkg 1.2.3
|
||||||
|
vendor/locked 3.0.0 description of locked package',
|
||||||
|
];
|
||||||
|
|
||||||
yield 'with -a show available packages with description but no version' => [
|
yield 'with -a show available packages with description but no version' => [
|
||||||
['-a' => true],
|
['-a' => true],
|
||||||
'outdated/major outdated/major v2.0.0 description
|
'outdated/major outdated/major v2.0.0 description
|
||||||
|
@ -112,6 +138,21 @@ outdated/minor 1.0.0 <highlight>! 1.1.1</highlight>
|
||||||
outdated/patch 1.0.0 <highlight>! 1.0.1</highlight>',
|
outdated/patch 1.0.0 <highlight>! 1.0.1</highlight>',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
yield 'outdated deps sorting by age' => [
|
||||||
|
['command' => 'outdated', '--sort-by-age' => true],
|
||||||
|
'Legend:
|
||||||
|
! patch or minor release available - update recommended
|
||||||
|
~ major release available - update possible
|
||||||
|
|
||||||
|
Direct dependencies required in composer.json:
|
||||||
|
Everything up to date
|
||||||
|
|
||||||
|
Transitive dependencies not required in composer.json:
|
||||||
|
outdated/minor 1.0.0 <highlight>! 1.1.1</highlight> 2 years old
|
||||||
|
outdated/patch 1.0.0 <highlight>! 1.0.1</highlight> 2 weeks old
|
||||||
|
outdated/major 1.0.0 ~ 2.0.0 from today',
|
||||||
|
];
|
||||||
|
|
||||||
yield 'outdated deps with --direct only show direct deps with updated' => [
|
yield 'outdated deps with --direct only show direct deps with updated' => [
|
||||||
['command' => 'outdated', '--direct' => true],
|
['command' => 'outdated', '--direct' => true],
|
||||||
'Legend:
|
'Legend:
|
||||||
|
@ -258,6 +299,23 @@ Transitive dependencies not required in composer.json:
|
||||||
vendor/package 1.1.0 <highlight>! 1.2.0</highlight>", trim($appTester->getDisplay(true)));
|
vendor/package 1.1.0 <highlight>! 1.2.0</highlight>", trim($appTester->getDisplay(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testShowDirectWithNameOnlyShowsDirectDependents(): void
|
||||||
|
{
|
||||||
|
self::expectException(InvalidArgumentException::class);
|
||||||
|
self::expectExceptionMessage('Package "vendor/package" is installed but not a direct dependent of the root package.');
|
||||||
|
|
||||||
|
$this->initTempComposer([
|
||||||
|
'repositories' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->createInstalledJson([
|
||||||
|
self::getPackage('vendor/package', '1.0.0'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$appTester = $this->getApplicationTester();
|
||||||
|
$appTester->run(['command' => 'show', '--direct' => true, 'package' => 'vendor/package']);
|
||||||
|
}
|
||||||
|
|
||||||
public function testShowPlatformOnlyShowsPlatformPackages(): void
|
public function testShowPlatformOnlyShowsPlatformPackages(): void
|
||||||
{
|
{
|
||||||
$this->initTempComposer([
|
$this->initTempComposer([
|
||||||
|
@ -533,7 +591,7 @@ OUTPUT;
|
||||||
|
|
||||||
public function testSelf(): void
|
public function testSelf(): void
|
||||||
{
|
{
|
||||||
$this->initTempComposer(['name' => 'vendor/package']);
|
$this->initTempComposer(['name' => 'vendor/package', 'time' => date('Y-m-d')]);
|
||||||
|
|
||||||
$appTester = $this->getApplicationTester();
|
$appTester = $this->getApplicationTester();
|
||||||
$appTester->run(['command' => 'show', '--self' => true]);
|
$appTester->run(['command' => 'show', '--self' => true]);
|
||||||
|
@ -542,6 +600,7 @@ OUTPUT;
|
||||||
'descrip.' => '',
|
'descrip.' => '',
|
||||||
'keywords' => '',
|
'keywords' => '',
|
||||||
'versions' => '* 1.0.0+no-version-set',
|
'versions' => '* 1.0.0+no-version-set',
|
||||||
|
'released' => date('Y-m-d'). ', today',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'homepage' => '',
|
'homepage' => '',
|
||||||
'source' => '[] ',
|
'source' => '[] ',
|
||||||
|
|
|
@ -29,7 +29,7 @@ class UpdateCommandTest extends TestCase
|
||||||
$appTester = $this->getApplicationTester();
|
$appTester = $this->getApplicationTester();
|
||||||
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true], $command));
|
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true], $command));
|
||||||
|
|
||||||
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
$this->assertStringMatchesFormat(trim($expected), trim($appTester->getDisplay(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function provideUpdates(): \Generator
|
public static function provideUpdates(): \Generator
|
||||||
|
@ -67,6 +67,29 @@ Package operations: 2 installs, 0 updates, 0 removals
|
||||||
OUTPUT
|
OUTPUT
|
||||||
];
|
];
|
||||||
|
|
||||||
|
yield 'simple update with very verbose output' => [
|
||||||
|
$rootDepAndTransitiveDep,
|
||||||
|
['-vv' => true],
|
||||||
|
<<<OUTPUT
|
||||||
|
Loading composer repositories with package information
|
||||||
|
Pool optimizer completed in %f seconds
|
||||||
|
Found %d package versions referenced in your dependency graph. %d (%d%%) were optimized away.
|
||||||
|
Updating dependencies
|
||||||
|
Dependency resolution completed in %f seconds
|
||||||
|
Analyzed %d packages to resolve dependencies
|
||||||
|
Analyzed %d rules to resolve dependencies
|
||||||
|
Lock file operations: 2 installs, 0 updates, 0 removals
|
||||||
|
Installs: dep/pkg:1.0.2, root/req:1.0.0
|
||||||
|
- Locking dep/pkg (1.0.2) from package repo (defining 4 packages)
|
||||||
|
- Locking root/req (1.0.0) from package repo (defining 4 packages)
|
||||||
|
Installing dependencies from lock file (including require-dev)
|
||||||
|
Package operations: 2 installs, 0 updates, 0 removals
|
||||||
|
Installs: dep/pkg:1.0.2, root/req:1.0.0
|
||||||
|
- Installing dep/pkg (1.0.2)
|
||||||
|
- Installing root/req (1.0.0)
|
||||||
|
OUTPUT
|
||||||
|
];
|
||||||
|
|
||||||
yield 'update with temporary constraint + --no-install' => [
|
yield 'update with temporary constraint + --no-install' => [
|
||||||
$rootDepAndTransitiveDep,
|
$rootDepAndTransitiveDep,
|
||||||
['--with' => ['dep/pkg:1.0.0'], '--no-install' => true],
|
['--with' => ['dep/pkg:1.0.0'], '--no-install' => true],
|
||||||
|
|
|
@ -25,6 +25,7 @@ final class IgnoreAllPlatformRequirementFilterTest extends TestCase
|
||||||
$platformRequirementFilter = new IgnoreAllPlatformRequirementFilter();
|
$platformRequirementFilter = new IgnoreAllPlatformRequirementFilter();
|
||||||
|
|
||||||
$this->assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req));
|
$this->assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req));
|
||||||
|
$this->assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -48,4 +48,37 @@ final class IgnoreListPlatformRequirementFilterTest extends TestCase
|
||||||
'list entries are not completing each other' => [['ext-', 'foo'], 'ext-foo', false],
|
'list entries are not completing each other' => [['ext-', 'foo'], 'ext-foo', false],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataIsUpperBoundIgnored
|
||||||
|
*
|
||||||
|
* @param string[] $reqList
|
||||||
|
*/
|
||||||
|
public function testIsUpperBoundIgnored(array $reqList, string $req, bool $expectIgnored): void
|
||||||
|
{
|
||||||
|
$platformRequirementFilter = new IgnoreListPlatformRequirementFilter($reqList);
|
||||||
|
|
||||||
|
$this->assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed[]>
|
||||||
|
*/
|
||||||
|
public static function dataIsUpperBoundIgnored(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ext-json is ignored if listed and fully ignored' => [['ext-json', 'monolog/monolog'], 'ext-json', true],
|
||||||
|
'ext-json is ignored if listed and upper bound ignored' => [['ext-json+', 'monolog/monolog'], 'ext-json', true],
|
||||||
|
'php is not ignored if not listed' => [['ext-json+', 'monolog/monolog'], 'php', false],
|
||||||
|
'monolog/monolog is not ignored even if listed' => [['monolog/monolog'], 'monolog/monolog', false],
|
||||||
|
'ext-json is ignored if ext-* is listed' => [['ext-*+'], 'ext-json', true],
|
||||||
|
'php is ignored if php* is listed' => [['ext-*+', 'php*+'], 'php', true],
|
||||||
|
'ext-json is ignored if * is listed' => [['foo', '*+'], 'ext-json', true],
|
||||||
|
'php is ignored if * is listed' => [['*+', 'foo'], 'php', true],
|
||||||
|
'monolog/monolog is not ignored even if * or monolog/* are listed' => [['*+', 'monolog/*+'], 'monolog/monolog', false],
|
||||||
|
'empty list entry does not ignore' => [[''], 'ext-foo', false],
|
||||||
|
'empty array does not ignore' => [[], 'ext-foo', false],
|
||||||
|
'list entries are not completing each other' => [['ext-', 'foo'], 'ext-foo', false],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ final class IgnoreNothingPlatformRequirementFilterTest extends TestCase
|
||||||
$platformRequirementFilter = new IgnoreNothingPlatformRequirementFilter();
|
$platformRequirementFilter = new IgnoreNothingPlatformRequirementFilter();
|
||||||
|
|
||||||
$this->assertFalse($platformRequirementFilter->isIgnored($req)); // @phpstan-ignore-line
|
$this->assertFalse($platformRequirementFilter->isIgnored($req)); // @phpstan-ignore-line
|
||||||
|
$this->assertFalse($platformRequirementFilter->isUpperBoundIgnored($req)); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
--TEST--
|
||||||
|
Installs a simple package with exact match requirement
|
||||||
|
--CONDITION--
|
||||||
|
putenv('COMPOSER_FUND=1')
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{
|
||||||
|
"name": "a/a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }],
|
||||||
|
"require": {
|
||||||
|
"d/d": "^1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b/b",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "c/c",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "d/d",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"require": {
|
||||||
|
"b/b": "^1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/a": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--RUN--
|
||||||
|
install
|
||||||
|
--EXPECT-OUTPUT--
|
||||||
|
<warning>No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.</warning>
|
||||||
|
Loading composer repositories with package information
|
||||||
|
Updating dependencies
|
||||||
|
Lock file operations: 3 installs, 0 updates, 0 removals
|
||||||
|
- Locking a/a (1.0.0)
|
||||||
|
- Locking b/b (1.0.0)
|
||||||
|
- Locking d/d (1.0.0)
|
||||||
|
Writing lock file
|
||||||
|
Installing dependencies from lock file (including require-dev)
|
||||||
|
Package operations: 3 installs, 0 updates, 0 removals
|
||||||
|
Generating autoload files
|
||||||
|
2 packages you are using are looking for funding.
|
||||||
|
Use the `composer fund` command to find out more!
|
||||||
|
--EXPECT--
|
||||||
|
Installing b/b (1.0.0)
|
||||||
|
Installing d/d (1.0.0)
|
||||||
|
Installing a/a (1.0.0)
|
|
@ -0,0 +1,60 @@
|
||||||
|
--TEST--
|
||||||
|
Installs a simple package with exact match requirement
|
||||||
|
--CONDITION--
|
||||||
|
putenv('COMPOSER_FUND=0')
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{
|
||||||
|
"name": "a/a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }],
|
||||||
|
"require": {
|
||||||
|
"d/d": "^1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b/b",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "c/c",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "d/d",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"require": {
|
||||||
|
"b/b": "^1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/a": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--RUN--
|
||||||
|
install
|
||||||
|
--EXPECT-OUTPUT--
|
||||||
|
<warning>No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.</warning>
|
||||||
|
Loading composer repositories with package information
|
||||||
|
Updating dependencies
|
||||||
|
Lock file operations: 3 installs, 0 updates, 0 removals
|
||||||
|
- Locking a/a (1.0.0)
|
||||||
|
- Locking b/b (1.0.0)
|
||||||
|
- Locking d/d (1.0.0)
|
||||||
|
Writing lock file
|
||||||
|
Installing dependencies from lock file (including require-dev)
|
||||||
|
Package operations: 3 installs, 0 updates, 0 removals
|
||||||
|
Generating autoload files
|
||||||
|
--EXPECT--
|
||||||
|
Installing b/b (1.0.0)
|
||||||
|
Installing d/d (1.0.0)
|
||||||
|
Installing a/a (1.0.0)
|
|
@ -59,6 +59,7 @@ class InstallerTest extends TestCase
|
||||||
{
|
{
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
Platform::clearEnv('COMPOSER_POOL_OPTIMIZER');
|
Platform::clearEnv('COMPOSER_POOL_OPTIMIZER');
|
||||||
|
Platform::clearEnv('COMPOSER_FUND');
|
||||||
|
|
||||||
chdir($this->prevCwd);
|
chdir($this->prevCwd);
|
||||||
if (isset($this->tempComposerHome) && is_dir($this->tempComposerHome)) {
|
if (isset($this->tempComposerHome) && is_dir($this->tempComposerHome)) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ class VersionBumperTest extends TestCase
|
||||||
{
|
{
|
||||||
// constraint, version, expected recommendation, [branch-alias]
|
// constraint, version, expected recommendation, [branch-alias]
|
||||||
yield 'upgrade caret' => ['^1.0', '1.2.1', '^1.2.1'];
|
yield 'upgrade caret' => ['^1.0', '1.2.1', '^1.2.1'];
|
||||||
|
yield 'upgrade caret with v' => ['^v1.0', '1.2.1', '^1.2.1'];
|
||||||
yield 'skip trailing .0s' => ['^1.0', '1.0.0', '^1.0'];
|
yield 'skip trailing .0s' => ['^1.0', '1.0.0', '^1.0'];
|
||||||
yield 'skip trailing .0s/2' => ['^1.2', '1.2.0', '^1.2'];
|
yield 'skip trailing .0s/2' => ['^1.2', '1.2.0', '^1.2'];
|
||||||
yield 'preserve major.minor.patch format when installed minor is 0' => ['^1.0.0', '1.2.0', '^1.2.0'];
|
yield 'preserve major.minor.patch format when installed minor is 0' => ['^1.0.0', '1.2.0', '^1.2.0'];
|
||||||
|
@ -58,6 +59,7 @@ class VersionBumperTest extends TestCase
|
||||||
yield 'dev version does not upgrade' => ['^3.2', 'dev-main', '^3.2'];
|
yield 'dev version does not upgrade' => ['^3.2', 'dev-main', '^3.2'];
|
||||||
yield 'upgrade dev version if aliased' => ['^3.2', 'dev-main', '^3.3', '3.3.x-dev'];
|
yield 'upgrade dev version if aliased' => ['^3.2', 'dev-main', '^3.3', '3.3.x-dev'];
|
||||||
yield 'upgrade major wildcard to caret' => ['2.*', '2.4.0', '^2.4'];
|
yield 'upgrade major wildcard to caret' => ['2.*', '2.4.0', '^2.4'];
|
||||||
|
yield 'upgrade major wildcard to caret with v' => ['v2.*', '2.4.0', '^2.4'];
|
||||||
yield 'upgrade major wildcard as x to caret' => ['2.x', '2.4.0', '^2.4'];
|
yield 'upgrade major wildcard as x to caret' => ['2.x', '2.4.0', '^2.4'];
|
||||||
yield 'upgrade major wildcard as x to caret/2' => ['2.x.x', '2.4.0', '^2.4.0'];
|
yield 'upgrade major wildcard as x to caret/2' => ['2.x.x', '2.4.0', '^2.4.0'];
|
||||||
yield 'leave minor wildcard alone' => ['2.4.*', '2.4.3', '2.4.*'];
|
yield 'leave minor wildcard alone' => ['2.4.*', '2.4.3', '2.4.*'];
|
||||||
|
@ -66,6 +68,7 @@ class VersionBumperTest extends TestCase
|
||||||
yield 'update patch-only-tilde alone' => ['~2.2.3', '2.2.6', '~2.2.6'];
|
yield 'update patch-only-tilde alone' => ['~2.2.3', '2.2.6', '~2.2.6'];
|
||||||
yield 'leave extra-only-tilde alone' => ['~2.2.3.1', '2.2.4.5', '~2.2.3.1'];
|
yield 'leave extra-only-tilde alone' => ['~2.2.3.1', '2.2.4.5', '~2.2.3.1'];
|
||||||
yield 'upgrade bigger-or-eq to latest' => ['>=3.0', '3.4.5', '>=3.4.5'];
|
yield 'upgrade bigger-or-eq to latest' => ['>=3.0', '3.4.5', '>=3.4.5'];
|
||||||
|
yield 'upgrade bigger-or-eq to latest with v' => ['>=v3.0', '3.4.5', '>=3.4.5'];
|
||||||
yield 'leave bigger-than untouched' => ['>2.2.3', '2.2.6', '>2.2.3'];
|
yield 'leave bigger-than untouched' => ['>2.2.3', '2.2.6', '>2.2.3'];
|
||||||
yield 'upgrade full wildcard to bigger-or-eq' => ['*', '1.2.3', '>=1.2.3'];
|
yield 'upgrade full wildcard to bigger-or-eq' => ['*', '1.2.3', '>=1.2.3'];
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,6 +346,9 @@ class VersionSelectorTest extends TestCase
|
||||||
['0.1.3', '^0.1.3'],
|
['0.1.3', '^0.1.3'],
|
||||||
['0.0.3', '^0.0.3'],
|
['0.0.3', '^0.0.3'],
|
||||||
['0.0.3-alpha', '^0.0.3@alpha'],
|
['0.0.3-alpha', '^0.0.3@alpha'],
|
||||||
|
['0.0.3.4-alpha', '^0.0.3@alpha'],
|
||||||
|
['3.0.0.2-RC2', '^3.0@RC'],
|
||||||
|
['1.2.1.1020402', '^1.2'],
|
||||||
// date-based versions are not touched at all
|
// date-based versions are not touched at all
|
||||||
['v20121020', 'v20121020'],
|
['v20121020', 'v20121020'],
|
||||||
['v20121020.2', 'v20121020.2'],
|
['v20121020.2', 'v20121020.2'],
|
||||||
|
|
Loading…
Reference in New Issue