Repository: php/pie (php)

Issue:
PIE 0.1.0: Could not find release by tag name for mongodb/mongodb-extension:1.20.0
When trying out PIE 0.1.0, I figured out that the `@todo` above [this line](https://github.com/php/pie/blob/6d83fec3c1af1ddd44557acacf81dfcb310fb72a/src/Downloading/GithubPackageReleaseAssets.php#L80) actually applies. 😛 

The issue was initially reported on the `mongodb/mongo-php-driver` repo: https://github.com/mongodb/mongo-php-driver/issues/1716


```
php .\pie.phar install mongodb/mongodb-extension -vvv

Box Requirements Checker
========================

> Using PHP 8.3.12
> PHP is using the following php.ini file:
  C:\Apache24\php\php.ini

> Checking Box requirements:
  ✔ This application requires a PHP version matching "8.1.*||8.2.*||8.3.*||8.4.*".
  ✔ The package "guzzlehttp/guzzle" requires the extension "json".
  ✔ The package "composer/ca-bundle" requires the extension "openssl".
  ✔ The package "composer/ca-bundle" requires the extension "pcre".
  ✔ This application requires the extension "zip".
  ✔ The package "symfony/service-contracts" conflicts with the extension "psr".


 [OK] Your system is ready to run the application.


Loading config file C:/Users/mv/AppData/Roaming/Composer/config.json
Loading config file C:/Users/mv/AppData/Roaming/Composer/auth.json
Checked CA file C:\Apache24\php\extras\ssl\curl-ca-bundle.crt: valid
Executing command (C:\): git branch -a --no-color --no-abbrev -v
Executing command (C:\): git describe --exact-match --tags
Executing command (CWD): git --version
Executing command (C:\): git log --pretty="%H" -n1 HEAD --no-show-signature
Executing command (C:\): hg branch
Executing command (C:\): fossil branch list
Executing command (C:\): fossil tag list
Executing command (C:\): svn info --xml
Reading C:/Users/mv/AppData/Roaming/Composer/composer.json (C:\Users\mv\AppData\Roaming\Composer\composer.json)
Loading config file C:/Users/mv/AppData/Roaming/Composer/config.json
Loading config file C:/Users/mv/AppData/Roaming/Composer/auth.json
Loading config file C:/Users/mv/AppData/Roaming/Composer/composer.json (C:\Users\mv\AppData\Roaming\Composer\composer.json)
Loading config file C:\Users\mv\AppData\Roaming\Composer/auth.json
Reading C:\Users\mv\AppData\Roaming\Composer/auth.json (C:\Users\mv\AppData\Roaming\Composer\auth.json)
This command may need elevated privileges, and may prompt you for your password.
You are running PHP 8.3.12
Target PHP installation: 8.3.12 ts, vs16, on Windows x86_64 (from C:\Apache24\php\php.exe)
Downloading https://repo.packagist.org/packages.json
[200] https://repo.packagist.org/packages.json
Writing C:/Users/mv/AppData/Local/Composer/repo/https---repo.packagist.org/packages.json into cache
Downloading https://repo.packagist.org/p2/mongodb/mongodb-extension.json
[200] https://repo.packagist.org/p2/mongodb/mongodb-extension.json
Writing C:/Users/mv/AppData/Local/Composer/repo/https---repo.packagist.org/provider-mongodb~mongodb-extension.json into cache
Found package: mongodb/mongodb-extension:1.20.0 which provides ext-mongodb

In CouldNotFindReleaseAsset.php line 28:

  [Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset]
  Could not find release by tag name for mongodb/mongodb-extension:1.20.0


Exception trace:
  at phar://C:/pie.phar/src/Downloading/Exception/CouldNotFindReleaseAsset.php:28
 Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset::forPackageWithMissingTag() at phar://C:/pie.phar/src/Downloading/GithubPackageReleaseAssets.php:100
 Php\Pie\Downloading\GithubPackageReleaseAssets->getReleaseAssetsForPackage() at phar://C:/pie.phar/src/Downloading/GithubPackageReleaseAssets.php:43
 Php\Pie\Downloading\GithubPackageReleaseAssets->findWindowsDownloadUrlForPackage() at phar://C:/pie.phar/src/Downloading/WindowsDownloadAndExtract.php:26
 Php\Pie\Downloading\WindowsDownloadAndExtract->__invoke() at phar://C:/pie.phar/src/Command/CommandHelper.php:183
 Php\Pie\Command\CommandHelper::downloadPackage() at phar://C:/pie.phar/src/Command/InstallCommand.php:52
 Php\Pie\Command\InstallCommand->execute() at phar://C:/pie.phar/vendor/symfony/console/Command/Command.php:326
 Symfony\Component\Console\Command\Command->run() at phar://C:/pie.phar/vendor/symfony/console/Application.php:1078
 Symfony\Component\Console\Application->doRunCommand() at phar://C:/pie.phar/vendor/symfony/console/Application.php:324
 Symfony\Component\Console\Application->doRun() at phar://C:/pie.phar/vendor/symfony/console/Application.php:175
 Symfony\Component\Console\Application->run() at phar://C:/pie.phar/bin/pie:63
 require() at C:\pie.phar:15

install [--with-php-config [WITH-PHP-CONFIG]] [--with-php-path [WITH-PHP-PATH]] [--] <requested-package-and-version>
```

It would also be nice to see where it is attempting to download the package from when running the `install` command with the `-vvv` flag.

Pull request description:
Fixed getReleaseAssetsForPackage() for repositories with names that do not match the package name.
Fixes #68

I'm not sure if this is the correct approach, but this was made in an attempt to further verify the installation of the `mongodb/mongodb-extension` on Windows.

Patch:
diff --git a/src/DependencyResolver/Package.php b/src/DependencyResolver/Package.php
index 58d9038..f9531a1 100644
--- a/src/DependencyResolver/Package.php
+++ b/src/DependencyResolver/Package.php
@@ -11,6 +11,12 @@ use Php\Pie\ExtensionType;
 
 use function array_key_exists;
 use function array_map;
+use function array_slice;
+use function explode;
+use function implode;
+use function parse_url;
+use function str_contains;
+use function str_starts_with;
 
 /**
  * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
@@ -71,4 +77,23 @@ final class Package
     {
         return $this->name . ':' . $this->version;
     }
+
+    public function githubOrgAndRepository(): string
+    {
+        if ($this->downloadUrl === null || str_contains($this->downloadUrl, '/' . $this->name . '/')) {
+            return $this->name;
+        }
+
+        if (! str_starts_with($this->downloadUrl, 'https://api.github.com/repos/')) {
+            return $this->name;
+        }
+
+        $parsed = parse_url($this->downloadUrl);
+        if ($parsed === false || ! array_key_exists('path', $parsed)) {
+            return $this->name;
+        }
+
+        // Converts https://api.github.com/repos/<user>/<repository>/zipball/<sha>" to "<user>/<repository>"
+        return implode('/', array_slice(explode('/', $parsed['path']), 2, 2));
+    }
 }
diff --git a/src/Downloading/GithubPackageReleaseAssets.php b/src/Downloading/GithubPackageReleaseAssets.php
index eaf1d1d..06bb10d 100644
--- a/src/Downloading/GithubPackageReleaseAssets.php
+++ b/src/Downloading/GithubPackageReleaseAssets.php
@@ -76,9 +76,8 @@ final class GithubPackageReleaseAssets implements PackageReleaseAssets
     /** @return list<array{name: non-empty-string, browser_download_url: non-empty-string, ...}> */
     private function getReleaseAssetsForPackage(Package $package): array
     {
-        // @todo confirm prettyName will always match the repo name - it might not
         $request = AddAuthenticationHeader::withAuthHeaderFromComposer(
-            new Request('GET', $this->githubApiBaseUrl . '/repos/' . $package->name . '/releases/tags/' . $package->version),
+            new Request('GET', $this->githubApiBaseUrl . '/repos/' . $package->githubOrgAndRepository() . '/releases/tags/' . $package->version),
             $package,
             $this->authHelper,
         );


Test patch:
diff --git a/test/unit/DependencyResolver/PackageTest.php b/test/unit/DependencyResolver/PackageTest.php
index 1454286..d529d06 100644
--- a/test/unit/DependencyResolver/PackageTest.php
+++ b/test/unit/DependencyResolver/PackageTest.php
@@ -6,7 +6,10 @@ namespace Php\PieUnitTest\DependencyResolver;
 
 use Composer\Package\CompletePackage;
 use Php\Pie\DependencyResolver\Package;
+use Php\Pie\ExtensionName;
+use Php\Pie\ExtensionType;
 use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
 #[CoversClass(Package::class)]
@@ -38,4 +41,40 @@ final class PackageTest extends TestCase
         self::assertSame('vendor/foo:1.2.3', $package->prettyNameAndVersion());
         self::assertNull($package->downloadUrl);
     }
+
+    /**
+     * @return array<string, array{0: string, 1: string|null, 2: string}>
+     *
+     * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131
+     */
+    public function githubOrgAndRepoFromPackage(): array
+    {
+        return [
+            'noDownloadUrl' => ['foo/bar', null, 'foo/bar'],
+            'gitlabMatchingPackage' => ['foo/bar', 'https://gitlab.com/api/v4/projects/foo%2Fbar/repository/archive.zip?sha=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'foo/bar'],
+            'gitlabDifferentPackage' => ['foo/bar', 'https://gitlab.com/api/v4/projects/abc%2Fdef/repository/archive.zip?sha=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'foo/bar'],
+            'githubMatchingPackage' => ['foo/bar', 'https://api.github.com/repos/foo/bar/zipball/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'foo/bar'],
+            'githubDifferentPackage' => ['foo/bar', 'https://api.github.com/repos/abc/def/zipball/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'abc/def'],
+            'githubIncompleteUrl' => ['foo/bar', 'https://api.github.com/', 'foo/bar'],
+        ];
+    }
+
+    #[DataProvider('githubOrgAndRepoFromPackage')]
+    public function testGithubOrgAndRepo(string $composerPackageName, string|null $downloadUrl, string $expectedGithubOrgAndRepo): void
+    {
+        $package = new Package(
+            ExtensionType::PhpModule,
+            ExtensionName::normaliseFromString('foo'),
+            $composerPackageName,
+            '1.2.3',
+            $downloadUrl,
+            [],
+            null,
+            '1.2.3.0',
+            true,
+            true,
+        );
+
+        self::assertSame($expectedGithubOrgAndRepo, $package->githubOrgAndRepository());
+    }
 }