Merge remote-tracking branch 'upstream/main' into git-merge-composer.lock-detection
commit
c2ebc9532f
|
@ -30,6 +30,7 @@ jobs:
|
|||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
dependencies: [locked]
|
||||
os: [ubuntu-latest]
|
||||
experimental: [false]
|
||||
|
@ -57,11 +58,11 @@ jobs:
|
|||
- php-version: "8.2"
|
||||
dependencies: lowest-ignore
|
||||
os: ubuntu-latest
|
||||
experimental: true
|
||||
experimental: false
|
||||
- php-version: "8.2"
|
||||
dependencies: highest-ignore
|
||||
os: ubuntu-latest
|
||||
experimental: true
|
||||
experimental: false
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"symfony/filesystem": "^5.4 || ^6.0 || ^7",
|
||||
"symfony/finder": "^5.4 || ^6.0 || ^7",
|
||||
"symfony/process": "^5.4 || ^6.0 || ^7",
|
||||
"react/promise": "^2.8",
|
||||
"react/promise": "^2.8 || ^3",
|
||||
"composer/pcre": "^2.1 || ^3.1",
|
||||
"symfony/polyfill-php73": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "c50c89580fa044b7523cb55c2d557c87",
|
||||
"content-hash": "4bceaf933dcf6bc05808134e78d21496",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
|
@ -692,23 +692,24 @@
|
|||
},
|
||||
{
|
||||
"name": "react/promise",
|
||||
"version": "v2.10.0",
|
||||
"version": "v3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise.git",
|
||||
"reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38"
|
||||
"reference": "c86753c76fd3be465d93b308f18d189f01a22be4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
|
||||
"reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/c86753c76fd3be465d93b308f18d189f01a22be4",
|
||||
"reference": "c86753c76fd3be465d93b308f18d189f01a22be4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
"php": ">=7.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36"
|
||||
"phpstan/phpstan": "1.10.20 || 1.4.10",
|
||||
"phpunit/phpunit": "^9.5 || ^7.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -752,7 +753,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/promise/issues",
|
||||
"source": "https://github.com/reactphp/promise/tree/v2.10.0"
|
||||
"source": "https://github.com/reactphp/promise/tree/v3.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -760,7 +761,7 @@
|
|||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2023-05-02T15:15:43+00:00"
|
||||
"time": "2023-07-11T16:12:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "seld/jsonlint",
|
||||
|
@ -2034,16 +2035,16 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.25",
|
||||
"version": "1.10.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "578f4e70d117f9a90699324c555922800ac38d8c"
|
||||
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/578f4e70d117f9a90699324c555922800ac38d8c",
|
||||
"reference": "578f4e70d117f9a90699324c555922800ac38d8c",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
|
||||
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2092,7 +2093,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-06T12:11:37+00:00"
|
||||
"time": "2023-07-19T12:44:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-deprecation-rules",
|
||||
|
|
|
@ -968,7 +968,7 @@ performance.
|
|||
* **--ignore-platform-req:** ignore a specific platform requirement (`php`, `hhvm`,
|
||||
`lib-*` and `ext-*`) and skip the [platform check](07-runtime.md#platform-check) for it.
|
||||
Multiple requirements can be ignored via wildcard.
|
||||
* **--strict-psr:** Return a failed status code (1) if PSR-4 or PSR-0 mapping errors
|
||||
* **--strict-psr:** Return a failed exit code (1) if PSR-4 or PSR-0 mapping errors
|
||||
are present. Requires --optimize to work.
|
||||
|
||||
## clear-cache / clearcache / cc
|
||||
|
|
|
@ -101,6 +101,24 @@ optionally be an object with package name patterns for keys for more granular in
|
|||
> configuration in global and package configurations the string notation
|
||||
> is translated to a `*` package pattern.
|
||||
|
||||
## audit
|
||||
|
||||
Security audit configuration options
|
||||
|
||||
### ignored
|
||||
|
||||
A set of advisory ids, remote ids or CVE ids that should be ignored and not reported as part of an audit.
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"audit": {
|
||||
"ignored": ["CVE-1234", "GHSA-xx", "PKSA-yy"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## use-parent-dir
|
||||
|
||||
When running Composer in a directory where there is no composer.json, if there
|
||||
|
|
|
@ -1665,6 +1665,11 @@ parameters:
|
|||
count: 3
|
||||
path: ../src/Composer/Downloader/FileDownloader.php
|
||||
|
||||
-
|
||||
message: "#^Strict comparison using \\=\\=\\= between null and Composer\\\\Util\\\\Http\\\\Response will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
path: ../src/Composer/Downloader/FileDownloader.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$cwd of method Composer\\\\Util\\\\ProcessExecutor\\:\\:execute\\(\\) expects string\\|null, string\\|false given\\.$#"
|
||||
count: 5
|
||||
|
@ -2305,11 +2310,6 @@ parameters:
|
|||
count: 1
|
||||
path: ../src/Composer/Installer/InstallationManager.php
|
||||
|
||||
-
|
||||
message: "#^Only booleans are allowed in an if condition, React\\\\Promise\\\\PromiseInterface\\|null given\\.$#"
|
||||
count: 1
|
||||
path: ../src/Composer/Installer/InstallationManager.php
|
||||
|
||||
-
|
||||
message: "#^Only booleans are allowed in an if condition, Symfony\\\\Component\\\\Console\\\\Helper\\\\ProgressBar\\|null given\\.$#"
|
||||
count: 1
|
||||
|
|
|
@ -15,6 +15,7 @@ parameters:
|
|||
excludePaths:
|
||||
- '../tests/Composer/Test/Fixtures/*'
|
||||
- '../tests/Composer/Test/Autoload/Fixtures/*'
|
||||
- '../tests/Composer/Test/Autoload/MinimumVersionSupport/vendor/'
|
||||
- '../tests/Composer/Test/Plugin/Fixtures/*'
|
||||
- '../tests/Composer/Test/PolyfillTestCase.php'
|
||||
|
||||
|
|
|
@ -325,6 +325,19 @@
|
|||
"type": ["string"]
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"type": "object",
|
||||
"description": "Security audit configuration options",
|
||||
"properties": {
|
||||
"ignored": {
|
||||
"type": "array",
|
||||
"description": "A set of advisory ids, remote ids or CVE ids that should be ignored and not reported as part of an audit.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notify-on-install": {
|
||||
"type": "boolean",
|
||||
"description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true."
|
||||
|
|
|
@ -44,12 +44,18 @@ class Auditor
|
|||
* @param PackageInterface[] $packages
|
||||
* @param self::FORMAT_* $format The format that will be used to output audit results.
|
||||
* @param bool $warningOnly If true, outputs a warning. If false, outputs an error.
|
||||
* @param string[] $ignoredIds Ignored advisory IDs, remote IDs or CVE IDs
|
||||
* @return int Amount of packages with vulnerabilities found
|
||||
* @throws InvalidArgumentException If no packages are passed in
|
||||
*/
|
||||
public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true): int
|
||||
public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoredIds = []): int
|
||||
{
|
||||
$advisories = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY);
|
||||
|
||||
if (\count($ignoredIds) > 0) {
|
||||
$advisories = $this->filterIgnoredAdvisories($advisories, $ignoredIds);
|
||||
}
|
||||
|
||||
if (self::FORMAT_JSON === $format) {
|
||||
$io->write(JsonFile::encode(['advisories' => $advisories]));
|
||||
|
||||
|
@ -73,6 +79,40 @@ class Auditor
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> $advisories
|
||||
* @param array<string> $ignoredIds
|
||||
* @phpstan-return array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>
|
||||
*/
|
||||
private function filterIgnoredAdvisories(array $advisories, array $ignoredIds): array
|
||||
{
|
||||
foreach ($advisories as $package => $pkgAdvisories) {
|
||||
$advisories[$package] = array_filter($pkgAdvisories, static function (PartialSecurityAdvisory $advisory) use ($ignoredIds) {
|
||||
if (in_array($advisory->advisoryId, $ignoredIds, true)) {
|
||||
return false;
|
||||
}
|
||||
if ($advisory instanceof SecurityAdvisory) {
|
||||
if (in_array($advisory->cve, $ignoredIds, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($advisory->sources as $source) {
|
||||
if (in_array($source['remoteId'], $ignoredIds, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
if (\count($advisories[$package]) === 0) {
|
||||
unset($advisories[$package]);
|
||||
}
|
||||
}
|
||||
|
||||
return $advisories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<PartialSecurityAdvisory>> $advisories
|
||||
* @return array{int, int} Count of affected packages and total count of advisories
|
||||
|
|
|
@ -63,7 +63,7 @@ EOT
|
|||
$repoSet->addRepository($repo);
|
||||
}
|
||||
|
||||
return min(255, $auditor->audit($this->getIO(), $repoSet, $packages, $this->getAuditFormat($input, 'format'), false));
|
||||
return min(255, $auditor->audit($this->getIO(), $repoSet, $packages, $this->getAuditFormat($input, 'format'), false, $composer->getConfig()->get('audit')['ignored'] ?? []));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -556,8 +556,27 @@ EOT
|
|||
return $vals;
|
||||
},
|
||||
],
|
||||
'audit.ignore' => [
|
||||
static function ($vals) {
|
||||
if (!is_array($vals)) {
|
||||
return 'array expected';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
static function ($vals) {
|
||||
return $vals;
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
// allow unsetting audit config entirely
|
||||
if ($input->getOption('unset') && $settingKey === 'audit') {
|
||||
$this->configSource->removeConfigSetting($settingKey);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) {
|
||||
if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) {
|
||||
$this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
|
||||
|
|
|
@ -442,6 +442,10 @@ EOT
|
|||
$exitCode = 0;
|
||||
$viewData = [];
|
||||
$viewMetaData = [];
|
||||
|
||||
$writeVersion = false;
|
||||
$writeDescription = false;
|
||||
|
||||
foreach (['platform' => true, 'locked' => true, 'available' => false, 'installed' => true] as $type => $showVersion) {
|
||||
if (isset($packages[$type])) {
|
||||
ksort($packages[$type]);
|
||||
|
@ -616,14 +620,14 @@ EOT
|
|||
$io->writeError('');
|
||||
$io->writeError('<info>Direct dependencies required in composer.json:</>');
|
||||
if (\count($directDeps) > 0) {
|
||||
$this->printPackages($io, $directDeps, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
||||
$this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
||||
} 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, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
||||
$this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
||||
} else {
|
||||
$io->writeError('Everything up to date');
|
||||
}
|
||||
|
@ -631,7 +635,7 @@ EOT
|
|||
if ($writeLatest && \count($packages) === 0) {
|
||||
$io->writeError('All your direct dependencies are up to date');
|
||||
} else {
|
||||
$this->printPackages($io, $packages, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
||||
$this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -649,15 +653,18 @@ EOT
|
|||
*/
|
||||
private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength): void
|
||||
{
|
||||
$padName = $writeVersion || $writeLatest || $writeDescription;
|
||||
$padVersion = $writeLatest || $writeDescription;
|
||||
$padLatest = $writeDescription;
|
||||
foreach ($packages as $package) {
|
||||
$link = $package['source'] ?? $package['homepage'] ?? '';
|
||||
if ($link !== '') {
|
||||
$io->write($indent . '<href='.OutputFormatter::escape($link).'>'.$package['name'].'</>'. str_repeat(' ', $nameLength - strlen($package['name'])), false);
|
||||
$io->write($indent . '<href='.OutputFormatter::escape($link).'>'.$package['name'].'</>'. str_repeat(' ', ($padName ? $nameLength - strlen($package['name']) : 0)), false);
|
||||
} else {
|
||||
$io->write($indent . str_pad($package['name'], $nameLength, ' '), false);
|
||||
$io->write($indent . str_pad($package['name'], ($padName ? $nameLength : 0), ' '), false);
|
||||
}
|
||||
if (isset($package['version']) && $writeVersion) {
|
||||
$io->write(' ' . str_pad($package['version'], $versionLength, ' '), false);
|
||||
$io->write(' ' . str_pad($package['version'], ($padVersion ? $versionLength : 0), ' '), false);
|
||||
}
|
||||
if (isset($package['latest']) && isset($package['latest-status']) && $writeLatest) {
|
||||
$latestVersion = $package['latest'];
|
||||
|
@ -666,7 +673,7 @@ EOT
|
|||
if (!$io->isDecorated()) {
|
||||
$latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion;
|
||||
}
|
||||
$io->write(' <' . $style . '>' . str_pad($latestVersion, $latestLength, ' ') . '</' . $style . '>', false);
|
||||
$io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '</' . $style . '>', false);
|
||||
}
|
||||
if (isset($package['description']) && $writeDescription) {
|
||||
$description = strtok($package['description'], "\r\n");
|
||||
|
@ -815,7 +822,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 ($installedRepo->hasPackage($package)) {
|
||||
if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) {
|
||||
$path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
|
||||
if (is_string($path)) {
|
||||
$io->write('<info>path</info> : ' . realpath($path));
|
||||
|
@ -976,7 +983,7 @@ EOT
|
|||
];
|
||||
}
|
||||
|
||||
if ($installedRepo->hasPackage($package)) {
|
||||
if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) {
|
||||
$path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
|
||||
if (is_string($path)) {
|
||||
$path = realpath($path);
|
||||
|
|
|
@ -37,6 +37,7 @@ class Config
|
|||
'allow-plugins' => [],
|
||||
'use-parent-dir' => 'prompt',
|
||||
'preferred-install' => 'dist',
|
||||
'audit' => ['ignored' => []],
|
||||
'notify-on-install' => true,
|
||||
'github-protocols' => ['https', 'ssh', 'git'],
|
||||
'gitlab-protocol' => null,
|
||||
|
@ -207,6 +208,11 @@ class Config
|
|||
$this->config[$key] = $val;
|
||||
$this->setSourceOfConfigValue($val, $key, $source);
|
||||
}
|
||||
} elseif ('audit' === $key) {
|
||||
$currentIgnores = $this->config['audit']['ignored'];
|
||||
$this->config[$key] = $val;
|
||||
$this->setSourceOfConfigValue($val, $key, $source);
|
||||
$this->config['audit']['ignored'] = array_merge($currentIgnores, $val['ignored']);
|
||||
} else {
|
||||
$this->config[$key] = $val;
|
||||
$this->setSourceOfConfigValue($val, $key, $source);
|
||||
|
|
|
@ -216,6 +216,7 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
*
|
||||
* @param string $file Extracted file
|
||||
* @param string $path Directory
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*
|
||||
* @throws \UnexpectedValueException If can not extract downloaded file to path
|
||||
*/
|
||||
|
|
|
@ -174,6 +174,7 @@ class DownloadManager
|
|||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @param PackageInterface|null $prevPackage previous package instance in case of updates
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*
|
||||
* @throws \InvalidArgumentException if package have no urls to download from
|
||||
* @throws \RuntimeException
|
||||
|
@ -241,6 +242,7 @@ class DownloadManager
|
|||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @param PackageInterface|null $prevPackage previous package instance in case of updates
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function prepare(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
|
||||
{
|
||||
|
@ -258,6 +260,7 @@ class DownloadManager
|
|||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*
|
||||
* @throws \InvalidArgumentException if package have no urls to download from
|
||||
* @throws \RuntimeException
|
||||
|
@ -279,6 +282,7 @@ class DownloadManager
|
|||
* @param PackageInterface $initial initial package version
|
||||
* @param PackageInterface $target target package version
|
||||
* @param string $targetDir target dir
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*
|
||||
* @throws \InvalidArgumentException if initial package is not installed
|
||||
*/
|
||||
|
@ -328,6 +332,7 @@ class DownloadManager
|
|||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function remove(PackageInterface $package, string $targetDir): PromiseInterface
|
||||
{
|
||||
|
@ -347,6 +352,7 @@ class DownloadManager
|
|||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @param PackageInterface|null $prevPackage previous package instance in case of updates
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function cleanup(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
|
||||
{
|
||||
|
|
|
@ -34,6 +34,7 @@ interface DownloaderInterface
|
|||
* This should do any network-related tasks to prepare for an upcoming install/update
|
||||
*
|
||||
* @param string $path download path
|
||||
* @phpstan-return PromiseInterface<void|null|string>
|
||||
*/
|
||||
public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;
|
||||
|
||||
|
@ -49,6 +50,7 @@ interface DownloaderInterface
|
|||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;
|
||||
|
||||
|
@ -57,6 +59,7 @@ interface DownloaderInterface
|
|||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function install(PackageInterface $package, string $path): PromiseInterface;
|
||||
|
||||
|
@ -66,6 +69,7 @@ interface DownloaderInterface
|
|||
* @param PackageInterface $initial initial package
|
||||
* @param PackageInterface $target updated package
|
||||
* @param string $path download path
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface;
|
||||
|
||||
|
@ -74,6 +78,7 @@ interface DownloaderInterface
|
|||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function remove(PackageInterface $package, string $path): PromiseInterface;
|
||||
|
||||
|
@ -88,6 +93,7 @@ interface DownloaderInterface
|
|||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;
|
||||
}
|
||||
|
|
|
@ -536,6 +536,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function discardChanges(string $path): PromiseInterface
|
||||
|
@ -551,6 +552,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function stashChanges(string $path): PromiseInterface
|
||||
|
|
|
@ -230,6 +230,9 @@ class SvnDownloader extends VcsDownloader
|
|||
return "Could not retrieve changes between $fromReference and $toReference due to missing revision information";
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
protected function discardChanges(string $path): PromiseInterface
|
||||
{
|
||||
if (0 !== $this->process->execute('svn revert -R .', $output, $path)) {
|
||||
|
|
|
@ -259,6 +259,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* if false (remove) the changes should be assumed to be lost if the operation is not aborted
|
||||
*
|
||||
* @throws \RuntimeException in case the operation must be aborted
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface
|
||||
{
|
||||
|
@ -286,6 +287,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* @param string $path download path
|
||||
* @param string $url package url
|
||||
* @param PackageInterface|null $prevPackage previous package (in case of an update)
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
abstract protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface;
|
||||
|
||||
|
@ -295,6 +297,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
* @param string $url package url
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
abstract protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface;
|
||||
|
||||
|
@ -305,6 +308,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* @param PackageInterface $target updated package
|
||||
* @param string $path download path
|
||||
* @param string $url package url
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface;
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ class ZipDownloader extends ArchiveDownloader
|
|||
*
|
||||
* @param string $file File to extract
|
||||
* @param string $path Path where to extract file
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
private function extractWithSystemUnzip(PackageInterface $package, string $file, string $path): PromiseInterface
|
||||
{
|
||||
|
@ -194,6 +195,7 @@ class ZipDownloader extends ArchiveDownloader
|
|||
*
|
||||
* @param string $file File to extract
|
||||
* @param string $path Path where to extract file
|
||||
* @phpstan-return PromiseInterface<void|null>
|
||||
*/
|
||||
private function extractWithZipArchive(PackageInterface $package, string $file, string $path): PromiseInterface
|
||||
{
|
||||
|
|
|
@ -402,7 +402,7 @@ class Installer
|
|||
$repoSet->addRepository($repo);
|
||||
}
|
||||
|
||||
return $auditor->audit($this->io, $repoSet, $packages, $this->auditFormat) > 0 ? self::ERROR_AUDIT_FAILED : 0;
|
||||
return $auditor->audit($this->io, $repoSet, $packages, $this->auditFormat, true, $this->config->get('audit')['ignored'] ?? []) > 0 ? self::ERROR_AUDIT_FAILED : 0;
|
||||
} catch (TransportException $e) {
|
||||
$this->io->error('Failed to audit '.$target.' packages.');
|
||||
if ($this->io->isVerbose()) {
|
||||
|
|
|
@ -401,7 +401,7 @@ if [ -n "\$bashSource" ]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
"\${dir}/$binFile" "\$@"
|
||||
sh "\${dir}/$binFile" "\$@"
|
||||
|
||||
PROXY;
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ class InstallationManager
|
|||
*/
|
||||
public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void
|
||||
{
|
||||
/** @var array<callable(): ?PromiseInterface> */
|
||||
/** @var array<callable(): ?PromiseInterface<void|null>> */
|
||||
$cleanupPromises = [];
|
||||
|
||||
$signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) {
|
||||
|
@ -237,8 +237,8 @@ class InstallationManager
|
|||
|
||||
/**
|
||||
* @param OperationInterface[] $operations List of operations to execute in this batch
|
||||
* @param array<callable(): ?PromiseInterface> $cleanupPromises
|
||||
* @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
|
||||
* @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
|
||||
*/
|
||||
private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations): void
|
||||
{
|
||||
|
@ -275,7 +275,7 @@ class InstallationManager
|
|||
|
||||
if ($opType !== 'uninstall') {
|
||||
$promise = $installer->download($package, $initialPackage);
|
||||
if ($promise) {
|
||||
if (null !== $promise) {
|
||||
$promises[] = $promise;
|
||||
}
|
||||
}
|
||||
|
@ -322,8 +322,8 @@ class InstallationManager
|
|||
|
||||
/**
|
||||
* @param OperationInterface[] $operations List of operations to execute in this batch
|
||||
* @param array<callable(): ?PromiseInterface> $cleanupPromises
|
||||
* @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
|
||||
* @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
|
||||
*/
|
||||
private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void
|
||||
{
|
||||
|
@ -413,7 +413,7 @@ class InstallationManager
|
|||
}
|
||||
|
||||
/**
|
||||
* @param PromiseInterface[] $promises
|
||||
* @param array<PromiseInterface<void|null>> $promises
|
||||
*/
|
||||
private function waitOnPromises(array $promises): void
|
||||
{
|
||||
|
@ -440,7 +440,7 @@ class InstallationManager
|
|||
/**
|
||||
* Executes download operation.
|
||||
*
|
||||
* $param PackageInterface $package
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function download(PackageInterface $package): ?PromiseInterface
|
||||
{
|
||||
|
@ -455,6 +455,7 @@ class InstallationManager
|
|||
*
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param InstallOperation $operation operation instance
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, InstallOperation $operation): ?PromiseInterface
|
||||
{
|
||||
|
@ -471,6 +472,7 @@ class InstallationManager
|
|||
*
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param UpdateOperation $operation operation instance
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function update(InstalledRepositoryInterface $repo, UpdateOperation $operation): ?PromiseInterface
|
||||
{
|
||||
|
@ -509,6 +511,7 @@ class InstallationManager
|
|||
*
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param UninstallOperation $operation operation instance
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function uninstall(InstalledRepositoryInterface $repo, UninstallOperation $operation): ?PromiseInterface
|
||||
{
|
||||
|
@ -638,8 +641,8 @@ class InstallationManager
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array<callable(): ?PromiseInterface> $cleanupPromises
|
||||
* @return void
|
||||
* @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
|
||||
*/
|
||||
private function runCleanup(array $cleanupPromises): void
|
||||
{
|
||||
|
@ -648,7 +651,7 @@ class InstallationManager
|
|||
$this->loop->abortJobs();
|
||||
|
||||
foreach ($cleanupPromises as $cleanup) {
|
||||
$promises[] = new \React\Promise\Promise(static function ($resolve, $reject) use ($cleanup): void {
|
||||
$promises[] = new \React\Promise\Promise(static function ($resolve) use ($cleanup): void {
|
||||
$promise = $cleanup();
|
||||
if (!$promise instanceof PromiseInterface) {
|
||||
$resolve();
|
||||
|
|
|
@ -48,6 +48,7 @@ interface InstallerInterface
|
|||
* @param PackageInterface $package package instance
|
||||
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function download(PackageInterface $package, ?PackageInterface $prevPackage = null);
|
||||
|
||||
|
@ -63,6 +64,7 @@ interface InstallerInterface
|
|||
* @param PackageInterface $package package instance
|
||||
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function prepare(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null);
|
||||
|
||||
|
@ -72,6 +74,7 @@ interface InstallerInterface
|
|||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $package package instance
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||
|
||||
|
@ -83,6 +86,7 @@ interface InstallerInterface
|
|||
* @param PackageInterface $target updated version
|
||||
* @throws InvalidArgumentException if $initial package is not installed
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);
|
||||
|
||||
|
@ -92,6 +96,7 @@ interface InstallerInterface
|
|||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $package package instance
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||
|
||||
|
@ -106,6 +111,7 @@ interface InstallerInterface
|
|||
* @param PackageInterface $package package instance
|
||||
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
public function cleanup(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null);
|
||||
|
||||
|
|
|
@ -272,6 +272,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
|
||||
/**
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
protected function installCode(PackageInterface $package)
|
||||
{
|
||||
|
@ -282,6 +283,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
|
||||
/**
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
protected function updateCode(PackageInterface $initial, PackageInterface $target)
|
||||
{
|
||||
|
@ -316,6 +318,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
|
||||
/**
|
||||
* @return PromiseInterface|null
|
||||
* @phpstan-return PromiseInterface<void|null>|null
|
||||
*/
|
||||
protected function removeCode(PackageInterface $package)
|
||||
{
|
||||
|
|
|
@ -322,9 +322,8 @@ class VersionGuesser
|
|||
$prettyVersion = 'dev-' . $candidateVersion;
|
||||
if ($length === 0) {
|
||||
foreach ($promises as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
// to support react/promise 2.x we wrap the promise in a resolve() call for safety
|
||||
\React\Promise\resolve($promise)->cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1070,6 +1070,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
|||
return ['namesFound' => $namesFound, 'packages' => $packages];
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return PromiseInterface<array{mixed, string}>
|
||||
*/
|
||||
private function startCachedAsyncDownload(string $fileName, ?string $packageName = null): PromiseInterface
|
||||
{
|
||||
if (null === $this->lazyProvidersUrl) {
|
||||
|
@ -1598,6 +1601,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return PromiseInterface<array<mixed>|true> true if the response was a 304 and the cache is fresh, otherwise it returns the decoded json
|
||||
*/
|
||||
private function asyncFetchFile(string $filename, string $cacheKey, ?string $lastModifiedTime = null): PromiseInterface
|
||||
{
|
||||
if ('' === $filename) {
|
||||
|
@ -1610,7 +1616,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
|||
|
||||
if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) {
|
||||
// make it look like we got a 304 response
|
||||
return \React\Promise\resolve(true);
|
||||
/** @var PromiseInterface<true> $promise */
|
||||
$promise = \React\Promise\resolve(true);
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
$httpDownloader = $this->httpDownloader;
|
||||
|
|
|
@ -238,7 +238,12 @@ class PlatformRepository extends ArrayRepository
|
|||
$parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips);
|
||||
$this->addLibrary($name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []);
|
||||
} else {
|
||||
$this->addLibrary($name.'-'.$library, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', ['curl-openssl']);
|
||||
if ($library === '(securetransport) openssl') {
|
||||
$shortlib = 'securetransport';
|
||||
} else {
|
||||
$shortlib = $library;
|
||||
}
|
||||
$this->addLibrary($name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', ['curl-openssl']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -310,7 +310,7 @@ class GitHubDriver extends VcsDriver
|
|||
$resource = $this->getContents($resource['git_url'])->decodeJson();
|
||||
}
|
||||
|
||||
if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) {
|
||||
if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content']))) {
|
||||
throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier);
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@ class Filesystem
|
|||
*
|
||||
* @throws \RuntimeException
|
||||
* @return PromiseInterface
|
||||
* @phpstan-return PromiseInterface<bool>
|
||||
*/
|
||||
public function removeDirectoryAsync(string $directory)
|
||||
{
|
||||
|
@ -784,6 +785,12 @@ class Filesystem
|
|||
if (!is_dir($target)) {
|
||||
throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target);
|
||||
}
|
||||
|
||||
// Removing any previously junction to ensure clean execution.
|
||||
if (!is_dir($junction) || $this->isJunction($junction)) {
|
||||
@rmdir($junction);
|
||||
}
|
||||
|
||||
$cmd = sprintf(
|
||||
'mklink /J %s %s',
|
||||
ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)),
|
||||
|
|
|
@ -357,6 +357,13 @@ class CurlDownloader
|
|||
continue;
|
||||
}
|
||||
|
||||
// TODO: Remove this as soon as https://github.com/curl/curl/issues/10591 is resolved
|
||||
if ($errno === 55 /* CURLE_SEND_ERROR */) {
|
||||
$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]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) {
|
||||
$timeoutWarning = true;
|
||||
$this->io->writeError('<warning>A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.</warning>');
|
||||
|
|
|
@ -28,7 +28,7 @@ use React\Promise\PromiseInterface;
|
|||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @phpstan-type Request array{url: non-empty-string, options: mixed[], copyTo: string|null}
|
||||
* @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: TransportException}
|
||||
* @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: \Throwable}
|
||||
*/
|
||||
class HttpDownloader
|
||||
{
|
||||
|
@ -123,6 +123,7 @@ class HttpDownloader
|
|||
* although not all options are supported when using the default curl downloader
|
||||
* @throws TransportException
|
||||
* @return PromiseInterface
|
||||
* @phpstan-return PromiseInterface<Http\Response>
|
||||
*/
|
||||
public function add(string $url, array $options = [])
|
||||
{
|
||||
|
@ -164,6 +165,7 @@ class HttpDownloader
|
|||
* although not all options are supported when using the default curl downloader
|
||||
* @throws TransportException
|
||||
* @return PromiseInterface
|
||||
* @phpstan-return PromiseInterface<Http\Response>
|
||||
*/
|
||||
public function addCopy(string $url, string $to, array $options = [])
|
||||
{
|
||||
|
@ -199,6 +201,7 @@ class HttpDownloader
|
|||
/**
|
||||
* @phpstan-param Request $request
|
||||
* @return array{Job, PromiseInterface}
|
||||
* @phpstan-return array{Job, PromiseInterface<Http\Response>}
|
||||
*/
|
||||
private function addJob(array $request, bool $sync = false): array
|
||||
{
|
||||
|
|
|
@ -25,7 +25,7 @@ class Loop
|
|||
private $httpDownloader;
|
||||
/** @var ProcessExecutor|null */
|
||||
private $processExecutor;
|
||||
/** @var PromiseInterface[][] */
|
||||
/** @var array<int, array<PromiseInterface<mixed>>> */
|
||||
private $currentPromises = [];
|
||||
/** @var int */
|
||||
private $waitIndex = 0;
|
||||
|
@ -52,18 +52,17 @@ class Loop
|
|||
}
|
||||
|
||||
/**
|
||||
* @param PromiseInterface[] $promises
|
||||
* @param ?ProgressBar $progress
|
||||
* @param array<PromiseInterface<mixed>> $promises
|
||||
* @param ProgressBar|null $progress
|
||||
*/
|
||||
public function wait(array $promises, ?ProgressBar $progress = null): void
|
||||
{
|
||||
/** @var \Exception|null */
|
||||
$uncaught = null;
|
||||
|
||||
\React\Promise\all($promises)->then(
|
||||
static function (): void {
|
||||
},
|
||||
static function ($e) use (&$uncaught): void {
|
||||
static function (\Throwable $e) use (&$uncaught): void {
|
||||
$uncaught = $e;
|
||||
}
|
||||
);
|
||||
|
@ -107,7 +106,7 @@ class Loop
|
|||
}
|
||||
|
||||
unset($this->currentPromises[$waitIndex]);
|
||||
if ($uncaught) {
|
||||
if (null !== $uncaught) {
|
||||
throw $uncaught;
|
||||
}
|
||||
}
|
||||
|
@ -116,9 +115,8 @@ class Loop
|
|||
{
|
||||
foreach ($this->currentPromises as $promiseGroup) {
|
||||
foreach ($promiseGroup as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
// to support react/promise 2.x we wrap the promise in a resolve() call for safety
|
||||
\React\Promise\resolve($promise)->cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,6 +155,7 @@ class ProcessExecutor
|
|||
*
|
||||
* @param string|list<string> $command the command to execute
|
||||
* @param string $cwd the working directory
|
||||
* @phpstan-return PromiseInterface<Process>
|
||||
*/
|
||||
public function executeAsync($command, ?string $cwd = null): PromiseInterface
|
||||
{
|
||||
|
|
|
@ -58,6 +58,7 @@ class SyncHelper
|
|||
* Waits for a promise to resolve
|
||||
*
|
||||
* @param Loop $loop Loop instance which you can get from $composer->getLoop()
|
||||
* @phpstan-param PromiseInterface<mixed>|null $promise
|
||||
*/
|
||||
public static function await(Loop $loop, ?PromiseInterface $promise = null): void
|
||||
{
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Test\Advisory;
|
|||
|
||||
use Composer\Advisory\PartialSecurityAdvisory;
|
||||
use Composer\Advisory\SecurityAdvisory;
|
||||
use Composer\IO\BufferIO;
|
||||
use Composer\IO\NullIO;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
|
@ -71,6 +72,35 @@ class AuditorTest extends TestCase
|
|||
$this->assertSame($expected, $result, $message);
|
||||
}
|
||||
|
||||
public function testAuditIgnoredIDs(): void
|
||||
{
|
||||
$packages = [
|
||||
new Package('vendor1/package1', '3.0.0.0', '3.0.0'),
|
||||
new Package('vendor1/package2', '3.0.0.0', '3.0.0'),
|
||||
new Package('vendorx/packagex', '3.0.0.0', '3.0.0'),
|
||||
new Package('vendor3/package1', '3.0.0.0', '3.0.0'),
|
||||
];
|
||||
|
||||
$ignoredIds = ['CVE1', 'ID2', 'RemoteIDx'];
|
||||
|
||||
$auditor = new Auditor();
|
||||
$result = $auditor->audit($io = $this->getIOMock(), $this->getRepoSet(), $packages, Auditor::FORMAT_PLAIN, false, $ignoredIds);
|
||||
$io->expects([
|
||||
['text' => 'Found 1 security vulnerability advisory affecting 1 package:'],
|
||||
['text' => 'Package: vendor3/package1'],
|
||||
['text' => 'CVE: CVE5'],
|
||||
['text' => 'Title: advisory7'],
|
||||
['text' => 'URL: https://advisory.example.com/advisory7'],
|
||||
['text' => 'Affected versions: >=3,<3.4.3|>=1,<2.5.6'],
|
||||
['text' => 'Reported at: 2015-05-25T13:21:00+00:00'],
|
||||
], true);
|
||||
$this->assertSame(1, $result);
|
||||
|
||||
// without ignored IDs, we should get all 4
|
||||
$result = $auditor->audit($io, $this->getRepoSet(), $packages, Auditor::FORMAT_PLAIN, false);
|
||||
$this->assertSame(4, $result);
|
||||
}
|
||||
|
||||
private function getRepoSet(): RepositorySet
|
||||
{
|
||||
$repo = $this
|
||||
|
@ -160,7 +190,7 @@ class AuditorTest extends TestCase
|
|||
'sources' => [
|
||||
[
|
||||
'name' => 'source2',
|
||||
'remoteId' => 'RemoteID2',
|
||||
'remoteId' => 'RemoteID4',
|
||||
],
|
||||
],
|
||||
'reportedAt' => '2022-05-25 13:21:00',
|
||||
|
@ -205,14 +235,14 @@ class AuditorTest extends TestCase
|
|||
[
|
||||
'advisoryId' => 'IDx',
|
||||
'packageName' => 'vendorx/packagex',
|
||||
'title' => 'advisory7',
|
||||
'link' => 'https://advisory.example.com/advisory7',
|
||||
'title' => 'advisory17',
|
||||
'link' => 'https://advisory.example.com/advisory17',
|
||||
'cve' => 'CVE5',
|
||||
'affectedVersions' => '>=3,<3.4.3|>=1,<2.5.6',
|
||||
'sources' => [
|
||||
[
|
||||
'name' => 'source2',
|
||||
'remoteId' => 'RemoteID4',
|
||||
'remoteId' => 'RemoteIDx',
|
||||
],
|
||||
],
|
||||
'reportedAt' => '2015-05-25 13:21:00',
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
/vendor/
|
||||
/composer.lock
|
||||
/vendor/
|
||||
|
|
|
@ -57,7 +57,7 @@ class HomeCommandTest extends TestCase
|
|||
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
||||
}
|
||||
|
||||
public function useCaseProvider(): Generator
|
||||
public static function useCaseProvider(): Generator
|
||||
{
|
||||
yield 'Invalid or missing repository URL' => [
|
||||
[
|
||||
|
|
|
@ -288,12 +288,19 @@ vendor/package 1.1.0 <highlight>! 1.2.0</highlight>", trim($appTester->getDispla
|
|||
unlink('./composer.json');
|
||||
unlink('./auth.json');
|
||||
|
||||
// listing packages
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'show', '-p' => true]);
|
||||
$output = trim($appTester->getDisplay(true));
|
||||
foreach (Regex::matchAll('{^(\w+)}m', $output)->matches as $m) {
|
||||
self::assertTrue(PlatformRepository::isPlatformPackage((string) $m[1]));
|
||||
}
|
||||
|
||||
// getting a single package
|
||||
$appTester->run(['command' => 'show', '-p' => true, 'package' => 'php']);
|
||||
$appTester->assertCommandIsSuccessful();
|
||||
$appTester->run(['command' => 'show', '-p' => true, '-f' => 'json', 'package' => 'php']);
|
||||
$appTester->assertCommandIsSuccessful();
|
||||
}
|
||||
|
||||
public function testOutdatedWithZeroMajor(): void
|
||||
|
@ -397,4 +404,45 @@ available:
|
|||
installed:
|
||||
vendor/installed 2.0.0 description of installed package', $output);
|
||||
}
|
||||
|
||||
public function testNameOnlyPrintsNoTrailingWhitespace(): void
|
||||
{
|
||||
$this->initTempComposer([
|
||||
'repositories' => [
|
||||
'packages' => [
|
||||
'type' => 'package',
|
||||
'package' => [
|
||||
// CAUTION: package names matter - output is sorted, and we want shorter before longer ones
|
||||
['name' => 'vendor/apackage', 'description' => 'generic description', 'version' => '1.0.0'],
|
||||
['name' => 'vendor/apackage', 'description' => 'generic description', 'version' => '1.1.0'],
|
||||
['name' => 'vendor/longpackagename', 'description' => 'generic description', 'version' => '1.0.0'],
|
||||
['name' => 'vendor/longpackagename', 'description' => 'generic description', 'version' => '1.1.0'],
|
||||
['name' => 'vendor/somepackage', 'description' => 'generic description', 'version' => '1.0.0'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->createInstalledJson([
|
||||
self::getPackage('vendor/apackage', '1.0.0'),
|
||||
self::getPackage('vendor/longpackagename', '1.0.0'),
|
||||
self::getPackage('vendor/somepackage', '1.0.0'),
|
||||
]);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'show', '-N' => true]);
|
||||
self::assertSame(
|
||||
'vendor/apackage
|
||||
vendor/longpackagename
|
||||
vendor/somepackage', trim($appTester->getDisplay(true))); // trim() is fine here, but see CAUTION above
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'show', '--outdated' => true, '-N' => true]);
|
||||
self::assertSame(
|
||||
'Legend:
|
||||
! patch or minor release available - update recommended
|
||||
~ major release available - update possible
|
||||
vendor/apackage
|
||||
vendor/longpackagename', trim($appTester->getDisplay(true))); // trim() is fine here, but see CAUTION above
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ class SuggestsCommandTest extends TestCase
|
|||
self::assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
||||
}
|
||||
|
||||
public function provideSuggest(): \Generator
|
||||
public static function provideSuggest(): \Generator
|
||||
{
|
||||
yield 'with lockfile, show suggested' => [
|
||||
true,
|
||||
|
|
|
@ -314,7 +314,7 @@ class ZipDownloaderTest extends TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @param ?\React\Promise\PromiseInterface $promise
|
||||
* @param ?\React\Promise\PromiseInterface<mixed> $promise
|
||||
*/
|
||||
private function wait($promise): void
|
||||
{
|
||||
|
@ -329,7 +329,7 @@ class ZipDownloaderTest extends TestCase
|
|||
$e = $ex;
|
||||
});
|
||||
|
||||
if ($e) {
|
||||
if ($e !== null) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ use Composer\Test\Mock\FactoryMock;
|
|||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
|
||||
class ArchiveManagerTest extends ArchiverTest
|
||||
class ArchiveManagerTest extends ArchiverTestCase
|
||||
{
|
||||
/**
|
||||
* @var ArchiveManager
|
||||
|
|
|
@ -17,7 +17,7 @@ use Composer\Util\Filesystem;
|
|||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Package\CompletePackage;
|
||||
|
||||
abstract class ArchiverTest extends TestCase
|
||||
abstract class ArchiverTestCase extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Composer\Util\Filesystem
|
|
@ -15,7 +15,7 @@ namespace Composer\Test\Package\Archiver;
|
|||
use Composer\Package\Archiver\PharArchiver;
|
||||
use Composer\Util\Platform;
|
||||
|
||||
class PharArchiverTest extends ArchiverTest
|
||||
class PharArchiverTest extends ArchiverTestCase
|
||||
{
|
||||
public function testTarArchive(): void
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ use Composer\Util\Platform;
|
|||
use ZipArchive;
|
||||
use Composer\Package\Archiver\ZipArchiver;
|
||||
|
||||
class ZipArchiverTest extends ArchiverTest
|
||||
class ZipArchiverTest extends ArchiverTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideGitignoreExcludeNegationTestCases
|
||||
|
|
|
@ -369,7 +369,6 @@ libSSH Version => libssh2/1.4.1',
|
|||
'curl: libssh not libssh2' => [
|
||||
'curl',
|
||||
'
|
||||
|
||||
curl
|
||||
|
||||
cURL support => enabled
|
||||
|
@ -412,6 +411,57 @@ libSSH Version => libssh/0.9.3/openssl/zlib',
|
|||
],
|
||||
[['curl_version', [], ['version' => '7.68.0']]],
|
||||
],
|
||||
'curl: SecureTransport' => [
|
||||
'curl',
|
||||
'
|
||||
curl
|
||||
|
||||
cURL support => enabled
|
||||
cURL Information => 8.1.2
|
||||
Age => 10
|
||||
Features
|
||||
AsynchDNS => Yes
|
||||
CharConv => No
|
||||
Debug => No
|
||||
GSS-Negotiate => No
|
||||
IDN => Yes
|
||||
IPv6 => Yes
|
||||
krb4 => No
|
||||
Largefile => Yes
|
||||
libz => Yes
|
||||
NTLM => Yes
|
||||
NTLMWB => Yes
|
||||
SPNEGO => Yes
|
||||
SSL => Yes
|
||||
SSPI => No
|
||||
TLS-SRP => Yes
|
||||
HTTP2 => Yes
|
||||
GSSAPI => Yes
|
||||
KERBEROS5 => Yes
|
||||
UNIX_SOCKETS => Yes
|
||||
PSL => No
|
||||
HTTPS_PROXY => Yes
|
||||
MULTI_SSL => Yes
|
||||
BROTLI => Yes
|
||||
ALTSVC => Yes
|
||||
HTTP3 => No
|
||||
UNICODE => No
|
||||
ZSTD => Yes
|
||||
HSTS => Yes
|
||||
GSASL => No
|
||||
Protocols => dict, file, ftp, ftps, gopher, gophers, http, https, imap, imaps, ldap, ldaps, mqtt, pop3, pop3s, rtmp, rtmpe, rtmps, rtmpt, rtmpte, rtmpts, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp
|
||||
Host => aarch64-apple-darwin22.4.0
|
||||
SSL Version => (SecureTransport) OpenSSL/3.1.1
|
||||
ZLib Version => 1.2.11
|
||||
libSSH Version => libssh2/1.11.0',
|
||||
[
|
||||
'lib-curl' => '8.1.2',
|
||||
'lib-curl-securetransport' => ['3.1.1', ['lib-curl-openssl']],
|
||||
'lib-curl-zlib' => '1.2.11',
|
||||
'lib-curl-libssh2' => '1.11.0',
|
||||
],
|
||||
[['curl_version', [], ['version' => '8.1.2']]],
|
||||
],
|
||||
'date' => [
|
||||
'date',
|
||||
'
|
||||
|
|
|
@ -401,6 +401,34 @@ class GitHubDriverTest extends TestCase
|
|||
];
|
||||
}
|
||||
|
||||
public function testGetEmptyFileContent(): void
|
||||
{
|
||||
$repoUrl = 'http://github.com/composer/packagist';
|
||||
|
||||
$io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
|
||||
$io->expects($this->any())
|
||||
->method('isInteractive')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$httpDownloader = $this->getHttpDownloaderMock($io, $this->config);
|
||||
$httpDownloader->expects(
|
||||
[
|
||||
['url' => 'https://api.github.com/repos/composer/packagist', 'body' => '{"master_branch": "test_master", "owner": {"login": "composer"}, "name": "packagist", "archived": true}'],
|
||||
['url' => 'https://api.github.com/repos/composer/packagist/contents/composer.json?ref=main', 'body' => '{"encoding":"base64","content":""}'],
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
$repoConfig = [
|
||||
'url' => $repoUrl,
|
||||
];
|
||||
|
||||
$gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $this->getProcessExecutorMock());
|
||||
$gitHubDriver->initialize();
|
||||
|
||||
$this->assertSame('', $gitHubDriver->getFileContent('composer.json', 'main'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|object $object
|
||||
* @param mixed $value
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
namespace Composer\Test\Util;
|
||||
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Test\TestCase;
|
||||
|
||||
|
@ -317,6 +318,38 @@ class FilesystemTest extends TestCase
|
|||
$this->assertDirectoryDoesNotExist($junction, $junction . ' is not a directory');
|
||||
}
|
||||
|
||||
public function testOverrideJunctions(): void
|
||||
{
|
||||
if (!Platform::isWindows()) {
|
||||
$this->markTestSkipped('Only runs on windows');
|
||||
}
|
||||
|
||||
@mkdir($this->workingDir.'/real/nesting/testing', 0777, true);
|
||||
$fs = new Filesystem();
|
||||
|
||||
$old_target = $this->workingDir.'/real/nesting/testing';
|
||||
$target = $this->workingDir.'/real/../real/nesting';
|
||||
$junction = $this->workingDir.'/junction';
|
||||
|
||||
// Override non-broken junction
|
||||
$fs->junction($old_target, $junction);
|
||||
$fs->junction($target, $junction);
|
||||
|
||||
$this->assertTrue($fs->isJunction($junction), $junction.': is a junction');
|
||||
$this->assertTrue($fs->isJunction($target.'/../../junction'), $target.'/../../junction: is a junction');
|
||||
|
||||
//Remove junction
|
||||
$this->assertTrue($fs->removeJunction($junction), $junction . ' has been removed');
|
||||
|
||||
// Override broken junction
|
||||
$fs->junction($old_target, $junction);
|
||||
$fs->removeDirectory($old_target);
|
||||
$fs->junction($target, $junction);
|
||||
|
||||
$this->assertTrue($fs->isJunction($junction), $junction.': is a junction');
|
||||
$this->assertTrue($fs->isJunction($target.'/../../junction'), $target.'/../../junction: is a junction');
|
||||
}
|
||||
|
||||
public function testCopy(): void
|
||||
{
|
||||
@mkdir($this->workingDir . '/foo/bar', 0777, true);
|
||||
|
|
|
@ -120,7 +120,6 @@ class ProcessExecutorTest extends TestCase
|
|||
$process = new ProcessExecutor($buffer = new BufferIO('', StreamOutput::VERBOSITY_DEBUG));
|
||||
$process->enableAsync();
|
||||
$start = microtime(true);
|
||||
/** @var Promise $promise */
|
||||
$promise = $process->executeAsync('sleep 2');
|
||||
$this->assertEquals(1, $process->countActiveJobs());
|
||||
$promise->cancel();
|
||||
|
|
Loading…
Reference in New Issue