1
0
Fork 0

Merge branch 'composer:main' into add-tests-for-GlobalCommand

pull/11769/head
Franck Ranaivo-Harisoa 2024-01-19 15:12:10 +01:00 committed by GitHub
commit 50c2a969d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 735 additions and 137 deletions

98
composer.lock generated
View File

@ -8,16 +8,16 @@
"packages": [
{
"name": "composer/ca-bundle",
"version": "1.3.7",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "76e46335014860eec1aa5a724799a00a2e47cc85"
"reference": "b66d11b7479109ab547f9405b97205640b17d385"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85",
"reference": "76e46335014860eec1aa5a724799a00a2e47cc85",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385",
"reference": "b66d11b7479109ab547f9405b97205640b17d385",
"shasum": ""
},
"require": {
@ -29,7 +29,7 @@
"phpstan/phpstan": "^0.12.55",
"psr/log": "^1.0",
"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",
"extra": {
@ -64,7 +64,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"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": [
{
@ -80,7 +80,7 @@
"type": "tidelift"
}
],
"time": "2023-08-30T09:31:38+00:00"
"time": "2023-12-18T12:05:55+00:00"
},
{
"name": "composer/class-map-generator",
@ -765,16 +765,16 @@
},
{
"name": "seld/jsonlint",
"version": "1.10.0",
"version": "1.10.1",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/jsonlint.git",
"reference": "594fd6462aad8ecee0b45ca5045acea4776667f1"
"reference": "76d449a358ece77d6f1d6331c68453e657172202"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/594fd6462aad8ecee0b45ca5045acea4776667f1",
"reference": "594fd6462aad8ecee0b45ca5045acea4776667f1",
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/76d449a358ece77d6f1d6331c68453e657172202",
"reference": "76d449a358ece77d6f1d6331c68453e657172202",
"shasum": ""
},
"require": {
@ -801,7 +801,7 @@
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
"homepage": "https://seld.be"
}
],
"description": "JSON Linter",
@ -813,7 +813,7 @@
],
"support": {
"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": [
{
@ -825,7 +825,7 @@
"type": "tidelift"
}
],
"time": "2023-05-11T13:16:46+00:00"
"time": "2023-12-18T13:03:25+00:00"
},
{
"name": "seld/phar-utils",
@ -938,16 +938,16 @@
},
{
"name": "symfony/console",
"version": "v5.4.32",
"version": "v5.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7"
"reference": "4b4d8cd118484aa604ec519062113dd87abde18c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
"reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
"url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c",
"reference": "4b4d8cd118484aa604ec519062113dd87abde18c",
"shasum": ""
},
"require": {
@ -1017,7 +1017,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.4.32"
"source": "https://github.com/symfony/console/tree/v5.4.34"
},
"funding": [
{
@ -1033,7 +1033,7 @@
"type": "tidelift"
}
],
"time": "2023-11-18T18:23:04+00:00"
"time": "2023-12-08T13:33:03+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -1802,16 +1802,16 @@
},
{
"name": "symfony/process",
"version": "v5.4.28",
"version": "v5.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b"
"reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b",
"reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b",
"url": "https://api.github.com/repos/symfony/process/zipball/8fa22178dfc368911dbd513b431cd9b06f9afe7a",
"reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a",
"shasum": ""
},
"require": {
@ -1844,7 +1844,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v5.4.28"
"source": "https://github.com/symfony/process/tree/v5.4.34"
},
"funding": [
{
@ -1860,7 +1860,7 @@
"type": "tidelift"
}
],
"time": "2023-08-07T10:36:04+00:00"
"time": "2023-12-02T08:41:43+00:00"
},
{
"name": "symfony/service-contracts",
@ -1947,16 +1947,16 @@
},
{
"name": "symfony/string",
"version": "v5.4.32",
"version": "v5.4.34",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "91bf4453d65d8231688a04376c3a40efe0770f04"
"reference": "e3f98bfc7885c957488f443df82d97814a3ce061"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/91bf4453d65d8231688a04376c3a40efe0770f04",
"reference": "91bf4453d65d8231688a04376c3a40efe0770f04",
"url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061",
"reference": "e3f98bfc7885c957488f443df82d97814a3ce061",
"shasum": ""
},
"require": {
@ -2013,7 +2013,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.4.32"
"source": "https://github.com/symfony/string/tree/v5.4.34"
},
"funding": [
{
@ -2029,22 +2029,22 @@
"type": "tidelift"
}
],
"time": "2023-11-26T13:43:46+00:00"
"time": "2023-12-09T13:20:28+00:00"
}
],
"packages-dev": [
{
"name": "phpstan/phpstan",
"version": "1.10.50",
"version": "1.10.55",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
"reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
"reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949",
"shasum": ""
},
"require": {
@ -2093,7 +2093,7 @@
"type": "tidelift"
}
],
"time": "2023-12-13T10:59:42+00:00"
"time": "2024-01-08T12:32:40+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@ -2246,16 +2246,16 @@
},
{
"name": "phpstan/phpstan-symfony",
"version": "1.3.5",
"version": "1.3.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-symfony.git",
"reference": "27ff6339f83796a7e0dd963cf445cd3c456fc620"
"reference": "34b3c43684834f6a20aa51af8d455480d9de8b88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/27ff6339f83796a7e0dd963cf445cd3c456fc620",
"reference": "27ff6339f83796a7e0dd963cf445cd3c456fc620",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/34b3c43684834f6a20aa51af8d455480d9de8b88",
"reference": "34b3c43684834f6a20aa51af8d455480d9de8b88",
"shasum": ""
},
"require": {
@ -2312,22 +2312,22 @@
"description": "Symfony Framework extensions and rules for PHPStan",
"support": {
"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",
"version": "v7.0.1",
"version": "v7.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
"reference": "c2d059b25e31274157dd7727131cd1cf33650207"
"reference": "92df075808c9437beca9540e25ae0c40eea1c061"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c2d059b25e31274157dd7727131cd1cf33650207",
"reference": "c2d059b25e31274157dd7727131cd1cf33650207",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/92df075808c9437beca9540e25ae0c40eea1c061",
"reference": "92df075808c9437beca9540e25ae0c40eea1c061",
"shasum": ""
},
"require": {
@ -2379,7 +2379,7 @@
"description": "Provides utilities for PHPUnit, especially user deprecation notices management",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.1"
"source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.2"
},
"funding": [
{
@ -2395,7 +2395,7 @@
"type": "tidelift"
}
],
"time": "2023-12-01T09:26:31+00:00"
"time": "2023-12-19T11:23:03+00:00"
}
],
"aliases": [],

