#!/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.php * BRIEF: Automate the moko-platform version branch release flow */ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; use MokoEnterprise\CliFramework; class ReleaseCli extends CliFramework { protected function configure(): void { $this->setDescription('Automate the moko-platform version branch release flow'); $this->addArgument('--bump', 'Bump type: patch, minor, or major', ''); } protected function run(): int { $bumpType = $this->getArgument('--bump'); if (empty($bumpType)) { $bumpType = null; } $repoRoot = dirname(__DIR__, 2); $syncFile = "{$repoRoot}/lib/Enterprise/RepositorySynchronizer.php"; // Check both workflow directories for the bulk-repo-sync workflow $bulkSyncFile = file_exists("{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml") ? "{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml" : "{$repoRoot}/.github/workflows/bulk-repo-sync.yml"; $cleanupFile = "{$repoRoot}/templates/workflows/shared/repository-cleanup.yml.template"; // -- Step 1: Read current version -- $readme = "{$repoRoot}/README.md"; $content = file_get_contents($readme); if (!preg_match('/^\s*VERSION:\s*(\d{2})\.(\d{2})\.(\d{2})/m', $content, $m)) { $this->log('ERROR', 'No VERSION found in README.md'); return 1; } $major = (int)$m[1]; $minor = (int)$m[2]; $patch = (int)$m[3]; $currentVersion = sprintf('%02d.%02d.%02d', $major, $minor, $patch); // -- Step 2: Bump version if requested -- if ($bumpType) { switch ($bumpType) { case 'major': $major++; $minor = 0; $patch = 0; break; case 'minor': $minor++; $patch = 0; break; case 'patch': $patch++; break; default: $this->log('ERROR', "Invalid bump type: {$bumpType} (use patch/minor/major)"); return 1; } $newVersion = sprintf('%02d.%02d.%02d', $major, $minor, $patch); echo "Bumping: {$currentVersion} -> {$newVersion}\n"; if (!$this->dryRun) { // Update README.md $content = preg_replace( '/^(\s*VERSION:\s*)\d{2}\.\d{2}\.\d{2}/m', '${1}' . $newVersion, $content, 1 ); file_put_contents($readme, $content); // Propagate to all files echo "Propagating version to all files...\n"; passthru("php {$repoRoot}/api/maintenance/update_version_from_readme.php --path {$repoRoot}"); } $currentVersion = $newVersion; } else { echo "Version: {$currentVersion}\n"; } // Derive major.minor for branch naming (patches update existing branch) $versionParts = explode('.', $currentVersion); $minorVersion = $versionParts[0] . '.' . $versionParts[1]; $branch = "version/{$minorVersion}"; // -- Step 3: Update STANDARDS_VERSION + STANDARDS_MINOR constants -- echo "Updating STANDARDS_VERSION -> {$currentVersion}\n"; echo "Updating STANDARDS_MINOR -> {$minorVersion}\n"; if (!$this->dryRun) { $syncContent = file_get_contents($syncFile); $syncContent = preg_replace( "/STANDARDS_VERSION\s*=\s*'[^']+'/", "STANDARDS_VERSION = '{$currentVersion}'", $syncContent ); $syncContent = preg_replace( "/STANDARDS_MINOR\s*=\s*'[^']+'/", "STANDARDS_MINOR = '{$minorVersion}'", $syncContent ); file_put_contents($syncFile, $syncContent); } // -- Step 4: Update bulk-repo-sync.yml checkout ref -- echo "Updating bulk-repo-sync.yml -> {$branch}\n"; if (!$this->dryRun) { $bulkContent = file_get_contents($bulkSyncFile); $bulkContent = preg_replace( '/ref:\s*version\/[\d.]+/', "ref: {$branch}", $bulkContent ); file_put_contents($bulkSyncFile, $bulkContent); } // -- Step 5: Update repository-cleanup.yml current branch -- echo "Updating repository-cleanup.yml -> chore/sync-mokostandards-v{$minorVersion}\n"; if (!$this->dryRun) { $cleanupContent = file_get_contents($cleanupFile); $cleanupContent = preg_replace( '/CURRENT="chore\/sync-mokostandards-v[^"]*"/', "CURRENT=\"chore/sync-mokostandards-v{$minorVersion}\"", $cleanupContent ); file_put_contents($cleanupFile, $cleanupContent); } // -- Step 6: Commit changes -- if (!$this->dryRun) { echo "Committing...\n"; passthru("cd {$repoRoot} && git add -A && git commit -m \"chore(release): prepare {$currentVersion} release [skip ci]\""); passthru("cd {$repoRoot} && git pull --rebase 2>/dev/null; git push"); } // -- Step 7: Create or update version branch -- $isPatch = ($versionParts[2] ?? '00') !== '00'; if ($isPatch) { echo "Updating version branch: {$branch} (patch update)\n"; if (!$this->dryRun) { passthru("cd " . escapeshellarg($repoRoot) . " && git push origin main:{$branch} --force 2>&1"); } } else { echo "Creating version branch: {$branch} (minor release)\n"; if (!$this->dryRun) { $exitCode = 0; passthru("cd " . escapeshellarg($repoRoot) . " && git push origin main:{$branch} 2>&1", $exitCode); if ($exitCode !== 0) { echo "Branch {$branch} already exists — force updating\n"; passthru("cd " . escapeshellarg($repoRoot) . " && git push origin main:{$branch} --force 2>&1"); } } } // -- Step 8: Create git tag (never overwrite existing) -- $tag = "v{$currentVersion}"; echo "Creating tag {$tag}\n"; if (!$this->dryRun) { $exitCode = 0; passthru("cd {$repoRoot} && git tag {$tag} 2>/dev/null && git push origin {$tag} 2>/dev/null", $exitCode); if ($exitCode !== 0) { echo "Tag {$tag} already exists — skipping\n"; } } echo "\nRelease {$currentVersion} complete\n"; echo " Branch: {$branch}\n"; echo " Tag: {$tag}\n"; echo " Next: run bulk sync to push to all repos\n"; return 0; } } $app = new ReleaseCli(); exit($app->execute());