#!/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/version_bump_remote.php * BRIEF: Bump version in manifest XML and CHANGELOG.md on a remote branch via Gitea API * * Usage: * php version_bump_remote.php --path . --branch dev --bump minor --token TOKEN --api-base URL * php version_bump_remote.php --path . --branch dev --bump patch --token TOKEN --api-base URL * php version_bump_remote.php --path . --branch dev --bump minor --no-changelog --token TOKEN --api-base URL * * Options: * --path Repository root (reads current version from local manifest) * --branch Target branch to bump (required, e.g. dev) * --bump Bump type: patch | minor | major (default: minor) * --token Gitea API token (or GA_TOKEN env var) * --api-base Gitea API base URL for the repo * --no-changelog Skip CHANGELOG.md bump * --repo Repository path (owner/repo) for API base construction * --gitea-url Gitea instance URL (default: env GITEA_URL) */ declare(strict_types=1); $path = '.'; $branch = null; $bumpType = 'minor'; $token = null; $apiBase = null; $noChangelog = false; $repo = null; $giteaUrl = null; foreach ($argv as $i => $arg) { if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1]; if ($arg === '--branch' && isset($argv[$i + 1])) $branch = $argv[$i + 1]; if ($arg === '--bump' && isset($argv[$i + 1])) $bumpType = $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 === '--no-changelog') $noChangelog = true; if ($arg === '--repo' && isset($argv[$i + 1])) $repo = $argv[$i + 1]; if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1]; } if ($token === null) $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null; if ($giteaUrl === null) $giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech'; if ($apiBase === null && $repo !== null) { $apiBase = rtrim($giteaUrl, '/') . '/api/v1/repos/' . $repo; } if ($branch === null || $token === null || $apiBase === null) { fwrite(STDERR, "Usage: version_bump_remote.php --branch BRANCH --token TOKEN --api-base URL [--bump minor|patch|major]\n"); fwrite(STDERR, " or: version_bump_remote.php --branch BRANCH --token TOKEN --repo owner/repo\n"); exit(1); } $root = realpath($path) ?: $path; // ── Read current version from local manifest ──────────────────────────── $version = null; $manifestFile = null; $searchDirs = ["{$root}/src", $root]; foreach ($searchDirs as $dir) { if (!is_dir($dir)) continue; foreach (glob("{$dir}/*.xml") ?: [] as $f) { $xml = file_get_contents($f); if (strpos($xml, '') !== false) { if (preg_match('|(\d{2}\.\d{2}\.\d{2})|', $xml, $m)) { if ($version === null || version_compare($m[1], $version, '>')) { $version = $m[1]; $manifestFile = basename($f); } } } } } if ($version === null) { fwrite(STDERR, "No version found in manifest XML\n"); exit(1); } // ── Compute next version ──────────────────────────────────────────────── if (!preg_match('/^(\d{2})\.(\d{2})\.(\d{2})$/', $version, $parts)) { fwrite(STDERR, "Invalid version format: {$version}\n"); exit(1); } $major = (int)$parts[1]; $minor = (int)$parts[2]; $patch = (int)$parts[3]; switch ($bumpType) { case 'major': $major++; $minor = 0; $patch = 0; break; case 'minor': $minor++; $patch = 0; break; default: $patch++; break; } $nextVersion = sprintf('%02d.%02d.%02d', $major, $minor, $patch); echo "{$version} -> {$nextVersion} ({$branch})\n"; // ── Helper: Gitea API request ─────────────────────────────────────────── function giteaApi(string $method, string $url, string $token, ?string $body = null): ?array { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: token {$token}", 'Content-Type: application/json', ], CURLOPT_CUSTOMREQUEST => $method, CURLOPT_TIMEOUT => 30, ]); 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 >= 400 || $response === false) { return null; } return json_decode($response, true) ?: []; } // ── Helper: Update a file on a remote branch ──────────────────────────── function updateRemoteFile( string $apiBase, string $token, string $filePath, string $branch, callable $transform, string $commitMessage ): bool { $url = "{$apiBase}/contents/{$filePath}?ref={$branch}"; $file = giteaApi('GET', $url, $token); if ($file === null || !isset($file['sha']) || !isset($file['content'])) { return false; } $content = base64_decode($file['content']); $newContent = $transform($content); if ($newContent === $content) { fwrite(STDERR, " {$filePath}: no changes needed\n"); return true; } $payload = json_encode([ 'content' => base64_encode($newContent), 'sha' => $file['sha'], 'message' => $commitMessage, 'branch' => $branch, ]); $result = giteaApi('PUT', "{$apiBase}/contents/{$filePath}", $token, $payload); if ($result === null) { fwrite(STDERR, " {$filePath}: failed to update\n"); return false; } echo " {$filePath}: updated on {$branch}\n"; return true; } // ── Update manifest XML on the remote branch ──────────────────────────── $manifestPaths = []; if ($manifestFile !== null) { $manifestPaths[] = "src/{$manifestFile}"; } $manifestPaths = array_merge($manifestPaths, [ 'src/templateDetails.xml', 'src/manifest.xml', ]); $manifestUpdated = false; foreach ($manifestPaths as $mPath) { $result = updateRemoteFile( $apiBase, $token, $mPath, $branch, function (string $content) use ($version, $nextVersion): string { return str_replace( "{$version}", "{$nextVersion}", $content ); }, "chore(version): bump {$version} -> {$nextVersion} [skip ci]" ); if ($result) { $manifestUpdated = true; break; } } if (!$manifestUpdated) { fwrite(STDERR, "WARNING: could not update manifest on {$branch}\n"); } // ── Update CHANGELOG.md on the remote branch ──────────────────────────── if (!$noChangelog) { updateRemoteFile( $apiBase, $token, 'CHANGELOG.md', $branch, function (string $content) use ($version, $nextVersion): string { $content = str_replace("VERSION: {$version}", "VERSION: {$nextVersion}", $content); if (strpos($content, '[Unreleased]') === false && strpos($content, "## [{$nextVersion}]") === false ) { $marker = "## [{$version}]"; if (strpos($content, $marker) !== false) { $unreleased = "## [{$nextVersion}] - Unreleased\n\n### Added\n\n### Changed\n\n### Fixed\n\n"; $content = str_replace($marker, $unreleased . $marker, $content); } } return $content; }, "chore(version): bump CHANGELOG {$version} -> {$nextVersion} [skip ci]" ); } exit(0);