mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-15 04:45:35 +08:00
v3 base
This commit is contained in:
146
src/StaticPHP/Artifact/Downloader/DownloadResult.php
Normal file
146
src/StaticPHP/Artifact/Downloader/DownloadResult.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader;
|
||||
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
|
||||
class DownloadResult
|
||||
{
|
||||
/**
|
||||
* @param string $cache_type Type of cache: 'archive', 'git', or 'local'
|
||||
* @param null|string $filename Filename for archive type
|
||||
* @param null|string $dirname Directory name for git/local type
|
||||
* @param mixed $extract Extraction path or configuration
|
||||
* @param bool $verified Whether the download has been verified (hash check)
|
||||
* @param null|string $version Version of the downloaded artifact (e.g., "1.2.3", "v2.0.0")
|
||||
* @param array $metadata Additional metadata (e.g., commit hash, release notes, etc.)
|
||||
*/
|
||||
private function __construct(
|
||||
public readonly string $cache_type,
|
||||
public readonly array $config,
|
||||
public readonly ?string $filename = null,
|
||||
public readonly ?string $dirname = null,
|
||||
public mixed $extract = null,
|
||||
public bool $verified = false,
|
||||
public readonly ?string $version = null,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
switch ($this->cache_type) {
|
||||
case 'archive':
|
||||
$this->filename !== null ?: throw new DownloaderException('Archive download result must have a filename.');
|
||||
$fn = FileSystem::isRelativePath($this->filename) ? (DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $this->filename) : $this->filename;
|
||||
file_exists($fn) ?: throw new DownloaderException("Downloaded archive file does not exist: {$fn}");
|
||||
break;
|
||||
case 'git':
|
||||
case 'local':
|
||||
$this->dirname !== null ?: throw new DownloaderException('Git/local download result must have a dirname.');
|
||||
$dn = FileSystem::isRelativePath($this->dirname) ? (DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $this->dirname) : $this->dirname;
|
||||
file_exists($dn) ?: throw new DownloaderException("Downloaded directory does not exist: {$dn}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a download result for an archive file.
|
||||
*
|
||||
* @param string $filename Filename of the downloaded archive
|
||||
* @param mixed $extract Extraction path or configuration
|
||||
* @param bool $verified Whether the archive has been hash-verified
|
||||
* @param null|string $version Version string of the downloaded artifact
|
||||
* @param array $metadata Additional metadata
|
||||
*/
|
||||
public static function archive(
|
||||
string $filename,
|
||||
array $config,
|
||||
mixed $extract = null,
|
||||
bool $verified = false,
|
||||
?string $version = null,
|
||||
array $metadata = []
|
||||
): DownloadResult {
|
||||
return new self('archive', config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a download result for a git clone.
|
||||
*
|
||||
* @param string $dirname Directory name of the cloned repository
|
||||
* @param mixed $extract Extraction path or configuration
|
||||
* @param null|string $version Version string (tag, branch, or commit)
|
||||
* @param array $metadata Additional metadata (e.g., commit hash)
|
||||
*/
|
||||
public static function git(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = []): DownloadResult
|
||||
{
|
||||
return new self('git', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a download result for a local directory.
|
||||
*
|
||||
* @param string $dirname Directory name
|
||||
* @param mixed $extract Extraction path or configuration
|
||||
* @param null|string $version Version string if known
|
||||
* @param array $metadata Additional metadata
|
||||
*/
|
||||
public static function local(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = []): DownloadResult
|
||||
{
|
||||
return new self('local', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if version information is available.
|
||||
*/
|
||||
public function hasVersion(): bool
|
||||
{
|
||||
return $this->version !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a metadata value by key.
|
||||
*
|
||||
* @param string $key Metadata key
|
||||
* @param mixed $default Default value if key doesn't exist
|
||||
*/
|
||||
public function getMeta(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->metadata[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DownloadResult with updated version.
|
||||
* (Immutable pattern - returns a new instance)
|
||||
*/
|
||||
public function withVersion(string $version): self
|
||||
{
|
||||
return new self(
|
||||
$this->cache_type,
|
||||
$this->config,
|
||||
$this->filename,
|
||||
$this->dirname,
|
||||
$this->extract,
|
||||
$this->verified,
|
||||
$version,
|
||||
$this->metadata
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DownloadResult with additional metadata.
|
||||
* (Immutable pattern - returns a new instance)
|
||||
*/
|
||||
public function withMeta(string $key, mixed $value): self
|
||||
{
|
||||
return new self(
|
||||
$this->cache_type,
|
||||
$this->config,
|
||||
$this->filename,
|
||||
$this->dirname,
|
||||
$this->extract,
|
||||
$this->verified,
|
||||
$this->version,
|
||||
array_merge($this->metadata, [$key => $value])
|
||||
);
|
||||
}
|
||||
}
|
||||
41
src/StaticPHP/Artifact/Downloader/Type/BitBucketTag.php
Normal file
41
src/StaticPHP/Artifact/Downloader/Type/BitBucketTag.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** bitbuckettag */
|
||||
class BitBucketTag implements DownloadTypeInterface
|
||||
{
|
||||
public const string BITBUCKET_API_URL = 'https://api.bitbucket.org/2.0/repositories/{repo}/refs/tags';
|
||||
|
||||
public const string BITBUCKET_DOWNLOAD_URL = 'https://bitbucket.org/{repo}/get/{version}.tar.gz';
|
||||
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
logger()->debug("Fetching {$name} API info from bitbucket tag");
|
||||
$data = default_shell()->executeCurl(str_replace('{repo}', $config['repo'], self::BITBUCKET_API_URL), retries: $downloader->getRetry());
|
||||
$data = json_decode($data ?: '', true);
|
||||
$ver = $data['values'][0]['name'] ?? null;
|
||||
if (!$ver) {
|
||||
throw new DownloaderException("Failed to get {$name} version from BitBucket API");
|
||||
}
|
||||
$download_url = str_replace(['{repo}', '{version}'], [$config['repo'], $ver], self::BITBUCKET_DOWNLOAD_URL);
|
||||
|
||||
$headers = default_shell()->executeCurl($download_url, method: 'HEAD', retries: $downloader->getRetry());
|
||||
preg_match('/^content-disposition:\s+attachment;\s*filename=("?)(?<filename>.+\.tar\.gz)\1/im', $headers, $matches);
|
||||
if ($matches) {
|
||||
$filename = $matches['filename'];
|
||||
} else {
|
||||
$filename = "{$name}-{$data['tag_name']}.tar.gz";
|
||||
}
|
||||
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
|
||||
logger()->debug("Downloading {$name} version {$ver} from BitBucket: {$download_url}");
|
||||
default_shell()->executeCurlDownload($download_url, $path, retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($filename, $config, extract: $config['extract'] ?? null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
|
||||
interface DownloadTypeInterface
|
||||
{
|
||||
/**
|
||||
* @param string $name Download item name
|
||||
* @param array $config Input configuration for the download
|
||||
* @param ArtifactDownloader $downloader Downloader instance
|
||||
*/
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult;
|
||||
}
|
||||
46
src/StaticPHP/Artifact/Downloader/Type/FileList.php
Normal file
46
src/StaticPHP/Artifact/Downloader/Type/FileList.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** filelist */
|
||||
class FileList implements DownloadTypeInterface
|
||||
{
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
logger()->debug("Fetching file list from {$config['url']}");
|
||||
$page = default_shell()->executeCurl($config['url'], retries: $downloader->getRetry());
|
||||
preg_match_all($config['regex'], $page ?: '', $matches);
|
||||
if (!$matches) {
|
||||
throw new DownloaderException("Failed to get {$name} file list from {$config['url']}");
|
||||
}
|
||||
$versions = [];
|
||||
foreach ($matches['version'] as $i => $version) {
|
||||
$lower = strtolower($version);
|
||||
foreach (['alpha', 'beta', 'rc', 'pre', 'nightly', 'snapshot', 'dev'] as $beta) {
|
||||
if (str_contains($lower, $beta)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$versions[$version] = $matches['file'][$i];
|
||||
}
|
||||
uksort($versions, 'version_compare');
|
||||
$filename = end($versions);
|
||||
$version = array_key_last($versions);
|
||||
if (isset($config['download-url'])) {
|
||||
$url = str_replace(['{file}', '{version}'], [$filename, $version], $config['download-url']);
|
||||
} else {
|
||||
$url = $config['url'] . $filename;
|
||||
}
|
||||
$filename = end($versions);
|
||||
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
|
||||
logger()->debug("Downloading {$name} from URL: {$url}");
|
||||
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($filename, $config, $config['extract'] ?? null);
|
||||
}
|
||||
}
|
||||
22
src/StaticPHP/Artifact/Downloader/Type/Git.php
Normal file
22
src/StaticPHP/Artifact/Downloader/Type/Git.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
|
||||
/** git */
|
||||
class Git implements DownloadTypeInterface
|
||||
{
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
$path = DOWNLOAD_PATH . "/{$name}";
|
||||
logger()->debug("Cloning git repository for {$name} from {$config['url']}");
|
||||
$shallow = !$downloader->getOption('no-shallow-clone', false);
|
||||
default_shell()->executeGitClone($config['url'], $config['rev'], $path, $shallow, $config['submodules'] ?? null);
|
||||
$version = "dev-{$config['rev']}";
|
||||
return DownloadResult::git($name, $config, extract: $config['extract'] ?? null, version: $version);
|
||||
}
|
||||
}
|
||||
98
src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php
Normal file
98
src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** ghrel */
|
||||
class GitHubRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
{
|
||||
use GitHubTokenSetupTrait;
|
||||
|
||||
public const string API_URL = 'https://api.github.com/repos/{repo}/releases';
|
||||
|
||||
public const string ASSET_URL = 'https://api.github.com/repos/{repo}/releases/assets/{id}';
|
||||
|
||||
private string $sha256 = '';
|
||||
|
||||
private ?string $version = null;
|
||||
|
||||
/**
|
||||
* Get the latest GitHub release assets for a given repository.
|
||||
* match_asset is provided, only return the asset that matches the regex.
|
||||
*/
|
||||
public function getLatestGitHubRelease(string $name, string $repo, bool $prefer_stable, string $match_asset): array
|
||||
{
|
||||
$url = str_replace('{repo}', $repo, self::API_URL);
|
||||
$headers = $this->getGitHubTokenHeaders();
|
||||
$data2 = default_shell()->executeCurl($url, headers: $headers);
|
||||
$data = json_decode($data2 ?: '', true);
|
||||
if (!is_array($data)) {
|
||||
throw new DownloaderException("Failed to get GitHub release API info for {$repo} from {$url}");
|
||||
}
|
||||
foreach ($data as $release) {
|
||||
if ($prefer_stable && $release['prerelease'] === true) {
|
||||
continue;
|
||||
}
|
||||
foreach ($release['assets'] as $asset) {
|
||||
if (preg_match("|{$match_asset}|", $asset['name'])) {
|
||||
if (isset($asset['id'], $asset['name'])) {
|
||||
// store ghrel asset array (id: ghrel.{$repo}.{stable|unstable}.{$match_asset})
|
||||
if ($asset['digest'] !== null && str_starts_with($asset['digest'], 'sha256:')) {
|
||||
$this->sha256 = substr($asset['digest'], 7);
|
||||
}
|
||||
$this->version = $release['tag_name'] ?? null;
|
||||
return $asset;
|
||||
}
|
||||
throw new DownloaderException("Failed to get asset name and id for {$repo}");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new DownloaderException("No suitable GitHub release found for {$repo}");
|
||||
}
|
||||
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
logger()->debug("Fetching GitHub release for {$name} from {$config['repo']}");
|
||||
if (!isset($config['match'])) {
|
||||
throw new DownloaderException("GitHubRelease downloader requires 'match' config for {$name}");
|
||||
}
|
||||
$rel = $this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match']);
|
||||
|
||||
// download file using curl
|
||||
$asset_url = str_replace(['{repo}', '{id}'], [$config['repo'], $rel['id']], self::ASSET_URL);
|
||||
$headers = array_merge(
|
||||
$this->getGitHubTokenHeaders(),
|
||||
['Accept: application/octet-stream']
|
||||
);
|
||||
$filename = $rel['name'];
|
||||
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
|
||||
logger()->debug("Downloading {$name} asset from URL: {$asset_url}");
|
||||
default_shell()->executeCurlDownload($asset_url, $path, headers: $headers, retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($filename, $config, extract: $config['extract'] ?? null, version: $this->version);
|
||||
}
|
||||
|
||||
public function validate(string $name, array $config, ArtifactDownloader $downloader, DownloadResult $result): bool
|
||||
{
|
||||
if ($result->cache_type != 'archive') {
|
||||
logger()->warning("GitHub release validator only supports archive download type for {$name} .");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->sha256 !== '') {
|
||||
$calculated_hash = hash_file('sha256', DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $result->filename);
|
||||
if ($this->sha256 !== $calculated_hash) {
|
||||
logger()->error("Hash mismatch for downloaded GitHub release asset of {$name}: expected {$this->sha256}, got {$calculated_hash}");
|
||||
return false;
|
||||
}
|
||||
logger()->debug("Hash verified for downloaded GitHub release asset of {$name}");
|
||||
return true;
|
||||
}
|
||||
logger()->debug("No sha256 digest found for GitHub release asset of {$name}, skipping hash validation");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
78
src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php
Normal file
78
src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** ghtar */
|
||||
/** ghtagtar */
|
||||
class GitHubTarball implements DownloadTypeInterface
|
||||
{
|
||||
use GitHubTokenSetupTrait;
|
||||
|
||||
public const string API_URL = 'https://api.github.com/repos/{repo}/{rel_type}';
|
||||
|
||||
private ?string $version = null;
|
||||
|
||||
/**
|
||||
* Get the GitHub tarball URL for a given repository and release type.
|
||||
* If match_url is provided, only return the tarball that matches the regex.
|
||||
* Otherwise, return the first tarball found.
|
||||
*/
|
||||
public function getGitHubTarballInfo(string $name, string $repo, string $rel_type, bool $prefer_stable = true, ?string $match_url = null, ?string $basename = null): array
|
||||
{
|
||||
$url = str_replace(['{repo}', '{rel_type}'], [$repo, $rel_type], self::API_URL);
|
||||
$data = default_shell()->executeCurl($url, headers: $this->getGitHubTokenHeaders());
|
||||
$data = json_decode($data ?: '', true);
|
||||
if (!is_array($data)) {
|
||||
throw new DownloaderException("Failed to get GitHub tarball URL for {$repo} from {$url}");
|
||||
}
|
||||
$url = null;
|
||||
foreach ($data as $rel) {
|
||||
if (($rel['prerelease'] ?? false) === true && $prefer_stable) {
|
||||
continue;
|
||||
}
|
||||
if ($match_url === null) {
|
||||
$url = $rel['tarball_url'] ?? null;
|
||||
$version = $rel['tag_name'] ?? null;
|
||||
break;
|
||||
}
|
||||
if (preg_match("|{$match_url}|", $rel['tarball_url'] ?? '')) {
|
||||
$url = $rel['tarball_url'];
|
||||
$version = $rel['tag_name'] ?? null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$url) {
|
||||
throw new DownloaderException("No suitable GitHub tarball found for {$repo}");
|
||||
}
|
||||
$this->version = $version ?? null;
|
||||
$head = default_shell()->executeCurl($url, 'HEAD', headers: $this->getGitHubTokenHeaders()) ?: '';
|
||||
preg_match('/^content-disposition:\s+attachment;\s*filename=("?)(?<filename>.+\.tar\.gz)\1/im', $head, $matches);
|
||||
if ($matches) {
|
||||
$filename = $matches['filename'];
|
||||
} else {
|
||||
$basename = $basename ?? basename($repo);
|
||||
$filename = "{$basename}-" . ($rel_type === 'releases' ? $data['tag_name'] : $data['name']) . '.tar.gz';
|
||||
}
|
||||
return [$url, $filename];
|
||||
}
|
||||
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
logger()->debug("Downloading GitHub tarball for {$name} from {$config['repo']}");
|
||||
$rel_type = match ($config['type']) {
|
||||
'ghtar' => 'releases',
|
||||
'ghtagtar' => 'tags',
|
||||
default => throw new DownloaderException("Invalid GitHubTarball type for {$name}"),
|
||||
};
|
||||
[$url, $filename] = $this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name);
|
||||
$path = DOWNLOAD_PATH . "/{$filename}";
|
||||
default_shell()->executeCurlDownload($url, $path, headers: $this->getGitHubTokenHeaders());
|
||||
return DownloadResult::archive($filename, $config, $config['extract'] ?? null, version: $this->version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
trait GitHubTokenSetupTrait
|
||||
{
|
||||
public function getGitHubTokenHeaders(): array
|
||||
{
|
||||
// GITHUB_TOKEN support
|
||||
if (($token = getenv('GITHUB_TOKEN')) !== false && ($user = getenv('GITHUB_USER')) !== false) {
|
||||
logger()->debug("Using 'GITHUB_TOKEN' with user {$user} for authentication");
|
||||
return ['Authorization: Basic ' . base64_encode("{$user}:{$token}")];
|
||||
}
|
||||
if (($token = getenv('GITHUB_TOKEN')) !== false) {
|
||||
logger()->debug("Using 'GITHUB_TOKEN' for authentication");
|
||||
return ["Authorization: Bearer {$token}"];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
18
src/StaticPHP/Artifact/Downloader/Type/LocalDir.php
Normal file
18
src/StaticPHP/Artifact/Downloader/Type/LocalDir.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
|
||||
/** local */
|
||||
class LocalDir implements DownloadTypeInterface
|
||||
{
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
logger()->debug("Using local source directory for {$name} from {$config['dirname']}");
|
||||
return DownloadResult::local($config['dirname'], $config, extract: $config['extract'] ?? null);
|
||||
}
|
||||
}
|
||||
47
src/StaticPHP/Artifact/Downloader/Type/PIE.php
Normal file
47
src/StaticPHP/Artifact/Downloader/Type/PIE.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** pie */
|
||||
class PIE implements DownloadTypeInterface
|
||||
{
|
||||
public const string PACKAGIST_URL = 'https://repo.packagist.org/p2/';
|
||||
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
$packagist_url = self::PACKAGIST_URL . "{$config['repo']}.json";
|
||||
logger()->debug("Fetching {$name} source from packagist index: {$packagist_url}");
|
||||
$data = default_shell()->executeCurl($packagist_url, retries: $downloader->getRetry());
|
||||
if ($data === false) {
|
||||
throw new DownloaderException("Failed to fetch packagist index for {$name} from {$packagist_url}");
|
||||
}
|
||||
$data = json_decode($data, true);
|
||||
if (!isset($data['packages'][$config['repo']]) || !is_array($data['packages'][$config['repo']])) {
|
||||
throw new DownloaderException("failed to find {$name} repo info from packagist");
|
||||
}
|
||||
// get the first version
|
||||
$first = $data['packages'][$config['repo']][0] ?? [];
|
||||
// check 'type' => 'php-ext' or contains 'php-ext' key
|
||||
if (!isset($first['php-ext'])) {
|
||||
throw new DownloaderException("failed to find {$name} php-ext info from packagist, maybe not a php extension package");
|
||||
}
|
||||
// get download link from dist
|
||||
$dist_url = $first['dist']['url'] ?? null;
|
||||
$dist_type = $first['dist']['type'] ?? null;
|
||||
if (!$dist_url || !$dist_type) {
|
||||
throw new DownloaderException("failed to find {$name} dist info from packagist");
|
||||
}
|
||||
$name = str_replace('/', '_', $config['repo']);
|
||||
$version = $first['version'] ?? 'unknown';
|
||||
$filename = "{$name}-{$version}." . ($dist_type === 'zip' ? 'zip' : 'tar.gz');
|
||||
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
|
||||
default_shell()->executeCurlDownload($dist_url, $path, retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($filename, $config, $config['extract'] ?? null);
|
||||
}
|
||||
}
|
||||
76
src/StaticPHP/Artifact/Downloader/Type/PhpRelease.php
Normal file
76
src/StaticPHP/Artifact/Downloader/Type/PhpRelease.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
class PhpRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
{
|
||||
public const string PHP_API = 'https://www.php.net/releases/index.php?json&version={version}';
|
||||
|
||||
public const string DOWNLOAD_URL = 'https://www.php.net/distributions/php-{version}.tar.xz';
|
||||
|
||||
private ?string $sha256 = '';
|
||||
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
$phpver = $downloader->getOption('with-php', '8.4');
|
||||
// Handle 'git' version to clone from php-src repository
|
||||
if ($phpver === 'git') {
|
||||
$this->sha256 = null;
|
||||
return (new Git())->download($name, ['url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $downloader);
|
||||
}
|
||||
|
||||
// Fetch PHP release info first
|
||||
$info = default_shell()->executeCurl(str_replace('{version}', $phpver, self::PHP_API), retries: $downloader->getRetry());
|
||||
if ($info === false) {
|
||||
throw new DownloaderException("Failed to fetch PHP release info for version {$phpver}");
|
||||
}
|
||||
$info = json_decode($info, true);
|
||||
if (!is_array($info) || !isset($info['version'])) {
|
||||
throw new DownloaderException("Invalid PHP release info received for version {$phpver}");
|
||||
}
|
||||
$version = $info['version'];
|
||||
foreach ($info['source'] as $source) {
|
||||
if (str_ends_with($source['filename'], '.tar.xz')) {
|
||||
$this->sha256 = $source['sha256'];
|
||||
$filename = $source['filename'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isset($filename)) {
|
||||
throw new DownloaderException("No suitable source tarball found for PHP version {$version}");
|
||||
}
|
||||
$url = str_replace('{version}', $version, self::DOWNLOAD_URL);
|
||||
logger()->debug("Downloading PHP release {$version} from {$url}");
|
||||
$path = DOWNLOAD_PATH . "/{$filename}";
|
||||
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($filename, config: $config, extract: $config['extract'] ?? null, version: $version);
|
||||
}
|
||||
|
||||
public function validate(string $name, array $config, ArtifactDownloader $downloader, DownloadResult $result): bool
|
||||
{
|
||||
if ($this->sha256 === null) {
|
||||
logger()->debug('Php-src is downloaded from non-release source, skipping validation.');
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->sha256 === '') {
|
||||
logger()->error("No SHA256 checksum available for validation of {$name}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = DOWNLOAD_PATH . "/{$result->filename}";
|
||||
$hash = hash_file('sha256', $path);
|
||||
if ($hash !== $this->sha256) {
|
||||
logger()->error("SHA256 checksum mismatch for {$name}: expected {$this->sha256}, got {$hash}");
|
||||
return false;
|
||||
}
|
||||
logger()->debug("SHA256 checksum validated successfully for {$name}.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
23
src/StaticPHP/Artifact/Downloader/Type/Url.php
Normal file
23
src/StaticPHP/Artifact/Downloader/Type/Url.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
|
||||
/** url */
|
||||
class Url implements DownloadTypeInterface
|
||||
{
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
$url = $config['url'];
|
||||
$filename = $config['filename'] ?? basename($url);
|
||||
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
|
||||
logger()->debug("Downloading {$name} from URL: {$url}");
|
||||
$version = $config['version'] ?? null;
|
||||
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($filename, config: $config, extract: $config['extract'] ?? null, version: $version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
|
||||
interface ValidatorInterface
|
||||
{
|
||||
public function validate(string $name, array $config, ArtifactDownloader $downloader, DownloadResult $result): bool;
|
||||
}
|
||||
Reference in New Issue
Block a user