#!/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_create.php * BRIEF: Create or overwrite a Gitea release with proper naming * * Usage: * php release_create.php --version 09.01.00 --tag stable --token TOKEN --api-base URL * php release_create.php --version 09.01.00 --tag development --token TOKEN --api-base URL --prerelease * php release_create.php --version 09.01.00 --tag stable --token TOKEN --api-base URL --path . --repo MyRepo * * Replaces the inline bash in auto-release.yml Step 7b. * Detects extension metadata from manifest, builds a proper release name, * generates release notes, and creates (or overwrites) a Gitea release. */ declare(strict_types=1); // ── Argument parsing ──────────────────────────────────────────────────────── $path = '.'; $version = null; $tag = null; $token = null; $apiBase = null; $branch = 'main'; $repoName = ''; $prerelease = false; foreach ($argv as $i => $arg) { if ($arg === '--path' && isset($argv[$i + 1])) { $path = $argv[$i + 1]; } if ($arg === '--version' && isset($argv[$i + 1])) { $version = $argv[$i + 1]; } if ($arg === '--tag' && isset($argv[$i + 1])) { $tag = $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 === '--branch' && isset($argv[$i + 1])) { $branch = $argv[$i + 1]; } if ($arg === '--repo' && isset($argv[$i + 1])) { $repoName = $argv[$i + 1]; } if ($arg === '--prerelease') { $prerelease = true; } } // Allow token from environment if ($token === null) { $envToken = getenv('GA_TOKEN'); if ($envToken === false || $envToken === '') { $envToken = getenv('GITEA_TOKEN'); } if ($envToken !== false && $envToken !== '') { $token = $envToken; } } if ($version === null || $tag === null || $token === null || $apiBase === null) { fwrite(STDERR, "Usage: release_create.php --version VER --tag TAG --token TOKEN --api-base URL [options]\n"); fwrite(STDERR, " --path . Repo root for manifest detection (default: .)\n"); fwrite(STDERR, " --branch main Target commitish (default: main)\n"); fwrite(STDERR, " --repo REPO Repo name for fallback element detection\n"); fwrite(STDERR, " --prerelease Mark release as prerelease\n"); fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n"); exit(1); } // ── Helper: Gitea API request ─────────────────────────────────────────────── /** * Send a request to the Gitea API. * * @param string $url Full API URL * @param string $token Authorization token * @param string $method HTTP method (GET, POST, DELETE, etc.) * @param string|null $body JSON request body * * @return array|null Decoded response or null on failure */ function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array { $ch = curl_init($url); if ($ch === false) { return null; } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: token {$token}", 'Content-Type: application/json', ], CURLOPT_TIMEOUT => 30, CURLOPT_CUSTOMREQUEST => $method, ]); if ($body !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, $body); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode < 200 || $httpCode >= 300 || empty($response) || !is_string($response)) { return null; } $decoded = json_decode($response, true); return is_array($decoded) ? $decoded : null; } // ── Detect element metadata ───────────────────────────────────────────────── $root = realpath($path) ?: $path; $extElement = ''; $extType = ''; $extFolder = ''; $extName = ''; $typePrefix = ''; // Detect platform from manifest.xml $platform = 'generic'; $manifestXml = "{$root}/.mokogitea/manifest.xml"; if (file_exists($manifestXml)) { $content = file_get_contents($manifestXml); if ($content !== false && preg_match('/([^<]+)<\/platform>/', $content, $pm)) { $platform = trim($pm[1]); } } // Find extension manifest (Joomla XML) $extManifest = null; $manifestFiles = array_merge( glob("{$root}/src/pkg_*.xml") ?: [], glob("{$root}/src/*.xml") ?: [], glob("{$root}/*.xml") ?: [] ); foreach ($manifestFiles as $file) { $c = file_get_contents($file); if ($c !== false && strpos($c, ', plugin= attribute, , or filename if (preg_match('/([^<]+)<\/element>/', $xml, $em)) { $extElement = $em[1]; } if (empty($extElement) && preg_match('/plugin="([^"]*)"/', $xml, $pm2)) { $extElement = $pm2[1]; } if ($extType === 'package' && preg_match('/([^<]+)<\/packagename>/', $xml, $pn)) { $extElement = $pn[1]; } if (empty($extElement)) { $extElement = strtolower(basename($extManifest, '.xml')); if (in_array($extElement, ['templatedetails', 'manifest'], true)) { $extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root))); } } // Human-readable name if (preg_match('/([^<]+)<\/name>/', $xml, $nm)) { $extName = trim($nm[1]); } break; case in_array($platform, ['dolibarr', 'crm-module'], true) && $modFile !== null: $extType = 'dolibarr-module'; $modBasename = basename($modFile, '.class.php'); $extElement = strtolower(preg_replace('/^mod/', '', $modBasename) ?? $modBasename); $modContent = file_get_contents($modFile); if ($modContent !== false && preg_match('/\$this->name\s*=\s*[\'"]([^\'"]+)[\'"]/', $modContent, $nm2)) { $extName = $nm2[1]; } break; default: $extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root))); $extType = 'generic'; break; } // Strip existing type prefix from element to prevent duplication $extElement = preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $extElement) ?? $extElement; // Compute type prefix switch ($extType) { case 'plugin': $typePrefix = "plg_{$extFolder}_"; break; case 'module': $typePrefix = 'mod_'; break; case 'component': $typePrefix = 'com_'; break; case 'template': $typePrefix = 'tpl_'; break; case 'library': $typePrefix = 'lib_'; break; case 'package': $typePrefix = 'pkg_'; break; } // Fallback name if (empty($extName)) { $extName = $repoName !== '' ? $repoName : basename($root); } echo "Element: {$extElement}, Type: {$extType}, Prefix: {$typePrefix}, Name: {$extName}\n"; // ── Build release name ────────────────────────────────────────────────────── $releaseName = "{$extName} {$version} ({$typePrefix}{$extElement}-{$version})"; echo "Release name: {$releaseName}\n"; // ── Generate release notes ────────────────────────────────────────────────── $releaseNotes = "Release {$version}"; $releaseNotesScript = dirname(__DIR__) . '/cli/release_notes.php'; if (file_exists($releaseNotesScript)) { $cmd = sprintf( 'php %s --path %s --version %s', escapeshellarg($releaseNotesScript), escapeshellarg($root), escapeshellarg($version) ); $output = []; $exitCode = 0; exec($cmd, $output, $exitCode); if ($exitCode === 0 && count($output) > 0) { $notes = implode("\n", $output); if (trim($notes) !== '') { $releaseNotes = $notes; echo "Release notes: generated from CHANGELOG.md\n"; } } } // ── Delete existing release at tag (if present) ───────────────────────────── $existing = giteaApi("{$apiBase}/releases/tags/{$tag}", $token); if ($existing !== null && !empty($existing['id'])) { $existingId = $existing['id']; echo "Deleting existing release: {$tag} (id: {$existingId})\n"; // Delete release giteaApi("{$apiBase}/releases/{$existingId}", $token, 'DELETE'); // Delete tag giteaApi("{$apiBase}/tags/{$tag}", $token, 'DELETE'); } // ── Create new release ────────────────────────────────────────────────────── $payload = json_encode([ 'tag_name' => $tag, 'target_commitish' => $branch, 'name' => $releaseName, 'body' => $releaseNotes, 'prerelease' => $prerelease, ]); $newRelease = giteaApi("{$apiBase}/releases", $token, 'POST', $payload !== false ? $payload : '{}'); if ($newRelease === null || empty($newRelease['id'])) { fwrite(STDERR, "Failed to create release at tag: {$tag}\n"); exit(1); } $releaseId = $newRelease['id']; echo "Created release: {$tag} (id: {$releaseId})\n"; // Output release_id to stdout for CI consumption echo "release_id={$releaseId}\n"; exit(0);