From e98ebd28b7315beddc48d710a874c8955d48bcc7 Mon Sep 17 00:00:00 2001 From: Dmitry Balabka Date: Fri, 28 Aug 2026 18:53:24 +0300 Subject: [PATCH] Fix private GitHub release asset downloads --- .../OverrideDownloadUrlInstallListener.php | 28 ++++++++++++++--- .../GithubPackageReleaseAssets.php | 22 ++++++++------ src/Downloading/PackageReleaseAssets.php | 6 ++-- src/Downloading/ReleaseAsset.php | 21 +++++++++++++ .../GithubPackageReleaseAssetsTest.php | 11 +++++-- ...OverrideDownloadUrlInstallListenerTest.php | 30 ++++++++++++------- .../GithubPackageReleaseAssetsTest.php | 24 ++++++++------- 7 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 src/Downloading/ReleaseAsset.php diff --git a/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php b/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php index 46272f47..1bd4511f 100644 --- a/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php +++ b/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php @@ -134,7 +134,7 @@ function (OperationInterface $operation): void { $packageReleaseAssets = $this->container->get(PackageReleaseAssets::class); try { - $url = $packageReleaseAssets->findMatchingReleaseAssetUrl( + $releaseAsset = $packageReleaseAssets->findMatchingReleaseAsset( $targetPlatform, $piePackage, new HttpDownloader($this->io, $this->composer->getConfig()), @@ -147,8 +147,28 @@ function (OperationInterface $operation): void { continue; } - $this->composerRequest->pieOutput->write('Found prebuilt archive: ' . $url); - $composerPackage->setDistUrl($url); + $this->composerRequest->pieOutput->write('Found prebuilt archive: ' . $releaseAsset->url); + $composerPackage->setDistUrl($releaseAsset->url); + + if ($releaseAsset->headers !== []) { + $transportOptions = $composerPackage->getTransportOptions(); + $httpOptions = $transportOptions['http'] ?? []; + if (! is_array($httpOptions)) { + $httpOptions = []; + } + + $headers = $httpOptions['header'] ?? []; + if (! is_array($headers)) { + $headers = []; + } + + $httpOptions['header'] = [ + ...$headers, + ...$releaseAsset->headers, + ]; + $transportOptions['http'] = $httpOptions; + $composerPackage->setTransportOptions($transportOptions); + } // Composer's dist-sha was computed against the original // Packagist URL; once we swap to a release-asset URL the @@ -159,7 +179,7 @@ function (OperationInterface $operation): void { 'Note: dist-sha integrity check is not available for prebuilt-binary URLs; HTTPS to the release-asset origin is the only integrity guarantee.', ); - if (pathinfo($url, PATHINFO_EXTENSION) === 'tgz') { + if (pathinfo($releaseAsset->name, PATHINFO_EXTENSION) === 'tgz') { $composerPackage->setDistType('tar'); } diff --git a/src/Downloading/GithubPackageReleaseAssets.php b/src/Downloading/GithubPackageReleaseAssets.php index ad1bd13a..047513bc 100644 --- a/src/Downloading/GithubPackageReleaseAssets.php +++ b/src/Downloading/GithubPackageReleaseAssets.php @@ -25,15 +25,15 @@ public function __construct( /** * @param non-empty-list $possibleReleaseAssetNames * - * @return non-empty-string + * @return ReleaseAsset */ - public function findMatchingReleaseAssetUrl( + public function findMatchingReleaseAsset( TargetPlatform $targetPlatform, Package $package, HttpDownloader $httpDownloader, DownloadUrlMethod $downloadUrlMethod, array $possibleReleaseAssetNames, - ): string { + ): ReleaseAsset { $releaseAsset = $this->selectMatchingReleaseAsset( $targetPlatform, $package, @@ -42,16 +42,20 @@ public function findMatchingReleaseAssetUrl( $possibleReleaseAssetNames, ); - return $releaseAsset['browser_download_url']; + return new ReleaseAsset( + $releaseAsset['url'], + $releaseAsset['name'], + ['Accept: application/octet-stream'], + ); } /** @link https://github.com/squizlabs/PHP_CodeSniffer/issues/3734 */ // phpcs:disable Squiz.Commenting.FunctionComment.MissingParamName /** - * @param list $releaseAssets + * @param list $releaseAssets * @param non-empty-list $possibleReleaseAssetNames * - * @return array{name: non-empty-string, browser_download_url: non-empty-string, ...} + * @return array{name: non-empty-string, url: non-empty-string, ...} */ // phpcs:enable private function selectMatchingReleaseAsset( @@ -70,7 +74,7 @@ private function selectMatchingReleaseAsset( throw Exception\CouldNotFindReleaseAsset::forPackage($targetPlatform, $package, $downloadUrlMethod, $possibleReleaseAssetNames); } - /** @return list */ + /** @return list */ private function getReleaseAssetsForPackage( Package $package, HttpDownloader $httpDownloader, @@ -106,8 +110,8 @@ private function getReleaseAssetsForPackage( static function (array $asset): array { Assert::keyExists($asset, 'name'); Assert::stringNotEmpty($asset['name']); - Assert::keyExists($asset, 'browser_download_url'); - Assert::stringNotEmpty($asset['browser_download_url']); + Assert::keyExists($asset, 'url'); + Assert::stringNotEmpty($asset['url']); return $asset; }, diff --git a/src/Downloading/PackageReleaseAssets.php b/src/Downloading/PackageReleaseAssets.php index 622bebdb..225fafef 100644 --- a/src/Downloading/PackageReleaseAssets.php +++ b/src/Downloading/PackageReleaseAssets.php @@ -14,13 +14,13 @@ interface PackageReleaseAssets /** * @param non-empty-list $possibleReleaseAssetNames * - * @return non-empty-string + * @return ReleaseAsset */ - public function findMatchingReleaseAssetUrl( + public function findMatchingReleaseAsset( TargetPlatform $targetPlatform, Package $package, HttpDownloader $httpDownloader, DownloadUrlMethod $downloadUrlMethod, array $possibleReleaseAssetNames, - ): string; + ): ReleaseAsset; } diff --git a/src/Downloading/ReleaseAsset.php b/src/Downloading/ReleaseAsset.php new file mode 100644 index 00000000..3253b63c --- /dev/null +++ b/src/Downloading/ReleaseAsset.php @@ -0,0 +1,21 @@ + $headers + */ + public function __construct( + public readonly string $url, + public readonly string $name, + public readonly array $headers = [], + ) { + } +} diff --git a/test/integration/Downloading/GithubPackageReleaseAssetsTest.php b/test/integration/Downloading/GithubPackageReleaseAssetsTest.php index 2d04ec80..e194979e 100644 --- a/test/integration/Downloading/GithubPackageReleaseAssetsTest.php +++ b/test/integration/Downloading/GithubPackageReleaseAssetsTest.php @@ -11,6 +11,7 @@ use Php\Pie\DependencyResolver\Package; use Php\Pie\Downloading\DownloadUrlMethod; use Php\Pie\Downloading\GithubPackageReleaseAssets; +use Php\Pie\Downloading\ReleaseAsset; use Php\Pie\ExtensionName; use Php\Pie\ExtensionType; use Php\Pie\Platform\Architecture; @@ -60,10 +61,14 @@ public function testDeterminingReleaseAssetUrlForWindows(): void $config = Factory::createConfig(); $io->loadConfiguration($config); - self::assertSame( - 'https://github.com/asgrim/example-pie-extension/releases/download/2.0.2/php_example_pie_extension-2.0.2-8.3-ts-vs16-x86_64.zip', + self::assertEquals( + new ReleaseAsset( + 'https://api.github.com/repos/asgrim/example-pie-extension/releases/assets/197867674', + 'php_example_pie_extension-2.0.2-8.3-ts-vs16-x86_64.zip', + ['Accept: application/octet-stream'], + ), (new GithubPackageReleaseAssets('https://api.github.com')) - ->findMatchingReleaseAssetUrl( + ->findMatchingReleaseAsset( $targetPlatform, $package, new HttpDownloader($io, $config), diff --git a/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php b/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php index f7ce589a..a3dcd268 100644 --- a/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php +++ b/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php @@ -22,6 +22,7 @@ use Php\Pie\Downloading\DownloadUrlMethod; use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset; use Php\Pie\Downloading\PackageReleaseAssets; +use Php\Pie\Downloading\ReleaseAsset; use Php\Pie\Platform\Architecture; use Php\Pie\Platform\OperatingSystem; use Php\Pie\Platform\OperatingSystemFamily; @@ -283,8 +284,8 @@ public function testDistUrlIsUpdatedForWindowsInstallers(): void $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class); $packageReleaseAssets ->expects(self::once()) - ->method('findMatchingReleaseAssetUrl') - ->willReturn('https://example.com/windows-download-url'); + ->method('findMatchingReleaseAsset') + ->willReturn(new ReleaseAsset('https://example.com/windows-download-url', 'windows-download-url.zip', ['Accept: application/octet-stream'])); $this->container ->method('get') @@ -319,6 +320,15 @@ public function testDistUrlIsUpdatedForWindowsInstallers(): void $composerPackage->getDistUrl(), ); self::assertSame(DownloadUrlMethod::WindowsBinaryDownload, DownloadUrlMethod::fromComposerPackage($composerPackage)); + $transportOptions = $composerPackage->getTransportOptions(); + self::assertArrayHasKey('http', $transportOptions); + self::assertIsArray($transportOptions['http']); + $httpOptions = $transportOptions['http']; + self::assertArrayHasKey('header', $httpOptions); + self::assertSame( + ['Accept: application/octet-stream'], + $httpOptions['header'], + ); } public function testDistUrlIsUpdatedForPrePackagedTgzSource(): void @@ -343,8 +353,8 @@ public function testDistUrlIsUpdatedForPrePackagedTgzSource(): void $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class); $packageReleaseAssets ->expects(self::once()) - ->method('findMatchingReleaseAssetUrl') - ->willReturn('https://example.com/pre-packaged-source-download-url.tgz'); + ->method('findMatchingReleaseAsset') + ->willReturn(new ReleaseAsset('https://example.com/pre-packaged-source-download-url.tgz', 'pre-packaged-source-download-url.tgz')); $this->container ->method('get') @@ -404,8 +414,8 @@ public function testDistUrlIsUpdatedForPrePackagedTgzBinaryWhenBinaryIsFound(): $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class); $packageReleaseAssets ->expects(self::once()) - ->method('findMatchingReleaseAssetUrl') - ->willReturn('https://example.com/pre-packaged-binary-download-url.tgz'); + ->method('findMatchingReleaseAsset') + ->willReturn(new ReleaseAsset('https://example.com/pre-packaged-binary-download-url.tgz', 'pre-packaged-binary-download-url.tgz')); $this->container ->method('get') @@ -465,7 +475,7 @@ public function testDistUrlIsUpdatedForPrePackagedTgzBinaryWhenBinaryIsNotFound( $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class); $packageReleaseAssets ->expects(self::once()) - ->method('findMatchingReleaseAssetUrl') + ->method('findMatchingReleaseAsset') ->willThrowException(new CouldNotFindReleaseAsset('nope not found')); $this->container @@ -583,8 +593,8 @@ public function testDistUrlIsUpdatedForWindowsInstallersOnUpdateOperations(): vo $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class); $packageReleaseAssets ->expects(self::once()) - ->method('findMatchingReleaseAssetUrl') - ->willReturn('https://example.com/windows-download-url'); + ->method('findMatchingReleaseAsset') + ->willReturn(new ReleaseAsset('https://example.com/windows-download-url', 'windows-download-url.zip')); $this->container ->method('get') @@ -643,7 +653,7 @@ public function testNoSelectedDownloadUrlMethodWillThrowException(): void $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class); $packageReleaseAssets ->expects(self::once()) - ->method('findMatchingReleaseAssetUrl') + ->method('findMatchingReleaseAsset') ->willThrowException(new CouldNotFindReleaseAsset('nope not found')); $this->container diff --git a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php index e36347b5..745f02b8 100644 --- a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php +++ b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php @@ -12,6 +12,7 @@ use Php\Pie\Downloading\DownloadUrlMethod; use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset; use Php\Pie\Downloading\GithubPackageReleaseAssets; +use Php\Pie\Downloading\ReleaseAsset; use Php\Pie\ExtensionName; use Php\Pie\ExtensionType; use Php\Pie\Platform\Architecture; @@ -28,6 +29,7 @@ use function uniqid; #[CoversClass(GithubPackageReleaseAssets::class)] +#[CoversClass(ReleaseAsset::class)] final class GithubPackageReleaseAssetsTest extends TestCase { public function testUrlIsReturnedWhenFindingWindowsDownloadUrl(): void @@ -56,11 +58,11 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrl(): void 'assets' => [ [ 'name' => 'php_foo-1.2.3-8.3-vc14-nts-x86.zip', - 'browser_download_url' => 'wrong_download_url', + 'url' => 'wrong_download_url', ], [ 'name' => 'php_foo-1.2.3-8.3-vc14-ts-x86.zip', - 'browser_download_url' => 'actual_download_url', + 'url' => 'actual_download_url', ], ], ]); @@ -82,9 +84,9 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrl(): void $releaseAssets = new GithubPackageReleaseAssets('https://test-github-api-base-url.thephp.foundation'); - self::assertSame( - 'actual_download_url', - $releaseAssets->findMatchingReleaseAssetUrl( + self::assertEquals( + new ReleaseAsset('actual_download_url', 'php_foo-1.2.3-8.3-vc14-ts-x86.zip', ['Accept: application/octet-stream']), + $releaseAssets->findMatchingReleaseAsset( $targetPlatform, $package, $httpDownloader, @@ -123,11 +125,11 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrlWithCompilerAndThr 'assets' => [ [ 'name' => 'php_foo-1.2.3-8.3-nts-vc14-x86.zip', - 'browser_download_url' => 'wrong_download_url', + 'url' => 'wrong_download_url', ], [ 'name' => 'php_foo-1.2.3-8.3-ts-vc14-x86.zip', - 'browser_download_url' => 'actual_download_url', + 'url' => 'actual_download_url', ], ], ]); @@ -149,9 +151,9 @@ public function testUrlIsReturnedWhenFindingWindowsDownloadUrlWithCompilerAndThr $releaseAssets = new GithubPackageReleaseAssets('https://test-github-api-base-url.thephp.foundation'); - self::assertSame( - 'actual_download_url', - $releaseAssets->findMatchingReleaseAssetUrl( + self::assertEquals( + new ReleaseAsset('actual_download_url', 'php_foo-1.2.3-8.3-ts-vc14-x86.zip', ['Accept: application/octet-stream']), + $releaseAssets->findMatchingReleaseAsset( $targetPlatform, $package, $httpDownloader, @@ -198,7 +200,7 @@ public function testFindWindowsDownloadUrlForPackageThrowsExceptionWhenAssetNotF $releaseAssets = new GithubPackageReleaseAssets('https://test-github-api-base-url.thephp.foundation'); $this->expectException(CouldNotFindReleaseAsset::class); - $releaseAssets->findMatchingReleaseAssetUrl( + $releaseAssets->findMatchingReleaseAsset( $targetPlatform, $package, $httpDownloader,