Files
moko-platform/cli/release_publish.php
T
2026-05-30 15:33:15 +00:00

346 lines
15 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: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/release_publish.php
* VERSION: 09.14.00
* BRIEF: Publish a release and create copies for all lesser stability streams.
*
* When a release is published at a given stability, copies are created for all
* lower stability streams with the same base version and their respective suffix.
* updates.xml is updated for ALL streams and synced to ALL branches.
*
* Usage:
* php release_publish.php --path . --stability stable --token TOKEN
* php release_publish.php --path . --stability rc --token TOKEN --bump minor
* php release_publish.php --path . --stability dev --token TOKEN --bump patch
* php release_publish.php --path . --stability stable --token TOKEN --dry-run
*
* Options:
* --path Repository root (default: .)
* --stability Target stability: dev|alpha|beta|rc|stable (required)
* --token Gitea API token (required)
* --bump Version bump type before release: patch|minor|none (default: none)
* --branch Current branch (default: auto-detect)
* --gitea-url Gitea URL (default: env GITEA_URL)
* --org Organization (default: env GITEA_ORG)
* --repo Repository name (default: env GITEA_REPO)
* --dry-run Preview without making changes
*/
declare(strict_types=1);
$path = '.';
$stability = '';
$token = '';
$bumpType = 'none';
$branch = '';
$giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
$org = getenv('GITEA_ORG') ?: '';
$repo = getenv('GITEA_REPO') ?: '';
$dryRun = false;
$repoUrl = '';
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1];
if ($arg === '--bump' && isset($argv[$i + 1])) $bumpType = $argv[$i + 1];
if ($arg === '--branch' && isset($argv[$i + 1])) $branch = $argv[$i + 1];
if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1];
if ($arg === '--org' && isset($argv[$i + 1])) $org = $argv[$i + 1];
if ($arg === '--repo' && isset($argv[$i + 1])) $repo = $argv[$i + 1];
if ($arg === '--repo-url' && isset($argv[$i + 1])) $repoUrl = $argv[$i + 1];
if ($arg === '--dry-run') $dryRun = true;
}
if (empty($stability) || empty($token)) {
fwrite(STDERR, "Usage: release_publish.php --stability <dev|alpha|beta|rc|stable> --token TOKEN [options]\n");
exit(1);
}
$cli = __DIR__;
$php = '"' . PHP_BINARY . '"';
$giteaUrl = rtrim($giteaUrl, '/');
// Auto-detect org/repo from git remote if not set
if (empty($org) || empty($repo)) {
$remote = trim((string) @shell_exec("cd " . escapeshellarg($path) . " && git remote get-url origin 2>/dev/null"));
if (preg_match('#/([^/]+)/([^/.]+?)(?:\.git)?$#', $remote, $m)) {
if (empty($org)) $org = $m[1];
if (empty($repo)) $repo = $m[2];
}
}
// Auto-construct repo URL for git auth if not provided
if (empty($repoUrl) && !empty($token) && !empty($org) && !empty($repo)) {
$host = preg_replace('#^https?://#', '', $giteaUrl);
$repoUrl = "https://x-access-token:{$token}@{$host}/{$org}/{$repo}.git";
}
// Auto-detect branch
if (empty($branch)) {
$branch = getenv('GITHUB_REF_NAME') ?: trim((string) @shell_exec("cd " . escapeshellarg($path) . " && git rev-parse --abbrev-ref HEAD 2>/dev/null"));
}
$apiBase = "{$giteaUrl}/api/v1/repos/{$org}/{$repo}";
// Stability ordering and suffix mapping
$allStabilities = ['dev', 'alpha', 'beta', 'rc', 'stable'];
$suffixMap = [
'dev' => '-dev',
'alpha' => '-alpha',
'beta' => '-beta',
'rc' => '-rc',
'stable' => '',
];
$releaseTagMap = [
'dev' => 'development',
'alpha' => 'alpha',
'beta' => 'beta',
'rc' => 'release-candidate',
'stable' => 'stable',
];
$stabilityIndex = array_search($stability, $allStabilities);
if ($stabilityIndex === false) {
fwrite(STDERR, "Invalid stability: {$stability}\n");
exit(1);
}
echo "=== Release Publish ===\n";
echo "Stability: {$stability} | Bump: {$bumpType} | Branch: {$branch}\n";
echo "Repo: {$org}/{$repo}\n";
// -- Step 1: Version bump (if requested) --
if ($bumpType !== 'none') {
$bumpFlag = $bumpType === 'minor' ? '--minor' : '';
echo "\n--- Step 1: Version bump ({$bumpType}) ---\n";
if (!$dryRun) {
passthru("{$php} {$cli}/version_bump.php --path " . escapeshellarg($path) . " {$bumpFlag} 2>&1");
} else {
echo "[DRY-RUN] Would run version_bump.php {$bumpFlag}\n";
}
}
// -- Step 2: Read version and set stability suffix --
echo "\n--- Step 2: Set version suffix ---\n";
$versionOutput = [];
exec("{$php} {$cli}/version_read.php --path " . escapeshellarg($path) . " 2>/dev/null", $versionOutput);
$version = trim($versionOutput[0] ?? '');
if (empty($version)) {
fwrite(STDERR, "No version found\n");
exit(1);
}
// Strip existing suffix to get base version
$baseVersion = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $version);
if (!$dryRun) {
passthru("{$php} {$cli}/version_set_platform.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($baseVersion)
. " --branch " . escapeshellarg($branch)
. " --stability " . escapeshellarg($stability) . " 2>&1");
passthru("{$php} {$cli}/version_check.php --path " . escapeshellarg($path) . " --fix 2>/dev/null");
}
$releaseVersion = $baseVersion . $suffixMap[$stability];
echo "Release version: {$releaseVersion}\n";
// -- Step 2b: Update badges and changelog --
if (!$dryRun) {
passthru("{$php} {$cli}/badge_update.php --path " . escapeshellarg($path) . " --version " . escapeshellarg($baseVersion) . " 2>/dev/null");
$changelogFile = realpath($path) . '/CHANGELOG.md';
if (file_exists($changelogFile)) {
passthru("{$php} {$cli}/changelog_promote.php --path " . escapeshellarg($path) . " --version " . escapeshellarg($baseVersion) . " 2>/dev/null");
passthru("{$php} {$cli}/changelog_prune.php --path " . escapeshellarg($path) . " --keep 5 2>/dev/null");
}
}
// -- Step 2c: Commit version changes before building --
$root = realpath($path) ?: $path;
if (!$dryRun) {
// 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));
}
// Ensure we're on the actual branch (not detached HEAD from PR merge)
@shell_exec("cd " . escapeshellarg($root) . " && git fetch origin " . escapeshellarg($branch) . " 2>/dev/null");
@shell_exec("cd " . escapeshellarg($root) . " && git checkout -B " . escapeshellarg($branch) . " FETCH_HEAD 2>/dev/null");
// Re-apply version changes on the checked-out branch
passthru("{$php} {$cli}/version_set_platform.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($baseVersion)
. " --branch " . escapeshellarg($branch)
. " --stability " . escapeshellarg($stability) . " 2>/dev/null");
passthru("{$php} {$cli}/version_check.php --path " . escapeshellarg($path) . " --fix 2>/dev/null");
passthru("{$php} {$cli}/badge_update.php --path " . escapeshellarg($path) . " --version " . escapeshellarg($baseVersion) . " 2>/dev/null");
$diffCheck = trim((string) @shell_exec("cd " . escapeshellarg($root) . " && git diff --quiet && git diff --cached --quiet 2>&1 && echo clean || echo dirty"));
if ($diffCheck === 'dirty') {
@shell_exec("cd " . escapeshellarg($root) . " && git add -A");
@shell_exec("cd " . escapeshellarg($root) . " && git commit -m " . escapeshellarg("chore(release): build {$releaseVersion} [skip ci]")
. " --author=\"gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>\"");
$pushResult = @shell_exec("cd " . escapeshellarg($root) . " && git push origin " . escapeshellarg($branch) . " 2>&1");
echo " Committed release changes\n";
echo " Push: " . trim($pushResult ?? '') . "\n";
}
}
// -- Step 3: Build release package --
echo "\n--- Step 3: Build and upload release ---\n";
$releaseTag = $releaseTagMap[$stability];
$sha256 = '';
if (!$dryRun) {
// Create release
passthru("{$php} {$cli}/release_create.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($releaseVersion)
. " --tag " . escapeshellarg($releaseTag)
. " --token " . escapeshellarg($token)
. " --api-base " . escapeshellarg($apiBase)
. " --repo " . escapeshellarg($repo)
. " --branch " . escapeshellarg($branch) . " 2>&1");
// Build and upload package
$packageOutput = [];
exec("{$php} {$cli}/release_package.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($releaseVersion)
. " --tag " . escapeshellarg($releaseTag)
. " --token " . escapeshellarg($token)
. " --api-base " . escapeshellarg($apiBase)
. " --repo " . escapeshellarg($repo)
. " --output /tmp 2>&1", $packageOutput);
foreach ($packageOutput as $line) {
echo $line . "\n";
// Extract SHA from output
if (preg_match('/sha256_zip=([a-f0-9]{64})/i', $line, $m)) {
$sha256 = $m[1];
}
}
// Also check GITHUB_OUTPUT
$ghOutput = getenv('GITHUB_OUTPUT');
if ($ghOutput && file_exists($ghOutput)) {
$ghContent = file_get_contents($ghOutput);
if (preg_match('/sha256_zip=([a-f0-9]{64})/i', $ghContent, $m)) {
$sha256 = $m[1];
}
}
} else {
echo "[DRY-RUN] Would build and upload {$releaseVersion} to {$releaseTag}\n";
}
// -- Step 4: Build separate packages for all lesser stability streams --
// Each stream gets its own ZIP with the correct version INSIDE templateDetails.xml.
// Joomla reads version from the ZIP after install, so it must match.
echo "\n--- Step 4: Build packages for lesser streams ---\n";
for ($i = 0; $i < $stabilityIndex; $i++) {
$lesserStability = $allStabilities[$i];
$lesserTag = $releaseTagMap[$lesserStability];
$lesserVersion = $baseVersion . $suffixMap[$lesserStability];
echo " Building {$lesserStability} release: {$lesserVersion}\n";
if (!$dryRun) {
// Set version to lesser stream's suffixed version in source files
passthru("{$php} {$cli}/version_set_platform.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($baseVersion)
. " --branch " . escapeshellarg($lesserStability)
. " --stability " . escapeshellarg($lesserStability) . " 2>/dev/null");
// Create release tag
passthru("{$php} {$cli}/release_create.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($lesserVersion)
. " --tag " . escapeshellarg($lesserTag)
. " --token " . escapeshellarg($token)
. " --api-base " . escapeshellarg($apiBase)
. " --repo " . escapeshellarg($repo)
. " --branch " . escapeshellarg($branch) . " 2>&1");
// Build and upload package (ZIP will contain the lesser version)
passthru("{$php} {$cli}/release_package.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($lesserVersion)
. " --tag " . escapeshellarg($lesserTag)
. " --token " . escapeshellarg($token)
. " --api-base " . escapeshellarg($apiBase)
. " --repo " . escapeshellarg($repo)
. " --output /tmp 2>&1");
} else {
echo " [DRY-RUN] Would build {$lesserVersion} ZIP and upload to {$lesserTag}\n";
}
}
// Restore primary release version in source files
if (!$dryRun && $stabilityIndex > 0) {
passthru("{$php} {$cli}/version_set_platform.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($baseVersion)
. " --branch " . escapeshellarg($branch)
. " --stability " . escapeshellarg($stability) . " 2>/dev/null");
}
// -- Step 5: Update ALL streams in updates.xml --
echo "\n--- Step 5: Update updates.xml for ALL streams ---\n";
// Write entry for the primary stream and all lesser streams
$streamsToWrite = array_slice($allStabilities, 0, $stabilityIndex + 1);
foreach ($streamsToWrite as $stream) {
$streamVersion = $baseVersion . $suffixMap[$stream];
$shaFlag = !empty($sha256) ? "--sha {$sha256}" : '';
echo " Writing {$stream} stream: {$streamVersion}\n";
if (!$dryRun) {
passthru("{$php} {$cli}/updates_xml_build.php --path " . escapeshellarg($path)
. " --version " . escapeshellarg($streamVersion)
. " --stability " . escapeshellarg($stream)
. " --gitea-url " . escapeshellarg($giteaUrl)
. " --org " . escapeshellarg($org)
. " --repo " . escapeshellarg($repo)
. " {$shaFlag} 2>&1");
}
}
// -- Step 6: Commit updates.xml and sync to all branches --
echo "\n--- Step 6: Commit and sync updates.xml ---\n";
$root = realpath($path) ?: $path;
if (!$dryRun) {
$diffCheck = trim((string) @shell_exec("cd " . escapeshellarg($root) . " && git diff --quiet updates.xml 2>&1 && echo clean || echo dirty"));
if ($diffCheck === 'dirty') {
@shell_exec("cd " . escapeshellarg($root) . " && git add updates.xml");
@shell_exec("cd " . escapeshellarg($root) . " && git commit -m " . escapeshellarg("chore: update channels for {$releaseVersion} [skip ci]")
. " --author=\"gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>\"");
@shell_exec("cd " . escapeshellarg($root) . " && git push origin " . escapeshellarg($branch) . " 2>&1");
echo " Committed updates.xml\n";
}
// Sync to all branches
passthru("{$php} {$cli}/updates_xml_sync.php --path " . escapeshellarg($path)
. " --current " . escapeshellarg($branch) . " --all"
. " --version " . escapeshellarg($releaseVersion)
. " --token " . escapeshellarg($token)
. " --gitea-url " . escapeshellarg($giteaUrl)
. " --org " . escapeshellarg($org)
. " --repo " . escapeshellarg($repo) . " 2>&1");
} else {
echo "[DRY-RUN] Would commit updates.xml and sync to all branches\n";
}
echo "\n=== Release published: {$releaseVersion} ===\n";
// Output for CI
$ghOutput = getenv('GITHUB_OUTPUT');
if ($ghOutput) {
file_put_contents($ghOutput, "version={$releaseVersion}\nbase_version={$baseVersion}\n", FILE_APPEND);
}
exit(0);