diff --git a/library/Tiger/Module/Github.php b/library/Tiger/Module/Github.php index 45ce057..68233b8 100644 --- a/library/Tiger/Module/Github.php +++ b/library/Tiger/Module/Github.php @@ -118,7 +118,15 @@ public static function latestRef($org, $repo) } /** - * GitHub's codeload tarball URL for a ref (redirects; download() follows). + * The GitHub API tarball endpoint for a ref — a 302 to a signed codeload URL (download() follows). + * + * We use the API endpoint (`api.github.com/repos/{org}/{repo}/tarball/{ref}`), NOT the web archive + * path (`github.com/{org}/{repo}/archive/{ref}.tar.gz`): the web path 404s for a PRIVATE repo even + * with a valid bearer token, whereas the API endpoint honours the token and 302s to a signed codeload + * URL (which then needs no auth — curl drops the Authorization header on the cross-host redirect). The + * API endpoint works for public repos too, so this one URL serves both the public directory and an + * authenticated private/company source. (The licensed/authority path mints its own signed URL and does + * not come through here.) * * @param string $org the repo owner * @param string $repo the repo name @@ -127,7 +135,7 @@ public static function latestRef($org, $repo) */ public static function tarballUrl($org, $repo, $ref) { - return "https://github.com/{$org}/{$repo}/archive/" . rawurlencode((string) $ref) . '.tar.gz'; + return self::API . "/repos/{$org}/{$repo}/tarball/" . rawurlencode((string) $ref); } /** diff --git a/tests/Unit/Module/GithubParseTest.php b/tests/Unit/Module/GithubParseTest.php index 363701e..0dfe174 100644 --- a/tests/Unit/Module/GithubParseTest.php +++ b/tests/Unit/Module/GithubParseTest.php @@ -11,7 +11,7 @@ /** * Tiger_Module_Github — the pure, network-free surface: parseRepo (every URL/slug shape it must accept or - * reject) and tarballUrl (the codeload archive URL it builds, ref-encoded). The HTTP methods (fetchRaw / + * reject) and tarballUrl (the API tarball URL it builds, ref-encoded). The HTTP methods (fetchRaw / * latestRef / download / get / _http) are live-network territory and are exercised only in integration. */ #[CoversClass(Tiger_Module_Github::class)] @@ -41,15 +41,15 @@ public function parse_repo_returns_null_for_garbage(): void } #[Test] - public function tarball_url_builds_a_ref_encoded_codeload_archive_url(): void + public function tarball_url_builds_a_ref_encoded_api_tarball_url(): void { $this->assertSame( - 'https://github.com/acme/widget/archive/v1.2.0.tar.gz', + 'https://api.github.com/repos/acme/widget/tarball/v1.2.0', Tiger_Module_Github::tarballUrl('acme', 'widget', 'v1.2.0') ); // A ref with a slash (a branch like release/1.x) is percent-encoded. $this->assertSame( - 'https://github.com/acme/widget/archive/release%2F1.x.tar.gz', + 'https://api.github.com/repos/acme/widget/tarball/release%2F1.x', Tiger_Module_Github::tarballUrl('acme', 'widget', 'release/1.x') ); } diff --git a/tests/Unit/Module/GithubTest.php b/tests/Unit/Module/GithubTest.php index 810f4fb..1698138 100644 --- a/tests/Unit/Module/GithubTest.php +++ b/tests/Unit/Module/GithubTest.php @@ -76,15 +76,17 @@ public function unrecognizedReferencesParseToNull(): void // ---- tarballUrl ------------------------------------------------------------ #[Test] - public function tarballUrlBuildsCodeloadWithAnEncodedRef(): void + public function tarballUrlBuildsTheApiEndpointWithAnEncodedRef(): void { + // The API tarball endpoint — honours a bearer token for PRIVATE repos (the web /archive/ path + // 404s for those) and 302s to codeload; works for public repos too. $this->assertSame( - 'https://github.com/WebTigers/TigerDocs/archive/v1.2.3-beta.tar.gz', + 'https://api.github.com/repos/WebTigers/TigerDocs/tarball/v1.2.3-beta', Tiger_Module_Github::tarballUrl('WebTigers', 'TigerDocs', 'v1.2.3-beta') ); // A ref with a slash (a branch like feature/x) is rawurlencoded so the URL stays well-formed. $this->assertSame( - 'https://github.com/o/r/archive/feature%2Fx.tar.gz', + 'https://api.github.com/repos/o/r/tarball/feature%2Fx', Tiger_Module_Github::tarballUrl('o', 'r', 'feature/x') ); }