View File

@ -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
same dependencies, which mitigates the potential for bugs affecting only some
parts of the deployments. Even if you develop alone, in six months when
reinstalling the project you can feel confident the dependencies installed are
still working even if your dependencies released many new versions since then.
reinstalling the project you can feel confident that the dependencies installed are
still working, even if the dependencies have released many new versions since then.
(See note below about using the `update` command.)
> **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
`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
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.
So after fetching new changes from your VCS repository it is recommended to run

View File

@ -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-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).
* **--lock:** Only updates the lock file hash to suppress warning about the
lock file being out of date.
* **--lock:** Overwrites the lock file hash to suppress warning about the lock file being out of
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
* **--no-autoloader:** Skips autoloader generation.
* **--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.
* **--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.
* **--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.
* **--strict:** Return a non-zero exit code when there are outdated packages.
* **--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.
* **--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.
* **--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.
* **--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.
@ -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.
### COMPOSER_FUND
If set to 0, this env suppresses funding notices when installing.
### COMPOSER_HOME
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
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
If you use a proxy, but it does not support the request_fulluri flag, then you

View File

@ -738,7 +738,7 @@ monolithic repository.
"repositories": [
{
"type": "path",
"url": "../../packages/my-package",
"url": "../../packages/*",
"options": {
"symlink": false
}
@ -772,7 +772,7 @@ The following modes exist:
"repositories": [
{
"type": "path",
"url": "../../packages/my-package",
"url": "../../packages/*",
"options": {
"reference": "config"
}

View File

@ -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.
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`

