GitHubDownloader
Downloads plugin releases from GitHub. Uses GitHub REST API v3 (no auth required for public repos). Optionally uses GITHUB_TOKEN env var for private repos or rate limits. Transport is a seam. With no PSR-18 client injected it goes over `file_get_contents()` with a stream context, exactly as it always has — nothing a host has to change. Injecting one is what makes everything above the transport reachable from a test: version listing, constraint resolution, the zipball download and its fallback tag were all unreachable while the only way in was the network. The same seam `milpa/ai-gateway` and `milpa/mcp-client` already carry. GitHub is one source of plugins, not the only conceivable one: what the installer actually depends on is {@see PluginDownloaderInterface}. The two public methods beyond that port — {@see self::listVersions()} and {@see self::getRepoInfo()} — are GitHub facts, and stay off it.
GitHubDownloader::__construct()
public function __construct(?string $token = null, ?Psr\Http\Client\ClientInterface $httpClient = null, ?Psr\Http\Message\RequestFactoryInterface $requestFactory = null):Parameters
| Name | Type | Description |
|---|---|---|
| $token | ?string | |
| $httpClient | ?Psr\Http\Client\ClientInterface | |
| $requestFactory | ?Psr\Http\Message\RequestFactoryInterface |
GitHubDownloader::parseSource()
public function parseSource(string $source): arrayParse a source string into owner, repo, and optional version constraint. Formats: "acme/mail-plugin" → owner=acme, repo=mail-plugin, constraint=null "acme/mail-plugin:^2.0" → owner=acme, repo=mail-plugin, constraint=^2.0 "https://github.com/acme/mail-plugin" → owner=acme, repo=mail-plugin, constraint=null
Parameters
| Name | Type | Description |
|---|---|---|
| $source | string |
Throws
\InvalidArgumentException
GitHubDownloader::listVersions()
public function listVersions(string $owner, string $repo): arrayList available versions (releases + tags) from GitHub. Filters to valid semver only.
Parameters
| Name | Type | Description |
|---|---|---|
| $owner | string | |
| $repo | string |
Returns
Sorted descending (newest first)
GitHubDownloader::resolveVersion()
public function resolveVersion(string $owner, string $repo, ?string $constraint = null): Milpa\ValueObjects\SemanticVersionFind the best matching version for a constraint.
Parameters
| Name | Type | Description |
|---|---|---|
| $owner | string | |
| $repo | string | |
| $constraint | (string | null) | Semver constraint (e.g., "^2.0", ">=1.5"). If null, returns latest stable. |
Throws
\RuntimeException If no matching version found
GitHubDownloader::download()
public function download(string $owner, string $repo, Milpa\ValueObjects\SemanticVersion $version): stringDownload and extract a specific version to a temporary directory.
Parameters
| Name | Type | Description |
|---|---|---|
| $owner | string | |
| $repo | string | |
| $version | Milpa\ValueObjects\SemanticVersion |
Returns
Path to the extracted plugin directory
Throws
\RuntimeException On download or extraction failure
GitHubDownloader::getRepoInfo()
public function getRepoInfo(string $owner, string $repo): ?arrayGet repository info from GitHub API.
Parameters
| Name | Type | Description |
|---|---|---|
| $owner | string | |
| $repo | string |
GitHubDownloader::cleanup()
public function cleanup(string $path): voidClean up a temporary directory recursively.
Parameters
| Name | Type | Description |
|---|---|---|
| $path | string |