#!/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 * * Usage: * # Create a release * php release_manage.php --action create --tag stable --name "My Plugin 04.01.00" \ * --body "Release notes" --target main --token TOKEN --api-base URL * * # Upload assets to a release * php release_manage.php --action upload --tag stable --files "/tmp/pkg.zip,/tmp/pkg.tar.gz" \ * --token TOKEN --api-base URL * * # Update release body (e.g. add SHA checksums) * php release_manage.php --action update-body --tag stable --body "New body" \ * --token TOKEN --api-base URL * * # Delete a release and its tag * php release_manage.php --action delete --tag stable --token TOKEN --api-base URL * * Options: * --action create | upload | update-body | delete (required) * --tag Release tag name (required) * --name Release name/title (for create) * --body Release body/description (for create, update-body) * --body-file Read body from file instead of --body * --target Target branch/commitish (for create, default: main) * --files Comma-separated file paths to upload (for upload) * --token Gitea API token (or GA_TOKEN/GITEA_TOKEN env var) * --api-base Gitea API base URL (e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo) * * NOTE: This script uses PHP curl for all HTTP operations (no shell calls). */ declare(strict_types=1); $action = null; $tag = null; $name = null; $body = null; $bodyFile = null; $target = 'main'; $files = []; $token = null; $apiBase = null; foreach ($argv as $i => $arg) { if ($arg === '--action' && isset($argv[$i + 1])) $action = $argv[$i + 1]; if ($arg === '--tag' && isset($argv[$i + 1])) $tag = $argv[$i + 1]; if ($arg === '--name' && isset($argv[$i + 1])) $name = $argv[$i + 1]; if ($arg === '--body' && isset($argv[$i + 1])) $body = $argv[$i + 1]; if ($arg === '--body-file' && isset($argv[$i + 1])) $bodyFile = $argv[$i + 1]; if ($arg === '--target' && isset($argv[$i + 1])) $target = $argv[$i + 1]; if ($arg === '--files' && isset($argv[$i + 1])) $files = array_filter(explode(',', $argv[$i + 1])); if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1]; if ($arg === '--api-base' && isset($argv[$i + 1])) $apiBase = $argv[$i + 1]; } // Allow token from environment if ($token === null) { $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null; } // Read body from file if specified if ($bodyFile !== null && file_exists($bodyFile)) { $body = file_get_contents($bodyFile); } if ($action === null || $tag === null || $token === null || $apiBase === null) { fwrite(STDERR, "Usage: release_manage.php --action [create|upload|update-body|delete] --tag TAG --token TOKEN --api-base URL\n"); exit(1); } /** * Make a Gitea API request using curl */ 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); $data = json_decode($response ?: '{}', true) ?: []; return ['code' => $httpCode, 'data' => $data]; } /** * Get release by tag */ function getReleaseByTag(string $apiBase, string $tag, string $token): ?array { $result = releaseGiteaApi("{$apiBase}/releases/tags/{$tag}", 'GET', $token); if ($result['code'] === 200 && isset($result['data']['id'])) { return $result['data']; } return null; } // -- Action dispatch ---------------------------------------------------------- switch ($action) { case 'create': // Delete existing release if present $existing = getReleaseByTag($apiBase, $tag, $token); if ($existing !== null) { $existingId = $existing['id']; releaseGiteaApi("{$apiBase}/releases/{$existingId}", 'DELETE', $token); 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 = 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 { fwrite(STDERR, "Failed to create release: HTTP {$result['code']}\n"); fwrite(STDERR, json_encode($result['data']) . "\n"); exit(1); } break; case 'upload': if (empty($files)) { fwrite(STDERR, "No files specified. Use --files /path/to/file1,/path/to/file2\n"); exit(1); } $release = getReleaseByTag($apiBase, $tag, $token); if ($release === null) { fwrite(STDERR, "No release found for tag: {$tag}\n"); exit(1); } $releaseId = $release['id']; // Get existing assets to avoid duplicates $assetsResult = releaseGiteaApi("{$apiBase}/releases/{$releaseId}/assets", 'GET', $token); $existingAssets = $assetsResult['data'] ?? []; foreach ($files as $filePath) { $filePath = trim($filePath); if (!file_exists($filePath)) { fwrite(STDERR, "File not found: {$filePath}\n"); continue; } $fileName = basename($filePath); // Delete existing asset with same name foreach ($existingAssets as $asset) { if (($asset['name'] ?? '') === $fileName) { releaseGiteaApi("{$apiBase}/releases/{$releaseId}/assets/{$asset['id']}", 'DELETE', $token); echo "Deleted existing asset: {$fileName}\n"; break; } } // Upload $uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($fileName); $result = releaseGiteaApi($uploadUrl, 'POST', $token, null, $filePath); if ($result['code'] >= 200 && $result['code'] < 300) { echo "Uploaded: {$fileName}\n"; } else { fwrite(STDERR, "Failed to upload {$fileName}: HTTP {$result['code']}\n"); } } break; case 'update-body': $release = getReleaseByTag($apiBase, $tag, $token); if ($release === null) { fwrite(STDERR, "No release found for tag: {$tag}\n"); exit(1); } $releaseId = $release['id']; $payload = json_encode(['body' => $body ?? '']); $result = releaseGiteaApi("{$apiBase}/releases/{$releaseId}", 'PATCH', $token, $payload); if ($result['code'] >= 200 && $result['code'] < 300) { echo "Release body updated for tag: {$tag}\n"; } else { fwrite(STDERR, "Failed to update body: HTTP {$result['code']}\n"); exit(1); } break; case 'delete': $existing = getReleaseByTag($apiBase, $tag, $token); if ($existing !== null) { releaseGiteaApi("{$apiBase}/releases/{$existing['id']}", 'DELETE', $token); releaseGiteaApi("{$apiBase}/tags/{$tag}", 'DELETE', $token); echo "Deleted: {$tag} (id: {$existing['id']})\n"; } else { echo "No release found for tag: {$tag}\n"; } break; default: fwrite(STDERR, "Unknown action: {$action}\n"); fwrite(STDERR, "Valid actions: create, upload, update-body, delete\n"); exit(1); } exit(0);