cbfa23c4c4
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Repository health (push) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 45s
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: PR Check / Build RC Package (pull_request) Successful in 2s
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Failing after 44s
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Failing after 48s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Failing after 48s
Platform: moko-platform CI / Gate 4: Governance (push) Successful in 48s
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Failing after 50s
Platform: moko-platform CI / Gate 5: Template Integrity (push) Failing after 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Successful in 1m13s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Failing after 5s
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Failing after 42s
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Failing after 45s
Platform: moko-platform CI / Gate 4: Governance (pull_request) Successful in 44s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Failing after 47s
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Failing after 49s
Real bugs found and fixed: - bulk_joomla_template: $org undefined in heredoc (missing parameter) - RepositorySynchronizer: $root undefined (should be $repoRoot), duplicate array key - RepositoryHealthChecker: wrong class name (UnifiedValidation → UnifiedValidator) - scan_drift: missing $adapter property declaration - auto_detect_platform: wrong method name (detectProjectType → detect) - EnterpriseReadinessValidator: void return used as value - check_client_theme: extra parameter to printSummary() - ApiClient: unused constructor parameter now stored - GitPlatformAdapter: added listBranches/getCloneUrl/cloneRepo to interface - MokoGiteaAdapter/GitHubAdapter: implemented new interface methods 3 legacy CLIApp scripts excluded (need migration to CliFramework): repo_cleanup.php, push_files.php, joomla_release.php Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
240 lines
7.7 KiB
PHP
240 lines
7.7 KiB
PHP
#!/usr/bin/env php
|
|
<?php
|
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* 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);
|