diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index dc76039..8673649 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -16,6 +16,10 @@ on: push: branches: - dev + - alpha + - beta + - rc + - 'feature/**' env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -57,29 +61,7 @@ jobs: - name: Bump version run: | - BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true - echo "$BUMP" - - VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true - [ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; } - - # Propagate to platform manifests with -dev suffix - php ${MOKO_CLI}/version_set_platform.php \ - --path . --version "$VERSION" --branch dev --stability dev 2>/dev/null || true - php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true - VERSION="${VERSION}-dev" - - # Commit if anything changed - if git diff --quiet && git diff --cached --quiet; then - echo "No version changes to commit" - exit 0 - fi - - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" - git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - git add -A - git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ - --author="gitea-actions[bot] " - git push origin dev - echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY + php ${MOKO_CLI}/version_auto_bump.php \ + --path . --branch "${GITHUB_REF_NAME}" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" \ + --repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 72ce95a..618cb76 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -165,9 +165,8 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi - # Strip any pre-release suffix merged from dev (e.g. 01.02.20-dev → 01.02.20) - VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') - MAJOR=$(echo "$VERSION" | cut -d. -f1) + # version_set_platform strips suffixes internally when --stability stable + MAJOR=$(echo "$VERSION" | cut -d. -f1 | sed 's/-.*//') echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "release_tag=stable" >> "$GITHUB_OUTPUT" echo "skip=false" >> "$GITHUB_OUTPUT" @@ -181,7 +180,7 @@ jobs: API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" RC_JSON=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ "${API_BASE}/releases/tags/release-candidate" 2>/dev/null || echo "{}") - RC_ID=$(echo "$RC_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true) + RC_ID=$(echo "$RC_JSON" | php -r "\$d=json_decode(file_get_contents('php://stdin'),true); echo \$d['id'] ?? '';" 2>/dev/null || true) if [ -n "$RC_ID" ] && [ "$RC_ID" != "None" ] && [ "$RC_ID" != "" ]; then echo "promote=true" >> "$GITHUB_OUTPUT" @@ -201,8 +200,7 @@ jobs: MOKO_API="/tmp/moko-platform-api/cli" php ${MOKO_API}/version_bump.php --path . --minor 2>&1 || true VERSION=$(php ${MOKO_API}/version_read.php --path .) - # Strip any pre-release suffix — stable releases have no suffix - VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') + # version_set_platform handles suffix stripping — just pass clean base version echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "Bumped to: ${VERSION}" @@ -376,7 +374,7 @@ jobs: API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" curl -sf -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/contents/updates.xml?ref=main" 2>/dev/null | \ - python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \ + php -r "\$d=json_decode(file_get_contents('php://stdin'),true); echo base64_decode(\$d['content'] ?? '');" \ > updates.xml 2>/dev/null || true SHA_FLAG="" diff --git a/README.md b/README.md index 30f610e..c559669 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DEFGROUP: MokoStandards.Root INGROUP: MokoStandards REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform PATH: /README.md -VERSION: 09.05.00 +VERSION: 09.05.04 BRIEF: Project overview and documentation --> diff --git a/cli/joomla_release.php b/cli/joomla_release.php index d57905e..4845f0a 100644 --- a/cli/joomla_release.php +++ b/cli/joomla_release.php @@ -392,7 +392,7 @@ class JoomlaRelease extends CliFramework foreach ($release['assets'] ?? [] as $asset) { if ($asset['name'] === $fileName) { - $this->api->delete("/repos/{$repo}/releases/assets/{$asset['id']}"); + $this->api->delete("/repos/{$repo}/releases/{$release['id']}/assets/{$asset['id']}"); } } diff --git a/cli/version_auto_bump.php b/cli/version_auto_bump.php new file mode 100644 index 0000000..ecb4450 --- /dev/null +++ b/cli/version_auto_bump.php @@ -0,0 +1,137 @@ +#!/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_auto_bump.php + * VERSION: 01.00.00 + * BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash + * + * Usage: + * php version_auto_bump.php --path . --branch dev + * php version_auto_bump.php --path . --branch feature/my-feature --token TOKEN --repo-url URL + * php version_auto_bump.php --path . --branch alpha --dry-run + */ + +declare(strict_types=1); + +$path = '.'; +$branch = null; +$token = ''; +$repoUrl = ''; +$dryRun = false; + +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 === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1]; + if ($arg === '--repo-url' && isset($argv[$i + 1])) $repoUrl = $argv[$i + 1]; + if ($arg === '--dry-run') $dryRun = true; +} + +// Auto-detect branch from git or CI env +if ($branch === null) { + $branch = getenv('GITHUB_REF_NAME') ?: trim((string) @shell_exec('git rev-parse --abbrev-ref HEAD 2>/dev/null')); + if (empty($branch) || $branch === 'HEAD') { + fwrite(STDERR, "Cannot detect branch — pass --branch\n"); + exit(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/')) { + $stability = 'dev'; +} else { + $stability = 'dev'; +} + +$cli = __DIR__; +$php = PHP_BINARY; + +// 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 +$versionOutput = []; +exec("{$php} {$cli}/version_read.php --path " . escapeshellarg($path) . " 2>&1", $versionOutput, $versionRc); +$version = trim($versionOutput[0] ?? ''); + +if (empty($version)) { + echo "No version found — skipping\n"; + exit(0); +} + +echo "Version: {$version} | Branch: {$branch} | Stability: {$stability}\n"; + +// Step 3: Set platform version with stability suffix +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); + +// 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 ($dryRun) { + echo "[DRY-RUN] Would commit and push {$displayVersion} to {$branch}\n"; + exit(0); +} + +// Step 5: Git commit and push +$root = realpath($path) ?: $path; + +// Check if anything changed +$diffStatus = trim((string) @shell_exec("cd " . 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"; + exit(0); +} + +// Configure git +@shell_exec("cd " . escapeshellarg($root) . " && git config --local user.email \"gitea-actions[bot]@mokoconsulting.tech\""); +@shell_exec("cd " . escapeshellarg($root) . " && git config --local user.name \"gitea-actions[bot]\""); + +if (!empty($repoUrl)) { + @shell_exec("cd " . escapeshellarg($root) . " && git remote set-url origin " . escapeshellarg($repoUrl)); +} + +@shell_exec("cd " . escapeshellarg($root) . " && git add -A"); +$commitMsg = "chore(version): auto-bump patch {$displayVersion} [skip ci]"; +@shell_exec("cd " . escapeshellarg($root) . " && git commit -m " . escapeshellarg($commitMsg) + . " --author=\"gitea-actions[bot] \""); + +$pushResult = @shell_exec("cd " . escapeshellarg($root) . " && git push origin " . escapeshellarg($branch) . " 2>&1"); +echo $pushResult ?? ''; + +echo "Bumped to {$displayVersion}\n"; +exit(0); diff --git a/cli/version_bump.php b/cli/version_bump.php index ece23de..afe3713 100644 --- a/cli/version_bump.php +++ b/cli/version_bump.php @@ -31,7 +31,7 @@ $mokoManifest = "{$root}/.mokogitea/manifest.xml"; $mokoContent = ''; if (file_exists($mokoManifest)) { $mokoContent = file_get_contents($mokoManifest); - if (preg_match('|(\d{2}\.\d{2}\.\d{2})(?:-((?:(?:dev|alpha|beta|rc)-?)+))?|', $mokoContent, $m)) { + if (preg_match('#(\d{2}\.\d{2}\.\d{2})(?:-((?:(?:dev|alpha|beta|rc)-?)+))?#', $mokoContent, $m)) { $mokoVersion = $m[1]; $mokoSuffix = isset($m[2]) ? $m[2] : ''; } @@ -64,7 +64,7 @@ foreach ($manifestFiles as $xmlFile) { if (strpos($xmlContent, '') === false) { continue; } - if (preg_match('|(\d{2}\.\d{2}\.\d{2})((?:-(?:dev|alpha|beta|rc))+)?|', $xmlContent, $xm)) { + if (preg_match('#(\d{2}\.\d{2}\.\d{2})((?:-(?:dev|alpha|beta|rc))+)?#', $xmlContent, $xm)) { $candidate = $xm[1]; if ($manifestVersion === null || version_compare($candidate, $manifestVersion, '>')) { $manifestVersion = $candidate; @@ -120,7 +120,7 @@ $newFull = $new; // -- Update .mokogitea/manifest.xml (canonical — preserves suffix) -- if (file_exists($mokoManifest) && !empty($mokoContent)) { $updated = preg_replace( - '|\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?|', + '#\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?#', "{$newFull}", $mokoContent, 1 @@ -160,7 +160,7 @@ foreach ($xmlPatterns as $pattern) { continue; } $newContent = preg_replace( - '|\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?|', + '#\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?#', "{$newFull}", $content ); @@ -205,5 +205,80 @@ if (file_exists($pyprojectFile)) { } } +// -- Update CHANGELOG.md -- +$changelogFile = "{$root}/CHANGELOG.md"; +if (file_exists($changelogFile)) { + $clContent = file_get_contents($changelogFile); + $updatedCl = preg_replace( + '/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?/m', + '${1}' . $newFull, + $clContent + ); + if ($updatedCl !== null && $updatedCl !== $clContent) { + file_put_contents($changelogFile, $updatedCl); + fwrite(STDERR, "Updated CHANGELOG.md\n"); + } +} + +// -- Generic VERSION: pattern scan across all text files -- +$scanExtensions = ['php', 'yml', 'yaml', 'md', 'txt', 'xml', 'sh', 'toml', 'ini', 'css', 'js']; +$excludeDirs = ['.git', 'vendor', 'node_modules', 'build', 'dist', '.claude']; +$versionPattern = '/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?/m'; + +$directory = new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS); +$filter = new RecursiveCallbackFilterIterator($directory, function ($current, $key, $iterator) use ($excludeDirs) { + if ($current->isDir() && in_array($current->getFilename(), $excludeDirs, true)) { + return false; + } + return true; +}); +$iterator = new RecursiveIteratorIterator($filter); + +$genericUpdated = []; +foreach ($iterator as $fileInfo) { + if ($fileInfo->isDir()) { + continue; + } + + $ext = strtolower($fileInfo->getExtension()); + if (!in_array($ext, $scanExtensions, true)) { + continue; + } + + $filePath = $fileInfo->getPathname(); + + // Skip files already handled above + $relPath = str_replace([$root . '/', $root . '\\'], '', $filePath); + if (in_array($relPath, ['README.md', 'CHANGELOG.md', 'package.json', 'pyproject.toml'], true)) { + continue; + } + if (in_array($relPath, $updatedFiles ?? [], true)) { + continue; + } + if (strpos($relPath, '.mokogitea/manifest.xml') !== false) { + continue; + } + + $content = @file_get_contents($filePath); + if ($content === false) { + continue; + } + + // Skip synced files — they have their own version managed by their source repo + if (preg_match('/^#\s*REPO:\s*https?:\/\//m', $content)) { + continue; + } + + $updated = preg_replace($versionPattern, '${1}' . $newFull, $content); + if ($updated !== null && $updated !== $content) { + file_put_contents($filePath, $updated); + $genericUpdated[] = $relPath; + } +} + +if (!empty($genericUpdated)) { + fwrite(STDERR, "Updated VERSION: in " . count($genericUpdated) . " file(s): " . implode(', ', $genericUpdated) . "\n"); +} + echo "{$old} -> {$newFull}\n"; exit(0); diff --git a/cli/version_check.php b/cli/version_check.php index ff4d538..1cc3e01 100644 --- a/cli/version_check.php +++ b/cli/version_check.php @@ -34,6 +34,19 @@ $root = realpath($path) ?: $path; $errors = 0; $versions = []; +// ── Read .mokogitea/manifest.xml (canonical) ──────────────────────────────── +$mokoManifest = "{$root}/.mokogitea/manifest.xml"; +if (file_exists($mokoManifest)) { + $xml = @simplexml_load_file($mokoManifest); + if ($xml !== false) { + $v = (string)($xml->identity->version ?? ''); + $base = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $v); + if (preg_match('/^\d{2}\.\d{2}\.\d{2}$/', $base)) { + $versions['.mokogitea/manifest.xml'] = $base; + } + } +} + // ── Read README.md version ─────────────────────────────────────────────────── $readme = "{$root}/README.md"; if (file_exists($readme)) { @@ -43,6 +56,33 @@ if (file_exists($readme)) { } } +// ── Read CHANGELOG.md version ─────────────────────────────────────────────── +$changelog = "{$root}/CHANGELOG.md"; +if (file_exists($changelog)) { + $content = file_get_contents($changelog); + if (preg_match('/VERSION:\s*(\d{2}\.\d{2}\.\d{2})/m', $content, $m)) { + $versions['CHANGELOG.md'] = $m[1]; + } +} + +// ── Read package.json version ─────────────────────────────────────────────── +$packageJson = "{$root}/package.json"; +if (file_exists($packageJson)) { + $pkg = json_decode(file_get_contents($packageJson), true); + if (isset($pkg['version']) && preg_match('/^\d{2}\.\d{2}\.\d{2}$/', $pkg['version'])) { + $versions['package.json'] = $pkg['version']; + } +} + +// ── Read pyproject.toml version ───────────────────────────────────────────── +$pyproject = "{$root}/pyproject.toml"; +if (file_exists($pyproject)) { + $content = file_get_contents($pyproject); + if (preg_match('/^version\s*=\s*"(\d{2}\.\d{2}\.\d{2})"/m', $content, $m)) { + $versions['pyproject.toml'] = $m[1]; + } +} + // ── Read manifest XML versions ─────────────────────────────────────────────── $xmlGlobs = [ "{$root}/src/pkg_*.xml", @@ -59,7 +99,7 @@ foreach ($xmlGlobs as $glob) { $xmlContent = file_get_contents($file); if (strpos($xmlContent, '(\d{2}\.\d{2}\.\d{2})(?:(?:-(?:dev|alpha|beta|rc))+)?|', $xmlContent, $xm)) { + if (preg_match('#(\d{2}\.\d{2}\.\d{2})(?:(?:-(?:dev|alpha|beta|rc))+)?#', $xmlContent, $xm)) { $relPath = str_replace($root . '/', '', $file); $relPath = str_replace($root . '\\', '', $relPath); $versions[$relPath] = $xm[1]; @@ -111,9 +151,65 @@ if (count($uniqueVersions) === 1) { echo " Fixed: README.md -> {$highestVersion}\n"; } + // Fix .mokogitea/manifest.xml + if (isset($versions['.mokogitea/manifest.xml']) && $versions['.mokogitea/manifest.xml'] !== $highestVersion) { + $content = file_get_contents($mokoManifest); + $updated = preg_replace( + '#\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?#', + "{$highestVersion}", + $content + ); + if ($updated !== null) { + file_put_contents($mokoManifest, $updated); + } + echo " Fixed: .mokogitea/manifest.xml -> {$highestVersion}\n"; + } + + // Fix CHANGELOG.md + if (isset($versions['CHANGELOG.md']) && $versions['CHANGELOG.md'] !== $highestVersion) { + $content = file_get_contents($changelog); + $updated = preg_replace( + '/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?/m', + '${1}' . $highestVersion, + $content + ); + if ($updated !== null) { + file_put_contents($changelog, $updated); + } + echo " Fixed: CHANGELOG.md -> {$highestVersion}\n"; + } + + // Fix package.json + if (isset($versions['package.json']) && $versions['package.json'] !== $highestVersion) { + $content = file_get_contents($packageJson); + $updated = preg_replace( + '/("version"\s*:\s*")\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?(")/m', + '${1}' . $highestVersion . '${2}', + $content + ); + if ($updated !== null) { + file_put_contents($packageJson, $updated); + } + echo " Fixed: package.json -> {$highestVersion}\n"; + } + + // Fix pyproject.toml + if (isset($versions['pyproject.toml']) && $versions['pyproject.toml'] !== $highestVersion) { + $content = file_get_contents($pyproject); + $updated = preg_replace( + '/^(version\s*=\s*")\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?(")/m', + '${1}' . $highestVersion . '${2}', + $content + ); + if ($updated !== null) { + file_put_contents($pyproject, $updated); + } + echo " Fixed: pyproject.toml -> {$highestVersion}\n"; + } + // Fix XML manifests foreach ($versions as $source => $ver) { - if ($source === 'README.md') continue; + if (in_array($source, ['README.md', 'CHANGELOG.md', '.mokogitea/manifest.xml', 'package.json', 'pyproject.toml'], true)) continue; if ($ver === $highestVersion) continue; $file = "{$root}/{$source}"; @@ -121,7 +217,7 @@ if (count($uniqueVersions) === 1) { $content = file_get_contents($file); $updated = preg_replace( - '|[^<]*|', + '#[^<]*#', "{$highestVersion}", $content ); diff --git a/cli/version_read.php b/cli/version_read.php index 5c76856..3324b36 100644 --- a/cli/version_read.php +++ b/cli/version_read.php @@ -66,7 +66,7 @@ foreach ($manifestFiles as $xmlFile) { if (strpos($xmlContent, '') === false) { continue; } - if (preg_match('|(\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?)|', $xmlContent, $xm)) { + if (preg_match('#(\d{2}\.\d{2}\.\d{2}(?:(?:-(?:dev|alpha|beta|rc))+)?)#', $xmlContent, $xm)) { $candidate = $xm[1]; $candidateBase = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $candidate); $currentBase = $manifestVersion ? preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $manifestVersion) : null; @@ -119,7 +119,7 @@ if ($version === null) { // -- Backfill: if manifest.xml exists but lacks , insert it -- if (file_exists($mokoManifest)) { $content = file_get_contents($mokoManifest); - if (!preg_match('|\d{2}\.\d{2}\.\d{2}((?:-(?:dev|alpha|beta|rc))+)?|', $content)) { + if (!preg_match('#\d{2}\.\d{2}\.\d{2}((?:-(?:dev|alpha|beta|rc))+)?#', $content)) { if (strpos($content, 'request('DELETE', $endpoint, $body); + return $this->request('DELETE', $endpoint, $body ?? []); } /**