From e746f71e95ffb8de0dc4e3c677ba53baf699989b Mon Sep 17 00:00:00 2001 From: Juliette <663378+jrfnl@users.noreply.github.com> Date: Tue, 25 Oct 2022 11:54:48 +0200 Subject: [PATCH 1/7] GH Actions: fix use of deprecated `set-output` (#11126) GitHub has deprecated the use of `set-output` (and `set-state`) in favour of new environment files. This commit updates workflows to use the new methodology. Refs: * https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ * https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files Co-authored-by: jrfnl --- .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 b5015adba..a260e3fc7 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -45,7 +45,7 @@ jobs: - name: "Determine composer cache directory" id: "determine-composer-cache-directory" - run: "echo \"::set-output name=directory::$(composer config cache-dir)\"" + run: "echo \"directory=$(composer config cache-dir)\" >> $GITHUB_OUTPUT" - name: "Cache dependencies installed with composer" uses: "actions/cache@v3" From 86db6ffdae569a94539fa547ccb8dac93002bcd8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Oct 2022 11:34:15 +0200 Subject: [PATCH 2/7] Retry cache writes if they fail, refs #11076 --- src/Composer/Cache.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 8a5ab1a71..4e1fa7429 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -137,6 +137,8 @@ class Cache */ public function write(string $file, string $contents) { + $wasEnabled = $this->enabled === true; + if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); @@ -146,6 +148,14 @@ class Cache try { return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file); } catch (\ErrorException $e) { + // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to + // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076 + if ($wasEnabled) { + clearstatcache(); + $this->enabled = null; + return $this->write($file, $contents); + } + $this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG); if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { // Remove partial file. From 8d3a3042339cff6b62280c4bfdb5bc3dd6294979 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Oct 2022 14:43:53 +0200 Subject: [PATCH 3/7] Fix outdated command outputting some of the legend to stdout --- src/Composer/Command/ShowCommand.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 47ef25de9..d81869ab8 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -600,19 +600,19 @@ EOT } } - $io->write(''); - $io->write('Direct dependencies required in composer.json:'); + $io->writeError(''); + $io->writeError('Direct dependencies required in composer.json:'); if (\count($directDeps) > 0) { $this->printPackages($io, $directDeps, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength); } else { - $io->write('Everything up to date'); + $io->writeError('Everything up to date'); } - $io->write(''); - $io->write('Transitive dependencies not required in composer.json:'); + $io->writeError(''); + $io->writeError('Transitive dependencies not required in composer.json:'); if (\count($transitiveDeps) > 0) { $this->printPackages($io, $transitiveDeps, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength); } else { - $io->write('Everything up to date'); + $io->writeError('Everything up to date'); } } else { $this->printPackages($io, $packages, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength); From e5b8f2d838a71ea6f3e98245de4c93539b66bc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Wer=C5=82os?= Date: Mon, 19 Sep 2022 08:45:49 +0200 Subject: [PATCH 4/7] Add "--dry-run" to bump command (#11047) --- doc/03-cli.md | 1 + src/Composer/Command/BumpCommand.php | 28 +++++++--- .../Composer/Test/Command/BumpCommandTest.php | 52 ++++++++++++++++++- 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index e871d055b..bf372f839 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -356,6 +356,7 @@ are local to the library and do not affect consumers of the package. * **--dev-only:** Only bump requirements in "require-dev". * **--no-dev-only:** Only bump requirements in "require". +* **--dry-run:** Outputs the packages to bump, but will not execute anything. ## reinstall diff --git a/src/Composer/Command/BumpCommand.php b/src/Composer/Command/BumpCommand.php index b36908983..99fe9eaba 100644 --- a/src/Composer/Command/BumpCommand.php +++ b/src/Composer/Command/BumpCommand.php @@ -47,6 +47,7 @@ final class BumpCommand extends BaseCommand new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()), new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'), new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.'), ]) ->setHelp( <<getOption('no-dev-only')) { - $tasks['require-dev'] = $composer->getPackage()->getDevRequires(); - } if (!$input->getOption('dev-only')) { $tasks['require'] = $composer->getPackage()->getRequires(); } + if (!$input->getOption('no-dev-only')) { + $tasks['require-dev'] = $composer->getPackage()->getDevRequires(); + } $packagesFilter = $input->getArgument('packages'); if (count($packagesFilter) > 0) { @@ -170,7 +171,9 @@ EOT } } - if (!$this->updateFileCleanly($composerJson, $updates)) { + $dryRun = $input->getOption('dry-run'); + + if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) { $composerDefinition = $composerJson->read(); foreach ($updates as $key => $packages) { foreach ($packages as $package => $version) { @@ -182,12 +185,21 @@ EOT $changeCount = array_sum(array_map('count', $updates)); if ($changeCount > 0) { - $io->write(''.$composerJsonPath.' has been updated ('.$changeCount.' changes).'); + if ($dryRun) { + $io->write('' . $composerJsonPath . ' would be updated with:'); + foreach ($updates as $requireType => $packages) { + foreach ($packages as $package => $version) { + $io->write(sprintf(' - %s.%s: %s', $requireType, $package, $version)); + } + } + } else { + $io->write('' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).'); + } } else { $io->write('No requirements to update in '.$composerJsonPath.'.'); } - if ($composer->getLocker()->isLocked() && $changeCount > 0) { + if (!$dryRun && $composer->getLocker()->isLocked() && $changeCount > 0) { $contents = file_get_contents($composerJson->getPath()); if (false === $contents) { throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.'); @@ -198,6 +210,10 @@ EOT $lock->write($lockData); } + if ($dryRun && $changeCount > 0) { + return self::ERROR_GENERIC; + } + return 0; } diff --git a/tests/Composer/Test/Command/BumpCommandTest.php b/tests/Composer/Test/Command/BumpCommandTest.php index cf0dc3723..2953c8971 100644 --- a/tests/Composer/Test/Command/BumpCommandTest.php +++ b/tests/Composer/Test/Command/BumpCommandTest.php @@ -23,7 +23,7 @@ class BumpCommandTest extends TestCase * @param array $command * @param array $expected */ - public function testBump(array $composerJson, array $command, array $expected, bool $lock = true): void + public function testBump(array $composerJson, array $command, array $expected, bool $lock = true, int $exitCode = 0): void { $this->initTempComposer($composerJson); @@ -41,7 +41,7 @@ class BumpCommandTest extends TestCase } $appTester = $this->getApplicationTester(); - $appTester->run(array_merge(['command' => 'bump'], $command)); + $this->assertSame($exitCode, $appTester->run(array_merge(['command' => 'bump'], $command))); $json = new JsonFile('./composer.json'); $this->assertSame($expected, $json->read()); @@ -153,5 +153,53 @@ class BumpCommandTest extends TestCase ], false, ]; + + yield 'bump with --dry-run with packages to bump' => [ + [ + 'require' => [ + 'first/pkg' => '^2.0', + 'second/pkg' => '3.*', + ], + 'require-dev' => [ + 'dev/pkg' => '~2.0', + ], + ], + ['--dry-run' => true], + [ + 'require' => [ + 'first/pkg' => '^2.0', + 'second/pkg' => '3.*', + ], + 'require-dev' => [ + 'dev/pkg' => '~2.0', + ], + ], + true, + 1, + ]; + + yield 'bump with --dry-run without packages to bump' => [ + [ + 'require' => [ + 'first/pkg' => '^2.3.4', + 'second/pkg' => '^3.4', + ], + 'require-dev' => [ + 'dev/pkg' => '^2.3.4.5', + ], + ], + ['--dry-run' => true], + [ + 'require' => [ + 'first/pkg' => '^2.3.4', + 'second/pkg' => '^3.4', + ], + 'require-dev' => [ + 'dev/pkg' => '^2.3.4.5', + ], + ], + true, + 0, + ]; } } From 803e4e5dbda1c84c0937c87993848e078680343e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Oct 2022 15:04:08 +0200 Subject: [PATCH 5/7] Catch runtime exception while initializing Composer to make sure a missing composer.json does not fail >tryComposer, refs #11133 --- src/Composer/Console/Application.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 635fb04d3..743c07235 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -17,6 +17,7 @@ use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\Silencer; use LogicException; +use RuntimeException; use Seld\Signal\SignalHandler; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Exception\CommandNotFoundException; @@ -512,6 +513,10 @@ class Application extends BaseApplication if ($required) { throw $e; } + } catch (RuntimeException $e) { + if ($required) { + throw $e; + } } } From 855473148af3e8e7eabcad659644f0c99267ef76 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Oct 2022 15:05:35 +0200 Subject: [PATCH 6/7] Fix regression in loading Composer on SMB/network shares, refs #8231 #11077 --- src/Composer/Json/JsonFile.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 206ee662e..d4f851f8a 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -13,6 +13,7 @@ namespace Composer\Json; use Composer\Pcre\Preg; +use Composer\Util\Filesystem; use JsonSchema\Validator; use Seld\JsonLint\JsonParser; use Seld\JsonLint\ParsingException; @@ -93,7 +94,7 @@ class JsonFile if ($this->httpDownloader) { $json = $this->httpDownloader->get($this->path)->getBody(); } else { - if (!is_readable($this->path)) { + if (!Filesystem::isReadable($this->path)) { throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); } if ($this->io && $this->io->isDebug()) { @@ -193,7 +194,7 @@ class JsonFile */ public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null): bool { - if (!is_readable($this->path)) { + if (!Filesystem::isReadable($this->path)) { throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); } $content = file_get_contents($this->path); From 90673e4f66fed79ec4d9ccf0247333b01dc3b444 Mon Sep 17 00:00:00 2001 From: Ayesh Karunaratne Date: Thu, 20 Oct 2022 16:28:51 +0530 Subject: [PATCH 7/7] Update URL masking patterns for new GitHub fine-grained PATs Updates GitHub Personal Access Token regex pattern to detect new [fine-grained PATs](https://github.blog/changelog/2022-10-18-introducing-fine-grained-personal-access-tokens/) --- src/Composer/Util/Url.php | 2 +- tests/Composer/Test/Util/UrlTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index 2372bbfc3..040d17db0 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -111,7 +111,7 @@ class Url $url = Preg::replaceCallback('{^(?P[a-z0-9]+://)?(?P[^:/\s@]+):(?P[^@\s/]+)@}i', static function ($m): string { // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that - if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}', $m['user'])) { + if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}', $m['user'])) { return $m['prefix'].'***:***@'; } diff --git a/tests/Composer/Test/Util/UrlTest.php b/tests/Composer/Test/Util/UrlTest.php index 7b1b4bc3a..267b1c1bd 100644 --- a/tests/Composer/Test/Util/UrlTest.php +++ b/tests/Composer/Test/Util/UrlTest.php @@ -82,6 +82,7 @@ class UrlTest extends TestCase ['https://example.org/foo/bar?access_token=***', 'https://example.org/foo/bar?access_token=abcdef'], ['https://example.org/foo/bar?foo=bar&access_token=***', 'https://example.org/foo/bar?foo=bar&access_token=abcdef'], ['https://***:***@github.com/acme/repo', 'https://ghp_1234567890abcdefghijklmnopqrstuvwxyzAB:x-oauth-basic@github.com/acme/repo'], + ['https://***:***@github.com/acme/repo', 'https://github_pat_1234567890abcdefghijkl_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW:x-oauth-basic@github.com/acme/repo'], // without scheme ['foo:***@example.org/', 'foo:bar@example.org/'], ['foo@example.org/', 'foo@example.org/'],