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" 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. diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index dd32e6537..c7497d390 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); diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 4cd51dc4b..872670f95 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; @@ -514,6 +515,10 @@ class Application extends BaseApplication if ($required) { throw $e; } + } catch (RuntimeException $e) { + if ($required) { + throw $e; + } } } 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); 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/'],