View File

@ -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
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:**
On linux, it seems that running this command helps to make ipv4 traffic have a

View File

@ -55,6 +55,11 @@ parameters:
count: 1
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\\.$#"
count: 2
@ -85,6 +90,11 @@ parameters:
count: 1
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\\.$#"
count: 1
@ -270,11 +280,21 @@ parameters:
count: 2
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\\.$#"
count: 1
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\\.$#"
count: 1

View File

@ -765,16 +765,6 @@ parameters:
count: 1
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\\.$#"
count: 2
@ -4314,7 +4304,7 @@ parameters:
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
path: ../src/Composer/Util/Http/CurlDownloader.php

View File

@ -247,6 +247,7 @@ class Auditor
foreach ($packageAdvisories as $advisory) {
$headers = [
'Package',
'Severity',
'CVE',
'Title',
'URL',
@ -255,6 +256,7 @@ class Auditor
];
$row = [
$advisory->packageName,
$this->getSeverity($advisory),
$this->getCVE($advisory),
$advisory->title,
$this->getURL($advisory),
@ -289,6 +291,7 @@ class Auditor
$error[] = '--------';
}
$error[] = "Package: ".$advisory->packageName;
$error[] = "Severity: ".$this->getSeverity($advisory);
$error[] = "CVE: ".$this->getCVE($advisory);
$error[] = "Title: ".OutputFormatter::escape($advisory->title);
$error[] = "URL: ".$this->getURL($advisory);
@ -350,6 +353,15 @@ class Auditor
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
{
if ($advisory->cve === null) {

View File

@ -26,9 +26,9 @@ class IgnoredSecurityAdvisory extends SecurityAdvisory
/**
* @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;
}

View File

@ -44,7 +44,7 @@ class PartialSecurityAdvisory implements JsonSerializable
{
$constraint = $parser->parseConstraints($data['affectedVersions']);
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);

View File

@ -47,10 +47,16 @@ class SecurityAdvisory extends PartialSecurityAdvisory
*/
public $sources;
/**
* @var string|null
* @readonly
*/
public $severity;
/**
* @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);
@ -59,6 +65,7 @@ class SecurityAdvisory extends PartialSecurityAdvisory
$this->reportedAt = $reportedAt;
$this->cve = $cve;
$this->link = $link;
$this->severity = $severity;
}
/**
@ -75,7 +82,8 @@ class SecurityAdvisory extends PartialSecurityAdvisory
$this->reportedAt,
$this->cve,
$this->link,
$ignoreReason
$ignoreReason,
$this->severity
);
}

View File

@ -12,14 +12,23 @@
namespace Composer\Command;
use Composer\Advisory\Auditor;
use Composer\Composer;
use Composer\Factory;
use Composer\Config;
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\Repository\ComposerRepository;
use Composer\Repository\FilesystemRepository;
use Composer\Repository\PlatformRepository;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\RepositorySet;
use Composer\Repository\RootPackageRepository;
use Composer\Util\ConfigValidator;
use Composer\Util\Git;
use Composer\Util\IniHelper;
@ -153,10 +162,13 @@ EOT
$io->write('Checking pubkeys: ', false);
$this->outputResult($this->checkPubKeys($config));
$io->write('Checking composer version: ', false);
$io->write('Checking Composer version: ', false);
$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()));
$platformOverrides = $config->get('platform') ?: [];
@ -438,6 +450,48 @@ EOT
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
{
if (extension_loaded('curl')) {
@ -499,7 +553,7 @@ EOT
if ($result) {
foreach ($result as $message) {
$io->write($message);
$io->write(trim($message));
}
}
}
@ -722,6 +776,11 @@ EOT
$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;
}

View File

@ -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('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('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('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.'),
@ -97,6 +98,9 @@ EOT
if ($input->getOption('no-dev')) {
$args['--no-dev'] = true;
}
if ($input->getOption('sort-by-age')) {
$args['--sort-by-age'] = true;
}
$args['--ignore-platform-req'] = $input->getOption('ignore-platform-req');
if ($input->getOption('ignore-platform-reqs')) {
$args['--ignore-platform-reqs'] = true;

View File

@ -349,6 +349,10 @@ EOT
}
throw $e;
} finally {
if ($input->getOption('dry-run') && $this->newlyCreated) {
@unlink($this->json->getPath());
}
$signalHandler->unregister();
}
}

View File

@ -156,6 +156,10 @@ EOT
return $this->rollback($output, $rollbackDir, $localFilename);
}
if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') {
$input->setArgument('version', null);
}
$latest = $versionsUtil->getLatest();
$latestStable = $versionsUtil->getLatest('stable');
try {

View File

@ -27,6 +27,7 @@ use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\ArrayRepository;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\ComposerRepository;
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('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('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('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']),
@ -141,7 +143,7 @@ EOT
$composer = $this->tryComposer();
$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>');
}
@ -198,7 +200,7 @@ EOT
$platformRepo = new PlatformRepository([], $platformOverrides);
$lockedRepo = null;
if ($input->getOption('self')) {
if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) {
$package = clone $this->requireComposer()->getPackage();
if ($input->getOption('name-only')) {
$io->write($package->getName());
@ -242,6 +244,9 @@ EOT
}
$locker = $composer->getLocker();
$lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev'));
if ($input->getOption('self')) {
$lockedRepo->addPackage(clone $composer->getPackage());
}
$repos = $installedRepo = new InstalledRepository([$lockedRepo]);
} else {
// --installed / default case
@ -249,19 +254,32 @@ EOT
$composer = $this->requireComposer();
}
$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')) {
$packages = RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg);
$repos = $installedRepo = new InstalledRepository([new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface {
$packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg);
$repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface {
return clone $pkg;
}, $packages))]);
} else {
$repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]);
}
if (!$installedRepo->getPackages() && ($rootPkg->getRequires() || $rootPkg->getDevRequires())) {
if (!$installedRepo->getPackages()) {
$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>');
}
}
}
if ($composer) {
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output);
@ -281,6 +299,12 @@ EOT
} elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) {
[$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)) {
$options = $input->getOptions();
$hint = '';
@ -293,7 +317,7 @@ EOT
if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) {
$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';
}
@ -450,7 +474,7 @@ EOT
if (isset($packages[$type])) {
ksort($packages[$type]);
$nameLength = $versionLength = $latestLength = 0;
$nameLength = $versionLength = $latestLength = $releaseDateLength = 0;
if ($showLatest && $showVersion) {
foreach ($packages[$type] as $package) {
@ -469,9 +493,20 @@ EOT
$writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
$writeLatest = $writeVersion && $showLatest;
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
$writeReleaseDate = $writeLatest && $input->getOption('sort-by-age');
$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] = [];
foreach ($packages[$type] as $package) {
$packageViewData = [];
@ -505,6 +540,17 @@ EOT
$packageViewData['version'] = $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) {
$packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
$packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
@ -552,7 +598,9 @@ EOT
'nameLength' => $nameLength,
'versionLength' => $versionLength,
'latestLength' => $latestLength,
'releaseDateLength' => $releaseDateLength,
'writeLatest' => $writeLatest,
'writeReleaseDate' => $writeReleaseDate,
];
if ($input->getOption('strict') && $hasOutdatedPackages) {
$exitCode = 1;
@ -588,11 +636,14 @@ EOT
$nameLength = $viewMetaData[$type]['nameLength'];
$versionLength = $viewMetaData[$type]['versionLength'];
$latestLength = $viewMetaData[$type]['latestLength'];
$releaseDateLength = $viewMetaData[$type]['releaseDateLength'];
$writeLatest = $viewMetaData[$type]['writeLatest'];
$writeReleaseDate = $viewMetaData[$type]['writeReleaseDate'];
$versionFits = $nameLength + $versionLength + 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()) {
$latestLength += 2;
@ -620,14 +671,14 @@ EOT
$io->writeError('');
$io->writeError('<info>Direct dependencies required in composer.json:</>');
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 {
$io->writeError('Everything up to date');
}
$io->writeError('');
$io->writeError('<info>Transitive dependencies not required in composer.json:</>');
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 {
$io->writeError('Everything up to date');
}
@ -635,7 +686,7 @@ EOT
if ($writeLatest && \count($packages) === 0) {
$io->writeError('All your direct dependencies are up to date');
} 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
*/
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;
$padVersion = $writeLatest || $writeDescription;
$padLatest = $writeDescription;
$padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription;
$padVersion = $writeLatest || $writeReleaseDate || $writeDescription;
$padLatest = $writeDescription || $writeReleaseDate;
$padReleaseDate = $writeDescription;
foreach ($packages as $package) {
$link = $package['source'] ?? $package['homepage'] ?? '';
if ($link !== '') {
@ -674,10 +726,13 @@ EOT
$latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion;
}
$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) {
$description = strtok($package['description'], "\r\n");
$remaining = $width - $nameLength - $versionLength - 4;
$remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4;
if ($writeLatest) {
$remaining -= $latestLength;
}
@ -806,14 +861,20 @@ EOT
*/
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->write('<info>name</info> : ' . $package->getPrettyName());
$io->write('<info>descrip.</info> : ' . $package->getDescription());
$io->write('<info>keywords</info> : ' . implode(', ', $package->getKeywords() ?: []));
$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) {
$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 {
$latestPackage = $package;
}
@ -822,7 +883,7 @@ EOT
$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>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);
if (is_string($path)) {
$io->write('<info>path</info> : ' . realpath($path));
@ -993,6 +1054,10 @@ EOT
} else {
$json['path'] = null;
}
if ($package->getReleaseDate() !== null) {
$json['released'] = $package->getReleaseDate()->format(DATE_ATOM);
}
}
if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
@ -1447,4 +1512,30 @@ EOT
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';
}
}

