mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-17 20:34:51 +08:00
Implement check-update functionality for artifacts and enhance download result handling
This commit is contained in:
parent
2d550a8db4
commit
ed5a516004
@ -18,7 +18,8 @@ class ArtifactCache
|
||||
* filename?: string,
|
||||
* dirname?: string,
|
||||
* extract: null|'&custom'|string,
|
||||
* hash: null|string
|
||||
* hash: null|string,
|
||||
* downloader: null|string
|
||||
* },
|
||||
* binary: array{
|
||||
* windows-x86_64?: null|array{
|
||||
@ -28,7 +29,8 @@ class ArtifactCache
|
||||
* dirname?: string,
|
||||
* extract: null|'&custom'|string,
|
||||
* hash: null|string,
|
||||
* version?: null|string
|
||||
* version?: null|string,
|
||||
* downloader: null|string
|
||||
* }
|
||||
* }
|
||||
* }>
|
||||
@ -108,6 +110,7 @@ class ArtifactCache
|
||||
'hash' => sha1_file(DOWNLOAD_PATH . '/' . $download_result->filename),
|
||||
'version' => $download_result->version,
|
||||
'config' => $download_result->config,
|
||||
'downloader' => $download_result->downloader,
|
||||
];
|
||||
} elseif ($download_result->cache_type === 'file') {
|
||||
$obj = [
|
||||
@ -118,6 +121,7 @@ class ArtifactCache
|
||||
'hash' => sha1_file(DOWNLOAD_PATH . '/' . $download_result->filename),
|
||||
'version' => $download_result->version,
|
||||
'config' => $download_result->config,
|
||||
'downloader' => $download_result->downloader,
|
||||
];
|
||||
} elseif ($download_result->cache_type === 'git') {
|
||||
$obj = [
|
||||
@ -128,6 +132,7 @@ class ArtifactCache
|
||||
'hash' => trim(exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $download_result->dirname) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD')),
|
||||
'version' => $download_result->version,
|
||||
'config' => $download_result->config,
|
||||
'downloader' => $download_result->downloader,
|
||||
];
|
||||
} elseif ($download_result->cache_type === 'local') {
|
||||
$obj = [
|
||||
@ -138,6 +143,7 @@ class ArtifactCache
|
||||
'hash' => null,
|
||||
'version' => $download_result->version,
|
||||
'config' => $download_result->config,
|
||||
'downloader' => $download_result->downloader,
|
||||
];
|
||||
}
|
||||
if ($obj === null) {
|
||||
|
||||
@ -6,6 +6,8 @@ namespace StaticPHP\Artifact;
|
||||
|
||||
use Psr\Log\LogLevel;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Artifact\Downloader\Type\CheckUpdateInterface;
|
||||
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
|
||||
use StaticPHP\Artifact\Downloader\Type\DownloadTypeInterface;
|
||||
use StaticPHP\Artifact\Downloader\Type\Git;
|
||||
use StaticPHP\Artifact\Downloader\Type\LocalDir;
|
||||
@ -323,6 +325,43 @@ class ArtifactDownloader
|
||||
}
|
||||
}
|
||||
|
||||
public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false): CheckUpdateResult
|
||||
{
|
||||
$artifact = ArtifactLoader::getArtifactInstance($artifact_name);
|
||||
if ($artifact === null) {
|
||||
throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name.");
|
||||
}
|
||||
if ($bare) {
|
||||
$config = $artifact->getDownloadConfig('source');
|
||||
if (!is_array($config)) {
|
||||
throw new WrongUsageException("Artifact '{$artifact_name}' has no source config for bare update check.");
|
||||
}
|
||||
$cls = $this->downloaders[$config['type']] ?? null;
|
||||
if (!is_a($cls, CheckUpdateInterface::class, true)) {
|
||||
throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking.");
|
||||
}
|
||||
/** @var CheckUpdateInterface $downloader */
|
||||
$downloader = new $cls();
|
||||
return $downloader->checkUpdate($artifact_name, $config, null, $this);
|
||||
}
|
||||
$cache = ApplicationContext::get(ArtifactCache::class);
|
||||
if ($prefer_source) {
|
||||
$info = $cache->getSourceInfo($artifact_name) ?? $cache->getBinaryInfo($artifact_name, SystemTarget::getCurrentPlatformString());
|
||||
} else {
|
||||
$info = $cache->getBinaryInfo($artifact_name, SystemTarget::getCurrentPlatformString()) ?? $cache->getSourceInfo($artifact_name);
|
||||
}
|
||||
if ($info === null) {
|
||||
throw new WrongUsageException("Artifact '{$artifact_name}' is not downloaded yet, cannot check update.");
|
||||
}
|
||||
if (is_a($info['downloader'] ?? null, CheckUpdateInterface::class, true)) {
|
||||
$cls = $info['downloader'];
|
||||
/** @var CheckUpdateInterface $downloader */
|
||||
$downloader = new $cls();
|
||||
return $downloader->checkUpdate($artifact_name, $info['config'], $info['version'], $this);
|
||||
}
|
||||
throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking, exit.");
|
||||
}
|
||||
|
||||
public function getRetry(): int
|
||||
{
|
||||
return $this->retry;
|
||||
|
||||
@ -17,6 +17,7 @@ class DownloadResult
|
||||
* @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.)
|
||||
* @param null|string $downloader Class name of the downloader that performed this download
|
||||
*/
|
||||
private function __construct(
|
||||
public readonly string $cache_type,
|
||||
@ -27,6 +28,7 @@ class DownloadResult
|
||||
public bool $verified = false,
|
||||
public readonly ?string $version = null,
|
||||
public readonly array $metadata = [],
|
||||
public readonly ?string $downloader = null,
|
||||
) {
|
||||
switch ($this->cache_type) {
|
||||
case 'archive':
|
||||
@ -59,11 +61,12 @@ class DownloadResult
|
||||
mixed $extract = null,
|
||||
bool $verified = false,
|
||||
?string $version = null,
|
||||
array $metadata = []
|
||||
array $metadata = [],
|
||||
?string $downloader = null,
|
||||
): DownloadResult {
|
||||
// judge if it is archive or just a pure file
|
||||
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
|
||||
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata);
|
||||
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
|
||||
}
|
||||
|
||||
public static function file(
|
||||
@ -71,10 +74,11 @@ class DownloadResult
|
||||
array $config,
|
||||
bool $verified = false,
|
||||
?string $version = null,
|
||||
array $metadata = []
|
||||
array $metadata = [],
|
||||
?string $downloader = null,
|
||||
): DownloadResult {
|
||||
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
|
||||
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata);
|
||||
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,9 +89,9 @@ class DownloadResult
|
||||
* @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
|
||||
public static function git(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = [], ?string $downloader = null): DownloadResult
|
||||
{
|
||||
return new self('git', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata);
|
||||
return new self('git', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata, downloader: $downloader);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,9 +102,9 @@ class DownloadResult
|
||||
* @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
|
||||
public static function local(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = [], ?string $downloader = null): DownloadResult
|
||||
{
|
||||
return new self('local', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata);
|
||||
return new self('local', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata, downloader: $downloader);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,7 +140,8 @@ class DownloadResult
|
||||
$this->extract,
|
||||
$this->verified,
|
||||
$version,
|
||||
$this->metadata
|
||||
$this->metadata,
|
||||
$this->downloader,
|
||||
);
|
||||
}
|
||||
|
||||
@ -154,7 +159,8 @@ class DownloadResult
|
||||
$this->extract,
|
||||
$this->verified,
|
||||
$this->version,
|
||||
array_merge($this->metadata, [$key => $value])
|
||||
array_merge($this->metadata, [$key => $value]),
|
||||
$this->downloader,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,6 @@ class BitBucketTag implements DownloadTypeInterface
|
||||
$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);
|
||||
return DownloadResult::archive($filename, $config, extract: $config['extract'] ?? null, downloader: static::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
|
||||
interface CheckUpdateInterface
|
||||
{
|
||||
/**
|
||||
* Check if an update is available for the given artifact.
|
||||
*
|
||||
* @param string $name the name of the artifact
|
||||
* @param array $config the configuration for the artifact
|
||||
* @param string $old_version old version or identifier of the artifact to compare against
|
||||
* @param ArtifactDownloader $downloader the artifact downloader instance
|
||||
*/
|
||||
public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult;
|
||||
}
|
||||
14
src/StaticPHP/Artifact/Downloader/Type/CheckUpdateResult.php
Normal file
14
src/StaticPHP/Artifact/Downloader/Type/CheckUpdateResult.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Artifact\Downloader\Type;
|
||||
|
||||
readonly class CheckUpdateResult
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $old,
|
||||
public string $new,
|
||||
public bool $needUpdate,
|
||||
) {}
|
||||
}
|
||||
@ -9,9 +9,34 @@ use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** filelist */
|
||||
class FileList implements DownloadTypeInterface
|
||||
class FileList implements DownloadTypeInterface, CheckUpdateInterface
|
||||
{
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
[$filename, $version, $versions] = $this->fetchFileList($name, $config, $downloader);
|
||||
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, version: $version, downloader: static::class);
|
||||
}
|
||||
|
||||
public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
[, $version] = $this->fetchFileList($name, $config, $downloader);
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $version,
|
||||
needUpdate: $old_version === null || version_compare($version, $old_version, '>'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function fetchFileList(string $name, array $config, ArtifactDownloader $downloader): array
|
||||
{
|
||||
logger()->debug("Fetching file list from {$config['url']}");
|
||||
$page = default_shell()->executeCurl($config['url'], retries: $downloader->getRetry());
|
||||
@ -33,15 +58,6 @@ class FileList implements DownloadTypeInterface
|
||||
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);
|
||||
return [$filename, $version, $versions];
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ use StaticPHP\Exception\DownloaderException;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
|
||||
/** git */
|
||||
class Git implements DownloadTypeInterface
|
||||
class Git implements DownloadTypeInterface, CheckUpdateInterface
|
||||
{
|
||||
public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
@ -21,8 +21,10 @@ class Git implements DownloadTypeInterface
|
||||
// direct branch clone
|
||||
if (isset($config['rev'])) {
|
||||
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);
|
||||
$hash_result = shell(false)->execWithResult(SPC_GIT_EXEC . ' -C ' . escapeshellarg($path) . ' rev-parse HEAD');
|
||||
$hash = ($hash_result[0] === 0 && !empty($hash_result[1])) ? trim($hash_result[1][0]) : '';
|
||||
$version = $hash !== '' ? "dev-{$config['rev']}+{$hash}" : "dev-{$config['rev']}";
|
||||
return DownloadResult::git($name, $config, extract: $config['extract'] ?? null, version: $version, downloader: static::class);
|
||||
}
|
||||
if (!isset($config['regex'])) {
|
||||
throw new DownloaderException('Either "rev" or "regex" must be specified for git download type.');
|
||||
@ -64,8 +66,62 @@ class Git implements DownloadTypeInterface
|
||||
$branch = $matched_version_branch[$version];
|
||||
logger()->info("Matched version {$version} from branch {$branch} for {$name}");
|
||||
default_shell()->executeGitClone($config['url'], $branch, $path, $shallow, $config['submodules'] ?? null);
|
||||
return DownloadResult::git($name, $config, extract: $config['extract'] ?? null, version: $version);
|
||||
return DownloadResult::git($name, $config, extract: $config['extract'] ?? null, version: $version, downloader: static::class);
|
||||
}
|
||||
throw new DownloaderException("No matching branch found for regex {$config['regex']} (checked {$matched_count} branches).");
|
||||
}
|
||||
|
||||
public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
if (isset($config['rev'])) {
|
||||
$shell = PHP_OS_FAMILY === 'Windows' ? cmd(false) : shell(false);
|
||||
$result = $shell->execWithResult(SPC_GIT_EXEC . ' ls-remote ' . escapeshellarg($config['url']) . ' ' . escapeshellarg('refs/heads/' . $config['rev']));
|
||||
if ($result[0] !== 0 || empty($result[1])) {
|
||||
throw new DownloaderException("Failed to ls-remote from {$config['url']}");
|
||||
}
|
||||
$new_hash = substr($result[1][0], 0, 40);
|
||||
$new_version = "dev-{$config['rev']}+{$new_hash}";
|
||||
// Extract stored hash from "dev-{rev}+{hash}", null if bare mode or old format without hash
|
||||
$old_hash = ($old_version !== null && str_contains($old_version, '+')) ? substr(strrchr($old_version, '+'), 1) : null;
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $new_version,
|
||||
needUpdate: $old_hash === null || $new_hash !== $old_hash,
|
||||
);
|
||||
}
|
||||
if (!isset($config['regex'])) {
|
||||
throw new DownloaderException('Either "rev" or "regex" must be specified for git download type.');
|
||||
}
|
||||
|
||||
$shell = PHP_OS_FAMILY === 'Windows' ? cmd(false) : shell(false);
|
||||
$result = $shell->execWithResult(SPC_GIT_EXEC . ' ls-remote ' . escapeshellarg($config['url']));
|
||||
if ($result[0] !== 0) {
|
||||
throw new DownloaderException("Failed to ls-remote from {$config['url']}");
|
||||
}
|
||||
$refs = $result[1];
|
||||
$matched_version_branch = [];
|
||||
|
||||
$regex = '/^' . $config['regex'] . '$/';
|
||||
foreach ($refs as $ref) {
|
||||
$matches = null;
|
||||
if (preg_match('/^[0-9a-f]{40}\s+refs\/heads\/(.+)$/', $ref, $matches)) {
|
||||
$branch = $matches[1];
|
||||
if (preg_match($regex, $branch, $vermatch) && isset($vermatch['version'])) {
|
||||
$matched_version_branch[$vermatch['version']] = $vermatch[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
uksort($matched_version_branch, function ($a, $b) {
|
||||
return version_compare($b, $a);
|
||||
});
|
||||
if (!empty($matched_version_branch)) {
|
||||
$version = array_key_first($matched_version_branch);
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $version,
|
||||
needUpdate: $old_version === null || version_compare($version, $old_version, '>'),
|
||||
);
|
||||
}
|
||||
throw new DownloaderException("No matching branch found for regex {$config['regex']}.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** ghrel */
|
||||
class GitHubRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
class GitHubRelease implements DownloadTypeInterface, ValidatorInterface, CheckUpdateInterface
|
||||
{
|
||||
use GitHubTokenSetupTrait;
|
||||
|
||||
@ -48,6 +48,7 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
*/
|
||||
public function getLatestGitHubRelease(string $name, string $repo, bool $prefer_stable, string $match_asset, ?string $query = null): array
|
||||
{
|
||||
logger()->debug("Fetching {$name} GitHub release from {$repo}");
|
||||
$url = str_replace('{repo}', $repo, self::API_URL);
|
||||
$url .= ($query ?? '');
|
||||
$headers = $this->getGitHubTokenHeaders();
|
||||
@ -95,7 +96,7 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
$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);
|
||||
return DownloadResult::archive($filename, $config, extract: $config['extract'] ?? null, version: $this->version, downloader: static::class);
|
||||
}
|
||||
|
||||
public function validate(string $name, array $config, ArtifactDownloader $downloader, DownloadResult $result): bool
|
||||
@ -117,4 +118,18 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
logger()->debug("No sha256 digest found for GitHub release asset of {$name}, skipping hash validation");
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
if (!isset($config['match'])) {
|
||||
throw new DownloaderException("GitHubRelease downloader requires 'match' config for {$name}");
|
||||
}
|
||||
$this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match'], $config['query'] ?? null);
|
||||
$new_version = $this->version ?? $old_version ?? '';
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $new_version,
|
||||
needUpdate: $old_version === null || $new_version !== $old_version,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** ghtar */
|
||||
/** ghtagtar */
|
||||
class GitHubTarball implements DownloadTypeInterface
|
||||
class GitHubTarball implements DownloadTypeInterface, CheckUpdateInterface
|
||||
{
|
||||
use GitHubTokenSetupTrait;
|
||||
|
||||
@ -77,6 +77,22 @@ class GitHubTarball implements DownloadTypeInterface
|
||||
[$url, $filename] = $this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name, $config['query'] ?? null);
|
||||
$path = DOWNLOAD_PATH . "/{$filename}";
|
||||
default_shell()->executeCurlDownload($url, $path, headers: $this->getGitHubTokenHeaders());
|
||||
return DownloadResult::archive($filename, $config, $config['extract'] ?? null, version: $this->version);
|
||||
return DownloadResult::archive($filename, $config, $config['extract'] ?? null, version: $this->version, downloader: static::class);
|
||||
}
|
||||
|
||||
public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
$rel_type = match ($config['type']) {
|
||||
'ghtar' => 'releases',
|
||||
'ghtagtar' => 'tags',
|
||||
default => throw new DownloaderException("Invalid GitHubTarball type for {$name}"),
|
||||
};
|
||||
$this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name, $config['query'] ?? null);
|
||||
$new_version = $this->version ?? $old_version ?? '';
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $new_version,
|
||||
needUpdate: $old_version === null || $new_version !== $old_version,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ class HostedPackageBin implements DownloadTypeInterface
|
||||
public static function getReleaseInfo(): array
|
||||
{
|
||||
if (empty(self::$release_info)) {
|
||||
$rel = (new GitHubRelease())->getGitHubReleases('hosted', self::BASE_REPO);
|
||||
$rel = new GitHubRelease()->getGitHubReleases('hosted', self::BASE_REPO);
|
||||
if (empty($rel)) {
|
||||
throw new DownloaderException('No releases found for hosted package-bin');
|
||||
}
|
||||
@ -55,7 +55,7 @@ class HostedPackageBin implements DownloadTypeInterface
|
||||
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
|
||||
$headers = $this->getGitHubTokenHeaders();
|
||||
default_shell()->executeCurlDownload($download_url, $path, headers: $headers, retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($filename, $config, extract: $config['extract'] ?? null, version: $version);
|
||||
return DownloadResult::archive($filename, $config, extract: $config['extract'] ?? null, version: $version, downloader: static::class);
|
||||
}
|
||||
}
|
||||
throw new DownloaderException("No matching asset found for hosted package-bin {$name}: {$find_str}");
|
||||
|
||||
@ -13,6 +13,6 @@ 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);
|
||||
return DownloadResult::local($config['dirname'], $config, extract: $config['extract'] ?? null, downloader: static::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,28 +9,13 @@ use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
/** pie */
|
||||
class PIE implements DownloadTypeInterface
|
||||
class PIE implements DownloadTypeInterface, CheckUpdateInterface
|
||||
{
|
||||
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");
|
||||
}
|
||||
$first = $this->fetchPackagistInfo($name, $config, $downloader);
|
||||
// get download link from dist
|
||||
$dist_url = $first['dist']['url'] ?? null;
|
||||
$dist_type = $first['dist']['type'] ?? null;
|
||||
@ -42,6 +27,39 @@ class PIE implements DownloadTypeInterface
|
||||
$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);
|
||||
return DownloadResult::archive($filename, $config, $config['extract'] ?? null, downloader: static::class);
|
||||
}
|
||||
|
||||
public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
$first = $this->fetchPackagistInfo($name, $config, $downloader);
|
||||
$new_version = $first['version'] ?? null;
|
||||
if ($new_version === null) {
|
||||
throw new DownloaderException("failed to find version info for {$name} from packagist");
|
||||
}
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $new_version,
|
||||
needUpdate: $old_version === null || version_compare($new_version, $old_version, '>'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function fetchPackagistInfo(string $name, array $config, ArtifactDownloader $downloader): array
|
||||
{
|
||||
$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");
|
||||
}
|
||||
$first = $data['packages'][$config['repo']][0] ?? [];
|
||||
if (!isset($first['php-ext'])) {
|
||||
throw new DownloaderException("failed to find {$name} php-ext info from packagist, maybe not a php extension package");
|
||||
}
|
||||
return $first;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
|
||||
class PhpRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
class PhpRelease implements DownloadTypeInterface, ValidatorInterface, CheckUpdateInterface
|
||||
{
|
||||
public const string PHP_API = 'https://www.php.net/releases/index.php?json&version={version}';
|
||||
|
||||
@ -24,16 +24,7 @@ class PhpRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
$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}");
|
||||
}
|
||||
$info = $this->fetchPhpReleaseInfo($name, $downloader);
|
||||
$version = $info['version'];
|
||||
foreach ($info['source'] as $source) {
|
||||
if (str_ends_with($source['filename'], '.tar.xz')) {
|
||||
@ -49,7 +40,7 @@ class PhpRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
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);
|
||||
return DownloadResult::archive($filename, config: $config, extract: $config['extract'] ?? null, version: $version, downloader: static::class);
|
||||
}
|
||||
|
||||
public function validate(string $name, array $config, ArtifactDownloader $downloader, DownloadResult $result): bool
|
||||
@ -73,4 +64,41 @@ class PhpRelease implements DownloadTypeInterface, ValidatorInterface
|
||||
logger()->debug("SHA256 checksum validated successfully for {$name}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
$phpver = $downloader->getOption('with-php', '8.4');
|
||||
if ($phpver === 'git') {
|
||||
// git version: delegate to Git checkUpdate with master branch
|
||||
return (new Git())->checkUpdate($name, ['url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $old_version, $downloader);
|
||||
}
|
||||
$info = $this->fetchPhpReleaseInfo($name, $downloader);
|
||||
$new_version = $info['version'];
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $new_version,
|
||||
needUpdate: $old_version === null || $new_version !== $old_version,
|
||||
);
|
||||
}
|
||||
|
||||
protected function fetchPhpReleaseInfo(string $name, ArtifactDownloader $downloader): array
|
||||
{
|
||||
$phpver = $downloader->getOption('with-php', '8.4');
|
||||
// Handle 'git' version to clone from php-src repository
|
||||
if ($phpver === 'git') {
|
||||
// cannot fetch release info for git version, return empty info to skip validation
|
||||
throw new DownloaderException("Cannot fetch PHP release info for 'git' version.");
|
||||
}
|
||||
|
||||
// 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}");
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,6 @@ class Url implements DownloadTypeInterface
|
||||
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);
|
||||
return DownloadResult::archive($filename, config: $config, extract: $config['extract'] ?? null, version: $version, downloader: static::class);
|
||||
}
|
||||
}
|
||||
|
||||
64
src/StaticPHP/Command/CheckUpdateCommand.php
Normal file
64
src/StaticPHP/Command/CheckUpdateCommand.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Command;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Exception\SPCException;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand('check-update', description: 'Check for updates for a specific artifact')]
|
||||
class CheckUpdateCommand extends BaseCommand
|
||||
{
|
||||
public bool $no_motd = true;
|
||||
|
||||
public function configure(): void
|
||||
{
|
||||
$this->addArgument('artifact', InputArgument::REQUIRED, 'The name of the artifact(s) to check for updates, comma-separated');
|
||||
$this->addOption('json', null, null, 'Output result in JSON format');
|
||||
$this->addOption('bare', null, null, 'Check update without requiring the artifact to be downloaded first (old version will be null)');
|
||||
|
||||
// --with-php option for checking updates with a specific PHP version context
|
||||
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'PHP version in major.minor format (default 8.4)', '8.4');
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$artifacts = parse_comma_list($this->input->getArgument('artifact'));
|
||||
|
||||
try {
|
||||
$downloader = new ArtifactDownloader($this->input->getOptions());
|
||||
$bare = (bool) $this->getOption('bare');
|
||||
if ($this->getOption('json')) {
|
||||
$outputs = [];
|
||||
foreach ($artifacts as $artifact) {
|
||||
$result = $downloader->checkUpdate($artifact, bare: $bare);
|
||||
$outputs[$artifact] = [
|
||||
'need-update' => $result->needUpdate,
|
||||
'old' => $result->old,
|
||||
'new' => $result->new,
|
||||
];
|
||||
}
|
||||
$this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
return static::OK;
|
||||
}
|
||||
foreach ($artifacts as $artifact) {
|
||||
$result = $downloader->checkUpdate($artifact, bare: $bare);
|
||||
if (!$result->needUpdate) {
|
||||
$this->output->writeln("Artifact <info>{$artifact}</info> is already up to date (version: {$result->new})");
|
||||
} else {
|
||||
$this->output->writeln("<comment>Update available for artifact: {$artifact}</comment>");
|
||||
$this->output->writeln(" Old version: <error>{$result->old}</error>");
|
||||
$this->output->writeln(" New version: <info>{$result->new}</info>");
|
||||
}
|
||||
}
|
||||
return static::OK;
|
||||
} catch (SPCException $e) {
|
||||
$e->setSimpleOutput();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ namespace StaticPHP;
|
||||
|
||||
use StaticPHP\Command\BuildLibsCommand;
|
||||
use StaticPHP\Command\BuildTargetCommand;
|
||||
use StaticPHP\Command\CheckUpdateCommand;
|
||||
use StaticPHP\Command\Dev\DumpCapabilitiesCommand;
|
||||
use StaticPHP\Command\Dev\DumpStagesCommand;
|
||||
use StaticPHP\Command\Dev\EnvCommand;
|
||||
@ -63,6 +64,7 @@ class ConsoleApplication extends Application
|
||||
new SPCConfigCommand(),
|
||||
new DumpLicenseCommand(),
|
||||
new ResetCommand(),
|
||||
new CheckUpdateCommand(),
|
||||
|
||||
// dev commands
|
||||
new ShellCommand(),
|
||||
|
||||
@ -29,12 +29,6 @@ class ExceptionHandler
|
||||
RegistryException::class,
|
||||
];
|
||||
|
||||
public const array MINOR_LOG_EXCEPTIONS = [
|
||||
InterruptException::class,
|
||||
WrongUsageException::class,
|
||||
RegistryException::class,
|
||||
];
|
||||
|
||||
/** @var array<string, mixed> Build PHP extra info binding */
|
||||
private static array $build_php_extra_info = [];
|
||||
|
||||
@ -57,10 +51,7 @@ class ExceptionHandler
|
||||
};
|
||||
self::logError($head_msg);
|
||||
|
||||
// ----------------------------------------
|
||||
$minor_logs = in_array($class, self::MINOR_LOG_EXCEPTIONS, true);
|
||||
|
||||
if ($minor_logs) {
|
||||
if ($e->isSimpleOutput()) {
|
||||
return self::getReturnCode($e);
|
||||
}
|
||||
|
||||
@ -283,6 +274,6 @@ class ExceptionHandler
|
||||
self::printArrayInfo($info);
|
||||
}
|
||||
|
||||
self::logError("---------------------------------------------------------\n", color: 'none');
|
||||
self::logError("-----------------------------------------------------------\n", color: 'none');
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user