From c66894278c03bf88ccae12fa3d8f3a9d68e64495 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 25 Oct 2023 17:13:25 +0200 Subject: [PATCH 01/75] Bump dev version to 2.7, fix issues with symfony 7 --- composer.json | 2 +- src/Composer/Command/AuditCommand.php | 2 +- src/Composer/Command/BumpCommand.php | 2 +- src/Composer/Command/DiagnoseCommand.php | 2 +- src/Composer/Command/DumpAutoloadCommand.php | 2 +- src/Composer/Command/ExecCommand.php | 2 +- src/Composer/Command/InitCommand.php | 2 +- src/Composer/Command/InstallCommand.php | 2 +- src/Composer/Command/RemoveCommand.php | 2 +- src/Composer/Command/RequireCommand.php | 2 +- src/Composer/Command/ShowCommand.php | 2 +- src/Composer/Command/UpdateCommand.php | 2 +- src/Composer/Composer.php | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 70f48850667d..c27a8407f64e 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,7 @@ }, "extra": { "branch-alias": { - "dev-main": "2.6-dev" + "dev-main": "2.7-dev" }, "phpstan": { "includes": [ diff --git a/src/Composer/Command/AuditCommand.php b/src/Composer/Command/AuditCommand.php index 3788b8e37964..f2abfaecaa0b 100644 --- a/src/Composer/Command/AuditCommand.php +++ b/src/Composer/Command/AuditCommand.php @@ -46,7 +46,7 @@ protected function configure(): void ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); $packages = $this->getPackages($composer, $input); diff --git a/src/Composer/Command/BumpCommand.php b/src/Composer/Command/BumpCommand.php index 99fe9eabab42..db5b9464a0dc 100644 --- a/src/Composer/Command/BumpCommand.php +++ b/src/Composer/Command/BumpCommand.php @@ -70,7 +70,7 @@ protected function configure(): void /** * @throws \Seld\JsonLint\ParsingException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { /** @readonly */ $composerJsonPath = Factory::getComposerFile(); diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index b1af4d6be886..d1b4fbee548f 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -67,7 +67,7 @@ protected function configure(): void ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->tryComposer(); $io = $this->getIO(); diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 9336ab3b29b3..c30fae7a82b2 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -54,7 +54,7 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); diff --git a/src/Composer/Command/ExecCommand.php b/src/Composer/Command/ExecCommand.php index edec5c6dbc8d..e8c7c96e9485 100644 --- a/src/Composer/Command/ExecCommand.php +++ b/src/Composer/Command/ExecCommand.php @@ -75,7 +75,7 @@ protected function interact(InputInterface $input, OutputInterface $output): voi $input->setArgument('binary', $binaries[$binary]); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); if ($input->getOption('list') || null === $input->getArgument('binary')) { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 3a61ee76af5e..606bff8207bd 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -82,7 +82,7 @@ protected function configure() /** * @throws \Seld\JsonLint\ParsingException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO(); diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 7ab60260cd66..1d45eaf48a8b 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -79,7 +79,7 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO(); if ($input->getOption('dev')) { diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index e808d63abc51..a97896df9f27 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -83,7 +83,7 @@ protected function configure() /** * @throws \Seld\JsonLint\ParsingException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { if ($input->getArgument('packages') === [] && !$input->getOption('unused')) { throw new InvalidArgumentException('Not enough arguments (missing: "packages").'); diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 760bfdb38fe5..51cb69c1d25f 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -129,7 +129,7 @@ protected function configure() /** * @throws \Seld\JsonLint\ParsingException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->file = Factory::getComposerFile(); $io = $this->getIO(); diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index ea5a9db3ba92..083a49ec3021 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -131,7 +131,7 @@ protected function suggestPackageBasedOnMode(): \Closure }; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->versionParser = new VersionParser; if ($input->getOption('tree')) { diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 7b6948004441..b371157e9db4 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -112,7 +112,7 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO(); if ($input->getOption('dev')) { diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index cb619e1f5747..4bf1679f6ff6 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -54,7 +54,7 @@ class Composer extends PartialComposer public const VERSION = '@package_version@'; public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.6.999-dev+source'; + public const SOURCE_VERSION = '2.7.999-dev+source'; /** * Version number of the internal composer-runtime-api package From c2414c1d17fe3096a308dcb03247504f59b176d2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 25 Oct 2023 17:18:32 +0200 Subject: [PATCH 02/75] Fix lock file --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 48a8ba35bdda..a6730c995ff0 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "4bceaf933dcf6bc05808134e78d21496", + "content-hash": "5ecfd05f35343c3780b3a896c59e6cb0", "packages": [ { "name": "composer/ca-bundle", From 7a09e05560652f24a2d14e5966fa2f118f499e3e Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Wed, 25 Oct 2023 17:04:52 +0100 Subject: [PATCH 03/75] Bump wildcard constraints to >=current (#11694) --- src/Composer/Package/Version/VersionBumper.php | 4 +++- tests/Composer/Test/Package/Version/VersionBumperTest.php | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/Package/Version/VersionBumper.php b/src/Composer/Package/Version/VersionBumper.php index 690dfbeeddac..aa86d66deeae 100644 --- a/src/Composer/Package/Version/VersionBumper.php +++ b/src/Composer/Package/Version/VersionBumper.php @@ -40,6 +40,7 @@ class VersionBumper * * ^3@dev + 3.2.99999-dev -> ^3.2@dev * * ~2 + 2.0-beta.1 -> ~2 * * dev-master + dev-master -> dev-master + * * * + 1.2.3 -> >=1.2.3 */ public function bumpRequirement(ConstraintInterface $constraint, PackageInterface $package): string { @@ -86,6 +87,7 @@ public function bumpRequirement(ConstraintInterface $constraint, PackageInterfac | ~'.$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 + | \* # full wildcard ) (?=,|$|\ |\||@) # trailing separator }x'; @@ -99,7 +101,7 @@ public function bumpRequirement(ConstraintInterface $constraint, PackageInterfac } if (str_starts_with($match[0], '~') && substr_count($match[0], '.') === 2) { $replacement = '~'.$versionWithoutSuffix.$suffix; - } elseif (str_starts_with($match[0], '>=')) { + } elseif ($match[0] === '*' || str_starts_with($match[0], '>=')) { $replacement = '>='.$versionWithoutSuffix.$suffix; } else { $replacement = $newPrettyConstraint.$suffix; diff --git a/tests/Composer/Test/Package/Version/VersionBumperTest.php b/tests/Composer/Test/Package/Version/VersionBumperTest.php index b8f07844f6c0..4d0b610c0a0d 100644 --- a/tests/Composer/Test/Package/Version/VersionBumperTest.php +++ b/tests/Composer/Test/Package/Version/VersionBumperTest.php @@ -67,5 +67,6 @@ public static function provideBumpRequirementTests(): Generator 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 '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']; } } From 899dcedf66fcd9b8f6876248ee813e5d82a0de68 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 26 Oct 2023 10:25:04 +0200 Subject: [PATCH 04/75] Add --minimal-changes mode to perform partial updates --with-dependencies while changing only what is necessary in other dependencies (#11665) --- doc/03-cli.md | 11 ++++ src/Composer/Command/BaseCommand.php | 1 + src/Composer/Command/RemoveCommand.php | 2 + src/Composer/Command/RequireCommand.php | 2 + src/Composer/Command/UpdateCommand.php | 2 + .../DependencyResolver/DefaultPolicy.php | 24 ++++++- src/Composer/Installer.php | 41 +++++++++--- .../DependencyResolver/DefaultPolicyTest.php | 53 ++++++++++++++++ .../update-allow-list-minimal-changes.test | 62 +++++++++++++++++++ tests/Composer/Test/InstallerTest.php | 4 +- 10 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test diff --git a/doc/03-cli.md b/doc/03-cli.md index dd6e7001e869..166fed193f61 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -229,6 +229,8 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.* * **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal versions of requirements, generally used with `--prefer-stable`. Can also be set via the COMPOSER_PREFER_LOWEST=1 env var. +* **--minimal-changes:** During a partial update with `-w`/`-W`, only perform absolutely necessary + changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. * **--interactive:** Interactive interface with autocompletion to select the packages to update. * **--root-reqs:** Restricts the update to your first degree dependencies. @@ -288,6 +290,8 @@ If you do not specify a package, Composer will prompt you to search for a packag * **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal versions of requirements, generally used with `--prefer-stable`. Can also be set via the COMPOSER_PREFER_LOWEST=1 env var. +* **--minimal-changes:** During an update with `-w`/`-W`, only perform absolutely necessary + changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. * **--sort-packages:** Keep packages sorted in `composer.json`. * **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster autoloader. This is recommended especially for production, but @@ -326,6 +330,8 @@ uninstalled. (Deprecated, is now default behavior) * **--update-with-all-dependencies (-W):** Allows all inherited dependencies to be updated, including those that are root requirements. +* **--minimal-changes:** During an update with `-w`/`-W`, only perform absolutely necessary + changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. * **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine does not fulfill these. @@ -1290,6 +1296,11 @@ If set to `1`, it is the equivalent of passing the `--prefer-stable` option to If set to `1`, it is the equivalent of passing the `--prefer-lowest` option to `update` or `require`. +### COMPOSER_MINIMAL_CHANGES + +If set to `1`, it is the equivalent of passing the `--minimal-changes` option to +`update`, `require` or `remove`. + ### COMPOSER_IGNORE_PLATFORM_REQ or COMPOSER_IGNORE_PLATFORM_REQS If `COMPOSER_IGNORE_PLATFORM_REQS` set to `1`, it is the equivalent of passing the `--ignore-platform-reqs` argument. diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index a33f3a5b3845..ed318e079589 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -248,6 +248,7 @@ protected function initialize(InputInterface $input, OutputInterface $output) 'COMPOSER_NO_DEV' => ['no-dev', 'update-no-dev'], 'COMPOSER_PREFER_STABLE' => ['prefer-stable'], 'COMPOSER_PREFER_LOWEST' => ['prefer-lowest'], + 'COMPOSER_MINIMAL_CHANGES' => ['minimal-changes'], ]; foreach ($envOptions as $envName => $optionNames) { foreach ($optionNames as $optionName) { diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index a97896df9f27..b40fb774e45a 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -59,6 +59,7 @@ protected function configure() new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'), new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('unused', null, InputOption::VALUE_NONE, 'Remove all packages which are locked but not required by any other package.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), @@ -286,6 +287,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ->setDryRun($dryRun) ->setAudit(!$input->getOption('no-audit')) ->setAuditFormat($this->getAuditFormat($input)) + ->setMinimalUpdate($input->getOption('minimal-changes')) ; // if no lock is present, we do not do a partial update as diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 51cb69c1d25f..658d58de3141 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -103,6 +103,7 @@ protected function configure() new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), @@ -479,6 +480,7 @@ private function doUpdate(InputInterface $input, OutputInterface $output, IOInte ->setPreferLowest($input->getOption('prefer-lowest')) ->setAudit(!$input->getOption('no-audit')) ->setAuditFormat($this->getAuditFormat($input)) + ->setMinimalUpdate($input->getOption('minimal-changes')) ; // if no lock is present, or the file is brand new, we do not do a diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index b371157e9db4..621f984c2060 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -75,6 +75,7 @@ protected function configure() new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), ]) @@ -238,6 +239,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ->setTemporaryConstraints($temporaryConstraints) ->setAudit(!$input->getOption('no-audit')) ->setAuditFormat($this->getAuditFormat($input)) + ->setMinimalUpdate($input->getOption('minimal-changes')) ; if ($input->getOption('no-plugins')) { diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 8d27a6602b9e..f8176ae7288b 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -28,15 +28,21 @@ class DefaultPolicy implements PolicyInterface private $preferStable; /** @var bool */ private $preferLowest; + /** @var array|null */ + private $preferredVersions; /** @var array>> */ private $preferredPackageResultCachePerPool; /** @var array> */ private $sortingCachePerPool; - public function __construct(bool $preferStable = false, bool $preferLowest = false) + /** + * @param array|null $preferredVersions Must be an array of package name => normalized version + */ + public function __construct(bool $preferStable = false, bool $preferLowest = false, ?array $preferredVersions = null) { $this->preferStable = $preferStable; $this->preferLowest = $preferLowest; + $this->preferredVersions = $preferredVersions; } /** @@ -204,6 +210,22 @@ protected function replaces(BasePackage $source, BasePackage $target): bool */ protected function pruneToBestVersion(Pool $pool, array $literals): array { + if ($this->preferredVersions !== null) { + $name = $pool->literalToPackage($literals[0])->getName(); + if (isset($this->preferredVersions[$name])) { + $preferredVersion = $this->preferredVersions[$name]; + $bestLiterals = []; + foreach ($literals as $literal) { + if ($pool->literalToPackage($literal)->getVersion() === $preferredVersion) { + $bestLiterals[] = $literal; + } + } + if (\count($bestLiterals) > 0) { + return $bestLiterals; + } + } + } + $operator = $this->preferLowest ? '<' : '>'; $bestLiterals = [$literals[0]]; $bestPackage = $pool->literalToPackage($literals[0]); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 9c005477897b..0efbcd6f377c 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -165,6 +165,8 @@ class Installer /** @var bool */ protected $preferLowest = false; /** @var bool */ + protected $minimalUpdate = false; + /** @var bool */ protected $writeLock; /** @var bool */ protected $executeOperations = true; @@ -464,7 +466,7 @@ protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doIns $this->io->writeError('Loading composer repositories with package information'); // creating repository set - $policy = $this->createPolicy(true); + $policy = $this->createPolicy(true, $lockedRepository); $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases); $repositories = $this->repositoryManager->getRepositories(); foreach ($repositories as $repository) { @@ -904,7 +906,7 @@ private function createRepositorySet(bool $forUpdate, PlatformRepository $platfo return $repositorySet; } - private function createPolicy(bool $forUpdate): DefaultPolicy + private function createPolicy(bool $forUpdate, ?LockArrayRepository $lockedRepo = null): DefaultPolicy { $preferStable = null; $preferLowest = null; @@ -921,7 +923,18 @@ private function createPolicy(bool $forUpdate): DefaultPolicy $preferLowest = $this->preferLowest; } - return new DefaultPolicy($preferStable, $preferLowest); + $preferredVersions = null; + if ($forUpdate && $this->minimalUpdate && $this->updateAllowList !== null && $lockedRepo !== null) { + $preferredVersions = []; + foreach ($lockedRepo->getPackages() as $pkg) { + if ($pkg instanceof AliasPackage || in_array($pkg->getName(), $this->updateAllowList, true)) { + continue; + } + $preferredVersions[$pkg->getName()] = $pkg->getVersion(); + } + } + + return new DefaultPolicy($preferStable, $preferLowest, $preferredVersions); } /** @@ -1384,7 +1397,7 @@ public function setUpdateAllowTransitiveDependencies(int $updateAllowTransitiveD */ public function setPreferStable(bool $preferStable = true): self { - $this->preferStable = (bool) $preferStable; + $this->preferStable = $preferStable; return $this; } @@ -1396,7 +1409,21 @@ public function setPreferStable(bool $preferStable = true): self */ public function setPreferLowest(bool $preferLowest = true): self { - $this->preferLowest = (bool) $preferLowest; + $this->preferLowest = $preferLowest; + + return $this; + } + + /** + * Only relevant for partial updates (with setUpdateAllowList), if this is enabled currently locked versions will be preferred for packages which are not in the allowlist + * + * This reduces the update to + * + * @return Installer + */ + public function setMinimalUpdate(bool $minimalUpdate = true): self + { + $this->minimalUpdate = $minimalUpdate; return $this; } @@ -1410,7 +1437,7 @@ public function setPreferLowest(bool $preferLowest = true): self */ public function setWriteLock(bool $writeLock = true): self { - $this->writeLock = (bool) $writeLock; + $this->writeLock = $writeLock; return $this; } @@ -1424,7 +1451,7 @@ public function setWriteLock(bool $writeLock = true): self */ public function setExecuteOperations(bool $executeOperations = true): self { - $this->executeOperations = (bool) $executeOperations; + $this->executeOperations = $executeOperations; return $this; } diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index 61d5206f2294..2f888d768c1e 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -121,6 +121,59 @@ public function testSelectNewestWithDevPicksNonDev(): void $this->assertSame($expected, $selected); } + public function testSelectNewestWithPreferredVersionPicksPreferredVersionIfAvailable(): void + { + $this->repo->addPackage($packageA1 = self::getPackage('A', '1.0.0')); + $this->repo->addPackage($packageA2 = self::getPackage('A', '1.1.0')); + $this->repo->addPackage($packageA2b = self::getPackage('A', '1.1.0')); + $this->repo->addPackage($packageA3 = self::getPackage('A', '1.2.0')); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->createPoolForPackage('A', $this->repoLocked); + + $literals = [$packageA1->getId(), $packageA2->getId(), $packageA2b->getId(), $packageA3->getId()]; + $expected = [$packageA2->getId(), $packageA2b->getId()]; + + $policy = new DefaultPolicy(false, false, ['a' => '1.1.0.0']); + $selected = $policy->selectPreferredPackages($pool, $literals); + + $this->assertSame($expected, $selected); + } + + public function testSelectNewestWithPreferredVersionPicksNewestOtherwise(): void + { + $this->repo->addPackage($packageA1 = self::getPackage('A', '1.0.0')); + $this->repo->addPackage($packageA2 = self::getPackage('A', '1.2.0')); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->createPoolForPackage('A', $this->repoLocked); + + $literals = [$packageA1->getId(), $packageA2->getId()]; + $expected = [$packageA2->getId()]; + + $policy = new DefaultPolicy(false, false, ['a' => '1.1.0.0']); + $selected = $policy->selectPreferredPackages($pool, $literals); + + $this->assertSame($expected, $selected); + } + + public function testSelectNewestWithPreferredVersionPicksLowestIfPreferLowest(): void + { + $this->repo->addPackage($packageA1 = self::getPackage('A', '1.0.0')); + $this->repo->addPackage($packageA2 = self::getPackage('A', '1.2.0')); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->createPoolForPackage('A', $this->repoLocked); + + $literals = [$packageA1->getId(), $packageA2->getId()]; + $expected = [$packageA1->getId()]; + + $policy = new DefaultPolicy(false, true, ['a' => '1.1.0.0']); + $selected = $policy->selectPreferredPackages($pool, $literals); + + $this->assertSame($expected, $selected); + } + public function testRepositoryOrderingAffectsPriority(): void { $repo1 = new ArrayRepository; diff --git a/tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test b/tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test new file mode 100644 index 000000000000..889555ed0acb --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-allow-list-minimal-changes.test @@ -0,0 +1,62 @@ +--TEST-- +Updating transitive dependencies only updates what is really required when a minimal update is requested + +* dependency/pkg has to upgrade to 2.x +* dependency/pkg2 remains at 1.0.0 and does not upgrade to 1.1.0 even though it would without minimal update +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "allowed/pkg", "version": "1.1.0", "require": { "dependency/pkg": "2.*", "dependency/pkg2": "1.*" } }, + { "name": "allowed/pkg", "version": "1.0.0", "require": { "dependency/pkg": "1.*", "dependency/pkg2": "1.*" } }, + { "name": "dependency/pkg", "version": "2.0.0" }, + { "name": "dependency/pkg", "version": "1.1.0" }, + { "name": "dependency/pkg", "version": "1.0.0" }, + { "name": "dependency/pkg2", "version": "2.0.0" }, + { "name": "dependency/pkg2", "version": "1.1.0" }, + { "name": "dependency/pkg2", "version": "1.0.0" }, + { "name": "unrelated/pkg", "version": "1.1.0", "require": { "unrelated/pkg-dependency": "1.*" } }, + { "name": "unrelated/pkg", "version": "1.0.0", "require": { "unrelated/pkg-dependency": "1.*" } }, + { "name": "unrelated/pkg-dependency", "version": "1.1.0" }, + { "name": "unrelated/pkg-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "allowed/pkg": "1.*", + "unrelated/pkg": "1.*" + } +} +--INSTALLED-- +[ + { "name": "allowed/pkg", "version": "1.0.0", "require": { "dependency/pkg": "1.*", "dependency/pkg2": "1.*" } }, + { "name": "dependency/pkg", "version": "1.0.0" }, + { "name": "dependency/pkg2", "version": "1.0.0" }, + { "name": "unrelated/pkg", "version": "1.0.0", "require": { "unrelated/pkg-dependency": "1.*" } }, + { "name": "unrelated/pkg-dependency", "version": "1.0.0" } +] +--LOCK-- +{ + "packages": [ + { "name": "allowed/pkg", "version": "1.0.0", "require": { "dependency/pkg": "1.*", "dependency/pkg2": "1.*" } }, + { "name": "dependency/pkg", "version": "1.0.0" }, + { "name": "dependency/pkg2", "version": "1.0.0" }, + { "name": "unrelated/pkg", "version": "1.0.0", "require": { "unrelated/pkg-dependency": "1.*" } }, + { "name": "unrelated/pkg-dependency", "version": "1.0.0" } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update allowed/pkg --with-all-dependencies --minimal-changes +--EXPECT-- +Upgrading dependency/pkg (1.0.0 => 2.0.0) +Upgrading allowed/pkg (1.0.0 => 1.1.0) diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index eea6f0d739ab..511d649a854a 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -381,6 +381,7 @@ private function doTestIntegration(string $file, string $message, ?string $condi $update->addOption('lock', null, InputOption::VALUE_NONE); $update->addOption('with-all-dependencies', null, InputOption::VALUE_NONE); $update->addOption('with-dependencies', null, InputOption::VALUE_NONE); + $update->addOption('minimal-changes', null, InputOption::VALUE_NONE); $update->addOption('prefer-stable', null, InputOption::VALUE_NONE); $update->addOption('prefer-lowest', null, InputOption::VALUE_NONE); $update->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL); @@ -412,7 +413,8 @@ private function doTestIntegration(string $file, string $message, ?string $condi ->setPreferStable($input->getOption('prefer-stable')) ->setPreferLowest($input->getOption('prefer-lowest')) ->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)) - ->setAudit(false); + ->setAudit(false) + ->setMinimalUpdate($input->getOption('minimal-changes')); return $installer->run(); }); From aefa46dfbabea3497437480061496ba54773bf46 Mon Sep 17 00:00:00 2001 From: Travis Carden Date: Fri, 27 Oct 2023 05:36:59 -0400 Subject: [PATCH 05/75] Add support for "scripts-aliases" in composer.json (#11666) --- doc/articles/scripts.md | 16 ++++++++++++ res/composer-schema.json | 7 +++++ src/Composer/Command/ScriptAliasCommand.php | 15 ++++++++++- src/Composer/Config/JsonConfigSource.php | 2 +- src/Composer/Console/Application.php | 4 ++- src/Composer/Util/ConfigValidator.php | 11 ++++++++ .../Test/Command/RunScriptCommandTest.php | 26 +++++++++++++++++++ .../Test/Util/ConfigValidatorTest.php | 11 ++++++++ .../Fixtures/composer_scripts-aliases.json | 10 +++++++ 9 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 tests/Composer/Test/Util/Fixtures/composer_scripts-aliases.json diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index f83db407268b..b7339cbc5a05 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -441,3 +441,19 @@ The descriptions are used in `composer list` or `composer run -l` commands to describe what the scripts do when the command is run. > **Note:** You can only set custom descriptions of custom commands. + +## Custom aliases. + +You can set custom script aliases with the following in your `composer.json`: + +```json +{ + "scripts-aliases": { + "phpstan": ["stan", "analyze"] + } +} +``` + +The aliases provide alternate command names. + +> **Note:** You can only set custom aliases of custom commands. diff --git a/res/composer-schema.json b/res/composer-schema.json index 47c087b3e891..a77becb4b3e1 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -694,6 +694,13 @@ "additionalProperties": { "type": "string" } + }, + "scripts-aliases": { + "type": ["object"], + "description": "Aliases for custom commands.", + "additionalProperties": { + "type": "array" + } } }, "definitions": { diff --git a/src/Composer/Command/ScriptAliasCommand.php b/src/Composer/Command/ScriptAliasCommand.php index b773f235d896..f2de686fee2b 100644 --- a/src/Composer/Command/ScriptAliasCommand.php +++ b/src/Composer/Command/ScriptAliasCommand.php @@ -27,11 +27,23 @@ class ScriptAliasCommand extends BaseCommand private $script; /** @var string */ private $description; + /** @var string[] */ + private $aliases; - public function __construct(string $script, ?string $description) + /** + * @param string[] $aliases + */ + public function __construct(string $script, ?string $description, array $aliases = []) { $this->script = $script; $this->description = $description ?? 'Runs the '.$script.' script as defined in composer.json'; + $this->aliases = $aliases; + + foreach ($this->aliases as $alias) { + if (!is_string($alias)) { + throw new \InvalidArgumentException('"scripts-aliases" element array values should contain only strings'); + } + } $this->ignoreValidationErrors(); @@ -43,6 +55,7 @@ protected function configure(): void $this ->setName($this->script) ->setDescription($this->description) + ->setAliases($this->aliases) ->setDefinition([ new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), diff --git a/src/Composer/Config/JsonConfigSource.php b/src/Composer/Config/JsonConfigSource.php index bff50d8694d9..db3d36dc4806 100644 --- a/src/Composer/Config/JsonConfigSource.php +++ b/src/Composer/Config/JsonConfigSource.php @@ -249,7 +249,7 @@ private function manipulateJson(string $method, callable $fallback, ...$args): v $this->arrayUnshiftRef($args, $config); $fallback(...$args); // avoid ending up with arrays for keys that should be objects - foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'support'] as $prop) { + foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'scripts-aliases', 'support'] as $prop) { if (isset($config[$prop]) && $config[$prop] === []) { $config[$prop] = new \stdClass; } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index da77636d112d..a74a6c69d3cf 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -365,7 +365,9 @@ function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknow $description = $composer['scripts-descriptions'][$script]; } - $this->add(new Command\ScriptAliasCommand($script, $description)); + $aliases = $composer['scripts-aliases'][$script] ?? []; + + $this->add(new Command\ScriptAliasCommand($script, $description, $aliases)); } } } diff --git a/src/Composer/Util/ConfigValidator.php b/src/Composer/Util/ConfigValidator.php index 44c2c3a2c2d8..ac57199ee336 100644 --- a/src/Composer/Util/ConfigValidator.php +++ b/src/Composer/Util/ConfigValidator.php @@ -196,6 +196,17 @@ public function validate(string $file, int $arrayLoaderValidationFlags = Validat } } + // report scripts-aliases for non-existent scripts + $scriptAliases = $manifest['scripts-aliases'] ?? []; + foreach ($scriptAliases as $scriptName => $scriptAlias) { + if (!array_key_exists($scriptName, $scripts)) { + $warnings[] = sprintf( + 'Aliases for non-existent script "%s" found in "scripts-aliases"', + $scriptName + ); + } + } + // check for empty psr-0/psr-4 namespace prefixes if (isset($manifest['autoload']['psr-0'][''])) { $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; diff --git a/tests/Composer/Test/Command/RunScriptCommandTest.php b/tests/Composer/Test/Command/RunScriptCommandTest.php index 88e6f390b07c..dc724ae8d296 100644 --- a/tests/Composer/Test/Command/RunScriptCommandTest.php +++ b/tests/Composer/Test/Command/RunScriptCommandTest.php @@ -109,6 +109,32 @@ public function testCanListScripts(): void $this->assertStringContainsString('Run the codestyle fixer', $output, 'The custom description for the fix-cs script should be printed'); } + public function testCanDefineAliases(): void + { + $expectedAliases = ['one', 'two', 'three']; + + $this->initTempComposer([ + 'scripts' => [ + 'test' => '@php test', + ], + 'scripts-aliases' => [ + 'test' => $expectedAliases, + ], + ]); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'test', '--help' => true, '--format' => 'json']); + + $appTester->assertCommandIsSuccessful(); + + $output = $appTester->getDisplay(); + $array = json_decode($output, true); + $actualAliases = $array['usage']; + array_shift($actualAliases); + + $this->assertSame($expectedAliases, $actualAliases, 'The custom aliases for the test command should be printed'); + } + public function testExecutionOfCustomSymfonyCommand(): void { $this->initTempComposer([ diff --git a/tests/Composer/Test/Util/ConfigValidatorTest.php b/tests/Composer/Test/Util/ConfigValidatorTest.php index a43fc6358b82..c6d2bbfde3da 100644 --- a/tests/Composer/Test/Util/ConfigValidatorTest.php +++ b/tests/Composer/Test/Util/ConfigValidatorTest.php @@ -46,6 +46,17 @@ public function testConfigValidatorWarnsOnScriptDescriptionForNonexistentScript( ); } + public function testConfigValidatorWarnsOnScriptAliasForNonexistentScript(): void + { + $configValidator = new ConfigValidator(new NullIO()); + [, , $warnings] = $configValidator->validate(__DIR__ . '/Fixtures/composer_scripts-aliases.json'); + + $this->assertContains( + 'Aliases for non-existent script "phpcsxxx" found in "scripts-aliases"', + $warnings + ); + } + public function testConfigValidatorWarnsOnUnnecessaryProvideReplace(): void { $configValidator = new ConfigValidator(new NullIO()); diff --git a/tests/Composer/Test/Util/Fixtures/composer_scripts-aliases.json b/tests/Composer/Test/Util/Fixtures/composer_scripts-aliases.json new file mode 100644 index 000000000000..21b552d8f299 --- /dev/null +++ b/tests/Composer/Test/Util/Fixtures/composer_scripts-aliases.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "test": "phpunit", + "phpcs": "phpcs" + }, + "scripts-aliases": { + "test": ["t"], + "phpcsxxx": ["x"] + } +} From da83d29d8ac634783bc70b0f3b7847bae3e081d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Wed, 8 Nov 2023 12:02:00 +0100 Subject: [PATCH 06/75] "URL" in caps (#11706) --- doc/articles/authentication-for-private-packages.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/articles/authentication-for-private-packages.md b/doc/articles/authentication-for-private-packages.md index 1def99f18933..c1c186b3c013 100644 --- a/doc/articles/authentication-for-private-packages.md +++ b/doc/articles/authentication-for-private-packages.md @@ -232,7 +232,7 @@ php composer.phar config [--global] --editor ## gitlab-oauth > **Note:** For the gitlab authentication to work on private gitlab instances, the -> [`gitlab-domains`](../06-config.md#gitlab-domains) section should also contain the url. +> [`gitlab-domains`](../06-config.md#gitlab-domains) section should also contain the URL. ### Command line gitlab-oauth @@ -262,7 +262,7 @@ php composer.phar config [--global] --editor --auth ## gitlab-token > **Note:** For the gitlab authentication to work on private gitlab instances, the -> [`gitlab-domains`](../06-config.md#gitlab-domains) section should also contain the url. +> [`gitlab-domains`](../06-config.md#gitlab-domains) section should also contain the URL. To create a new access token, go to your [access tokens section on GitLab](https://gitlab.com/-/profile/personal_access_tokens) (or the equivalent URL on your private instance) and create a new token. See also [the GitLab access token documentation](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token) for more information. @@ -331,7 +331,7 @@ php composer.phar config [--global] --editor --auth ## bitbucket-oauth -The BitBucket driver uses OAuth to access your private repositories via the BitBucket REST APIs, and you will need to create an OAuth consumer to use the driver, please refer to [Atlassian's Documentation](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/). You will need to fill the callback url with something to satisfy BitBucket, but the address does not need to go anywhere and is not used by Composer. +The BitBucket driver uses OAuth to access your private repositories via the BitBucket REST APIs, and you will need to create an OAuth consumer to use the driver, please refer to [Atlassian's Documentation](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/). You will need to fill the callback URL with something to satisfy BitBucket, but the address does not need to go anywhere and is not used by Composer. ### Command line bitbucket-oauth From cc2568216c0f3b77f626201cbdd28579dd9e661f Mon Sep 17 00:00:00 2001 From: Maxime Morlet Date: Fri, 8 Dec 2023 18:24:33 +0100 Subject: [PATCH 07/75] Update 01-basic-usage.md (#11729) Broken link! --- doc/01-basic-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 41e43389bbbb..12df964683db 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -59,7 +59,7 @@ you to require certain versions of server software. See ### Package version constraints In our example, we are requesting the Monolog package with the version constraint -[`2.0.*`](https://semver.mwl.be/#?package=monolog%2Fmonolog&version=2.0.*). +[`2.0.*`](https://semver.madewithlove.com/?package=monolog%2Fmonolog&constraint=2.0.*). This means any version in the `2.0` development branch, or any version that is greater than or equal to 2.0 and less than 2.1 (`>=2.0 <2.1`). From 63850796958e4dae4960b9d5afd20fea5c50ceac Mon Sep 17 00:00:00 2001 From: Juliette <663378+jrfnl@users.noreply.github.com> Date: Fri, 8 Dec 2023 18:25:18 +0100 Subject: [PATCH 08/75] GH Actions: update the CI workflow for the release of PHP 8.3 (#11726) PHP 8.4 has been added as experimental. And the "high" PHP builds for the variations have been updated to use PHP 8.3. Co-authored-by: jrfnl --- .github/workflows/continuous-integration.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index bf0bf10be317..0fddd07d6931 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -44,26 +44,26 @@ jobs: dependencies: lowest os: ubuntu-latest experimental: false - - php-version: "8.1" + - php-version: "8.3" dependencies: highest os: ubuntu-latest experimental: false - - php-version: "8.1" + - php-version: "8.3" os: windows-latest dependencies: locked experimental: false - - php-version: "8.1" + - php-version: "8.3" os: macos-latest dependencies: locked experimental: false - - php-version: "8.3" + - php-version: "8.4" dependencies: lowest-ignore os: ubuntu-latest - experimental: false - - php-version: "8.3" + experimental: true + - php-version: "8.4" dependencies: highest-ignore os: ubuntu-latest - experimental: false + experimental: true steps: - name: "Checkout" From d463df10210d6511f82877ba0419af64afb73b2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 18:25:33 +0100 Subject: [PATCH 09/75] Bump actions/github-script from 6 to 7 (#11718) Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/github-script dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e76ea0337d04..82a35f05d74e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,7 +86,7 @@ jobs: # This step requires a secret token with `pull` access to composer/docker. The default # secrets.GITHUB_TOKEN is scoped to this repository only which is not sufficient. - name: "Open issue @ Docker repository" - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }} script: | From aaff0ae4df7bcd44ec4c293daf0047e8c8b0c7e9 Mon Sep 17 00:00:00 2001 From: theoboldalex <44616505+theoboldalex@users.noreply.github.com> Date: Fri, 8 Dec 2023 17:26:05 +0000 Subject: [PATCH 10/75] Adds a test for UpdateCommand (#11724) * test: Interactive mode should throw if no package * PHPStan fix. Missing return type on test method --- tests/Composer/Test/Command/UpdateCommandTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php index 5564eafae3ea..cf84015e144c 100644 --- a/tests/Composer/Test/Command/UpdateCommandTest.php +++ b/tests/Composer/Test/Command/UpdateCommandTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\Command; use Composer\Test\TestCase; +use InvalidArgumentException; class UpdateCommandTest extends TestCase { @@ -101,4 +102,14 @@ public static function provideUpdates(): \Generator OUTPUT ]; } + + public function testInteractiveModeThrowsIfNoPackageEntered(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('You must enter minimum one package.'); + + $appTester = $this->getApplicationTester(); + $appTester->setInputs(['']); + $appTester->run(['command' => 'update', '--interactive' => true]); + } } From eaa7dd46f560e234c7456bae15325c09213ad308 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 8 Dec 2023 18:32:27 +0100 Subject: [PATCH 11/75] Reverting release version changes --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index e4a95b584506..cb619e1f5747 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '2.6.6'; - public const BRANCH_ALIAS_VERSION = ''; - public const RELEASE_DATE = '2023-12-08 18:32:26'; - public const SOURCE_VERSION = ''; + public const VERSION = '@package_version@'; + public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + public const RELEASE_DATE = '@release_date@'; + public const SOURCE_VERSION = '2.6.999-dev+source'; /** * Version number of the internal composer-runtime-api package From e14d28baeceda293b1efdffc49d9ef97db0acf72 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Dec 2023 10:11:33 +0100 Subject: [PATCH 12/75] Update deps --- composer.json | 2 +- composer.lock | 108 ++++++++++---------- src/Composer/Autoload/AutoloadGenerator.php | 4 + 3 files changed, 59 insertions(+), 55 deletions(-) diff --git a/composer.json b/composer.json index 78dae74a8948..eec21b806fa7 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "seld/signal-handler": "^2.0" }, "require-dev": { - "symfony/phpunit-bridge": "^6.0 || ^7", + "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1", "phpstan/phpstan": "^1.9.3", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-deprecation-rules": "^1", diff --git a/composer.lock b/composer.lock index d6c04be604cf..f1b3e00484ba 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "eee3f28cd8914387a15be876bfe67f35", + "content-hash": "6301182f669abc92b1306f932e92c173", "packages": [ { "name": "composer/ca-bundle", @@ -378,16 +378,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.5.7", + "version": "1.5.8", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "c848241796da2abf65837d51dce1fae55a960149" + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", - "reference": "c848241796da2abf65837d51dce1fae55a960149", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", "shasum": "" }, "require": { @@ -436,9 +436,9 @@ "validator" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" + "source": "https://github.com/composer/spdx-licenses/tree/1.5.8" }, "funding": [ { @@ -454,7 +454,7 @@ "type": "tidelift" } ], - "time": "2022-05-23T07:37:50+00:00" + "time": "2023-11-20T07:44:33+00:00" }, { "name": "composer/xdebug-handler", @@ -692,24 +692,24 @@ }, { "name": "react/promise", - "version": "v3.0.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "c86753c76fd3be465d93b308f18d189f01a22be4" + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/c86753c76fd3be465d93b308f18d189f01a22be4", - "reference": "c86753c76fd3be465d93b308f18d189f01a22be4", + "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.20 || 1.4.10", - "phpunit/phpunit": "^9.5 || ^7.5" + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { @@ -753,7 +753,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.0.0" + "source": "https://github.com/reactphp/promise/tree/v3.1.0" }, "funding": [ { @@ -761,7 +761,7 @@ "type": "open_collective" } ], - "time": "2023-07-11T16:12:49+00:00" + "time": "2023-11-16T16:21:57+00:00" }, { "name": "seld/jsonlint", @@ -938,16 +938,16 @@ }, { "name": "symfony/console", - "version": "v5.4.28", + "version": "v5.4.32", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827" + "reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f4f71842f24c2023b91237c72a365306f3c58827", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827", + "url": "https://api.github.com/repos/symfony/console/zipball/c70df1ffaf23a8d340bded3cfab1b86752ad6ed7", + "reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7", "shasum": "" }, "require": { @@ -1017,7 +1017,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.28" + "source": "https://github.com/symfony/console/tree/v5.4.32" }, "funding": [ { @@ -1033,7 +1033,7 @@ "type": "tidelift" } ], - "time": "2023-08-07T06:12:30+00:00" + "time": "2023-11-18T18:23:04+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1947,16 +1947,16 @@ }, { "name": "symfony/string", - "version": "v5.4.29", + "version": "v5.4.32", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "e41bdc93def20eaf3bfc1537c4e0a2b0680a152d" + "reference": "91bf4453d65d8231688a04376c3a40efe0770f04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e41bdc93def20eaf3bfc1537c4e0a2b0680a152d", - "reference": "e41bdc93def20eaf3bfc1537c4e0a2b0680a152d", + "url": "https://api.github.com/repos/symfony/string/zipball/91bf4453d65d8231688a04376c3a40efe0770f04", + "reference": "91bf4453d65d8231688a04376c3a40efe0770f04", "shasum": "" }, "require": { @@ -2013,7 +2013,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.29" + "source": "https://github.com/symfony/string/tree/v5.4.32" }, "funding": [ { @@ -2029,22 +2029,22 @@ "type": "tidelift" } ], - "time": "2023-09-13T11:47:41+00:00" + "time": "2023-11-26T13:43:46+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.39", + "version": "1.10.50", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4" + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4", - "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "shasum": "" }, "require": { @@ -2093,7 +2093,7 @@ "type": "tidelift" } ], - "time": "2023-10-17T15:46:26+00:00" + "time": "2023-12-13T10:59:42+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2197,21 +2197,21 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6" + "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6", - "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542", + "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.10.34" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -2240,22 +2240,22 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2" }, - "time": "2023-03-29T14:47:40+00:00" + "time": "2023-10-30T14:35:06+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.4", + "version": "1.3.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "383855999db6a7d65d0bf580ce2762e17188c2a5" + "reference": "27ff6339f83796a7e0dd963cf445cd3c456fc620" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/383855999db6a7d65d0bf580ce2762e17188c2a5", - "reference": "383855999db6a7d65d0bf580ce2762e17188c2a5", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/27ff6339f83796a7e0dd963cf445cd3c456fc620", + "reference": "27ff6339f83796a7e0dd963cf445cd3c456fc620", "shasum": "" }, "require": { @@ -2312,33 +2312,33 @@ "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.4" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.5" }, - "time": "2023-09-29T14:10:11+00:00" + "time": "2023-10-30T14:52:15+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v6.3.6", + "version": "v7.0.1", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "c6f1df6a76c2c12bd14a0a5bf7c556dd935efe1d" + "reference": "c2d059b25e31274157dd7727131cd1cf33650207" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c6f1df6a76c2c12bd14a0a5bf7c556dd935efe1d", - "reference": "c6f1df6a76c2c12bd14a0a5bf7c556dd935efe1d", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c2d059b25e31274157dd7727131cd1cf33650207", + "reference": "c2d059b25e31274157dd7727131cd1cf33650207", "shasum": "" }, "require": { - "php": ">=7.1.3" + "php": ">=7.2.5" }, "conflict": { "phpunit/phpunit": "<7.5|9.1.2" }, "require-dev": { "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/error-handler": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", "symfony/polyfill-php81": "^1.27" }, "bin": [ @@ -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/v6.3.6" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.1" }, "funding": [ { @@ -2395,7 +2395,7 @@ "type": "tidelift" } ], - "time": "2023-10-12T15:02:41+00:00" + "time": "2023-12-01T09:26:31+00:00" } ], "aliases": [], diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 1fd22ddf5891..844b802073e5 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -1133,6 +1133,10 @@ class ComposerStaticInit$suffix $loader->setPsr4($namespace, $path); } + /** + * @var string $vendorDir + * @var string $baseDir + */ $classMap = require $targetDir . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); From 8f190fc09020c4ce1ab3c41da8719d2b82c8c365 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Dec 2023 11:19:05 +0100 Subject: [PATCH 13/75] Update baseline (1681, 92) --- phpstan/baseline.neon | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index afa3486b20d2..50f63ff257f8 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -2152,7 +2152,7 @@ parameters: - message: "#^Casting to bool something that's already bool\\.$#" - count: 15 + count: 11 path: ../src/Composer/Installer.php - @@ -2160,11 +2160,6 @@ parameters: count: 1 path: ../src/Composer/Installer.php - - - message: "#^Only booleans are allowed in &&, array\\\\|null given on the left side\\.$#" - count: 1 - path: ../src/Composer/Installer.php - - message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Repository\\\\LockArrayRepository\\|null given\\.$#" count: 1 @@ -2205,16 +2200,6 @@ parameters: count: 5 path: ../src/Composer/Installer.php - - - message: "#^Only booleans are allowed in an if condition, array\\\\|null given\\.$#" - count: 1 - path: ../src/Composer/Installer.php - - - - message: "#^Only booleans are allowed in \\|\\|, array\\\\|null given on the left side\\.$#" - count: 2 - path: ../src/Composer/Installer.php - - message: "#^Parameter \\#2 \\$stabilityFlags of class Composer\\\\Repository\\\\RepositorySet constructor expects array\\, non\\-empty\\-array\\ given\\.$#" count: 1 From e0f75276a2a42c9fc2c3e2d734593f759e7e6675 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Dec 2023 15:01:58 +0100 Subject: [PATCH 14/75] Switch default audit.abandoned to fail for 2.7 release --- src/Composer/Advisory/Auditor.php | 6 +----- src/Composer/Command/AuditCommand.php | 2 +- src/Composer/Config.php | 2 +- src/Composer/Installer.php | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php index 00ac610e6006..6ca3d8102861 100644 --- a/src/Composer/Advisory/Auditor.php +++ b/src/Composer/Advisory/Auditor.php @@ -56,12 +56,8 @@ class Auditor * @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, array $ignoreList = [], string $abandoned = self::ABANDONED_REPORT): int + public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL): int { - if ($abandoned === 'default' && $format !== self::FORMAT_SUMMARY) { - $io->writeError('The new audit.abandoned setting (currently defaulting to "report" will default to "fail" in Composer 2.7, make sure to set it to "report" or "ignore" explicitly by then if you do not want this.'); - } - $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY); // we need the CVE & remote IDs set to filter ignores correctly so if we have any matches using the optimized codepath above // and ignores are set then we need to query again the full data to make sure it can be filtered diff --git a/src/Composer/Command/AuditCommand.php b/src/Composer/Command/AuditCommand.php index f2abfaecaa0b..1097bb7af3f8 100644 --- a/src/Composer/Command/AuditCommand.php +++ b/src/Composer/Command/AuditCommand.php @@ -65,7 +65,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $auditConfig = $composer->getConfig()->get('audit'); - return min(255, $auditor->audit($this->getIO(), $repoSet, $packages, $this->getAuditFormat($input, 'format'), false, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_REPORT)); + return min(255, $auditor->audit($this->getIO(), $repoSet, $packages, $this->getAuditFormat($input, 'format'), false, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL)); } /** diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 1ad6226926d6..9296467f472f 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -38,7 +38,7 @@ class Config 'allow-plugins' => [], 'use-parent-dir' => 'prompt', 'preferred-install' => 'dist', - 'audit' => ['ignore' => [], 'abandoned' => 'default'], // TODO in 2.7 switch to ABANDONED_FAIL + 'audit' => ['ignore' => [], 'abandoned' => Auditor::ABANDONED_FAIL], 'notify-on-install' => true, 'github-protocols' => ['https', 'ssh', 'git'], 'gitlab-protocol' => null, diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 0efbcd6f377c..d974f8c29a8e 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -419,7 +419,7 @@ public function run(): int $auditConfig = $this->config->get('audit'); - return $auditor->audit($this->io, $repoSet, $packages, $this->auditFormat, true, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_REPORT) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0; + return $auditor->audit($this->io, $repoSet, $packages, $this->auditFormat, true, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0; } catch (TransportException $e) { $this->io->error('Failed to audit '.$target.' packages.'); if ($this->io->isVerbose()) { From 8410643e654e4f036e20b9c6d2d07f05763cb6b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 11:26:04 +0100 Subject: [PATCH 15/75] Bump actions/stale from 8 to 9 (#11753) Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v8...v9) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/close-stale-support.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/close-stale-support.yml b/.github/workflows/close-stale-support.yml index 521701afb5ff..efa085fcab2f 100644 --- a/.github/workflows/close-stale-support.yml +++ b/.github/workflows/close-stale-support.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 180 From 83f831b011d55b2499e61dab1a05c81494456a47 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Dec 2023 15:28:16 +0100 Subject: [PATCH 16/75] Make wildcard path repos more visible in docs, fixes #11732 --- doc/05-repositories.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index b6d5beb021a0..de6e323b04f7 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -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" } From 3cfd9bf51bd7afba0a339edb29f0b3a0eb23c554 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Dec 2023 15:51:39 +0100 Subject: [PATCH 17/75] Ensure composer.json gets deleted after a dry run require, fixes #11747 --- src/Composer/Command/RequireCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 658d58de3141..4a8a47559f44 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -349,6 +349,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int } throw $e; } finally { + if ($input->getOption('dry-run') && $this->newlyCreated) { + @unlink($this->json->getPath()); + } + $signalHandler->unregister(); } } From 8941a00d1b1c1b7558a49c2360e872e3bc1c3444 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Dec 2023 16:43:00 +0100 Subject: [PATCH 18/75] Update baseline --- phpstan/baseline-8.1.neon | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/phpstan/baseline-8.1.neon b/phpstan/baseline-8.1.neon index 9c13d82160e9..5a0aabf76033 100644 --- a/phpstan/baseline-8.1.neon +++ b/phpstan/baseline-8.1.neon @@ -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 From 4a209b7d3dc57130c10647277768f14ba53da9db Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Dec 2023 17:17:32 +0100 Subject: [PATCH 19/75] Fix bump command not bumping versions with a v prefix e.g. ^v2.4, fixes #11723 (#11764) --- src/Composer/Package/Version/VersionBumper.php | 8 ++++---- tests/Composer/Test/Command/BumpCommandTest.php | 2 +- tests/Composer/Test/Package/Version/VersionBumperTest.php | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Composer/Package/Version/VersionBumper.php b/src/Composer/Package/Version/VersionBumper.php index 690dfbeeddac..9292b6cdbd82 100644 --- a/src/Composer/Package/Version/VersionBumper.php +++ b/src/Composer/Package/Version/VersionBumper.php @@ -82,10 +82,10 @@ public function bumpRequirement(ConstraintInterface $constraint, PackageInterfac $pattern = '{ (?<=,|\ |\||^) # leading separator (?P - \^'.$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 ) (?=,|$|\ |\||@) # trailing separator }x'; diff --git a/tests/Composer/Test/Command/BumpCommandTest.php b/tests/Composer/Test/Command/BumpCommandTest.php index 8489ef017662..383aaf90c144 100644 --- a/tests/Composer/Test/Command/BumpCommandTest.php +++ b/tests/Composer/Test/Command/BumpCommandTest.php @@ -76,7 +76,7 @@ public static function provideTests(): \Generator yield 'bump all by default' => [ [ 'require' => [ - 'first/pkg' => '^2.0', + 'first/pkg' => '^v2.0', 'second/pkg' => '3.*', ], 'require-dev' => [ diff --git a/tests/Composer/Test/Package/Version/VersionBumperTest.php b/tests/Composer/Test/Package/Version/VersionBumperTest.php index b8f07844f6c0..c5cd86b15d2e 100644 --- a/tests/Composer/Test/Package/Version/VersionBumperTest.php +++ b/tests/Composer/Test/Package/Version/VersionBumperTest.php @@ -44,6 +44,7 @@ public static function provideBumpRequirementTests(): Generator { // 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 @@ public static function provideBumpRequirementTests(): Generator 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 @@ public static function provideBumpRequirementTests(): Generator 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']; } } From 9b0f9b40a44a8ed0f5a34bb2674b95561b5039d2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Dec 2023 17:17:48 +0100 Subject: [PATCH 20/75] Show package source in very verbose updates, fixes #11733 (#11763) --- src/Composer/Installer.php | 9 ++++++- .../Test/Command/UpdateCommandTest.php | 25 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index d974f8c29a8e..b4361a10251a 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -605,7 +605,14 @@ protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doIns // 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); } } diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php index cf84015e144c..fce735087872 100644 --- a/tests/Composer/Test/Command/UpdateCommandTest.php +++ b/tests/Composer/Test/Command/UpdateCommandTest.php @@ -29,7 +29,7 @@ public function testUpdate(array $composerJson, array $command, string $expected $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 @@ public static function provideUpdates(): \Generator OUTPUT ]; + yield 'simple update with very verbose output' => [ + $rootDepAndTransitiveDep, + ['-vv' => true], + << [ $rootDepAndTransitiveDep, ['--with' => ['dep/pkg:1.0.0'], '--no-install' => true], From 86cd3649019d08244543307020edf19c56b749e2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Dec 2023 18:11:50 +0000 Subject: [PATCH 21/75] Audit: add severity to plain and table output (#11702) --- src/Composer/Advisory/Auditor.php | 12 +++++++++++ .../Advisory/IgnoredSecurityAdvisory.php | 4 ++-- .../Advisory/PartialSecurityAdvisory.php | 2 +- src/Composer/Advisory/SecurityAdvisory.php | 12 +++++++++-- tests/Composer/Test/Advisory/AuditorTest.php | 20 +++++++++++++++++++ 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php index 6ca3d8102861..bc6520d55ffb 100644 --- a/src/Composer/Advisory/Auditor.php +++ b/src/Composer/Advisory/Auditor.php @@ -247,6 +247,7 @@ private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void foreach ($packageAdvisories as $advisory) { $headers = [ 'Package', + 'Severity', 'CVE', 'Title', 'URL', @@ -255,6 +256,7 @@ private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void ]; $row = [ $advisory->packageName, + $this->getSeverity($advisory), $this->getCVE($advisory), $advisory->title, $this->getURL($advisory), @@ -289,6 +291,7 @@ private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void $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 @@ private function getPackageNameWithLink(PackageInterface $package): string return $packageUrl !== null ? '' . $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) { diff --git a/src/Composer/Advisory/IgnoredSecurityAdvisory.php b/src/Composer/Advisory/IgnoredSecurityAdvisory.php index ba9079287b84..3d8b56a1c43a 100644 --- a/src/Composer/Advisory/IgnoredSecurityAdvisory.php +++ b/src/Composer/Advisory/IgnoredSecurityAdvisory.php @@ -26,9 +26,9 @@ class IgnoredSecurityAdvisory extends SecurityAdvisory /** * @param non-empty-array $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; } diff --git a/src/Composer/Advisory/PartialSecurityAdvisory.php b/src/Composer/Advisory/PartialSecurityAdvisory.php index 28dbdc4f439b..2867e9b60296 100644 --- a/src/Composer/Advisory/PartialSecurityAdvisory.php +++ b/src/Composer/Advisory/PartialSecurityAdvisory.php @@ -44,7 +44,7 @@ public static function create(string $packageName, array $data, VersionParser $p { $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); diff --git a/src/Composer/Advisory/SecurityAdvisory.php b/src/Composer/Advisory/SecurityAdvisory.php index e88228d60567..a3d58b462b10 100644 --- a/src/Composer/Advisory/SecurityAdvisory.php +++ b/src/Composer/Advisory/SecurityAdvisory.php @@ -47,10 +47,16 @@ class SecurityAdvisory extends PartialSecurityAdvisory */ public $sources; + /** + * @var string|null + * @readonly + */ + public $severity; + /** * @param non-empty-array $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 @@ public function __construct(string $packageName, string $advisoryId, ConstraintI $this->reportedAt = $reportedAt; $this->cve = $cve; $this->link = $link; + $this->severity = $severity; } /** @@ -75,7 +82,8 @@ public function toIgnoredAdvisory(?string $ignoreReason): IgnoredSecurityAdvisor $this->reportedAt, $this->cve, $this->link, - $ignoreReason + $ignoreReason, + $this->severity ); } diff --git a/tests/Composer/Test/Advisory/AuditorTest.php b/tests/Composer/Test/Advisory/AuditorTest.php index 656505844275..2253169f4934 100644 --- a/tests/Composer/Test/Advisory/AuditorTest.php +++ b/tests/Composer/Test/Advisory/AuditorTest.php @@ -54,6 +54,7 @@ public static function auditProvider() 'expected' => 1, 'output' => 'Found 2 security vulnerability advisories affecting 1 package: Package: vendor1/package1 +Severity: high CVE: CVE3 Title: advisory4 URL: https://advisory.example.com/advisory4 @@ -61,6 +62,7 @@ public static function auditProvider() 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 @@ public function ignoredIdsProvider(): \Generator { [ ['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 @@ public function ignoredIdsProvider(): \Generator { [ ['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 @@ public function ignoredIdsProvider(): \Generator { [ ['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 @@ public function ignoredIdsProvider(): \Generator { [ ['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 @@ public function ignoredIdsProvider(): \Generator { [ ['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 @@ public function ignoredIdsProvider(): \Generator { [ ['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 @@ public function ignoredIdsProvider(): \Generator { ['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 @@ public function ignoredIdsProvider(): \Generator { ['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 @@ public function ignoredIdsProvider(): \Generator { ['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 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2022-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], [ 'advisoryId' => 'ID4', @@ -396,6 +408,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2022-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'high', ], [ 'advisoryId' => 'ID5', @@ -412,6 +425,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2022-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], ], 'vendor1/package2' => [ @@ -430,6 +444,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2022-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], ], 'vendorx/packagex' => [ @@ -448,6 +463,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2015-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], ], 'vendor2/package1' => [ @@ -466,6 +482,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2022-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], [ 'advisoryId' => 'ID6', @@ -482,6 +499,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2015-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], ], 'vendory/packagey' => [ @@ -500,6 +518,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2015-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], ], 'vendor3/package1' => [ @@ -518,6 +537,7 @@ public static function getMockAdvisories(): array ], 'reportedAt' => '2015-05-25 13:21:00', 'composerRepository' => 'https://packagist.org', + 'severity' => 'medium', ], ], ]; From c8f1028ef95faedb2dbad69dcac2b1e6361a678b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Dec 2023 15:16:12 +0100 Subject: [PATCH 22/75] Fix minor error msg issue --- src/Composer/Command/ShowCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 083a49ec3021..3e010fc313bf 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -293,7 +293,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 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'; } From 53a1f32061684bfa153b120b2401d5e599b69011 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Dec 2023 15:37:27 +0100 Subject: [PATCH 23/75] Add --sort-by-age to show/outdated commands, and also release date for latest package in --latest mode (#11762) --- doc/03-cli.md | 2 + src/Composer/Command/OutdatedCommand.php | 4 + src/Composer/Command/ShowCommand.php | 92 ++++++++++++++++--- .../Composer/Test/Command/ShowCommandTest.php | 32 +++++-- 4 files changed, 111 insertions(+), 19 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 166fed193f61..8d4d88466efe 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -556,6 +556,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 +590,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. diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php index 05f22ebbb2b6..b6c71c1753ad 100644 --- a/src/Composer/Command/OutdatedCommand.php +++ b/src/Composer/Command/OutdatedCommand.php @@ -40,6 +40,7 @@ protected function configure(): void 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 @@ protected function execute(InputInterface $input, OutputInterface $output): int 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; diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 3e010fc313bf..c7d1ae9c12bc 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -98,6 +98,7 @@ protected function configure() 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']), @@ -450,7 +451,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 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 +470,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int $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 +517,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $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 +575,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'nameLength' => $nameLength, 'versionLength' => $versionLength, 'latestLength' => $latestLength, + 'releaseDateLength' => $releaseDateLength, 'writeLatest' => $writeLatest, + 'writeReleaseDate' => $writeReleaseDate, ]; if ($input->getOption('strict') && $hasOutdatedPackages) { $exitCode = 1; @@ -588,11 +613,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $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 +648,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->writeError(''); $io->writeError('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('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 +663,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 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 +679,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * @param array $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 +703,13 @@ private function printPackages(IOInterface $io, array $packages, string $indent, $latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion; } $io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '', 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 +838,20 @@ protected function printPackageInfo(CompletePackageInterface $package, array $ve */ 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('name : ' . $package->getPrettyName()); $io->write('descrip. : ' . $package->getDescription()); $io->write('keywords : ' . implode(', ', $package->getKeywords() ?: [])); $this->printVersions($package, $versions, $installedRepo); + if ($isInstalledPackage && $package->getReleaseDate() !== null) { + $io->write('released : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate())); + } if ($latestPackage) { $style = $this->getVersionStyle($latestPackage, $package); - $io->write('latest : <'.$style.'>' . $latestPackage->getPrettyVersion() . ''); + $releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate()); + $io->write('latest : <'.$style.'>' . $latestPackage->getPrettyVersion() . '' . $releasedTime); } else { $latestPackage = $package; } @@ -822,7 +860,7 @@ protected function printMeta(CompletePackageInterface $package, array $versions, $io->write('homepage : ' . $package->getHomepage()); $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('dist : ' . sprintf('[%s] %s %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('path : ' . realpath($path)); @@ -993,6 +1031,10 @@ protected function printPackageInfoAsJson(CompletePackageInterface $package, arr } else { $json['path'] = null; } + + if ($package->getReleaseDate() !== null) { + $json['released'] = $package->getReleaseDate()->format(DATE_ATOM); + } } if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { @@ -1447,4 +1489,30 @@ private function getRepositorySet(Composer $composer): RepositorySet return $this->repositorySet; } + + private function getRelativeTime(\DateTimeInterface $releaseDate): string + { + if ($releaseDate->format('Ymd') === date('Ymd')) { + return 'today'; + } + + $diff = $releaseDate->diff(new \DateTimeImmutable()); + if ($diff->days < 7) { + return 'this week'; + } + + if ($diff->days < 14) { + return 'last week'; + } + + if ($diff->m < 1 && $diff->days < 31) { + return floor($diff->days / 7) . ' weeks ago'; + } + + if ($diff->y < 1) { + return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago'; + } + + return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago'; + } } diff --git a/tests/Composer/Test/Command/ShowCommandTest.php b/tests/Composer/Test/Command/ShowCommandTest.php index 9601205d9fb8..b2b561bd3809 100644 --- a/tests/Composer/Test/Command/ShowCommandTest.php +++ b/tests/Composer/Test/Command/ShowCommandTest.php @@ -17,6 +17,7 @@ use Composer\Pcre\Regex; use Composer\Repository\PlatformRepository; use Composer\Test\TestCase; +use DateTimeImmutable; class ShowCommandTest extends TestCase { @@ -55,13 +56,14 @@ public function testShow(array $command, string $expected, array $requires = []) $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([ - $pkg, - self::getPackage('outdated/major', '1.0.0'), - self::getPackage('outdated/minor', '1.0.0'), - self::getPackage('outdated/patch', '1.0.0'), - ]); + $this->createInstalledJson([$pkg, $major, $minor, $patch]); $appTester = $this->getApplicationTester(); $appTester->run(array_merge(['command' => 'show'], $command)); @@ -112,6 +114,21 @@ public static function provideShow(): \Generator outdated/patch 1.0.0 ! 1.0.1', ]; + 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 ! 1.1.1 2 years old +outdated/patch 1.0.0 ! 1.0.1 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: @@ -533,7 +550,7 @@ public function testSelfAndPackageCombination(): void 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 +559,7 @@ public function testSelf(): void 'descrip.' => '', 'keywords' => '', 'versions' => '* 1.0.0+no-version-set', + 'released' => date('Y-m-d'). ', today', 'type' => 'library', 'homepage' => '', 'source' => '[] ', From 6198fc1053ede6af9b401a75f4badf0311d2adc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zoli=20Szab=C3=B3?= Date: Wed, 20 Dec 2023 17:42:45 +0200 Subject: [PATCH 24/75] Fix typo in composer-platform-dependencies.md (#11757) --- doc/articles/composer-platform-dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/composer-platform-dependencies.md b/doc/articles/composer-platform-dependencies.md index e166d6f4a103..4e6dedafe85a 100644 --- a/doc/articles/composer-platform-dependencies.md +++ b/doc/articles/composer-platform-dependencies.md @@ -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` From 8e62977cb5de315d5190c021a2b2bb4e59ee2f4f Mon Sep 17 00:00:00 2001 From: Roberto Guido Date: Wed, 20 Dec 2023 16:50:24 +0100 Subject: [PATCH 25/75] Exposing GitLab's project metadata (#11734) * Exposing GitLab's project metadata * Fixed check about GitLab project's metadata initialization --- src/Composer/Repository/Vcs/GitLabDriver.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 1e13154c5ad4..3721419b1788 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -43,7 +43,7 @@ class GitLabDriver extends VcsDriver /** * @var mixed[] Project data returned by GitLab API */ - private $project; + private $project = null; /** * @var array Keeps commits returned by GitLab API as commit id => info @@ -381,6 +381,10 @@ protected function getReferences(string $type): array 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 @@ public static function supports(IOInterface $io, Config $config, string $url, bo return true; } + /** + * Gives back the loaded /projects// result + * + * @return mixed[]|null + */ + public function getRepoData(): ?array + { + $this->fetchProject(); + + return $this->project; + } + protected function getNextPage(Response $response): ?string { $header = $response->getHeader('link'); From 12ed21705d4ad3feeb428e52540720177c3db65d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 22 Dec 2023 17:47:40 +0100 Subject: [PATCH 26/75] Check for non-platform requirements before warning that no deps are installed on show command, fixes #11760 --- src/Composer/Command/ShowCommand.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index ea5a9db3ba92..c2530c146510 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -258,8 +258,16 @@ protected function execute(InputInterface $input, OutputInterface $output) }, $packages))]); } - if (!$installedRepo->getPackages() && ($rootPkg->getRequires() || $rootPkg->getDevRequires())) { - $io->writeError('No dependencies installed. Try running composer install or update.'); + 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('No dependencies installed. Try running composer install or update.'); + } } } From efe6e44883cccc5320a361b0c26c698ab4f925c0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2024 10:55:59 +0100 Subject: [PATCH 27/75] Perform audit on Composer and its dependencies during diagnose, fixes #11216 (#11761) --- src/Composer/Command/DiagnoseCommand.php | 56 +++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index d1b4fbee548f..7d41bf554ed0 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -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 @@ protected function execute(InputInterface $input, OutputInterface $output): int $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: %s', Composer::getVersion())); $platformOverrides = $config->get('platform') ?: []; @@ -438,6 +450,48 @@ private function checkVersion(Config $config) 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 '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 'Failed performing audit: '.$e->getMessage().''; + } + + if ($result > 0) { + return 'Audit found some issues:' . PHP_EOL . $io->getOutput(); + } + + return true; + } + private function getCurlVersion(): string { if (extension_loaded('curl')) { From 8246892d484273a0f3c46e2d4f1d217628d5e31f Mon Sep 17 00:00:00 2001 From: Quynh Anh <152064901+lqat@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:49:27 +0100 Subject: [PATCH 28/75] Fix PackageInterface parameter comments (#11777) --- src/Composer/Downloader/DvcsDownloaderInterface.php | 2 +- src/Composer/Downloader/FileDownloader.php | 2 +- src/Composer/Downloader/VcsCapableDownloaderInterface.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/DvcsDownloaderInterface.php b/src/Composer/Downloader/DvcsDownloaderInterface.php index 879aab2edc1a..6e5b67c0a45c 100644 --- a/src/Composer/Downloader/DvcsDownloaderInterface.php +++ b/src/Composer/Downloader/DvcsDownloaderInterface.php @@ -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 */ diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 0c989a82d38c..2a2847f33f16 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -458,7 +458,7 @@ protected function getInstallOperationAppendix(PackageInterface $package, string /** * 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 diff --git a/src/Composer/Downloader/VcsCapableDownloaderInterface.php b/src/Composer/Downloader/VcsCapableDownloaderInterface.php index 8791702764c0..c99005aa4579 100644 --- a/src/Composer/Downloader/VcsCapableDownloaderInterface.php +++ b/src/Composer/Downloader/VcsCapableDownloaderInterface.php @@ -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 */ From d00e38a0387558773d4b32d97fb124f486a0db99 Mon Sep 17 00:00:00 2001 From: rkpiii Date: Thu, 4 Jan 2024 17:02:34 +0100 Subject: [PATCH 29/75] =?UTF-8?q?[11744]=20handle=20missing=20hyphen=20whe?= =?UTF-8?q?n=20attempting=20to=20run=20self-update=E2=80=A6=20(#11775)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [11744] handle missing hyphen when attempting to run self-update command * fix: [1744] silently fix the "self update" command --- src/Composer/Command/SelfUpdateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 45db77e529e6..26d9f75467d8 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -156,6 +156,10 @@ class_exists('Composer\Downloader\FilesystemException'); 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 { From 3be0ca84679bef00d3c16472d258bb88d12b23f6 Mon Sep 17 00:00:00 2001 From: theoboldalex <44616505+theoboldalex@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:03:34 +0000 Subject: [PATCH 30/75] Adds a test for invalid arg combo (#11783) --- tests/Composer/Test/Command/ConfigCommandTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Composer/Test/Command/ConfigCommandTest.php b/tests/Composer/Test/Command/ConfigCommandTest.php index 10dd170f2576..68653c622903 100644 --- a/tests/Composer/Test/Command/ConfigCommandTest.php +++ b/tests/Composer/Test/Command/ConfigCommandTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\Command; use Composer\Test\TestCase; +use RuntimeException; class ConfigCommandTest extends TestCase { @@ -139,4 +140,13 @@ public static function provideConfigReads(): \Generator '{"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]); + } } From 534bc20bebe8e81f0c9fa7f9da976b47a2952d26 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 8 Jan 2024 14:14:44 +0100 Subject: [PATCH 31/75] Add support for combining show --self with --installed or --locked (#11785) --- src/Composer/Command/ShowCommand.php | 19 +++++++++++---- .../Composer/Test/Command/ShowCommandTest.php | 23 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index c7d1ae9c12bc..e89d7cc23615 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -27,6 +27,7 @@ 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; @@ -142,7 +143,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $composer = $this->tryComposer(); $io = $this->getIO(); - if ($input->getOption('installed')) { + if ($input->getOption('installed') && !$input->getOption('self')) { $io->writeError('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.'); } @@ -199,7 +200,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $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()); @@ -243,6 +244,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $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 @@ -250,13 +254,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $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())) { diff --git a/tests/Composer/Test/Command/ShowCommandTest.php b/tests/Composer/Test/Command/ShowCommandTest.php index b2b561bd3809..bbafb3d6d8f0 100644 --- a/tests/Composer/Test/Command/ShowCommandTest.php +++ b/tests/Composer/Test/Command/ShowCommandTest.php @@ -29,6 +29,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', @@ -65,6 +67,12 @@ public function testShow(array $command, string $expected, array $requires = []) $this->createInstalledJson([$pkg, $major, $minor, $patch]); + $pkg = self::getPackage('vendor/locked', '3.0.0'); + $pkg->setDescription('description of locked package'); + $this->createComposerLock([ + $pkg, + ]); + $appTester = $this->getApplicationTester(); $appTester->run(array_merge(['command' => 'show'], $command)); self::assertSame(trim($expected), trim($appTester->getDisplay(true))); @@ -80,6 +88,21 @@ public static function provideShow(): \Generator 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 From 071fbcf3474cbfc948363b261c54d582f64f3d82 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 8 Jan 2024 14:48:24 +0100 Subject: [PATCH 32/75] Fix warnings incorrectly being shown when using require with upper bound ignored on platform requirements, fixes #11722 (#11786) --- .../IgnoreAllPlatformRequirementFilter.php | 5 +++ .../IgnoreListPlatformRequirementFilter.php | 9 +++++ ...IgnoreNothingPlatformRequirementFilter.php | 8 +++++ .../PlatformRequirementFilterInterface.php | 2 ++ .../Package/Version/VersionSelector.php | 8 +++++ ...IgnoreAllPlatformRequirementFilterTest.php | 1 + ...gnoreListPlatformRequirementFilterTest.php | 33 +++++++++++++++++++ ...reNothingPlatformRequirementFilterTest.php | 1 + 8 files changed, 67 insertions(+) diff --git a/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php b/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php index 491ad01b9a1c..8167fdd3e76b 100644 --- a/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php +++ b/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php @@ -20,4 +20,9 @@ public function isIgnored(string $req): bool { return PlatformRepository::isPlatformPackage($req); } + + public function isUpperBoundIgnored(string $req): bool + { + return $this->isIgnored($req); + } } diff --git a/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php b/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php index 43cc33469bb5..73d53637614a 100644 --- a/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php +++ b/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php @@ -60,6 +60,15 @@ public function isIgnored(string $req): bool 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 */ diff --git a/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php b/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php index 26570182464d..ab225d6c9fbc 100644 --- a/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php +++ b/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php @@ -21,4 +21,12 @@ public function isIgnored(string $req): bool { return false; } + + /** + * @return false + */ + public function isUpperBoundIgnored(string $req): bool + { + return false; + } } diff --git a/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php b/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php index af1f651c86b2..59e824591e8b 100644 --- a/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php +++ b/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php @@ -15,4 +15,6 @@ interface PlatformRequirementFilterInterface { public function isIgnored(string $req): bool; + + public function isUpperBoundIgnored(string $req): bool; } diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index 138b8fabfde8..0b6656816f96 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -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 @@ public function findBestCandidate(string $packageName, ?string $targetPackageVer // 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 diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php index 8df56d81d02a..8d148ec7cab6 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilterTest.php @@ -25,6 +25,7 @@ public function testIsIgnored(string $req, bool $expectIgnored): void $platformRequirementFilter = new IgnoreAllPlatformRequirementFilter(); $this->assertSame($expectIgnored, $platformRequirementFilter->isIgnored($req)); + $this->assertSame($expectIgnored, $platformRequirementFilter->isUpperBoundIgnored($req)); } /** diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php index a3a588cef518..5f3cc1f514dd 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilterTest.php @@ -48,4 +48,37 @@ public static function dataIsIgnored(): array '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 + */ + 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], + ]; + } } diff --git a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php index a1baac58b295..70c3fabd204d 100644 --- a/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php +++ b/tests/Composer/Test/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilterTest.php @@ -25,6 +25,7 @@ public function testIsIgnored(string $req): void $platformRequirementFilter = new IgnoreNothingPlatformRequirementFilter(); $this->assertFalse($platformRequirementFilter->isIgnored($req)); // @phpstan-ignore-line + $this->assertFalse($platformRequirementFilter->isUpperBoundIgnored($req)); // @phpstan-ignore-line } /** From be71bf056eb3d72fcdc1de6d5a26ea75e8c6031d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 8 Jan 2024 14:56:08 +0100 Subject: [PATCH 33/75] Fix support for versions with 4 components in VersionSelector, fixes #11716 --- src/Composer/Package/Version/VersionSelector.php | 3 ++- tests/Composer/Test/Package/Version/VersionSelectorTest.php | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index 138b8fabfde8..95c8efb4ec72 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -182,6 +182,7 @@ public function findBestCandidate(string $packageName, ?string $targetPackageVer * * 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 +228,7 @@ private function transformVersion(string $version, string $prettyVersion, string $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]); diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index e5d21d7a2b6a..aec5cf86caa7 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -346,6 +346,9 @@ public static function provideRecommendedRequireVersionPackages(): array ['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'], From 44f02a5c8614d9bb4f43f26b287d3fde29633c2b Mon Sep 17 00:00:00 2001 From: Sam L Date: Mon, 8 Jan 2024 09:10:49 -0500 Subject: [PATCH 34/75] Add COMPOSER_FUND=0 env var to disable calls for funding (#11779) --- doc/03-cli.md | 4 ++ src/Composer/Installer.php | 38 +++++++----- .../installer/install-funding-notice-env.test | 62 +++++++++++++++++++ ...tall-funding-notice-not-displayed-env.test | 60 ++++++++++++++++++ tests/Composer/Test/InstallerTest.php | 1 + 5 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/install-funding-notice-env.test create mode 100644 tests/Composer/Test/Fixtures/installer/install-funding-notice-not-displayed-env.test diff --git a/doc/03-cli.md b/doc/03-cli.md index 8d4d88466efe..cfdcd5c999cc 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1145,6 +1145,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 diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index b4361a10251a..fe50c8fdeda6 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -372,22 +372,30 @@ public function run(): int } } - $fundingCount = 0; - foreach ($localRepo->getPackages() as $package) { - if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) { - $fundingCount++; - } + $fundEnv = Platform::getEnv('COMPOSER_FUND'); + $showFunding = true; + if (is_numeric($fundEnv)) { + $showFunding = intval($fundEnv) !== 0; } - if ($fundingCount > 0) { - $this->io->writeError([ - sprintf( - "%d package%s you are using %s looking for funding.", - $fundingCount, - 1 === $fundingCount ? '' : 's', - 1 === $fundingCount ? 'is' : 'are' - ), - 'Use the `composer fund` command to find out more!', - ]); + + if ($showFunding) { + $fundingCount = 0; + foreach ($localRepo->getPackages() as $package) { + if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) { + $fundingCount++; + } + } + if ($fundingCount > 0) { + $this->io->writeError([ + sprintf( + "%d package%s you are using %s looking for funding.", + $fundingCount, + 1 === $fundingCount ? '' : 's', + 1 === $fundingCount ? 'is' : 'are' + ), + 'Use the `composer fund` command to find out more!', + ]); + } } if ($this->runScripts) { diff --git a/tests/Composer/Test/Fixtures/installer/install-funding-notice-env.test b/tests/Composer/Test/Fixtures/installer/install-funding-notice-env.test new file mode 100644 index 000000000000..c6f737f60544 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/install-funding-notice-env.test @@ -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-- +No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information. +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) diff --git a/tests/Composer/Test/Fixtures/installer/install-funding-notice-not-displayed-env.test b/tests/Composer/Test/Fixtures/installer/install-funding-notice-not-displayed-env.test new file mode 100644 index 000000000000..1f4ef80351e8 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/install-funding-notice-not-displayed-env.test @@ -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-- +No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information. +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) diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 511d649a854a..858d88e7c03c 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -59,6 +59,7 @@ protected function tearDown(): void { parent::tearDown(); Platform::clearEnv('COMPOSER_POOL_OPTIMIZER'); + Platform::clearEnv('COMPOSER_FUND'); chdir($this->prevCwd); if (isset($this->tempComposerHome) && is_dir($this->tempComposerHome)) { From 3ed4e16deaf9570979914bf9090920e13e372887 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 8 Jan 2024 16:05:46 +0100 Subject: [PATCH 35/75] Update deps --- composer.lock | 98 +++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/composer.lock b/composer.lock index f1b3e00484ba..ea0e3dd32528 100644 --- a/composer.lock +++ b/composer.lock @@ -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": [], From 5bc5c174a68a98fa3779ee4ab8f9c85f64d6b78c Mon Sep 17 00:00:00 2001 From: James <94084429+jpow18@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:21:34 -0500 Subject: [PATCH 36/75] Update 01-basic-usage.md (#11788) Changed a few clunky phrases --- doc/01-basic-usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 12df964683db..3d2571536195 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -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 From ca433076b16a377b5a19f1a63ee3c3adcf9e1538 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 10 Jan 2024 10:25:49 +0100 Subject: [PATCH 37/75] Sync up docs from command, fixes #11787 --- doc/03-cli.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index cfdcd5c999cc..a98a7d629c6c 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -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 From 042a8c212801aeac42b7a41b42cd1185ae28123a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 10 Jan 2024 13:33:49 +0100 Subject: [PATCH 38/75] Ensure dist url/type/checksum remain the same when doing lock hash updates, refs #11787 --- src/Composer/DependencyResolver/LockTransaction.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index 92eb147f5f4a..ea84771b9236 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -111,6 +111,9 @@ public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): if ($package->getName() === $presentPackage->getName() && $package->getVersion() === $presentPackage->getVersion()) { if ($presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) { $package->setSourceDistReferences($presentPackage->getSourceReference()); + $package->setDistUrl($presentPackage->getDistUrl()); + $package->setDistType($presentPackage->getDistType()); + $package->setDistSha1Checksum($presentPackage->getDistSha1Checksum()); } if ($presentPackage->getReleaseDate() !== null && $package instanceof Package) { $package->setReleaseDate($presentPackage->getReleaseDate()); From 547a635287ce17c55cf015c512fdd5f1f3a4aa7c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 10 Jan 2024 13:34:56 +0100 Subject: [PATCH 39/75] Fix build --- src/Composer/DependencyResolver/LockTransaction.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index ea84771b9236..2cfc08239a2f 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -113,7 +113,9 @@ public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): $package->setSourceDistReferences($presentPackage->getSourceReference()); $package->setDistUrl($presentPackage->getDistUrl()); $package->setDistType($presentPackage->getDistType()); - $package->setDistSha1Checksum($presentPackage->getDistSha1Checksum()); + if ($package instanceof Package) { + $package->setDistSha1Checksum($presentPackage->getDistSha1Checksum()); + } } if ($presentPackage->getReleaseDate() !== null && $package instanceof Package) { $package->setReleaseDate($presentPackage->getReleaseDate()); From 10667db1ba369254e6a7d0231bff139fedb6fc80 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 10 Jan 2024 13:42:01 +0100 Subject: [PATCH 40/75] Only override ist url if it is not handled gracefully already --- src/Composer/DependencyResolver/LockTransaction.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index 2cfc08239a2f..d5ff59135326 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -15,6 +15,7 @@ use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\Package; +use Composer\Pcre\Preg; /** * @author Nils Adermann @@ -111,7 +112,10 @@ public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): if ($package->getName() === $presentPackage->getName() && $package->getVersion() === $presentPackage->getVersion()) { if ($presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) { $package->setSourceDistReferences($presentPackage->getSourceReference()); - $package->setDistUrl($presentPackage->getDistUrl()); + // if the dist url is not one of those handled gracefully by setSourceDistReferences then we should overwrite it with the old one + if (!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()); From 3427bee1f2b50fbe108c3fe81b8ba9b9fae2d960 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 10 Jan 2024 13:47:26 +0100 Subject: [PATCH 41/75] :facepalm: --- src/Composer/DependencyResolver/LockTransaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index d5ff59135326..70603b867a26 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -113,7 +113,7 @@ public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): 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 (!Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) { + 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()); From 55db88f51bf1291567adfad0502ff5f65a304db3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 11 Jan 2024 09:52:36 +0100 Subject: [PATCH 42/75] Add error when composer show --direct is used to show a dependency which is not direct, fixes #11728 --- src/Composer/Command/ShowCommand.php | 6 ++++++ .../Composer/Test/Command/ShowCommandTest.php | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 63715afae132..fc5940b91587 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -299,6 +299,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } 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 = ''; diff --git a/tests/Composer/Test/Command/ShowCommandTest.php b/tests/Composer/Test/Command/ShowCommandTest.php index bbafb3d6d8f0..acab3ccc9f19 100644 --- a/tests/Composer/Test/Command/ShowCommandTest.php +++ b/tests/Composer/Test/Command/ShowCommandTest.php @@ -18,6 +18,7 @@ use Composer\Repository\PlatformRepository; use Composer\Test\TestCase; use DateTimeImmutable; +use InvalidArgumentException; class ShowCommandTest extends TestCase { @@ -298,6 +299,23 @@ public function testOutdatedFiltersAccordingToPlatformReqsWithoutWarningForHighe vendor/package 1.1.0 ! 1.2.0", 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([ From 75fd2bbeb2a04cce78ca9f5488583ea8c420a4d5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 11 Jan 2024 16:44:27 +0100 Subject: [PATCH 43/75] Ensure we respect available-package-patterns and available-packages directives when fetching security advisories, fixes #11704 (#11773) --- src/Composer/Repository/ComposerRepository.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 1dcf991aa0b6..58069c9d708a 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -637,6 +637,15 @@ public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPa $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 $data From 3491986ad3b529b0b6ea291093d099d3b121981f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 11 Jan 2024 17:13:54 +0100 Subject: [PATCH 44/75] Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE env var (#11791) * Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE env var, fixes #530 * Address feedback * Add warning in diagnose command when COMPOSER_IPRESOLVE is set --- doc/03-cli.md | 5 +++++ doc/articles/troubleshooting.md | 5 +++++ phpstan/baseline.neon | 12 +--------- src/Composer/Command/DiagnoseCommand.php | 7 +++++- src/Composer/Util/Http/CurlDownloader.php | 27 ++++++++++++++++++----- 5 files changed, 39 insertions(+), 17 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index a98a7d629c6c..6e6809a49658 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1244,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 diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 7d5afdea9bca..0607535d7aff 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -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 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 50f63ff257f8..b17166a49727 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -765,16 +765,6 @@ parameters: count: 1 path: ../src/Composer/Command/ShowCommand.php - - - message: "#^Only booleans are allowed in \\|\\|, array\\ given on the left side\\.$#" - count: 1 - path: ../src/Composer/Command/ShowCommand.php - - - - message: "#^Only booleans are allowed in \\|\\|, array\\ 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\\\\>, array\\\\>\\|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\\, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\, 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\\, 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\\, 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 diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 7d41bf554ed0..18b1911d75bd 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -553,7 +553,7 @@ private function outputResult($result): void if ($result) { foreach ($result as $message) { - $io->write($message); + $io->write(trim($message)); } } } @@ -776,6 +776,11 @@ private function checkPlatform() $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; } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 967aed2e79d0..f5bbe24aae10 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -28,7 +28,7 @@ * @internal * @author Jordi Boggiano * @author Nicolas Grekas - * @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 @@ public function download(callable $resolve, callable $reject, string $origin, st /** * @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 @@ private function initDownload(callable $resolve, callable $reject, string $origi '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 @@ private function initDownload(callable $resolve, callable $reject, string $origi 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 @@ public function tick(): void || (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 @@ private function isAuthenticatedRetryNeeded(array $job, Response $response): arr * @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 @@ private function restartJob(array $job, string $url, array $attributes = []): vo * @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 { From a29acbdd2ed087940ed12447fe7d76bb13a0b94f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 12 Jan 2024 13:16:53 +0100 Subject: [PATCH 45/75] Ensure repos declaring security-advisories have at least an API or a restricted set of packages to avoid too many wasteful requests --- src/Composer/Repository/ComposerRepository.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 58069c9d708a..68cf7bd602d6 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -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; /** @@ -1257,9 +1257,11 @@ protected function loadRootServerFile(?int $rootMaxAge = null) 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.'); + } } } @@ -1289,12 +1291,16 @@ protected function loadRootServerFile(?int $rootMaxAge = null) } /** - * @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; } From 4e5be9ee7d924d8efc58d676439b0c7bd18a9ce4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 12 Jan 2024 14:20:59 +0100 Subject: [PATCH 46/75] Emit warning instead of crashing on invalid security advisory API response, fixes #11767 --- src/Composer/Repository/ComposerRepository.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 68cf7bd602d6..ec244b129963 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -709,8 +709,16 @@ static function ($data) use ($name, $create) { $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(''.$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)).''); + $warned = true; + } + continue; + } if (count($list) > 0) { $advisories[$name] = array_filter(array_map( static function ($data) use ($name, $create) { From 952256247c6441d5a444d15c1607228a6d97316f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 26 Jan 2024 17:11:16 +0100 Subject: [PATCH 47/75] Only include installed versions class when plugins and scripts are allowed, as it is not needed otherwise --- src/Composer/Factory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index f82a9c390095..b198f0f14fd9 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -350,8 +350,8 @@ public function createComposer(IOInterface $io, $localConfig = null, $disablePlu // load auth configs into the IO instance $io->loadConfiguration($config); - // load existing Composer\InstalledVersions instance if available - if (!class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/InstalledVersions.php')) { + // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it + if (!$disablePlugins && !$disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/InstalledVersions.php')) { include $installedVersionsPath; } } From b1bd22f37c63e319f89a660dde631b8067c47448 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 26 Jan 2024 17:27:42 +0100 Subject: [PATCH 48/75] Fix type error --- src/Composer/Factory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index b198f0f14fd9..cb7ebc185ffc 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -351,7 +351,7 @@ public function createComposer(IOInterface $io, $localConfig = null, $disablePlu $io->loadConfiguration($config); // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it - if (!$disablePlugins && !$disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/InstalledVersions.php')) { + if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/InstalledVersions.php')) { include $installedVersionsPath; } } From 7048ff3808dd6576628099b53b5b664ec16cba63 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 26 Jan 2024 17:39:30 +0100 Subject: [PATCH 49/75] Fix automatic disabling of plugins when running non-interactive as root --- src/Composer/Command/BaseCommand.php | 9 +++++++++ src/Composer/Console/Application.php | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index a33f3a5b3845..20d046ee701c 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -223,6 +223,15 @@ protected function initialize(InputInterface $input, OutputInterface $output) // initialize a plugin-enabled Composer instance, either local or global $disablePlugins = $input->hasParameterOption('--no-plugins'); $disableScripts = $input->hasParameterOption('--no-scripts'); + + $application = parent::getApplication(); + if ($application instanceof Application && $application->getDisablePluginsByDefault()) { + $disablePlugins = true; + } + if ($application instanceof Application && $application->getDisableScriptsByDefault()) { + $disableScripts = true; + } + if ($this instanceof SelfUpdateCommand) { $disablePlugins = true; $disableScripts = true; diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index da77636d112d..265f4d7ce172 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -657,6 +657,16 @@ public function getInitialWorkingDirectory() return $this->initialWorkingDirectory; } + public function getDisablePluginsByDefault(): bool + { + return $this->disablePluginsByDefault; + } + + public function getDisableScriptsByDefault(): bool + { + return $this->disableScriptsByDefault; + } + /** * @return 'prompt'|bool */ From 8a69c0555bf38f20987a635827efc45eb5d42bf1 Mon Sep 17 00:00:00 2001 From: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> Date: Tue, 6 Feb 2024 12:57:34 +0100 Subject: [PATCH 50/75] Update plugin documentation (#11813) --- doc/articles/plugins.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 019ab5d450ea..6c729a4e0a37 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -271,6 +271,8 @@ class Command extends BaseCommand protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln('Executing'); + + return 0; } } ``` @@ -285,7 +287,7 @@ Plugins for an event can be run manually by the `run-script` command. This works [running scripts manually](scripts.md#running-scripts-manually). If it is another type of plugin the best way to test it is probably using a [path repository](../05-repositories.md#path) -to require the plugin in a test project, and then `rm -rf vendor && composer update` +to require the plugin in a test project. If you are developing locally and want to test frequently, you can make sure the path repository uses symlinks, as changes are updated immediately. Otherwise, you'll have to run `rm -rf vendor && composer update` every time you want to install/run it again. ## Using Plugins From 0f70c0a9c91b61c4b2ad7bb4f6e92ddf715bc273 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 6 Feb 2024 12:57:52 +0100 Subject: [PATCH 51/75] Add detection of constraints which do not match anything in validate command, fixes #11802 (#11829) --- .../Package/Loader/ValidatingArrayLoader.php | 7 +++++++ .../Package/Loader/ValidatingArrayLoaderTest.php | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index dc50a84d0771..9f25ed2197f2 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -17,6 +17,8 @@ use Composer\Semver\Constraint\Constraint; use Composer\Package\Version\VersionParser; use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\MatchNoneConstraint; +use Composer\Semver\Intervals; use Composer\Spdx\SpdxLicenses; /** @@ -290,6 +292,11 @@ public function load(array $config, string $class = 'Composer\Package\CompletePa ) { $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning'; } + + $compacted = Intervals::compactConstraint($linkConstraint); + if ($compacted instanceof MatchNoneConstraint) { + $this->warnings[] = $linkType.'.'.$package.' : this version constraint cannot possibly match anything ('.$constraint.')'; + } } if ($linkType === 'conflict' && isset($this->config['replace']) && $keys = array_intersect_key($this->config['replace'], $this->config['conflict'])) { diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 43a4749972d0..bcd029733633 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -480,6 +480,20 @@ public static function warningProvider(): array ], false, ], + [ + [ + 'name' => 'foo/bar', + 'require' => [ + 'foo/baz' => '>1, <0.5', + 'bar/baz' => 'dev-main, >0.5', + ], + ], + [ + 'require.foo/baz : this version constraint cannot possibly match anything (>1, <0.5)', + 'require.bar/baz : this version constraint cannot possibly match anything (dev-main, >0.5)', + ], + false, + ], [ [ 'name' => 'foo/bar', From bff129f4f57733298d97de6ac242b4b67c571eda Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 6 Feb 2024 13:27:55 +0100 Subject: [PATCH 52/75] Update require docs, fixes #11823 --- doc/03-cli.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 6e6809a49658..00734f605a2a 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -242,6 +242,9 @@ Specifying one of the words `mirrors`, `lock`, or `nothing` as an argument has t The `require` command adds new packages to the `composer.json` file from the current directory. If no file exists one will be created on the fly. +If you do not specify a package, Composer will prompt you to search for a package, and given +results, provide a list of matches to require. + ```shell php composer.phar require ``` @@ -256,7 +259,14 @@ to the command. php composer.phar require "vendor/package:2.*" vendor/package2:dev-master ``` -If you do not specify a package, Composer will prompt you to search for a package, and given results, provide a list of matches to require. +If you do not specify a version constraint, composer will choose a suitable one based +on the available package versions. + +```shell +php composer.phar require vendor/package vendor/package2 +``` + +If you do not want to install the new dependencies immediately you can call it with --no-update ### Options From ef6c224ec2191eaaaf094fc234c42f3c7f393d81 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 6 Feb 2024 13:46:46 +0100 Subject: [PATCH 53/75] Fix require command crashing at the end if no lock file is present, fixes #11814 --- src/Composer/Command/RequireCommand.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 4a8a47559f44..b7ee15681f55 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -558,13 +558,15 @@ private function updateRequirementsAfterResolution(array $requirementsToUpdate, throw new \RuntimeException('Unable to read '.$this->json->getPath().' contents to update the lock file hash.'); } $lockFile = Factory::getLockFile($this->json->getPath()); - $lockMtime = filemtime($lockFile); - $lock = new JsonFile($lockFile); - $lockData = $lock->read(); - $lockData['content-hash'] = Locker::getContentHash($contents); - $lock->write($lockData); - if (is_int($lockMtime)) { - @touch($lockFile, $lockMtime); + if (file_exists($lockFile)) { + $lockMtime = filemtime($lockFile); + $lock = new JsonFile($lockFile); + $lockData = $lock->read(); + $lockData['content-hash'] = Locker::getContentHash($contents); + $lock->write($lockData); + if (is_int($lockMtime)) { + @touch($lockFile, $lockMtime); + } } } } From ebb6a82099cc8b97765ecca627c5bc4ff8b0315b Mon Sep 17 00:00:00 2001 From: Derek Stephen McLean Date: Tue, 6 Feb 2024 16:53:18 +0100 Subject: [PATCH 54/75] issue #11811 auth token links on separate lines (#11812) * issue #11811 auth token links on separate lines * 11811 - remove stray bracket * 11811 : links on separte lines --- src/Composer/Util/Bitbucket.php | 3 ++- src/Composer/Util/GitHub.php | 6 ++++-- src/Composer/Util/GitLab.php | 14 +++++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Composer/Util/Bitbucket.php b/src/Composer/Util/Bitbucket.php index 1c9eeb65d7fc..2be4a815cb05 100644 --- a/src/Composer/Util/Bitbucket.php +++ b/src/Composer/Util/Bitbucket.php @@ -143,7 +143,8 @@ public function authorizeOAuthInteractively(string $originUrl, ?string $message $localAuthConfig = $this->config->getLocalAuthConfigSource(); $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/'; - $this->io->writeError(sprintf('Follow the instructions on %s', $url)); + $this->io->writeError('Follow the instructions here:'); + $this->io->writeError($url); $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)'); diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 43a635450c6a..3574e5183215 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -92,12 +92,14 @@ public function authorizeOAuthInteractively(string $originUrl, ?string $message $note .= ' ' . date('Y-m-d Hi'); $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=&description=' . str_replace('%20', '+', rawurlencode($note)); - $this->io->writeError(sprintf('When working with _public_ GitHub repositories only, head to %s to retrieve a token.', $url)); + $this->io->writeError('When working with _public_ GitHub repositories only, head here to retrieve a token:'); + $this->io->writeError($url); $this->io->writeError('This token will have read-only permission for public information only.'); $localAuthConfig = $this->config->getLocalAuthConfigSource(); $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note)); - $this->io->writeError(sprintf('When you need to access _private_ GitHub repositories as well, go to %s', $url)); + $this->io->writeError('When you need to access _private_ GitHub repositories as well, go to:'); + $this->io->writeError($url); $this->io->writeError('Note that such tokens have broad read/write permissions on your behalf, even if not needed by Composer.'); $this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth'); diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index 40b0fec00797..e5985c2db7fe 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -126,9 +126,16 @@ public function authorizeOAuthInteractively(string $scheme, string $originUrl, ? } $localAuthConfig = $this->config->getLocalAuthConfigSource(); + $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/profile/personal_access_tokens'; + $revokeLink = $scheme.'://'.$originUrl.'/-/profile/applications'; $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); - $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/profile/applications'); - $this->io->writeError('Alternatively you can setup an personal access token on '.$scheme.'://'.$originUrl.'/-/profile/personal_access_tokens and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.'); + $this->io->writeError('To revoke access to this token you can visit:'); + $this->io->writeError($revokeLink); + $this->io->writeError('Alternatively you can setup an personal access token on:'); + $this->io->writeError($personalAccessTokenLink); + $this->io->writeError('and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.'); + $this->io->writeError('https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token'); + $this->io->writeError('for more details.'); $storeInLocalAuthConfig = false; if ($localAuthConfig !== null) { @@ -155,7 +162,8 @@ public function authorizeOAuthInteractively(string $scheme, string $originUrl, ? $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); } - $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at '.$scheme.'://'.$originUrl.'/profile/personal_access_tokens'); + $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at:'); + $this->io->writeError($scheme.'://'.$originUrl.'/profile/personal_access_tokens'); $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' "'); continue; From e88c7a8987e9dcb5c614b6ca3c6bf4bcb309865c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 6 Feb 2024 16:59:01 +0100 Subject: [PATCH 55/75] Add support for wildcards in outdated's --ignore arg, fixes #11831 --- doc/03-cli.md | 4 ++-- src/Composer/Command/OutdatedCommand.php | 2 +- src/Composer/Command/ShowCommand.php | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 00734f605a2a..f72c2ba82211 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -562,7 +562,7 @@ php composer.phar show monolog/monolog 1.0.2 * **--tree (-t):** List your dependencies as a tree. If you pass a package name it will show the dependency tree for that package. * **--latest (-l):** List all installed packages including their latest version. * **--outdated (-o):** Implies --latest, but this lists *only* packages that have a newer version available. -* **--ignore:** Ignore specified package(s). Use it with the --outdated option if you don't want to be informed about new versions of some packages +* **--ignore:** Ignore specified package(s). Can contain wildcards (`*`). Use it with the --outdated option if you don't want to be informed about new versions of some packages * **--no-dev:** Filters dev dependencies from the package list. * **--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. @@ -597,7 +597,7 @@ The color coding is as such: * **--all (-a):** Show all packages, not just outdated (alias for `composer show --latest`). * **--direct (-D):** Restricts the list of packages to your direct dependencies. * **--strict:** Returns non-zero exit code if any package is outdated. -* **--ignore:** Ignore specified package(s). Use it if you don't want to be informed about new versions of some packages +* **--ignore:** Ignore specified package(s). Can contain wildcards (`*`). Use it if you don't want to be informed about new versions of some packages * **--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. diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php index b6c71c1753ad..139fe45a85ec 100644 --- a/src/Composer/Command/OutdatedCommand.php +++ b/src/Composer/Command/OutdatedCommand.php @@ -42,7 +42,7 @@ protected function configure(): void 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('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index fc5940b91587..542482554b6b 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -95,7 +95,7 @@ protected function configure() new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), - new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), 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.'), @@ -459,7 +459,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $showMajorOnly = $input->getOption('major-only'); $showMinorOnly = $input->getOption('minor-only'); $showPatchOnly = $input->getOption('patch-only'); - $ignoredPackages = array_map('strtolower', $input->getOption('ignore')); + $ignoredPackagesRegex = BasePackage::packageNamesToRegexp(array_map('strtolower', $input->getOption('ignore'))); $indent = $showAllTypes ? ' ' : ''; /** @var PackageInterface[] $latestPackages */ $latestPackages = []; @@ -520,7 +520,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()); // When using --major-only, and no bigger version than current major is found then it is considered up to date $packageIsUpToDate = $packageIsUpToDate || ($latestPackage === null && $showMajorOnly); - $packageIsIgnored = \in_array($package->getPrettyName(), $ignoredPackages, true); + $packageIsIgnored = Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName()); if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) { continue; } From 9a656854adf77fb984bb806986d493bba7f673fd Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 6 Feb 2024 16:18:41 +0000 Subject: [PATCH 56/75] ValidatingArrayLoader: fix link validation with missing name (#11830) --- src/Composer/Package/Loader/ValidatingArrayLoader.php | 2 +- .../Test/Package/Loader/ValidatingArrayLoaderTest.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 9f25ed2197f2..a94448a6159b 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -253,7 +253,7 @@ public function load(array $config, string $class = 'Composer\Package\CompletePa if ($this->validateArray($linkType) && isset($this->config[$linkType])) { foreach ($this->config[$linkType] as $package => $constraint) { $package = (string) $package; - if (0 === strcasecmp($package, $this->config['name'])) { + if (isset($this->config['name']) && 0 === strcasecmp($package, $this->config['name'])) { $this->errors[] = $linkType.'.'.$package.' : a package cannot set a '.$linkType.' on itself'; unset($this->config[$linkType][$package]); continue; diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index bcd029733633..40f05894e037 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -426,6 +426,12 @@ public static function errorProvider(): array ], ['replace.0 : invalid version constraint (Could not parse version constraint acme/bar: Invalid version string "acme/bar")'], ], + [ + [ + 'require' => ['acme/bar' => '^1.0'] + ], + ['name : must be present'], + ] ]); } From 7745d56c147f2abcee82a313f00e6b0a71d5ffa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Wer=C5=82os?= Date: Wed, 7 Feb 2024 09:32:55 +0100 Subject: [PATCH 57/75] Do not show error that plugins have been disabled when they are already disabled (#11803) --- src/Composer/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index b747ea021a2e..774370b5178a 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -292,7 +292,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int $this->hasPluginCommands = true; } - if ($isNonAllowedRoot && !$io->isInteractive()) { + if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) { $io->writeError('Composer plugins have been disabled for safety in this non-interactive session. Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.'); $this->disablePluginsByDefault = true; } From d0b465ffd0299aea2b8fd832c402cc8e1f2bee1b Mon Sep 17 00:00:00 2001 From: Antoine M Date: Wed, 7 Feb 2024 10:10:05 +0100 Subject: [PATCH 58/75] chore(doc): add `_comment` documentation inside `composer.json` schema (#11825) * Update 04-schema.md * example --- doc/04-schema.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/04-schema.md b/doc/04-schema.md index 6bd169cf9b54..e36ec21115b9 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -967,6 +967,23 @@ Defaults to false. Optional. +### _comment + +Top level key used as a place to store comments (it can be a string or array of strings). + +```json +{ + "_comment": [ + "The package foo/bar was required for business logic", + "Remove package foo/baz when removing foo/bar" + ] +} +``` + +Defaults to empty. + +Optional. + ### non-feature-branches A list of regex patterns of branch names that are non-numeric (e.g. "latest" or something), From 158df56cccb248a5a2c50a0f2ad7fdc526fb6925 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:10:42 +0100 Subject: [PATCH 59/75] Bump actions/cache from 3 to 4 (#11807) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/phpstan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index b6300137f5f1..a4c25379a3ba 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -48,7 +48,7 @@ jobs: run: "echo \"directory=$(composer config cache-dir)\" >> $GITHUB_OUTPUT" - name: "Cache dependencies installed with composer" - uses: "actions/cache@v3" + uses: "actions/cache@v4" with: path: "${{ steps.determine-composer-cache-directory.outputs.directory }}" key: "php-${{ matrix.php-version }}-symfony-php-unit-version-${{ env.SYMFONY_PHPUNIT_VERSION }}-${{ hashFiles('**/composer.lock') }}" From 654da6f576519bf4b254b7b13f7a7dd2cfbe9e30 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 7 Feb 2024 11:10:21 +0100 Subject: [PATCH 60/75] Update deps, fixes #11801 --- composer.lock | 199 ++++++++++++++++++++++---------------------------- 1 file changed, 89 insertions(+), 110 deletions(-) diff --git a/composer.lock b/composer.lock index bd2f57c9c6c4..559544311aec 100644 --- a/composer.lock +++ b/composer.lock @@ -938,16 +938,16 @@ }, { "name": "symfony/console", - "version": "v5.4.34", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "4b4d8cd118484aa604ec519062113dd87abde18c" + "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c", - "reference": "4b4d8cd118484aa604ec519062113dd87abde18c", + "url": "https://api.github.com/repos/symfony/console/zipball/dbdf6adcb88d5f83790e1efb57ef4074309d3931", + "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931", "shasum": "" }, "require": { @@ -1017,7 +1017,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.34" + "source": "https://github.com/symfony/console/tree/v5.4.35" }, "funding": [ { @@ -1033,7 +1033,7 @@ "type": "tidelift" } ], - "time": "2023-12-08T13:33:03+00:00" + "time": "2024-01-23T14:28:09+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1104,16 +1104,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.25", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364" + "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", - "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/5a553607d4ffbfa9c0ab62facadea296c9db7086", + "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086", "shasum": "" }, "require": { @@ -1148,7 +1148,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.25" + "source": "https://github.com/symfony/filesystem/tree/v5.4.35" }, "funding": [ { @@ -1164,20 +1164,20 @@ "type": "tidelift" } ], - "time": "2023-05-31T13:04:02+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/finder", - "version": "v5.4.27", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d" + "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ff4bce3c33451e7ec778070e45bd23f74214cd5d", - "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d", + "url": "https://api.github.com/repos/symfony/finder/zipball/abe6d6f77d9465fed3cd2d029b29d03b56b56435", + "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435", "shasum": "" }, "require": { @@ -1211,7 +1211,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.27" + "source": "https://github.com/symfony/finder/tree/v5.4.35" }, "funding": [ { @@ -1227,20 +1227,20 @@ "type": "tidelift" } ], - "time": "2023-07-31T08:02:31+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -1254,9 +1254,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1293,7 +1290,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -1309,20 +1306,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { @@ -1333,9 +1330,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1374,7 +1368,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { @@ -1390,20 +1384,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { @@ -1414,9 +1408,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1458,7 +1449,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -1474,20 +1465,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -1501,9 +1492,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1541,7 +1529,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -1557,20 +1545,20 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", "shasum": "" }, "require": { @@ -1578,9 +1566,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1620,7 +1605,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" }, "funding": [ { @@ -1636,20 +1621,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -1657,9 +1642,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1703,7 +1685,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -1719,20 +1701,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", "shasum": "" }, "require": { @@ -1740,9 +1722,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1782,7 +1761,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" }, "funding": [ { @@ -1798,20 +1777,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v5.4.34", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a" + "reference": "cbc28e34015ad50166fc2f9c8962d28d0fe861eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8fa22178dfc368911dbd513b431cd9b06f9afe7a", - "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a", + "url": "https://api.github.com/repos/symfony/process/zipball/cbc28e34015ad50166fc2f9c8962d28d0fe861eb", + "reference": "cbc28e34015ad50166fc2f9c8962d28d0fe861eb", "shasum": "" }, "require": { @@ -1844,7 +1823,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.34" + "source": "https://github.com/symfony/process/tree/v5.4.35" }, "funding": [ { @@ -1860,7 +1839,7 @@ "type": "tidelift" } ], - "time": "2023-12-02T08:41:43+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/service-contracts", @@ -1947,16 +1926,16 @@ }, { "name": "symfony/string", - "version": "v5.4.34", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "e3f98bfc7885c957488f443df82d97814a3ce061" + "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061", - "reference": "e3f98bfc7885c957488f443df82d97814a3ce061", + "url": "https://api.github.com/repos/symfony/string/zipball/c209c4d0559acce1c9a2067612cfb5d35756edc2", + "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2", "shasum": "" }, "require": { @@ -2013,7 +1992,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.34" + "source": "https://github.com/symfony/string/tree/v5.4.35" }, "funding": [ { @@ -2029,22 +2008,22 @@ "type": "tidelift" } ], - "time": "2023-12-09T13:20:28+00:00" + "time": "2024-01-23T13:51:25+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.55", + "version": "1.10.57", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949" + "reference": "1627b1d03446904aaa77593f370c5201d2ecc34e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9a88f9d18ddf4cf54c922fbeac16c4cb164c5949", - "reference": "9a88f9d18ddf4cf54c922fbeac16c4cb164c5949", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1627b1d03446904aaa77593f370c5201d2ecc34e", + "reference": "1627b1d03446904aaa77593f370c5201d2ecc34e", "shasum": "" }, "require": { @@ -2093,7 +2072,7 @@ "type": "tidelift" } ], - "time": "2024-01-08T12:32:40+00:00" + "time": "2024-01-24T11:51:34+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2246,16 +2225,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.6", + "version": "1.3.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "34b3c43684834f6a20aa51af8d455480d9de8b88" + "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/34b3c43684834f6a20aa51af8d455480d9de8b88", - "reference": "34b3c43684834f6a20aa51af8d455480d9de8b88", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/ef7db637be9b85fa00278fc3477ac66abe8eb7d1", + "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1", "shasum": "" }, "require": { @@ -2312,22 +2291,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.6" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.7" }, - "time": "2023-12-22T11:22:34+00:00" + "time": "2024-01-10T21:54:42+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.0.2", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "92df075808c9437beca9540e25ae0c40eea1c061" + "reference": "0a2eeb0d9e68bf01660e3e903f8113508bb46132" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/92df075808c9437beca9540e25ae0c40eea1c061", - "reference": "92df075808c9437beca9540e25ae0c40eea1c061", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0a2eeb0d9e68bf01660e3e903f8113508bb46132", + "reference": "0a2eeb0d9e68bf01660e3e903f8113508bb46132", "shasum": "" }, "require": { @@ -2379,7 +2358,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.2" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.3" }, "funding": [ { @@ -2395,7 +2374,7 @@ "type": "tidelift" } ], - "time": "2023-12-19T11:23:03+00:00" + "time": "2024-01-23T15:02:46+00:00" } ], "aliases": [], From fd233813918fba2e17c3cb9826a3fd0e729e6a16 Mon Sep 17 00:00:00 2001 From: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:11:16 +0100 Subject: [PATCH 61/75] Add arguments to command call output (#11826) --- src/Composer/EventDispatcher/EventDispatcher.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 3326b8e31792..acc20beda9c1 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -194,6 +194,7 @@ protected function doDispatch(Event $event) $return = 0; $this->ensureBinDirIsInPath(); + $formattedEventNameWithArgs = $event->getName() . ($event->getArguments() !== [] ? ' (' . implode(', ', $event->getArguments()) . ')' : ''); if (!is_string($callable)) { if (!is_callable($callable)) { $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; @@ -201,11 +202,11 @@ protected function doDispatch(Event $event) throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); } if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) { - $this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE); + $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE); } $return = false === $callable($event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { - $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE); + $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), true, IOInterface::VERBOSE); $script = explode(' ', substr($callable, 1)); $scriptName = $script[0]; From fa040131b0ad4ede430cdf5dd9e68ef53cd82c54 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 7 Feb 2024 11:17:54 +0100 Subject: [PATCH 62/75] Add more details to event debug output, refs #11818 --- src/Composer/EventDispatcher/EventDispatcher.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index acc20beda9c1..b91b4e8edb7f 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -20,6 +20,8 @@ use Composer\Composer; use Composer\PartialComposer; use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PreCommandRunEvent; use Composer\Util\Platform; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Repository\RepositoryInterface; @@ -180,6 +182,10 @@ protected function doDispatch(Event $event) $details = null; if ($event instanceof PackageEvent) { $details = (string) $event->getOperation(); + } elseif ($event instanceof CommandEvent) { + $details = $event->getCommandName(); + } elseif ($event instanceof PreCommandRunEvent) { + $details = $event->getCommand(); } $this->io->writeError('Dispatching '.$event->getName().''.($details ? ' ('.$details.')' : '').' event'); } From 0c99bfc8fd2724696cb54560daac1fe7787a4729 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 7 Feb 2024 11:37:50 +0100 Subject: [PATCH 63/75] Fix root aliases causing problems when auditing locked dependencies, fixes #11771 --- src/Composer/Repository/RepositorySet.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 9940d0850041..48cf424a4f8a 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -30,6 +30,7 @@ use Composer\Semver\Constraint\ConstraintInterface; use Composer\Package\Version\StabilityFilter; use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; /** * @author Nils Adermann @@ -245,7 +246,15 @@ public function getMatchingSecurityAdvisories(array $packages, bool $allowPartia { $map = []; foreach ($packages as $package) { - $map[$package->getName()] = new Constraint('=', $package->getVersion()); + // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + continue; + } + if (isset($map[$package->getName()])) { + $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], false); + } else { + $map[$package->getName()] = new Constraint('=', $package->getVersion()); + } } return $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories); From 338bc16a115d866b108b38399562b06c988cd93f Mon Sep 17 00:00:00 2001 From: theoboldalex <44616505+theoboldalex@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:40:29 +0000 Subject: [PATCH 64/75] test: Covers audit of pkg with no sec advisories (#11789) --- tests/Composer/Test/Command/AuditCommandTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Composer/Test/Command/AuditCommandTest.php b/tests/Composer/Test/Command/AuditCommandTest.php index 53041f33d5f1..6bf751fabcec 100644 --- a/tests/Composer/Test/Command/AuditCommandTest.php +++ b/tests/Composer/Test/Command/AuditCommandTest.php @@ -41,4 +41,20 @@ public function testErrorAuditingLockFileWhenItIsMissing(): void $appTester = $this->getApplicationTester(); $appTester->run(['command' => 'audit', '--locked' => true]); } + + public function testAuditPackageWithNoSecurityVulnerabilities(): void + { + $this->initTempComposer(); + $packages = [self::getPackage()]; + $this->createInstalledJson($packages); + $this->createComposerLock($packages); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'audit', '--locked' => true]); + + self::assertStringContainsString( + 'No security vulnerability advisories found.', + trim($appTester->getDisplay(true)) + ); + } } From 18cd8a01a4ff8ab2b6586f6c467962553cf3f9c7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 7 Feb 2024 14:06:15 +0100 Subject: [PATCH 65/75] Update jsonlint --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 559544311aec..04c822b48fea 100644 --- a/composer.lock +++ b/composer.lock @@ -765,16 +765,16 @@ }, { "name": "seld/jsonlint", - "version": "1.10.1", + "version": "1.10.2", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "76d449a358ece77d6f1d6331c68453e657172202" + "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/76d449a358ece77d6f1d6331c68453e657172202", - "reference": "76d449a358ece77d6f1d6331c68453e657172202", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", "shasum": "" }, "require": { @@ -813,7 +813,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.1" + "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" }, "funding": [ { @@ -825,7 +825,7 @@ "type": "tidelift" } ], - "time": "2023-12-18T13:03:25+00:00" + "time": "2024-02-07T12:57:50+00:00" }, { "name": "seld/phar-utils", From e0807d381ebc90f1d1570e7751700374d3dcbfc7 Mon Sep 17 00:00:00 2001 From: Ayesh Karunaratne Date: Thu, 8 Feb 2024 03:30:24 +0700 Subject: [PATCH 66/75] Diagnose command: Add GitHub OAuth token expiration date information (#11688) GitHub's new fine-grained tokens have a cumpulsory expiration date, and their classic tokens also support an expiration date. https://github.blog/changelog/2021-07-26-expiration-options-for-personal-access-tokens/ This improves the `composer diagnose` command to display the expiration date and time if it is provided by the response headers (via `GitHub-Authentication-Token-Expiration`). --- src/Composer/Command/DiagnoseCommand.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 18b1911d75bd..fc594f7d94f7 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -326,7 +326,7 @@ private function checkHttpProxy() } /** - * @return string|true|\Exception + * @return string|\Exception */ private function checkGithubOauth(string $domain, string $token) { @@ -339,11 +339,17 @@ private function checkGithubOauth(string $domain, string $token) try { $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; - $this->httpDownloader->get($url, [ + $response = $this->httpDownloader->get($url, [ 'retry-auth-failure' => false, ]); - return true; + $expiration = $response->getHeader('github-authentication-token-expiration'); + + if ($expiration === null) { + return 'OK does not expire'; + } + + return 'OK expires on '. $expiration .''; } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { return 'The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it'; From 7cb92a90c8ce1fc8816078bb82f9caa180d082fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dezs=C5=91=20BICZ=C3=93?= Date: Wed, 7 Feb 2024 21:13:36 +0000 Subject: [PATCH 67/75] Introduce COMPOSER_AUDIT_ABANDONED env var (#11794) Co-authored-by: Jordi Boggiano --- doc/03-cli.md | 5 ++++ doc/06-config.md | 12 ++++++++++ src/Composer/Advisory/Auditor.php | 1 + src/Composer/Config.php | 14 +++++++++++ tests/Composer/Test/Advisory/AuditorTest.php | 1 + tests/Composer/Test/ConfigTest.php | 25 ++++++++++++++++++++ 6 files changed, 58 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index f72c2ba82211..b03ad1f8c3fc 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -1247,6 +1247,11 @@ similar use case), and need to support proxies, please provide the `CGI_HTTP_PRO environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further details. +### COMPOSER_AUDIT_ABANDONED + +Set to `ignore`, `report` or `fail` to override the [audit.abandoned](06-config.md#abandoned) +config option. + ### COMPOSER_MAX_PARALLEL_HTTP Set to an integer to configure how many files can be downloaded in parallel. This diff --git a/doc/06-config.md b/doc/06-config.md index a39c2872b44f..c6aa47491db1 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -143,6 +143,18 @@ Defaults to `report` in Composer 2.6, and defaults to `fail` from Composer 2.7 o - `report` means abandoned packages are reported as an error but do not cause the command to exit with a non-zero code. - `fail` means abandoned packages will cause audits to fail with a non-zero code. +```json +{ + "config": { + "audit": { + "abandoned": "report" + } + } +} +``` + +Since Composer 2.7 the option can be overriden via the [`COMPOSER_AUDIT_ABANDONED`](03-cli.md#composer-audit-abandoned) environment variable. + ## use-parent-dir When running Composer in a directory where there is no composer.json, if there diff --git a/src/Composer/Advisory/Auditor.php b/src/Composer/Advisory/Auditor.php index bc6520d55ffb..f0dc76ae5ad3 100644 --- a/src/Composer/Advisory/Auditor.php +++ b/src/Composer/Advisory/Auditor.php @@ -19,6 +19,7 @@ use Composer\Package\PackageInterface; use Composer\Repository\RepositorySet; use Composer\Util\PackageInfo; +use Composer\Util\Platform; use InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 9296467f472f..f9da7d30475e 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -436,6 +436,20 @@ public function get(string $key, int $flags = 0) return $this->process($this->config[$key], $flags); + case 'audit': + $result = $this->config[$key]; + $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED'); + if (false !== $abandonedEnv) { + if (!in_array($abandonedEnv, $validChoices = [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], true)) { + throw new \RuntimeException( + "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected ".Auditor::ABANDONED_IGNORE.", ".Auditor::ABANDONED_REPORT." or ".Auditor::ABANDONED_FAIL + ); + } + $result['abandoned'] = $abandonedEnv; + } + + return $result; + default: if (!isset($this->config[$key])) { return null; diff --git a/tests/Composer/Test/Advisory/AuditorTest.php b/tests/Composer/Test/Advisory/AuditorTest.php index 2253169f4934..748f6a5f8055 100644 --- a/tests/Composer/Test/Advisory/AuditorTest.php +++ b/tests/Composer/Test/Advisory/AuditorTest.php @@ -23,6 +23,7 @@ use Composer\Repository\RepositorySet; use Composer\Test\TestCase; use Composer\Advisory\Auditor; +use Composer\Util\Platform; use InvalidArgumentException; class AuditorTest extends TestCase diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index 428c4f26558d..8a169b745a3d 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -12,6 +12,7 @@ namespace Composer\Test; +use Composer\Advisory\Auditor; use Composer\Config; use Composer\IO\IOInterface; use Composer\Util\Platform; @@ -382,6 +383,30 @@ public function testGetSourceOfValueEnvVariables(): void $this->assertEquals('COMPOSER_HTACCESS_PROTECT', $result); } + public function testAudit(): void + { + $config = new Config(true); + $result = $config->get('audit'); + self::assertArrayHasKey('abandoned', $result); + self::assertArrayHasKey('ignore', $result); + self::assertSame(Auditor::ABANDONED_FAIL, $result['abandoned']); + self::assertSame([], $result['ignore']); + + Platform::putEnv('COMPOSER_AUDIT_ABANDONED', Auditor::ABANDONED_IGNORE); + $result = $config->get('audit'); + Platform::clearEnv('COMPOSER_AUDIT_ABANDONED'); + self::assertArrayHasKey('abandoned', $result); + self::assertArrayHasKey('ignore', $result); + self::assertSame(Auditor::ABANDONED_IGNORE, $result['abandoned']); + self::assertSame([], $result['ignore']); + + $config->merge(['config' => ['audit' => ['ignore' => ['A', 'B']]]]); + $config->merge(['config' => ['audit' => ['ignore' => ['A', 'C']]]]); + $result = $config->get('audit'); + self::assertArrayHasKey('ignore', $result); + self::assertSame(['A', 'B', 'A', 'C'], $result['ignore']); + } + public function testGetDefaultsToAnEmptyArray(): void { $config = new Config; From 754f2868fbfa8dac2a7542d9132523c9396c87a2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 7 Feb 2024 22:27:39 +0100 Subject: [PATCH 68/75] Add non-zero return codes when why-not finds a reason a package is not installable, or when why finds no reason it is there, fixes #11796 --- src/Composer/Command/BaseDependencyCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index e010c122a83c..2fb363979c10 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -133,6 +133,8 @@ protected function doExecute(InputInterface $input, OutputInterface $output, boo $renderTree = $input->getOption(self::OPTION_TREE); $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE); + $return = $inverted ? 1 : 0; + // Resolve dependencies $results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive); if (empty($results)) { @@ -142,6 +144,7 @@ protected function doExecute(InputInterface $input, OutputInterface $output, boo $needle, $extra )); + $return = $inverted ? 0 : 1; } elseif ($renderTree) { $this->initStyles($output); $root = $packages[0]; @@ -171,7 +174,7 @@ protected function doExecute(InputInterface $input, OutputInterface $output, boo $this->getIO()->writeError('Not finding what you were looking for? Try calling `composer '.$composerCommand.' "'.$needle.':'.$textConstraint.'" --dry-run` to get another view on the problem.'); } - return 0; + return $return; } /** From df8f9f05a310b3670fbd9d14eac4f57d2e9115b4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 7 Feb 2024 22:37:22 +0100 Subject: [PATCH 69/75] Update tests --- .../Command/BaseDependencyCommandTest.php | 55 +++++++++++-------- tests/Composer/Test/TestCase.php | 8 +++ 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/tests/Composer/Test/Command/BaseDependencyCommandTest.php b/tests/Composer/Test/Command/BaseDependencyCommandTest.php index 8b0230dfc571..4201caa3eaa3 100644 --- a/tests/Composer/Test/Command/BaseDependencyCommandTest.php +++ b/tests/Composer/Test/Command/BaseDependencyCommandTest.php @@ -228,7 +228,7 @@ public static function caseProvider(): Generator * * @param array $parameters */ - public function testWhyCommandOutputs(array $parameters, string $expectedOutput): void + public function testWhyCommandOutputs(array $parameters, string $expectedOutput, int $expectedStatusCode): void { $packageToBeInspected = $parameters['package']; $renderAsTree = $parameters['--tree'] ?? false; @@ -294,9 +294,9 @@ public function testWhyCommandOutputs(array $parameters, string $expectedOutput) '--locked' => true ]); - $appTester->assertCommandIsSuccessful(); + self::assertSame($expectedStatusCode, $appTester->getStatusCode()); - $this->assertEquals(trim($expectedOutput), trim($appTester->getDisplay(true))); + $this->assertEquals(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); } /** @@ -306,41 +306,46 @@ public static function caseWhyProvider(): Generator { yield 'there is no installed package depending on the package' => [ ['package' => 'vendor1/package1'], - 'There is no installed package depending on "vendor1/package1"' + 'There is no installed package depending on "vendor1/package1"', + 1 ]; yield 'a nested package dependency' => [ ['package' => 'vendor1/package3'], << [ ['package' => 'vendor1/package3', '--tree' => true], << [ ['package' => 'vendor1/package3', '--recursive' => true], << [ ['package' => 'vendor2/package1'], - '__root__ - requires (for development) vendor2/package1 (2.*)' + '__root__ - requires (for development) vendor2/package1 (2.*)', + 0 ]; } @@ -354,7 +359,7 @@ public static function caseWhyProvider(): Generator * * @param array $parameters */ - public function testWhyNotCommandOutputs(array $parameters, string $expectedOutput): void + public function testWhyNotCommandOutputs(array $parameters, string $expectedOutput, int $expectedStatusCode): void { $packageToBeInspected = $parameters['package']; $packageVersionToBeInspected = $parameters['version']; @@ -393,7 +398,7 @@ public function testWhyNotCommandOutputs(array $parameters, string $expectedOutp '1.4.*' ) ]); - $secondDevNestedRequiredPackage = self::getPackage('vendor2/package3', '1.4.0'); + $secondDevNestedRequiredPackage = self::getPackage('vendor2/package3', '1.4.0'); $this->createComposerLock( [$someRequiredPackage], @@ -411,8 +416,8 @@ public function testWhyNotCommandOutputs(array $parameters, string $expectedOutp 'version' => $packageVersionToBeInspected ]); - $appTester->assertCommandIsSuccessful(); - $this->assertSame(trim($expectedOutput), trim($appTester->getDisplay(true))); + self::assertSame($expectedStatusCode, $appTester->getStatusCode()); + $this->assertSame(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); } /** @@ -424,9 +429,10 @@ public function caseWhyNotProvider(): Generator ['package' => 'vendor1/package1', 'version' => '3.*'], << [ @@ -435,7 +441,8 @@ public function caseWhyNotProvider(): Generator Package "vendor1/package1" could not be found with constraint "^1.4", results below will most likely be incomplete. There is no installed package depending on "vendor1/package1" in versions not matching ^1.4 Not finding what you were looking for? Try calling `composer require "vendor1/package1:^1.4" --dry-run` to get another view on the problem. -OUTPUT +OUTPUT, + 0 ]; yield 'there is no installed package depending on the package in versions not matching a specific version' => [ @@ -443,15 +450,17 @@ public function caseWhyNotProvider(): Generator << [ ['package' => 'vendor2/package3', 'version' => '1.5.0'], << Date: Wed, 7 Feb 2024 22:44:22 +0100 Subject: [PATCH 70/75] Fix php7.2 --- .../Command/BaseDependencyCommandTest.php | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/Composer/Test/Command/BaseDependencyCommandTest.php b/tests/Composer/Test/Command/BaseDependencyCommandTest.php index 4201caa3eaa3..6442b7f70285 100644 --- a/tests/Composer/Test/Command/BaseDependencyCommandTest.php +++ b/tests/Composer/Test/Command/BaseDependencyCommandTest.php @@ -315,7 +315,8 @@ public static function caseWhyProvider(): Generator << Date: Thu, 8 Feb 2024 10:06:34 +0000 Subject: [PATCH 71/75] Adds a test for no dev (#11833) --- tests/Composer/Test/Command/AuditCommandTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Composer/Test/Command/AuditCommandTest.php b/tests/Composer/Test/Command/AuditCommandTest.php index 6bf751fabcec..4563e0fff1cd 100644 --- a/tests/Composer/Test/Command/AuditCommandTest.php +++ b/tests/Composer/Test/Command/AuditCommandTest.php @@ -57,4 +57,20 @@ public function testAuditPackageWithNoSecurityVulnerabilities(): void trim($appTester->getDisplay(true)) ); } + + public function testAuditPackageWithNoDevOptionPassed(): void + { + $this->initTempComposer(); + $devPackage = [self::getPackage()]; + $this->createInstalledJson([], $devPackage); + $this->createComposerLock([], $devPackage); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'audit', '--no-dev' => true]); + + self::assertStringContainsString( + 'No packages - skipping audit.', + trim($appTester->getDisplay(true)) + ); + } } From 7442981364656d7aa406f6cf10db7cc3d12e79c1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 8 Feb 2024 11:31:40 +0100 Subject: [PATCH 72/75] Add flag alias to docs --- doc/03-cli.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index b03ad1f8c3fc..d7d1fd136638 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -230,7 +230,7 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.* * **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal versions of requirements, generally used with `--prefer-stable`. Can also be set via the COMPOSER_PREFER_LOWEST=1 env var. -* **--minimal-changes:** During a partial update with `-w`/`-W`, only perform absolutely necessary +* **--minimal-changes (-m):** During a partial update with `-w`/`-W`, only perform absolutely necessary changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. * **--interactive:** Interactive interface with autocompletion to select the packages to update. * **--root-reqs:** Restricts the update to your first degree dependencies. @@ -301,7 +301,7 @@ If you do not want to install the new dependencies immediately you can call it w * **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal versions of requirements, generally used with `--prefer-stable`. Can also be set via the COMPOSER_PREFER_LOWEST=1 env var. -* **--minimal-changes:** During an update with `-w`/`-W`, only perform absolutely necessary +* **--minimal-changes (-m):** During an update with `-w`/`-W`, only perform absolutely necessary changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. * **--sort-packages:** Keep packages sorted in `composer.json`. * **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to @@ -341,7 +341,7 @@ uninstalled. (Deprecated, is now default behavior) * **--update-with-all-dependencies (-W):** Allows all inherited dependencies to be updated, including those that are root requirements. -* **--minimal-changes:** During an update with `-w`/`-W`, only perform absolutely necessary +* **--minimal-changes (-m):** During an update with `-w`/`-W`, only perform absolutely necessary changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var. * **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine does From 64e4eb356b159a30c766cd1ea83450a38dc23bf5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 8 Feb 2024 14:33:59 +0100 Subject: [PATCH 73/75] Merge pull request from GHSA-7c6p-848j-wh5h * Fix usage of possibly compromised installed.php/InstalledVersions.php at runtime, refs GHSA-7c6p-848j-wh5h * Fix InstalledVersionsTest regression --- src/Composer/Factory.php | 10 +- .../Repository/FilesystemRepository.php | 39 ++++++- tests/Composer/Test/InstalledVersionsTest.php | 4 +- .../Repository/FilesystemRepositoryTest.php | 43 +++++++- .../Test/Repository/Fixtures/installed.php | 56 ++++------ .../Repository/Fixtures/installed_complex.php | 26 +++++ .../Fixtures/installed_relative.php | 103 ++++++++++++++++++ 7 files changed, 237 insertions(+), 44 deletions(-) create mode 100644 tests/Composer/Test/Repository/Fixtures/installed_complex.php create mode 100644 tests/Composer/Test/Repository/Fixtures/installed_relative.php diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index cb7ebc185ffc..03a5fa2ae713 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -18,6 +18,7 @@ use Composer\Package\Archiver; use Composer\Package\Version\VersionGuesser; use Composer\Package\RootPackageInterface; +use Composer\Repository\FilesystemRepository; use Composer\Repository\RepositoryManager; use Composer\Repository\RepositoryFactory; use Composer\Util\Filesystem; @@ -351,8 +352,13 @@ public function createComposer(IOInterface $io, $localConfig = null, $disablePlu $io->loadConfiguration($config); // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it - if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/InstalledVersions.php')) { - include $installedVersionsPath; + // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once + if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/installed.php')) { + // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir + // as we cannot guarantee integrity of that file + if (class_exists('Composer\InstalledVersions')) { + FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath); + } } } diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index abe76950d738..ec37573f2f97 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -20,6 +20,7 @@ use Composer\Package\AliasPackage; use Composer\Package\Dumper\ArrayDumper; use Composer\Installer\InstallationManager; +use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Platform; @@ -173,6 +174,34 @@ public function write(bool $devMode, InstallationManager $installationManager) } } + /** + * As we load the file from vendor dir during bootstrap, we need to make sure it contains only expected code before executing it + * + * @internal + */ + public static function safelyLoadInstalledVersions(string $path): bool + { + $installedVersionsData = @file_get_contents($path); + $pattern = <<<'REGEX' +{(?(DEFINE) + (? -? \s*+ \d++ (?:\.\d++)? ) + (? true | false | null ) + (? (?&string) (?: \s*+ \. \s*+ (?&string))*+ ) + (? (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) ) + (? array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+ \s*+ \) ) + (? (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) ) +) +^<\?php\s++return\s++(?&array)\s*+;$}ix +REGEX; + if (is_string($installedVersionsData) && Preg::isMatch($pattern, trim($installedVersionsData))) { + \Composer\InstalledVersions::reload(eval('?>'.Preg::replace('{=>\s*+__DIR__\s*+\.\s*+([\'"])}', '=> '.var_export(dirname($path), true).' . $1', $installedVersionsData))); + + return true; + } + + return false; + } + /** * @param array $array */ @@ -183,7 +212,7 @@ private function dumpToPhpCode(array $array = [], int $level = 0): string foreach ($array as $key => $value) { $lines .= str_repeat(' ', $level); - $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => '; + $lines .= is_int($key) ? $key . ' => ' : var_export($key, true) . ' => '; if (is_array($value)) { if (!empty($value)) { @@ -197,8 +226,14 @@ private function dumpToPhpCode(array $array = [], int $level = 0): string } else { $lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n"; } - } else { + } elseif (is_string($value)) { $lines .= var_export($value, true) . ",\n"; + } elseif (is_bool($value)) { + $lines .= ($value ? 'true' : 'false') . ",\n"; + } elseif (is_null($value)) { + $lines .= "null,\n"; + } else { + throw new \UnexpectedValueException('Unexpected type '.gettype($value)); } } diff --git a/tests/Composer/Test/InstalledVersionsTest.php b/tests/Composer/Test/InstalledVersionsTest.php index c227675ca73b..3fa9b1bc1fc5 100644 --- a/tests/Composer/Test/InstalledVersionsTest.php +++ b/tests/Composer/Test/InstalledVersionsTest.php @@ -49,7 +49,7 @@ public function setUp(): void $this->root = self::getUniqueTmpDirectory(); $dir = $this->root; - InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed.php'); + InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed_relative.php'); } public function testGetInstalledPackages(): void @@ -222,7 +222,7 @@ public function testGetRootPackage(): void public function testGetRawData(): void { $dir = $this->root; - $this->assertSame(require __DIR__.'/Repository/Fixtures/installed.php', InstalledVersions::getRawData()); + $this->assertSame(require __DIR__.'/Repository/Fixtures/installed_relative.php', InstalledVersions::getRawData()); } /** diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index 6115dbd57007..e932ef4a85bc 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -158,6 +158,7 @@ public function testRepositoryWritesInstalledPhp(): void $repository->addPackage($pkg); $pkg = self::getPackage('c/c', '3.0'); + $pkg->setDistReference('{${passthru(\'bash -i\')}} Foo\\Bar' . "\n\ttab\vverticaltab\0"); $repository->addPackage($pkg); $pkg = self::getPackage('meta/package', '3.0'); @@ -177,7 +178,11 @@ public function testRepositoryWritesInstalledPhp(): void if ($package->getName() === 'c/c') { // check for absolute paths - return '/foo/bar/vendor/c/c'; + return '/foo/bar/ven\do{}r/c/c${}'; + } + + if ($package->getName() === 'a/provider') { + return 'vendor/{${passthru(\'bash -i\')}}'; } // check for cwd @@ -190,7 +195,41 @@ public function testRepositoryWritesInstalledPhp(): void })); $repository->write(true, $im); - $this->assertSame(require __DIR__.'/Fixtures/installed.php', require $dir.'/installed.php'); + $this->assertSame(file_get_contents(__DIR__.'/Fixtures/installed.php'), file_get_contents($dir.'/installed.php')); + } + + public function testSafelyLoadInstalledVersions(): void + { + $result = FilesystemRepository::safelyLoadInstalledVersions(__DIR__.'/Fixtures/installed_complex.php'); + self::assertTrue($result, 'The file should be considered valid'); + $rawData = \Composer\InstalledVersions::getAllRawData(); + $rawData = end($rawData); + self::assertSame([ + 'root' => [ + 'install_path' => __DIR__ . '/Fixtures/./', + 'aliases' => [ + 0 => '1.10.x-dev', + 1 => '2.10.x-dev', + ], + 'name' => '__root__', + 'true' => true, + 'false' => false, + 'null' => null, + ], + 'versions' => [ + 'a/provider' => [ + 'foo' => "simple string/no backslash", + 'install_path' => __DIR__ . '/Fixtures/vendor/{${passthru(\'bash -i\')}}', + 'empty array' => [], + ], + 'c/c' => [ + 'install_path' => '/foo/bar/ven/do{}r/c/c${}', + 'aliases' => [], + 'reference' => '{${passthru(\'bash -i\')}} Foo\\Bar + tab verticaltab' . "\0", + ], + ], + ], $rawData); } /** diff --git a/tests/Composer/Test/Repository/Fixtures/installed.php b/tests/Composer/Test/Repository/Fixtures/installed.php index cd918997cd53..dbdda5e3c67b 100644 --- a/tests/Composer/Test/Repository/Fixtures/installed.php +++ b/tests/Composer/Test/Repository/Fixtures/installed.php @@ -1,26 +1,13 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -return array( + array( 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', 'reference' => 'sourceref-by-default', 'type' => 'library', - // @phpstan-ignore-next-line - 'install_path' => $dir . '/./', + 'install_path' => __DIR__ . '/./', 'aliases' => array( - '1.10.x-dev', + 0 => '1.10.x-dev', ), 'dev' => true, ), @@ -30,10 +17,9 @@ 'version' => 'dev-master', 'reference' => 'sourceref-by-default', 'type' => 'library', - // @phpstan-ignore-next-line - 'install_path' => $dir . '/./', + 'install_path' => __DIR__ . '/./', 'aliases' => array( - '1.10.x-dev', + 0 => '1.10.x-dev', ), 'dev_requirement' => false, ), @@ -42,8 +28,7 @@ 'version' => '1.1.0.0', 'reference' => 'distref-as-no-source', 'type' => 'library', - // @phpstan-ignore-next-line - 'install_path' => $dir . '/vendor/a/provider', + 'install_path' => __DIR__ . '/vendor/{${passthru(\'bash -i\')}}', 'aliases' => array(), 'dev_requirement' => false, ), @@ -52,10 +37,9 @@ 'version' => '1.2.0.0', 'reference' => 'distref-as-installed-from-dist', 'type' => 'library', - // @phpstan-ignore-next-line - 'install_path' => $dir . '/vendor/a/provider2', + 'install_path' => __DIR__ . '/vendor/a/provider2', 'aliases' => array( - '1.4', + 0 => '1.4', ), 'dev_requirement' => false, ), @@ -64,42 +48,42 @@ 'version' => '2.2.0.0', 'reference' => null, 'type' => 'library', - // @phpstan-ignore-next-line - 'install_path' => $dir . '/vendor/b/replacer', + 'install_path' => __DIR__ . '/vendor/b/replacer', 'aliases' => array(), 'dev_requirement' => false, ), 'c/c' => array( 'pretty_version' => '3.0', 'version' => '3.0.0.0', - 'reference' => null, + 'reference' => '{${passthru(\'bash -i\')}} Foo\\Bar + tab verticaltab' . "\0" . '', 'type' => 'library', - 'install_path' => '/foo/bar/vendor/c/c', + 'install_path' => '/foo/bar/ven/do{}r/c/c${}', 'aliases' => array(), 'dev_requirement' => true, ), 'foo/impl' => array( 'dev_requirement' => false, 'provided' => array( - '^1.1', - '1.2', - '1.4', - '2.0', + 0 => '^1.1', + 1 => '1.2', + 2 => '1.4', + 3 => '2.0', ), ), 'foo/impl2' => array( 'dev_requirement' => false, 'provided' => array( - '2.0', + 0 => '2.0', ), 'replaced' => array( - '2.2', + 0 => '2.2', ), ), 'foo/replaced' => array( 'dev_requirement' => false, 'replaced' => array( - '^3.0', + 0 => '^3.0', ), ), 'meta/package' => array( @@ -110,6 +94,6 @@ 'install_path' => null, 'aliases' => array(), 'dev_requirement' => false, - ) + ), ), ); diff --git a/tests/Composer/Test/Repository/Fixtures/installed_complex.php b/tests/Composer/Test/Repository/Fixtures/installed_complex.php new file mode 100644 index 000000000000..1fd9d50063b4 --- /dev/null +++ b/tests/Composer/Test/Repository/Fixtures/installed_complex.php @@ -0,0 +1,26 @@ + array( + 'install_path' => __DIR__ . '/./', + 'aliases' => array( + 0 => '1.10.x-dev', + 1 => '2.10.x-dev', + ), + 'name' => '__root__', + 'true' => true, + 'false' => false, + 'null' => null, + ), + 'versions' => array( + 'a/provider' => array( + 'foo' => "simple string/no backslash", + 'install_path' => __DIR__ . '/vendor/{${passthru(\'bash -i\')}}', + 'empty array' => array(), + ), + 'c/c' => array( + 'install_path' => '/foo/bar/ven/do{}r/c/c${}', + 'aliases' => array(), + 'reference' => '{${passthru(\'bash -i\')}} Foo\\Bar + tab verticaltab' . "\0" . '', + ), + ), +); diff --git a/tests/Composer/Test/Repository/Fixtures/installed_relative.php b/tests/Composer/Test/Repository/Fixtures/installed_relative.php new file mode 100644 index 000000000000..443e460c124e --- /dev/null +++ b/tests/Composer/Test/Repository/Fixtures/installed_relative.php @@ -0,0 +1,103 @@ + array( + 'name' => '__root__', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => 'sourceref-by-default', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . '/./', + 'aliases' => array( + '1.10.x-dev', + ), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => 'sourceref-by-default', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . '/./', + 'aliases' => array( + '1.10.x-dev', + ), + 'dev_requirement' => false, + ), + 'a/provider' => array( + 'pretty_version' => '1.1', + 'version' => '1.1.0.0', + 'reference' => 'distref-as-no-source', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . '/vendor/a/provider', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'a/provider2' => array( + 'pretty_version' => '1.2', + 'version' => '1.2.0.0', + 'reference' => 'distref-as-installed-from-dist', + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . '/vendor/a/provider2', + 'aliases' => array( + '1.4', + ), + 'dev_requirement' => false, + ), + 'b/replacer' => array( + 'pretty_version' => '2.2', + 'version' => '2.2.0.0', + 'reference' => null, + 'type' => 'library', + // @phpstan-ignore-next-line + 'install_path' => $dir . '/vendor/b/replacer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'c/c' => array( + 'pretty_version' => '3.0', + 'version' => '3.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => '/foo/bar/vendor/c/c', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'foo/impl' => array( + 'dev_requirement' => false, + 'provided' => array( + '^1.1', + '1.2', + '1.4', + '2.0', + ), + ), + 'foo/impl2' => array( + 'dev_requirement' => false, + 'provided' => array( + '2.0', + ), + 'replaced' => array( + '2.2', + ), + ), + 'foo/replaced' => array( + 'dev_requirement' => false, + 'replaced' => array( + '^3.0', + ), + ), + 'meta/package' => array( + 'pretty_version' => '3.0', + 'version' => '3.0.0.0', + 'reference' => null, + 'type' => 'metapackage', + 'install_path' => null, + 'aliases' => array(), + 'dev_requirement' => false, + ) + ), +); From eea73daeacfaac96877bc8df354913fc8958be02 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 8 Feb 2024 14:34:27 +0100 Subject: [PATCH 74/75] Update changelog --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819b3f822cb1..13e177cbc66b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +### [2.7.0] 2024-02-08 + + * Security: Fixed code execution and possible privilege escalation via compromised vendor dir contents (GHSA-7c6p-848j-wh5h / CVE-2024-24821) + * Changed the default of the `audit.abandoned` config setting to `fail`, set it to `report` or `ignore` if you do not want this, or set it via `COMPOSER_AUDIT_ABANDONED` env var (#11643) + * Added --minimal-changes (-m) flag to `update`/`require`/`remove` commands to perform partial update with --with-dependencies while changing only what is absolutely necessary in transitive dependencies (#11665) + * Added --sort-by-age (-A) flag to `outdated`/`show` commands to allow sorting by and displaying the release date (most outdated first) (#11762) + * Added support for `--self` combined with `--installed` or `--locked` in `show` command, to add the root package to the package list being output (#11785) + * Added severity information to `audit` command output (#11702) + * Added `scripts-aliases` top level key in composer.json to define aliases for custom scripts you defined (#11666) + * Added IPv4 fallback on connection timeout, as well as a `COMPOSER_IPRESOLVE` env var to force IPv4 or IPv6, set it to `4` or `6` (#11791) + * Added support for wildcards in `outdated`'s --ignore arg (#11831) + * Added support for `bump` command bumping `*` to `>=current version` (#11694) + * Added detection of constraints that cannot possibly match anything to `validate` command (#11829) + * Added package source information to the output of `install` when running in very verbose (-vv) mode (#11763) + * Added audit of Composer's own bundled dependencies in `diagnose` command (#11761) + * Added GitHub token expiration date to `diagnose` command output (#11688) + * Added non-zero status code to why/why-not commands (#11796) + * Added error when calling `show --direct ` with an indirect/transitive dependency (#11728) + * Added `COMPOSER_FUND=0` env var to hide calls for funding (#11779) + * Fixed `bump` command not bumping packages required with a `v` prefix (#11764) + * Fixed automatic disabling of plugins when running non-interactive as root + * Fixed `update --lock` not keeping the dist reference/url/checksum pinned (#11787) + * Fixed `require` command crashing at the end if no lock file is present (#11814) + * Fixed root aliases causing problems when auditing locked dependencies (#11771) + * Fixed handling of versions with 4 components in `require` command (#11716) + * Fixed compatibility issues with Symfony 7 + * Fixed composer.json remaining behind after a --dry-run of the `require` command (#11747) + * Fixed warnings being shown incorrectly under some circumstances (#11786, #11760, #11803) + ### [2.6.6] 2023-12-08 * Fixed symfony/console requirement to exclude 7.x as Composer 2.6 is not compatible, 2.7 will be (#11741) @@ -1795,6 +1824,7 @@ * Initial release +[2.7.0]: https://github.com/composer/composer/compare/2.6.6...2.7.0 [2.6.6]: https://github.com/composer/composer/compare/2.6.5...2.6.6 [2.6.5]: https://github.com/composer/composer/compare/2.6.4...2.6.5 [2.6.4]: https://github.com/composer/composer/compare/2.6.3...2.6.4 From 96d107e2bfe61bb9eafe55a9d45bd7faed1dd461 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 8 Feb 2024 15:09:19 +0100 Subject: [PATCH 75/75] Release 2.7.0 --- src/Composer/Composer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 4bf1679f6ff6..2f989af5f121 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -51,10 +51,10 @@ class Composer extends PartialComposer * * @see getVersion() */ - public const VERSION = '@package_version@'; - public const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; - public const RELEASE_DATE = '@release_date@'; - public const SOURCE_VERSION = '2.7.999-dev+source'; + public const VERSION = '2.7.0'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2024-02-08 15:09:18'; + public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package