Files
MokoCLI/cli/version_auto_bump.php
T
jmiller 7728b6b4ac
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 12s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 10s
Platform: mokocli CI / Gate 1: Code Quality (pull_request) Failing after 1m3s
Platform: mokocli CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: mokocli CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: mokocli CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: mokocli CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
refactor(rename): Gitea/MokoGitea -> MokoGit across the codebase [#4]
MokoGitea instance rebrand to MokoGit. Content sweep over 265 files (ordered to avoid
moko-prefix doubling) plus path renames:
- .mokogitea/ -> .mokogit/ (repo + all mcp/servers/*)
- templates/mokogitea/ -> templates/mokogit/
- lib/Enterprise/MokoGiteaAdapter.php -> MokoGitAdapter.php (class MokoGitAdapter)
- mcp/servers/mokogitea_{skill,api} -> mokogit_{skill,api}; skills/mokogitea -> skills/mokogit
- automation/migrate_to_gitea.php -> migrate_to_mokogit.php
- env/secret names GITEA_* -> MOKOGIT_*

Note: ~/.claude/.mcp.json server flipped to @mokoconsulting/mcp-mokogit (publish pending).
2026-07-16 16:49:12 -05:00

305 lines
11 KiB
PHP

#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: mokocli.CLI
* INGROUP: mokocli
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
* PATH: /cli/version_auto_bump.php
* VERSION: 09.38.05
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
*/
declare(strict_types=1);
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
use MokoCli\CliFramework;
class VersionAutoBumpCli extends CliFramework
{
protected function configure(): void
{
$this->setDescription('Auto patch-bump, set stability suffix, and commit');
$this->addArgument('--path', 'Repository root path', '.');
$this->addArgument('--branch', 'Git branch name', '');
$this->addArgument('--token', 'API token for push', '');
$this->addArgument('--repo-url', 'Repository URL for git remote', '');
$this->addArgument('--watch-path', 'Path to watch for changes', '');
}
protected function run(): int
{
$path = $this->getArgument('--path');
$branch = $this->getArgument('--branch');
$token = $this->getArgument('--token');
$repoUrl = $this->getArgument('--repo-url');
$watchPath = $this->getArgument('--watch-path');
// Auto-detect branch from git or CI env
if ($branch === '') {
$branch = getenv('GITHUB_REF_NAME') ?: trim((string) @shell_exec('git rev-parse --abbrev-ref HEAD 2>/dev/null'));
if (empty($branch) || $branch === 'HEAD') {
$this->log('ERROR', 'Cannot detect branch — pass --branch');
return 1;
}
}
// Map branch to stability suffix
$stabilityMap = [
'dev' => 'dev',
'alpha' => 'alpha',
'beta' => 'beta',
'rc' => 'rc',
];
if (array_key_exists($branch, $stabilityMap)) {
$stability = $stabilityMap[$branch];
} elseif (str_starts_with($branch, 'feature/') || str_starts_with($branch, 'patch/')) {
$stability = 'dev';
} else {
$stability = 'dev';
}
$cli = __DIR__;
$php = '"' . PHP_BINARY . '"';
// Watch-path was previously read from .mokogit/manifest.xml <build><entry-point>.
// That manifest is retired; when --watch-path is not supplied we simply skip the
// change-gate and always bump (the prior no-manifest behavior).
// Check if code files actually changed (skip bump for docs/config-only changes)
$shouldBump = true;
if (!empty($watchPath)) {
$root = realpath($path) ?: $path;
$cdCmd = PHP_OS_FAMILY === 'Windows' ? "cd /d " : "cd ";
$diffOutput = trim((string) @shell_exec(
$cdCmd . escapeshellarg($root)
. " && git diff --name-only HEAD~1 HEAD -- "
. escapeshellarg($watchPath) . " 2>/dev/null"
));
if (empty($diffOutput)) {
echo "No changes in {$watchPath} — skipping version bump\n";
$shouldBump = false;
} else {
echo "Changes detected in {$watchPath}:\n{$diffOutput}\n";
}
}
if (!$shouldBump) {
echo "No code changes — nothing to do\n";
return 0;
}
// Step 1: Patch bump
$bumpOutput = [];
exec("{$php} {$cli}/version_bump.php --path " . escapeshellarg($path) . " 2>&1", $bumpOutput, $bumpRc);
foreach ($bumpOutput as $line) {
echo "{$line}\n";
}
// Step 2: Read version (--quiet suppresses banner so only the version is output)
$versionOutput = [];
exec("{$php} {$cli}/version_read.php --path " . escapeshellarg($path) . " --quiet 2>&1", $versionOutput, $versionRc);
// Take the last non-empty line — the version is always the final output
$version = '';
foreach (array_reverse($versionOutput) as $line) {
$line = trim($line);
if (preg_match('/^\d{2}\.\d{2}\.\d{2}/', $line)) {
$version = $line;
break;
}
}
if (empty($version)) {
echo "No version found — skipping\n";
return 0;
}
echo "Version: {$version} | Branch: {$branch} | Stability: {$stability}\n";
// Step 3: Set platform version with stability suffix
$setPlatOutput = [];
exec("{$php} {$cli}/version_set_platform.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($version)
. " --branch " . escapeshellarg($branch)
. " --stability " . escapeshellarg($stability) . " 2>&1", $setPlatOutput);
foreach ($setPlatOutput as $line) {
echo "{$line}\n";
}
// Step 4: Version consistency check and fix
exec("{$php} {$cli}/version_check.php --path " . escapeshellarg($path) . " --fix 2>&1", $checkOutput);
// Step 4b (#351/#354): keep Joomla #__schemas in lockstep with the stamped manifest.
// Must run AFTER version_set_platform/version_check have stamped each manifest <version>,
// and BEFORE the git commit below so markers are staged in the same bump commit.
$markerRoot = realpath($path) ?: $path;
foreach ($this->writeSchemaMarkers($markerRoot) as $marker) {
echo "Schema marker: {$marker}\n";
}
// Re-read version (now includes suffix from version_set_platform)
$suffixMap = [
'dev' => '-dev',
'alpha' => '-alpha',
'beta' => '-beta',
'rc' => '-rc',
];
$displayVersion = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $version) . ($suffixMap[$stability] ?? '');
if ($this->dryRun) {
echo "[DRY-RUN] Would commit and push {$displayVersion} to {$branch}\n";
return 0;
}
// Step 5: Git commit and push
$root = realpath($path) ?: $path;
// Check if anything changed
$cdPrefix = PHP_OS_FAMILY === 'Windows' ? "cd /d " : "cd ";
$diffStatus = trim((string) @shell_exec(
$cdPrefix . escapeshellarg($root)
. " && git diff --quiet && git diff --cached --quiet"
. " 2>&1 && echo clean || echo dirty"
));
if ($diffStatus === 'clean') {
echo "No version changes to commit\n";
return 0;
}
// Configure git
$cd = PHP_OS_FAMILY === 'Windows' ? "cd /d " : "cd ";
$cdRoot = $cd . escapeshellarg($root);
@shell_exec(
$cdRoot . " && git config --local user.email"
. " \"mokogit-actions[bot]@mokoconsulting.tech\""
);
@shell_exec(
$cdRoot . " && git config --local user.name"
. " \"mokogit-actions[bot]\""
);
if (!empty($repoUrl)) {
@shell_exec(
$cdRoot . " && git remote set-url origin "
. escapeshellarg($repoUrl)
);
}
@shell_exec($cdRoot . " && git add -A");
$commitMsg = $shouldBump
? "chore(version): auto-bump patch {$displayVersion} [skip ci]"
: "chore(version): set {$stability} suffix {$displayVersion} [skip ci]";
@shell_exec(
$cdRoot . " && git commit -m " . escapeshellarg($commitMsg)
. " --author=\"mokogit-actions[bot]"
. " <mokogit-actions[bot]@mokoconsulting.tech>\""
);
$pushResult = @shell_exec(
$cdRoot . " && git push origin "
. escapeshellarg($branch) . " 2>&1"
);
echo $pushResult ?? '';
echo "Bumped to {$displayVersion}\n";
return 0;
}
/**
* Issues #351/#354 — keep Joomla #__schemas in lockstep with the manifest version.
*
* Joomla advances #__schemas only to the highest schema-update file whose version is
* <= the manifest <version>. When auto-bump climbs the manifest without adding a matching
* marker, #__schemas freezes at the last real migration and com_installer perpetually
* reports the component "a schema version behind". For every Joomla extension manifest that
* declares <update><schemas><schemapath ...>, write a comment-only no-op marker named
* exactly <manifest-version>.sql (idempotent). Reading the version back out of the stamped
* manifest guarantees the filename equals — never exceeds — the manifest version.
*
* @param string $root Repository root.
* @return string[] Marker paths created (each suffixed " (would create)" under --dry-run).
*/
private function writeSchemaMarkers(string $root): array
{
$created = [];
// Joomla manifests may sit at the repo root or under a source/packages tree.
$manifests = array_unique(array_merge(
glob("{$root}/*.xml") ?: [],
glob("{$root}/*/*.xml") ?: [],
glob("{$root}/*/packages/*/*.xml") ?: []
));
foreach ($manifests as $file) {
$xml = @simplexml_load_file($file);
if ($xml === false) {
continue;
}
// Only Joomla extension manifests that actually declare a schema update path.
if (!isset($xml->update->schemas->schemapath)) {
continue;
}
$version = trim((string) $xml->version);
// Guard: only stamp when the manifest carries a well-formed platform version.
if (!preg_match('/^\d{2}\.\d{2}\.\d{2}((?:-(?:dev|alpha|beta|rc))+)?$/', $version)) {
continue;
}
// Resolve the admin-relative root: <administration><files folder="admin">.
$baseDir = dirname($file);
$adminFolder = isset($xml->administration->files['folder'])
? trim((string) $xml->administration->files['folder'])
: '';
$adminDir = $adminFolder !== '' ? "{$baseDir}/{$adminFolder}" : $baseDir;
// One marker per driver-specific schemapath (mysql, postgresql, …).
foreach ($xml->update->schemas->schemapath as $schemapath) {
$rel = trim((string) $schemapath);
if ($rel === '') {
continue;
}
$dir = "{$adminDir}/{$rel}";
$marker = "{$dir}/{$version}.sql";
// Idempotent: never overwrite an existing marker (real or prior no-op).
if (file_exists($marker)) {
continue;
}
if ($this->dryRun) {
$created[] = "{$marker} (would create)";
continue;
}
if (!is_dir($dir) && !@mkdir($dir, 0755, true) && !is_dir($dir)) {
$this->log('WARNING', "Could not create schemapath dir: {$dir}");
continue;
}
$body = "/* {$version} — no schema changes (auto version marker) */\n";
if (file_put_contents($marker, $body) !== false) {
$created[] = $marker;
}
}
}
return $created;
}
}
$app = new VersionAutoBumpCli();
exit($app->execute());