#!/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_cascade.php * BRIEF: Delete lesser pre-release channels from Gitea when promoting stability * * Usage: * php release_cascade.php --stability stable --token TOKEN --api-base URL * php release_cascade.php --stability rc --token TOKEN --api-base URL * php release_cascade.php --stability stable --version 09.01.00 --token TOKEN --api-base URL * * Cascade rules: * stable -> deletes development, alpha, beta, release-candidate * rc -> deletes development, alpha, beta * beta -> deletes development, alpha * alpha -> deletes development * * When --version is given, also deletes releases on any channel whose version * is lower than the specified version (prevents stale pre-releases lingering). */ declare(strict_types=1); $stability = null; $token = null; $apiBase = null; $version = null; foreach ($argv as $i => $arg) { if ($arg === '--stability' && isset($argv[$i + 1])) { $stability = $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]; } if ($arg === '--version' && isset($argv[$i + 1])) { $version = $argv[$i + 1]; } } // Allow token from environment if ($token === null) { $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null; } if ($stability === null || $token === null || $apiBase === null) { fwrite(STDERR, "Usage: release_cascade.php --stability [stable|rc|beta|alpha] --token TOKEN --api-base URL\n"); fwrite(STDERR, " --api-base: e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo\n"); fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n"); exit(1); } // Define cascade hierarchy $cascadeMap = [ 'stable' => ['development', 'alpha', 'beta', 'release-candidate'], 'release-candidate' => ['development', 'alpha', 'beta'], 'rc' => ['development', 'alpha', 'beta'], 'beta' => ['development', 'alpha'], 'alpha' => ['development'], ]; if (!isset($cascadeMap[$stability])) { fwrite(STDERR, "Unknown stability level: {$stability}\n"); fwrite(STDERR, "Valid options: stable, rc, beta, alpha\n"); exit(1); } $tagsToDelete = $cascadeMap[$stability]; $deleted = 0; foreach ($tagsToDelete as $tag) { // Get release by tag $ch = curl_init("{$apiBase}/releases/tags/{$tag}"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200 || empty($response)) { continue; } $data = json_decode($response, true); $releaseId = $data['id'] ?? null; if ($releaseId === null) { continue; } // Delete release $ch = curl_init("{$apiBase}/releases/{$releaseId}"); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30, ]); curl_exec($ch); curl_close($ch); // Delete tag $ch = curl_init("{$apiBase}/tags/{$tag}"); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30, ]); curl_exec($ch); curl_close($ch); echo "Deleted: {$tag} (release id: {$releaseId})\n"; $deleted++; } // ── Version-aware cleanup: delete releases with lesser version numbers ─────── if ($version !== null) { // Normalize version for comparison (strip any suffix) $baseVersion = preg_replace('/-[a-z]+$/', '', $version); // Check all channels (including ones not in the cascade map for this stability) $allChannels = ['development', 'alpha', 'beta', 'release-candidate', 'stable']; foreach ($allChannels as $tag) { // Skip the current stability channel if ($tag === $stability) { continue; } // Skip channels already deleted by cascade above if (in_array($tag, $tagsToDelete, true)) { continue; } $ch = curl_init("{$apiBase}/releases/tags/{$tag}"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200 || empty($response)) { continue; } $data = json_decode($response, true); $releaseId = $data['id'] ?? null; $releaseName = $data['name'] ?? ''; if ($releaseId === null) { continue; } // Extract version from release name (e.g. "element 09.00.01 (development)") $releaseVersion = null; if (preg_match('/(\d{2}\.\d{2}\.\d{2})/', $releaseName, $vm)) { $releaseVersion = $vm[1]; } if ($releaseVersion === null) { continue; } // Delete if release version is less than the promoted version if (version_compare($releaseVersion, $baseVersion, '<')) { $delCh = curl_init("{$apiBase}/releases/{$releaseId}"); curl_setopt_array($delCh, [ CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30, ]); curl_exec($delCh); curl_close($delCh); $tagCh = curl_init("{$apiBase}/tags/{$tag}"); curl_setopt_array($tagCh, [ CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30, ]); curl_exec($tagCh); curl_close($tagCh); echo "Deleted: {$tag} — version {$releaseVersion} < {$baseVersion}\n"; $deleted++; } } } echo "Cleaned up {$deleted} pre-release channel(s)\n"; exit(0);