View File

@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Package;
use Composer\Pcre\Preg;
/**
* @author Nils Adermann <naderman@naderman.de>
@ -111,6 +112,14 @@ class LockTransaction extends Transaction
if ($package->getName() === $presentPackage->getName() && $package->getVersion() === $presentPackage->getVersion()) {
if ($presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) {
$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) {
$package->setReleaseDate($presentPackage->getReleaseDate());

View File

@ -24,7 +24,7 @@ interface DvcsDownloaderInterface
/**
* Checks for unpushed changes to a current branch
*
* @param PackageInterface $package package directory
* @param PackageInterface $package package instance
* @param string $path package directory
* @return string|null changes or null
*/

View File

@ -458,7 +458,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
/**
* 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
* @throws \RuntimeException If any problem with the url
* @return non-empty-string url

View File

@ -24,7 +24,7 @@ interface VcsCapableDownloaderInterface
/**
* Gets the VCS Reference for the package at path
*
* @param PackageInterface $package package directory
* @param PackageInterface $package package instance
* @param string $path package directory
* @return string|null reference or null
*/

View File

@ -20,4 +20,9 @@ final class IgnoreAllPlatformRequirementFilter implements PlatformRequirementFil
{
return PlatformRepository::isPlatformPackage($req);
}
public function isUpperBoundIgnored(string $req): bool
{
return $this->isIgnored($req);
}
}

