#!/usr/bin/env php * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: MokoStandards.CLI * INGROUP: MokoStandards * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/release.php * BRIEF: Automate the MokoStandards version branch release flow * * USAGE * php cli/release.php # Release current version * php cli/release.php --bump minor # Bump minor, then release * php cli/release.php --bump major # Bump major, then release * php cli/release.php --dry-run # Preview without changes */ declare(strict_types=1); $dryRun = in_array('--dry-run', $argv); $bumpType = null; foreach ($argv as $i => $arg) { if ($arg === '--bump' && isset($argv[$i + 1])) { $bumpType = $argv[$i + 1]; // patch | minor | major } } $repoRoot = dirname(__DIR__, 2); $syncFile = "{$repoRoot}/lib/Enterprise/RepositorySynchronizer.php"; // Check both workflow directories for the bulk-repo-sync workflow $bulkSyncFile = file_exists("{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml") ? "{$repoRoot}/.gitea/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)) { fwrite(STDERR, "No VERSION found in README.md\n"); exit(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: fwrite(STDERR, "Invalid bump type: {$bumpType} (use patch/minor/major)\n"); exit(1); } $newVersion = sprintf('%02d.%02d.%02d', $major, $minor, $patch); echo "Bumping: {$currentVersion} → {$newVersion}\n"; if (!$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 (!$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 (!$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 (!$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 (!$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 (!$dryRun) { passthru("cd " . escapeshellarg($repoRoot) . " && git push origin main:{$branch} --force 2>&1"); } } else { echo "Creating version branch: {$branch} (minor release)\n"; if (!$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 (!$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 "\n✅ Release {$currentVersion} complete\n"; echo " Branch: {$branch}\n"; echo " Tag: {$tag}\n"; echo " Next: run bulk sync to push to all repos\n";