#!/usr/bin/env php * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: moko-platform.CLI * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/release_manage.php * BRIEF: Create/update Gitea releases, upload assets, update release body */ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; use MokoEnterprise\CliFramework; class ReleaseManageCli extends CliFramework { protected function configure(): void { $this->setDescription('Create/update Gitea releases, upload assets, update release body'); $this->addArgument('--action', 'create | upload | update-body | delete', null); $this->addArgument('--tag', 'Release tag name', null); $this->addArgument('--name', 'Release name/title', null); $this->addArgument('--body', 'Release body/description', null); $this->addArgument('--body-file', 'Read body from file', null); $this->addArgument('--target', 'Target branch/commitish', 'main'); $this->addArgument('--files', 'Comma-separated file paths to upload', null); $this->addArgument('--token', 'Gitea API token', null); $this->addArgument('--api-base', 'Gitea API base URL', null); } protected function run(): int { $action = $this->getArgument('--action'); $tag = $this->getArgument('--tag'); $name = $this->getArgument('--name'); $body = $this->getArgument('--body'); $bodyFile = $this->getArgument('--body-file'); $target = $this->getArgument('--target'); $filesArg = $this->getArgument('--files'); $token = $this->getArgument('--token'); $apiBase = $this->getArgument('--api-base'); $files = $filesArg !== null ? array_filter(explode(',', $filesArg)) : []; if ($token === null) { $token = getenv('MOKOGITEA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null; } if ($bodyFile !== null && file_exists($bodyFile)) { $body = file_get_contents($bodyFile); } if ($action === null || $tag === null || $token === null || $apiBase === null) { $this->log('ERROR', "Usage: release_manage.php --action [create|upload|update-body|delete] --tag TAG --token TOKEN --api-base URL"); return 1; } switch ($action) { case 'create': $existing = $this->getReleaseByTag($apiBase, $tag, $token); if ($existing !== null) { $existingId = $existing['id']; $this->releaseGiteaApi("{$apiBase}/releases/{$existingId}", 'DELETE', $token); $this->releaseGiteaApi("{$apiBase}/tags/{$tag}", 'DELETE', $token); echo "Deleted previous release: {$tag} (id: {$existingId})\n"; } $payload = json_encode(['tag_name' => $tag, 'name' => $name ?? $tag, 'body' => $body ?? '', 'target_commitish' => $target]); $result = $this->releaseGiteaApi("{$apiBase}/releases", 'POST', $token, $payload); if ($result['code'] >= 200 && $result['code'] < 300) { $releaseId = $result['data']['id'] ?? 'unknown'; echo "Release created: {$name} (tag: {$tag}, id: {$releaseId})\n"; } else { $this->log('ERROR', "Failed to create release: HTTP {$result['code']}"); return 1; } break; case 'upload': if (empty($files)) { $this->log('ERROR', "No files specified. Use --files /path/to/file1,/path/to/file2"); return 1; } $release = $this->getReleaseByTag($apiBase, $tag, $token); if ($release === null) { $this->log('ERROR', "No release found for tag: {$tag}"); return 1; } $releaseId = $release['id']; $assetsResult = $this->releaseGiteaApi("{$apiBase}/releases/{$releaseId}/assets", 'GET', $token); $existingAssets = $assetsResult['data'] ?? []; foreach ($files as $filePath) { $filePath = trim($filePath); if (!file_exists($filePath)) { $this->log('ERROR', "File not found: {$filePath}"); continue; } $fileName = basename($filePath); foreach ($existingAssets as $asset) { if (($asset['name'] ?? '') === $fileName) { $this->releaseGiteaApi("{$apiBase}/releases/{$releaseId}/assets/{$asset['id']}", 'DELETE', $token); echo "Deleted existing asset: {$fileName}\n"; break; } } $uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($fileName); $result = $this->releaseGiteaApi($uploadUrl, 'POST', $token, null, $filePath); if ($result['code'] >= 200 && $result['code'] < 300) { echo "Uploaded: {$fileName}\n"; } else { $this->log('ERROR', "Failed to upload {$fileName}: HTTP {$result['code']}"); } } break; case 'update-body': $release = $this->getReleaseByTag($apiBase, $tag, $token); if ($release === null) { $this->log('ERROR', "No release found for tag: {$tag}"); return 1; } $payload = json_encode(['body' => $body ?? '']); $result = $this->releaseGiteaApi("{$apiBase}/releases/{$release['id']}", 'PATCH', $token, $payload); if ($result['code'] >= 200 && $result['code'] < 300) { echo "Release body updated for tag: {$tag}\n"; } else { $this->log('ERROR', "Failed to update body: HTTP {$result['code']}"); return 1; } break; case 'delete': $existing = $this->getReleaseByTag($apiBase, $tag, $token); if ($existing !== null) { $this->releaseGiteaApi("{$apiBase}/releases/{$existing['id']}", 'DELETE', $token); $this->releaseGiteaApi("{$apiBase}/tags/{$tag}", 'DELETE', $token); echo "Deleted: {$tag} (id: {$existing['id']})\n"; } else { echo "No release found for tag: {$tag}\n"; } break; default: $this->log('ERROR', "Unknown action: {$action}"); return 1; } return 0; } private function releaseGiteaApi(string $url, string $method, string $token, ?string $jsonBody = null, ?string $filePath = null): array { $ch = curl_init($url); $headers = ["Authorization: token {$token}"]; $opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 60, CURLOPT_CUSTOMREQUEST => $method]; if ($jsonBody !== null) { $headers[] = 'Content-Type: application/json'; $opts[CURLOPT_POSTFIELDS] = $jsonBody; } elseif ($filePath !== null) { $headers[] = 'Content-Type: application/octet-stream'; $opts[CURLOPT_POSTFIELDS] = file_get_contents($filePath); } $opts[CURLOPT_HTTPHEADER] = $headers; curl_setopt_array($ch, $opts); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ['code' => $httpCode, 'data' => json_decode($response ?: '{}', true) ?: []]; } private function getReleaseByTag(string $apiBase, string $tag, string $token): ?array { $result = $this->releaseGiteaApi("{$apiBase}/releases/tags/{$tag}", 'GET', $token); return ($result['code'] === 200 && isset($result['data']['id'])) ? $result['data'] : null; } } $app = new ReleaseManageCli(); exit($app->execute());