View File

@ -60,6 +60,15 @@ final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFi
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
*/

View File

@ -21,4 +21,12 @@ final class IgnoreNothingPlatformRequirementFilter implements PlatformRequiremen
{
return false;
}
/**
* @return false
*/
public function isUpperBoundIgnored(string $req): bool
{
return false;
}
}

View File

@ -15,4 +15,6 @@ namespace Composer\Filter\PlatformRequirementFilter;
interface PlatformRequirementFilterInterface
{
public function isIgnored(string $req): bool;
public function isUpperBoundIgnored(string $req): bool;
}

View File

@ -372,6 +372,13 @@ class Installer
}
}
$fundEnv = Platform::getEnv('COMPOSER_FUND');
$showFunding = true;
if (is_numeric($fundEnv)) {
$showFunding = intval($fundEnv) !== 0;
}
if ($showFunding) {
$fundingCount = 0;
foreach ($localRepo->getPackages() as $package) {
if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
@ -389,6 +396,7 @@ class Installer
'<info>Use the `composer fund` command to find out more!</info>',
]);
}
}
if ($this->runScripts) {
// dispatch post event
@ -605,7 +613,14 @@ class Installer
// 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())) {
$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);
}
}

View File

@ -83,10 +83,10 @@ class VersionBumper
$pattern = '{
(?<=,|\ |\||^) # leading separator
(?P<constraint>
\^'.$major.'(?:\.\d+)* # e.g. ^2.anything
| ~'.$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
| >=\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc
\^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything
| ~v?'.$major.'(?:\.\d+){0,2} # e.g. ~2 or ~2.2 or ~2.2.2 but no more
| v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc
| >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc
| \* # full wildcard
)
(?=,|$|\ |\||@) # trailing separator

View File

@ -13,6 +13,7 @@
namespace Composer\Package\Version;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
@ -130,6 +131,13 @@ class VersionSelector
// constraint satisfied, go to next require
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
@ -182,6 +190,7 @@ class VersionSelector
*
* For example:
* * 1.2.1 -> ^1.2
* * 1.2.1.2 -> ^1.2
* * 1.2 -> ^1.2
* * v3.2.1 -> ^3.2
* * 2.0-beta.1 -> ^2.0@beta
@ -227,7 +236,7 @@ class VersionSelector
$semanticVersionParts = explode('.', $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)
if ($semanticVersionParts[0] === '0') {
unset($semanticVersionParts[3]);

View File

@ -109,7 +109,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
private $partialPackagesByName = null;
/** @var bool */
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;
/**
@ -637,6 +637,15 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$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();
/**
* @param array<mixed> $data
@ -700,8 +709,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]);
$response = $this->httpDownloader->get($apiUrl, $options);
$warned = false;
/** @var string $name */
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) {
$advisories[$name] = array_filter(array_map(
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'])) {
$this->securityAdvisoryConfig = [
'metadata' => $data['security-advisories']['metadata'] ?? false,
'api-url' => $data['security-advisories']['api-url'] ?? null,
'query-all' => $data['security-advisories']['query-all'] ?? false,
'api-url' => isset($data['security-advisories']['api-url']) && is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null,
];
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
*/
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)) {
return $matches[0] . $url;
}

View File

@ -43,7 +43,7 @@ class GitLabDriver extends VcsDriver
/**
* @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
@ -381,6 +381,10 @@ class GitLabDriver extends VcsDriver
protected function fetchProject(): void
{
if (!is_null($this->project)) {
return;
}
// we need to fetch the default branch from the api
$resource = $this->getApiUrl();
$this->project = $this->getContents($resource, true)->decodeJson();
@ -581,6 +585,18 @@ class GitLabDriver extends VcsDriver
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
{
$header = $response->getHeader('link');

View File

@ -28,7 +28,7 @@ use React\Promise\Promise;
* @internal
* @author Jordi Boggiano <j.boggiano@seld.be>
* @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}
*/
class CurlDownloader
@ -143,7 +143,7 @@ class CurlDownloader
/**
* @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
*
* @return int internal job id
@ -155,8 +155,15 @@ class CurlDownloader
'redirects' => 0,
'retries' => 0,
'storeAuth' => false,
'ipResolve' => null,
], $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;
// 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_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')) {
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'))
) && $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->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
$this->restartJobWithDelay($job, $job['url'], $attributes);
continue;
}
@ -582,7 +599,7 @@ class CurlDownloader
* @param Job $job
* @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
{
@ -600,7 +617,7 @@ class CurlDownloader
* @param Job $job
* @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
{

View File

@ -54,6 +54,7 @@ class AuditorTest extends TestCase
'expected' => 1,
'output' => '<warning>Found 2 security vulnerability advisories affecting 1 package:</warning>
Package: vendor1/package1
Severity: high
CVE: CVE3
Title: 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
--------
Package: vendor1/package1
Severity: medium
CVE: '.'
Title: 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' => 'Package: vendor1/package1'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE1'],
['text' => 'Title: 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' => 'Package: vendor1/package1'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE1'],
['text' => 'Title: 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' => 'Package: vendor1/package2'],
['text' => 'Severity: medium'],
['text' => 'CVE: '],
['text' => 'Title: 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' => 'Package: vendorx/packagex'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE5'],
['text' => 'Title: 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' => 'Package: vendor1/package1'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE1'],
['text' => 'Title: 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' => 'Package: vendor2/package1'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE2'],
['text' => 'Title: advisory3'],
['text' => 'URL: https://advisory.example.com/advisory3'],
@ -262,6 +270,7 @@ Found 2 abandoned packages:
['text' => 'Ignore reason: None specified'],
['text' => '--------'],
['text' => 'Package: vendor2/package1'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE4'],
['text' => 'Title: advisory6'],
['text' => 'URL: https://advisory.example.com/advisory6'],
@ -270,6 +279,7 @@ Found 2 abandoned packages:
['text' => 'Ignore reason: None specified'],
['text' => '--------'],
['text' => 'Package: vendorx/packagex'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE5'],
['text' => 'Title: advisory17'],
['text' => 'URL: https://advisory.example.com/advisory17'],
@ -278,6 +288,7 @@ Found 2 abandoned packages:
['text' => 'Ignore reason: None specified'],
['text' => 'Found 1 security vulnerability advisory affecting 1 package:'],
['text' => 'Package: vendor3/package1'],
['text' => 'Severity: medium'],
['text' => 'CVE: CVE5'],
['text' => 'Title: advisory7'],
['text' => 'URL: https://advisory.example.com/advisory7'],
@ -380,6 +391,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2022-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
[
'advisoryId' => 'ID4',
@ -396,6 +408,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2022-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'high',
],
[
'advisoryId' => 'ID5',
@ -412,6 +425,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2022-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
],
'vendor1/package2' => [
@ -430,6 +444,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2022-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
],
'vendorx/packagex' => [
@ -448,6 +463,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2015-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
],
'vendor2/package1' => [
@ -466,6 +482,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2022-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
[
'advisoryId' => 'ID6',
@ -482,6 +499,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2015-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
],
'vendory/packagey' => [
@ -500,6 +518,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2015-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
],
'vendor3/package1' => [
@ -518,6 +537,7 @@ Found 2 abandoned packages:
],
'reportedAt' => '2015-05-25 13:21:00',
'composerRepository' => 'https://packagist.org',
'severity' => 'medium',
],
],
];

View File

@ -76,7 +76,7 @@ class BumpCommandTest extends TestCase
yield 'bump all by default' => [
[
'require' => [
'first/pkg' => '^2.0',
'first/pkg' => '^v2.0',
'second/pkg' => '3.*',
],
'require-dev' => [

View File

@ -13,6 +13,7 @@
namespace Composer\Test\Command;
use Composer\Test\TestCase;
use RuntimeException;
class ConfigCommandTest extends TestCase
{
@ -139,4 +140,13 @@ class ConfigCommandTest extends TestCase
'{"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]);
}
}

View File

@ -17,6 +17,8 @@ use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use Composer\Repository\PlatformRepository;
use Composer\Test\TestCase;
use DateTimeImmutable;
use InvalidArgumentException;
class ShowCommandTest extends TestCase
{
@ -28,6 +30,8 @@ class ShowCommandTest extends TestCase
public function testShow(array $command, string $expected, array $requires = []): void
{
$this->initTempComposer([
'name' => 'root/pkg',
'version' => '1.2.3',
'repositories' => [
'packages' => [
'type' => 'package',
@ -55,12 +59,19 @@ class ShowCommandTest extends TestCase
$pkg = self::getPackage('vendor/package', '1.0.0');
$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,
self::getPackage('outdated/major', '1.0.0'),
self::getPackage('outdated/minor', '1.0.0'),
self::getPackage('outdated/patch', '1.0.0'),
]);
$appTester = $this->getApplicationTester();
@ -78,6 +89,21 @@ outdated/patch 1.0.0
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' => [
['-a' => true],
'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>',
];
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' => [
['command' => 'outdated', '--direct' => true],
'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)));
}
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
{
$this->initTempComposer([
@ -533,7 +591,7 @@ OUTPUT;
public function testSelf(): void
{
$this->initTempComposer(['name' => 'vendor/package']);
$this->initTempComposer(['name' => 'vendor/package', 'time' => date('Y-m-d')]);
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'show', '--self' => true]);
@ -542,6 +600,7 @@ OUTPUT;
'descrip.' => '',
'keywords' => '',
'versions' => '* 1.0.0+no-version-set',
'released' => date('Y-m-d'). ', today',
'type' => 'library',
'homepage' => '',
'source' => '[] ',

View File

@ -29,7 +29,7 @@ class UpdateCommandTest extends TestCase
$appTester = $this->getApplicationTester();
$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
@ -67,6 +67,29 @@ Package operations: 2 installs, 0 updates, 0 removals
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' => [
$rootDepAndTransitiveDep,
['--with' => ['dep/pkg:1.0.0'], '--no-install' => true],

View File

@ -25,6 +25,7 @@ final class IgnoreAllPlatformRequirementFilterTest extends TestCase
$platformRequirementFilter = new IgnoreAllPlatformRequirementFilter();
$this->assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req));
$this->assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req));
}
/**

View File

@ -48,4 +48,37 @@ final class IgnoreListPlatformRequirementFilterTest extends TestCase
'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],
];
}
}

View File

@ -25,6 +25,7 @@ final class IgnoreNothingPlatformRequirementFilterTest extends TestCase
$platformRequirementFilter = new IgnoreNothingPlatformRequirementFilter();
$this->assertFalse($platformRequirementFilter->isIgnored($req)); // @phpstan-ignore-line
$this->assertFalse($platformRequirementFilter->isUpperBoundIgnored($req)); // @phpstan-ignore-line
}
/**

View File

@ -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)

View File

@ -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)

View File

@ -59,6 +59,7 @@ class InstallerTest extends TestCase
{
parent::tearDown();
Platform::clearEnv('COMPOSER_POOL_OPTIMIZER');
Platform::clearEnv('COMPOSER_FUND');
chdir($this->prevCwd);
if (isset($this->tempComposerHome) && is_dir($this->tempComposerHome)) {

View File

@ -44,6 +44,7 @@ class VersionBumperTest extends TestCase
{
// constraint, version, expected recommendation, [branch-alias]
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/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'];
@ -58,6 +59,7 @@ class VersionBumperTest extends TestCase
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 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' => ['2.x.x', '2.4.0', '^2.4.0'];
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 '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 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 'upgrade full wildcard to bigger-or-eq' => ['*', '1.2.3', '>=1.2.3'];
}

View File

@ -346,6 +346,9 @@ class VersionSelectorTest extends TestCase
['0.1.3', '^0.1.3'],
['0.0.3', '^0.0.3'],
['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
['v20121020', 'v20121020'],
['v20121020.2', 'v20121020.2'],