feat(cli): add release automation CLI scripts (#35)
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
feat(cli): add release automation CLI scripts Add 6 new CLI scripts replacing inline bash in CI workflows. Update version_set_platform.php with --stability flag.
This commit was merged in pull request #35.
This commit is contained in:
@@ -687,6 +687,7 @@ modulebuilder.txt
|
||||
!/bin/moko
|
||||
/cache/*
|
||||
/cli/*
|
||||
!/cli/*.php
|
||||
/components/com_ajax/*
|
||||
/components/com_banners/*
|
||||
/components/com_config/*
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* 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/badge_update.php
|
||||
* BRIEF: Update [VERSION: XX.XX.XX] badges in all markdown files
|
||||
*
|
||||
* Usage:
|
||||
* php badge_update.php --path /repo --version 04.01.00
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$path = '.';
|
||||
$version = null;
|
||||
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
||||
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
|
||||
}
|
||||
|
||||
if ($version === null) {
|
||||
fwrite(STDERR, "Usage: badge_update.php --path . --version XX.YY.ZZ\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$root = realpath($path) ?: $path;
|
||||
$pattern = '/\[VERSION:\s*\d{2}\.\d{2}\.\d{2}\]/';
|
||||
$replacement = "[VERSION: {$version}]";
|
||||
$updated = 0;
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS)
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$filePath = $file->getPathname();
|
||||
|
||||
// Skip .git and vendor directories
|
||||
if (preg_match('#[/\\\\](\.git|vendor)[/\\\\]#', $filePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only process markdown files
|
||||
if (!preg_match('/\.md$/i', $filePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = file_get_contents($filePath);
|
||||
if (preg_match($pattern, $content)) {
|
||||
$newContent = preg_replace($pattern, $replacement, $content);
|
||||
if ($newContent !== $content) {
|
||||
file_put_contents($filePath, $newContent);
|
||||
$relative = str_replace($root . DIRECTORY_SEPARATOR, '', $filePath);
|
||||
echo "Updated: {$relative}\n";
|
||||
$updated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "Updated {$updated} file(s) to {$replacement}\n";
|
||||
exit(0);
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* 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/changelog_promote.php
|
||||
* BRIEF: Promote [Unreleased] section in CHANGELOG.md to a versioned entry
|
||||
*
|
||||
* Usage:
|
||||
* php changelog_promote.php --path /repo --version 04.01.00
|
||||
* php changelog_promote.php --path /repo --version 04.01.00 --date 2026-05-21
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$path = '.';
|
||||
$version = null;
|
||||
$date = date('Y-m-d');
|
||||
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
||||
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
|
||||
if ($arg === '--date' && isset($argv[$i + 1])) $date = $argv[$i + 1];
|
||||
}
|
||||
|
||||
if ($version === null) {
|
||||
fwrite(STDERR, "Usage: changelog_promote.php --path . --version XX.YY.ZZ [--date YYYY-MM-DD]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$changelog = realpath($path) . '/CHANGELOG.md';
|
||||
if (!file_exists($changelog)) {
|
||||
fwrite(STDERR, "No CHANGELOG.md found at {$path}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$content = file_get_contents($changelog);
|
||||
|
||||
// Check if [Unreleased] section exists
|
||||
if (!preg_match('/## \[?Unreleased\]?/i', $content)) {
|
||||
fwrite(STDERR, "No [Unreleased] section found in CHANGELOG.md\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Replace [Unreleased] with versioned entry
|
||||
$content = preg_replace(
|
||||
'/## \[Unreleased\]/i',
|
||||
"## [{$version}] --- {$date}",
|
||||
$content,
|
||||
1
|
||||
);
|
||||
$content = preg_replace(
|
||||
'/## Unreleased/i',
|
||||
"## [{$version}] --- {$date}",
|
||||
$content,
|
||||
1
|
||||
);
|
||||
|
||||
// Insert new [Unreleased] section after the first heading line (# Changelog)
|
||||
$lines = explode("\n", $content);
|
||||
$inserted = false;
|
||||
$result = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$result[] = $line;
|
||||
if (!$inserted && preg_match('/^# /', $line)) {
|
||||
$result[] = '';
|
||||
$result[] = '## [Unreleased]';
|
||||
$result[] = '';
|
||||
$inserted = true;
|
||||
}
|
||||
}
|
||||
|
||||
$content = implode("\n", $result);
|
||||
file_put_contents($changelog, $content);
|
||||
echo "CHANGELOG promoted: [Unreleased] -> [{$version}] --- {$date}\n";
|
||||
exit(0);
|
||||
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* 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/package_build.php
|
||||
* BRIEF: Build ZIP and tar.gz install packages for Joomla/Dolibarr/generic projects
|
||||
*
|
||||
* Usage:
|
||||
* php package_build.php --path /repo --version 04.01.00
|
||||
* php package_build.php --path /repo --version 04.01.00 --output-dir /tmp
|
||||
* php package_build.php --path /repo --version 04.01.00 --github-output
|
||||
*
|
||||
* Options:
|
||||
* --path Repository root (default: .)
|
||||
* --version Version string (required)
|
||||
* --output-dir Directory for built packages (default: /tmp)
|
||||
* --type-prefix Override type prefix (e.g. plg_system_)
|
||||
* --element Override element name
|
||||
* --github-output Export zip_name, tar_name, sha256_zip, sha256_tar to $GITHUB_OUTPUT
|
||||
*
|
||||
* NOTE: Uses PHP exec() with escapeshellarg() for tar — all arguments are escaped.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$path = '.';
|
||||
$version = null;
|
||||
$outputDir = '/tmp';
|
||||
$typePrefixOverride = null;
|
||||
$elementOverride = null;
|
||||
$githubOutput = false;
|
||||
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
||||
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
|
||||
if ($arg === '--output-dir' && isset($argv[$i + 1])) $outputDir = $argv[$i + 1];
|
||||
if ($arg === '--type-prefix' && isset($argv[$i + 1])) $typePrefixOverride = $argv[$i + 1];
|
||||
if ($arg === '--element' && isset($argv[$i + 1])) $elementOverride = $argv[$i + 1];
|
||||
if ($arg === '--github-output') $githubOutput = true;
|
||||
}
|
||||
|
||||
if ($version === null) {
|
||||
fwrite(STDERR, "Usage: package_build.php --path . --version XX.YY.ZZ [--output-dir /tmp]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$root = realpath($path) ?: $path;
|
||||
|
||||
// -- Determine source directory -----------------------------------------------
|
||||
$sourceDir = null;
|
||||
foreach (['src', 'htdocs'] as $candidate) {
|
||||
if (is_dir("{$root}/{$candidate}")) {
|
||||
$sourceDir = "{$root}/{$candidate}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($sourceDir === null) {
|
||||
fwrite(STDERR, "No src/ or htdocs/ directory found in {$root}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// -- Determine element and type prefix from manifest --------------------------
|
||||
$extElement = $elementOverride;
|
||||
$typePrefix = $typePrefixOverride ?? '';
|
||||
$extType = '';
|
||||
$isPackage = false;
|
||||
|
||||
if ($extElement === null || $typePrefixOverride === null) {
|
||||
// Find manifest
|
||||
$manifest = null;
|
||||
foreach (glob("{$sourceDir}/pkg_*.xml") ?: [] as $f) {
|
||||
if (strpos(file_get_contents($f), '<extension') !== false) {
|
||||
$manifest = $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($manifest === null) {
|
||||
foreach (glob("{$sourceDir}/*.xml") ?: [] as $f) {
|
||||
if (strpos(file_get_contents($f), '<extension') !== false) {
|
||||
$manifest = $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($manifest !== null) {
|
||||
$xml = file_get_contents($manifest);
|
||||
|
||||
if ($extElement === null) {
|
||||
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $m)) $extElement = $m[1];
|
||||
elseif (preg_match('/plugin="([^"]+)"/', $xml, $m)) $extElement = $m[1];
|
||||
elseif (preg_match('/module="([^"]+)"/', $xml, $m)) $extElement = $m[1];
|
||||
else $extElement = strtolower(pathinfo($manifest, PATHINFO_FILENAME));
|
||||
}
|
||||
|
||||
if (preg_match('/<extension[^>]*type="([^"]+)"/', $xml, $m)) $extType = $m[1];
|
||||
$extFolder = '';
|
||||
if (preg_match('/<extension[^>]*group="([^"]+)"/', $xml, $m)) $extFolder = $m[1];
|
||||
|
||||
if ($typePrefixOverride === null) {
|
||||
switch ($extType) {
|
||||
case 'plugin': $typePrefix = "plg_{$extFolder}_"; break;
|
||||
case 'module': $typePrefix = 'mod_'; break;
|
||||
case 'component': $typePrefix = 'com_'; break;
|
||||
case 'template': $typePrefix = 'tpl_'; break;
|
||||
case 'library': $typePrefix = 'lib_'; break;
|
||||
case 'package': $typePrefix = 'pkg_'; break;
|
||||
}
|
||||
}
|
||||
|
||||
$isPackage = ($extType === 'package' && is_dir("{$sourceDir}/packages"));
|
||||
}
|
||||
}
|
||||
|
||||
if ($extElement === null) {
|
||||
$extElement = strtolower(basename($root));
|
||||
}
|
||||
|
||||
$zipName = "{$typePrefix}{$extElement}-{$version}.zip";
|
||||
$tarName = "{$typePrefix}{$extElement}-{$version}.tar.gz";
|
||||
$zipPath = "{$outputDir}/{$zipName}";
|
||||
$tarPath = "{$outputDir}/{$tarName}";
|
||||
|
||||
// -- Exclude patterns ---------------------------------------------------------
|
||||
$excludePatterns = [
|
||||
'.ftpignore',
|
||||
'sftp-config*',
|
||||
'*.ppk',
|
||||
'*.pem',
|
||||
'*.key',
|
||||
'.env*',
|
||||
];
|
||||
|
||||
// -- Build packages -----------------------------------------------------------
|
||||
if ($isPackage) {
|
||||
echo "=== Building Joomla PACKAGE (multi-extension) ===\n";
|
||||
|
||||
$stagingDir = sys_get_temp_dir() . '/moko-pkg-' . uniqid();
|
||||
mkdir($stagingDir, 0755, true);
|
||||
|
||||
// ZIP each sub-extension
|
||||
foreach (glob("{$sourceDir}/packages/*/") ?: [] as $extDir) {
|
||||
$subName = basename($extDir);
|
||||
echo " Packaging sub-extension: {$subName}\n";
|
||||
|
||||
$subZip = new ZipArchive();
|
||||
$subZipPath = "{$stagingDir}/{$subName}.zip";
|
||||
if ($subZip->open($subZipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||
fwrite(STDERR, "Failed to create ZIP for {$subName}\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
addDirectoryToZip($subZip, $extDir, '', $excludePatterns);
|
||||
$subZip->close();
|
||||
}
|
||||
|
||||
// Copy package-level files
|
||||
foreach (array_merge(glob("{$sourceDir}/*.xml") ?: [], glob("{$sourceDir}/*.php") ?: []) as $f) {
|
||||
copy($f, "{$stagingDir}/" . basename($f));
|
||||
}
|
||||
|
||||
// Create ZIP from staging
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||
fwrite(STDERR, "Failed to create ZIP: {$zipPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
addDirectoryToZip($zip, $stagingDir, '', []);
|
||||
$zip->close();
|
||||
|
||||
// Create tar.gz — all arguments are escaped via escapeshellarg()
|
||||
$tarCmd = sprintf(
|
||||
'tar -czf %s -C %s .',
|
||||
escapeshellarg($tarPath),
|
||||
escapeshellarg($stagingDir)
|
||||
);
|
||||
passthru($tarCmd, $tarReturn);
|
||||
|
||||
// Cleanup staging
|
||||
$cleanCmd = sprintf('rm -rf %s', escapeshellarg($stagingDir));
|
||||
passthru($cleanCmd);
|
||||
|
||||
} else {
|
||||
echo "=== Building standard extension package ===\n";
|
||||
|
||||
// ZIP
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||
fwrite(STDERR, "Failed to create ZIP: {$zipPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
addDirectoryToZip($zip, $sourceDir, '', $excludePatterns);
|
||||
$zip->close();
|
||||
|
||||
// tar.gz — all arguments are escaped via escapeshellarg()
|
||||
$excludeArgs = '';
|
||||
foreach ($excludePatterns as $pattern) {
|
||||
$excludeArgs .= ' --exclude=' . escapeshellarg($pattern);
|
||||
}
|
||||
$tarCmd = sprintf(
|
||||
'tar -czf %s -C %s%s .',
|
||||
escapeshellarg($tarPath),
|
||||
escapeshellarg($sourceDir),
|
||||
$excludeArgs
|
||||
);
|
||||
passthru($tarCmd, $tarReturn);
|
||||
}
|
||||
|
||||
// -- Calculate SHA-256 --------------------------------------------------------
|
||||
$sha256Zip = hash_file('sha256', $zipPath);
|
||||
$sha256Tar = file_exists($tarPath) ? hash_file('sha256', $tarPath) : '';
|
||||
|
||||
$zipSize = filesize($zipPath);
|
||||
$tarSize = file_exists($tarPath) ? filesize($tarPath) : 0;
|
||||
|
||||
echo "\n";
|
||||
echo "ZIP: {$zipName} ({$zipSize} bytes)\n";
|
||||
echo " SHA-256: {$sha256Zip}\n";
|
||||
if ($tarSize > 0) {
|
||||
echo "TAR: {$tarName} ({$tarSize} bytes)\n";
|
||||
echo " SHA-256: {$sha256Tar}\n";
|
||||
}
|
||||
|
||||
// -- Export to GITHUB_OUTPUT --------------------------------------------------
|
||||
if ($githubOutput) {
|
||||
$ghOutput = getenv('GITHUB_OUTPUT');
|
||||
$lines = [
|
||||
"zip_name={$zipName}",
|
||||
"tar_name={$tarName}",
|
||||
"zip_path={$zipPath}",
|
||||
"tar_path={$tarPath}",
|
||||
"sha256_zip={$sha256Zip}",
|
||||
"sha256_tar={$sha256Tar}",
|
||||
"type_prefix={$typePrefix}",
|
||||
"ext_element={$extElement}",
|
||||
];
|
||||
if ($ghOutput) {
|
||||
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
|
||||
fwrite(STDERR, "Exported " . count($lines) . " fields to GITHUB_OUTPUT\n");
|
||||
} else {
|
||||
foreach ($lines as $line) echo "{$line}\n";
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
||||
// =============================================================================
|
||||
// Helper: recursively add directory contents to a ZipArchive
|
||||
// =============================================================================
|
||||
function addDirectoryToZip(ZipArchive $zip, string $dir, string $prefix, array $excludes): void
|
||||
{
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$filePath = $file->getPathname();
|
||||
$relativePath = $prefix . substr($filePath, strlen($dir) + 1);
|
||||
|
||||
// Check excludes
|
||||
$basename = basename($filePath);
|
||||
$skip = false;
|
||||
foreach ($excludes as $pattern) {
|
||||
if (fnmatch($pattern, $basename)) {
|
||||
$skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($skip) continue;
|
||||
|
||||
// Normalize path separators for ZIP
|
||||
$relativePath = str_replace('\\', '/', $relativePath);
|
||||
|
||||
if ($file->isDir()) {
|
||||
$zip->addEmptyDir($relativePath);
|
||||
} else {
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* 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_cascade.php
|
||||
* BRIEF: Delete lesser pre-release channels from Gitea when promoting stability
|
||||
*
|
||||
* Usage:
|
||||
* php release_cascade.php --stability stable --token TOKEN --api-base URL
|
||||
* php release_cascade.php --stability rc --token TOKEN --api-base URL
|
||||
*
|
||||
* Cascade rules:
|
||||
* stable -> deletes development, alpha, beta, release-candidate
|
||||
* rc -> deletes development, alpha, beta
|
||||
* beta -> deletes development, alpha
|
||||
* alpha -> deletes development
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$stability = null;
|
||||
$token = null;
|
||||
$apiBase = null;
|
||||
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
|
||||
if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1];
|
||||
if ($arg === '--api-base' && isset($argv[$i + 1])) $apiBase = $argv[$i + 1];
|
||||
}
|
||||
|
||||
// Allow token from environment
|
||||
if ($token === null) {
|
||||
$token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
||||
}
|
||||
|
||||
if ($stability === null || $token === null || $apiBase === null) {
|
||||
fwrite(STDERR, "Usage: release_cascade.php --stability [stable|rc|beta|alpha] --token TOKEN --api-base URL\n");
|
||||
fwrite(STDERR, " --api-base: e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo\n");
|
||||
fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Define cascade hierarchy
|
||||
$cascadeMap = [
|
||||
'stable' => ['development', 'alpha', 'beta', 'release-candidate'],
|
||||
'rc' => ['development', 'alpha', 'beta'],
|
||||
'beta' => ['development', 'alpha'],
|
||||
'alpha' => ['development'],
|
||||
];
|
||||
|
||||
if (!isset($cascadeMap[$stability])) {
|
||||
fwrite(STDERR, "Unknown stability level: {$stability}\n");
|
||||
fwrite(STDERR, "Valid options: stable, rc, beta, alpha\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$tagsToDelete = $cascadeMap[$stability];
|
||||
$deleted = 0;
|
||||
|
||||
foreach ($tagsToDelete as $tag) {
|
||||
// Get release by tag
|
||||
$ch = curl_init("{$apiBase}/releases/tags/{$tag}");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200 || empty($response)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$releaseId = $data['id'] ?? null;
|
||||
|
||||
if ($releaseId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete release
|
||||
$ch = curl_init("{$apiBase}/releases/{$releaseId}");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Delete tag
|
||||
$ch = curl_init("{$apiBase}/tags/{$tag}");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
echo "Deleted: {$tag} (release id: {$releaseId})\n";
|
||||
$deleted++;
|
||||
}
|
||||
|
||||
echo "Cleaned up {$deleted} pre-release channel(s)\n";
|
||||
exit(0);
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* 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_manage.php
|
||||
* BRIEF: Create/update Gitea releases, upload assets, update release body
|
||||
*
|
||||
* Usage:
|
||||
* # Create a release
|
||||
* php release_manage.php --action create --tag stable --name "My Plugin 04.01.00" \
|
||||
* --body "Release notes" --target main --token TOKEN --api-base URL
|
||||
*
|
||||
* # Upload assets to a release
|
||||
* php release_manage.php --action upload --tag stable --files "/tmp/pkg.zip,/tmp/pkg.tar.gz" \
|
||||
* --token TOKEN --api-base URL
|
||||
*
|
||||
* # Update release body (e.g. add SHA checksums)
|
||||
* php release_manage.php --action update-body --tag stable --body "New body" \
|
||||
* --token TOKEN --api-base URL
|
||||
*
|
||||
* # Delete a release and its tag
|
||||
* php release_manage.php --action delete --tag stable --token TOKEN --api-base URL
|
||||
*
|
||||
* Options:
|
||||
* --action create | upload | update-body | delete (required)
|
||||
* --tag Release tag name (required)
|
||||
* --name Release name/title (for create)
|
||||
* --body Release body/description (for create, update-body)
|
||||
* --body-file Read body from file instead of --body
|
||||
* --target Target branch/commitish (for create, default: main)
|
||||
* --files Comma-separated file paths to upload (for upload)
|
||||
* --token Gitea API token (or GA_TOKEN/GITEA_TOKEN env var)
|
||||
* --api-base Gitea API base URL (e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo)
|
||||
*
|
||||
* NOTE: This script uses PHP curl for all HTTP operations (no shell calls).
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$action = null;
|
||||
$tag = null;
|
||||
$name = null;
|
||||
$body = null;
|
||||
$bodyFile = null;
|
||||
$target = 'main';
|
||||
$files = [];
|
||||
$token = null;
|
||||
$apiBase = null;
|
||||
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--action' && isset($argv[$i + 1])) $action = $argv[$i + 1];
|
||||
if ($arg === '--tag' && isset($argv[$i + 1])) $tag = $argv[$i + 1];
|
||||
if ($arg === '--name' && isset($argv[$i + 1])) $name = $argv[$i + 1];
|
||||
if ($arg === '--body' && isset($argv[$i + 1])) $body = $argv[$i + 1];
|
||||
if ($arg === '--body-file' && isset($argv[$i + 1])) $bodyFile = $argv[$i + 1];
|
||||
if ($arg === '--target' && isset($argv[$i + 1])) $target = $argv[$i + 1];
|
||||
if ($arg === '--files' && isset($argv[$i + 1])) $files = array_filter(explode(',', $argv[$i + 1]));
|
||||
if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1];
|
||||
if ($arg === '--api-base' && isset($argv[$i + 1])) $apiBase = $argv[$i + 1];
|
||||
}
|
||||
|
||||
// Allow token from environment
|
||||
if ($token === null) {
|
||||
$token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
|
||||
}
|
||||
|
||||
// Read body from file if specified
|
||||
if ($bodyFile !== null && file_exists($bodyFile)) {
|
||||
$body = file_get_contents($bodyFile);
|
||||
}
|
||||
|
||||
if ($action === null || $tag === null || $token === null || $apiBase === null) {
|
||||
fwrite(STDERR, "Usage: release_manage.php --action [create|upload|update-body|delete] --tag TAG --token TOKEN --api-base URL\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a Gitea API request using curl
|
||||
*/
|
||||
function giteaApi(string $url, string $method, string $token, ?string $jsonBody = null, ?string $filePath = null): array
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
$headers = ["Authorization: token {$token}"];
|
||||
|
||||
$opts = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 60,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
];
|
||||
|
||||
if ($jsonBody !== null) {
|
||||
$headers[] = 'Content-Type: application/json';
|
||||
$opts[CURLOPT_POSTFIELDS] = $jsonBody;
|
||||
} elseif ($filePath !== null) {
|
||||
$headers[] = 'Content-Type: application/octet-stream';
|
||||
$opts[CURLOPT_POSTFIELDS] = file_get_contents($filePath);
|
||||
}
|
||||
|
||||
$opts[CURLOPT_HTTPHEADER] = $headers;
|
||||
curl_setopt_array($ch, $opts);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$data = json_decode($response ?: '{}', true) ?: [];
|
||||
return ['code' => $httpCode, 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get release by tag
|
||||
*/
|
||||
function getReleaseByTag(string $apiBase, string $tag, string $token): ?array
|
||||
{
|
||||
$result = giteaApi("{$apiBase}/releases/tags/{$tag}", 'GET', $token);
|
||||
if ($result['code'] === 200 && isset($result['data']['id'])) {
|
||||
return $result['data'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// -- Action dispatch ----------------------------------------------------------
|
||||
switch ($action) {
|
||||
case 'create':
|
||||
// Delete existing release if present
|
||||
$existing = getReleaseByTag($apiBase, $tag, $token);
|
||||
if ($existing !== null) {
|
||||
$existingId = $existing['id'];
|
||||
giteaApi("{$apiBase}/releases/{$existingId}", 'DELETE', $token);
|
||||
giteaApi("{$apiBase}/tags/{$tag}", 'DELETE', $token);
|
||||
echo "Deleted previous release: {$tag} (id: {$existingId})\n";
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
'tag_name' => $tag,
|
||||
'name' => $name ?? $tag,
|
||||
'body' => $body ?? '',
|
||||
'target_commitish' => $target,
|
||||
]);
|
||||
|
||||
$result = giteaApi("{$apiBase}/releases", 'POST', $token, $payload);
|
||||
if ($result['code'] >= 200 && $result['code'] < 300) {
|
||||
$releaseId = $result['data']['id'] ?? 'unknown';
|
||||
echo "Release created: {$name} (tag: {$tag}, id: {$releaseId})\n";
|
||||
} else {
|
||||
fwrite(STDERR, "Failed to create release: HTTP {$result['code']}\n");
|
||||
fwrite(STDERR, json_encode($result['data']) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'upload':
|
||||
if (empty($files)) {
|
||||
fwrite(STDERR, "No files specified. Use --files /path/to/file1,/path/to/file2\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$release = getReleaseByTag($apiBase, $tag, $token);
|
||||
if ($release === null) {
|
||||
fwrite(STDERR, "No release found for tag: {$tag}\n");
|
||||
exit(1);
|
||||
}
|
||||
$releaseId = $release['id'];
|
||||
|
||||
// Get existing assets to avoid duplicates
|
||||
$assetsResult = giteaApi("{$apiBase}/releases/{$releaseId}/assets", 'GET', $token);
|
||||
$existingAssets = $assetsResult['data'] ?? [];
|
||||
|
||||
foreach ($files as $filePath) {
|
||||
$filePath = trim($filePath);
|
||||
if (!file_exists($filePath)) {
|
||||
fwrite(STDERR, "File not found: {$filePath}\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
$fileName = basename($filePath);
|
||||
|
||||
// Delete existing asset with same name
|
||||
foreach ($existingAssets as $asset) {
|
||||
if (($asset['name'] ?? '') === $fileName) {
|
||||
giteaApi("{$apiBase}/releases/{$releaseId}/assets/{$asset['id']}", 'DELETE', $token);
|
||||
echo "Deleted existing asset: {$fileName}\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Upload
|
||||
$uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($fileName);
|
||||
$result = giteaApi($uploadUrl, 'POST', $token, null, $filePath);
|
||||
if ($result['code'] >= 200 && $result['code'] < 300) {
|
||||
echo "Uploaded: {$fileName}\n";
|
||||
} else {
|
||||
fwrite(STDERR, "Failed to upload {$fileName}: HTTP {$result['code']}\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'update-body':
|
||||
$release = getReleaseByTag($apiBase, $tag, $token);
|
||||
if ($release === null) {
|
||||
fwrite(STDERR, "No release found for tag: {$tag}\n");
|
||||
exit(1);
|
||||
}
|
||||
$releaseId = $release['id'];
|
||||
|
||||
$payload = json_encode(['body' => $body ?? '']);
|
||||
$result = giteaApi("{$apiBase}/releases/{$releaseId}", 'PATCH', $token, $payload);
|
||||
if ($result['code'] >= 200 && $result['code'] < 300) {
|
||||
echo "Release body updated for tag: {$tag}\n";
|
||||
} else {
|
||||
fwrite(STDERR, "Failed to update body: HTTP {$result['code']}\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$existing = getReleaseByTag($apiBase, $tag, $token);
|
||||
if ($existing !== null) {
|
||||
giteaApi("{$apiBase}/releases/{$existing['id']}", 'DELETE', $token);
|
||||
giteaApi("{$apiBase}/tags/{$tag}", 'DELETE', $token);
|
||||
echo "Deleted: {$tag} (id: {$existing['id']})\n";
|
||||
} else {
|
||||
echo "No release found for tag: {$tag}\n";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
fwrite(STDERR, "Unknown action: {$action}\n");
|
||||
fwrite(STDERR, "Valid actions: create, upload, update-body, delete\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* 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/updates_xml_build.php
|
||||
* BRIEF: Generate Joomla updates.xml from extension manifest metadata
|
||||
*
|
||||
* Usage:
|
||||
* php updates_xml_build.php --path /repo --version 04.01.00 --stability stable
|
||||
* php updates_xml_build.php --path /repo --version 04.01.00 --stability stable --sha SHA256
|
||||
* php updates_xml_build.php --path /repo --version 04.01.00 --stability stable --github-output
|
||||
*
|
||||
* Options:
|
||||
* --path Repository root (default: .)
|
||||
* --version Version string (required)
|
||||
* --stability One of: stable, rc, beta, alpha, development (default: stable)
|
||||
* --sha SHA-256 hash of the ZIP package (optional)
|
||||
* --gitea-url Gitea instance URL (default: env GITEA_URL or https://git.mokoconsulting.tech)
|
||||
* --org Organization (default: env GITEA_ORG)
|
||||
* --repo Repository name (default: env GITEA_REPO)
|
||||
* --output Output file path (default: updates.xml in --path)
|
||||
* --github-output Export ext_element, ext_name, ext_type, ext_folder to $GITHUB_OUTPUT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// -- Argument parsing ---------------------------------------------------------
|
||||
$path = '.';
|
||||
$version = null;
|
||||
$stability = 'stable';
|
||||
$sha = null;
|
||||
$giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
|
||||
$org = getenv('GITEA_ORG') ?: '';
|
||||
$repo = getenv('GITEA_REPO') ?: '';
|
||||
$outputFile = null;
|
||||
$githubOutput = false;
|
||||
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
||||
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
|
||||
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
|
||||
if ($arg === '--sha' && isset($argv[$i + 1])) $sha = $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 === '--output' && isset($argv[$i + 1])) $outputFile = $argv[$i + 1];
|
||||
if ($arg === '--github-output') $githubOutput = true;
|
||||
}
|
||||
|
||||
if ($version === null) {
|
||||
fwrite(STDERR, "Usage: updates_xml_build.php --path . --version XX.YY.ZZ [--stability stable] [--sha SHA]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$root = realpath($path) ?: $path;
|
||||
|
||||
// -- Locate Joomla manifest ---------------------------------------------------
|
||||
$manifest = null;
|
||||
|
||||
// Priority: pkg_*.xml in src/ > any extension XML in src/ > any in root
|
||||
$candidates = glob("{$root}/src/pkg_*.xml") ?: [];
|
||||
foreach ($candidates as $f) {
|
||||
if (strpos(file_get_contents($f), '<extension') !== false) {
|
||||
$manifest = $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($manifest === null) {
|
||||
$searchDirs = ["{$root}/src", "{$root}"];
|
||||
foreach ($searchDirs as $dir) {
|
||||
if (!is_dir($dir)) continue;
|
||||
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
|
||||
if (strpos(file_get_contents($f), '<extension') !== false) {
|
||||
$manifest = $f;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($manifest === null) {
|
||||
fwrite(STDERR, "No Joomla XML manifest found in {$root}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// -- Parse extension metadata -------------------------------------------------
|
||||
$xml = file_get_contents($manifest);
|
||||
|
||||
// Extract fields via regex (more portable than SimpleXML for malformed manifests)
|
||||
$extName = '';
|
||||
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $m)) $extName = $m[1];
|
||||
|
||||
$extType = '';
|
||||
if (preg_match('/<extension[^>]*type="([^"]+)"/', $xml, $m)) $extType = $m[1];
|
||||
|
||||
$extElement = '';
|
||||
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $m)) $extElement = $m[1];
|
||||
if (empty($extElement) && preg_match('/plugin="([^"]+)"/', $xml, $m)) $extElement = $m[1];
|
||||
if (empty($extElement) && preg_match('/module="([^"]+)"/', $xml, $m)) $extElement = $m[1];
|
||||
if (empty($extElement)) {
|
||||
$fname = strtolower(pathinfo($manifest, PATHINFO_FILENAME));
|
||||
if (in_array($fname, ['templatedetails', 'manifest'])) {
|
||||
$extElement = strtolower(str_replace([' ', '-'], '', $repo ?: basename($root)));
|
||||
} else {
|
||||
$extElement = $fname;
|
||||
}
|
||||
}
|
||||
|
||||
$extClient = '';
|
||||
if (preg_match('/<extension[^>]*client="([^"]+)"/', $xml, $m)) $extClient = $m[1];
|
||||
|
||||
$extFolder = '';
|
||||
if (preg_match('/<extension[^>]*group="([^"]+)"/', $xml, $m)) $extFolder = $m[1];
|
||||
|
||||
$targetPlatform = '';
|
||||
if (preg_match('/(<targetplatform[^\/]*\/>)/', $xml, $m)) $targetPlatform = $m[1];
|
||||
if (empty($targetPlatform)) {
|
||||
$targetPlatform = '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />';
|
||||
}
|
||||
|
||||
$phpMinimum = '';
|
||||
if (preg_match('/<php_minimum>([^<]+)<\/php_minimum>/', $xml, $m)) $phpMinimum = $m[1];
|
||||
|
||||
// Resolve language key names (e.g. PLG_SYSTEM_MOKOJOOMTOS)
|
||||
if (preg_match('/^[A-Z_]+$/', $extName)) {
|
||||
$iniFiles = [];
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS)
|
||||
);
|
||||
foreach ($iterator as $file) {
|
||||
if (preg_match('/\.sys\.ini$/i', $file->getFilename())) {
|
||||
$iniFiles[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
foreach ($iniFiles as $ini) {
|
||||
$content = file_get_contents($ini);
|
||||
if (preg_match('/^' . preg_quote($extName, '/') . '="([^"]+)"/m', $content, $m)) {
|
||||
$extName = $m[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallbacks
|
||||
if (empty($extName)) $extName = $repo ?: basename($root);
|
||||
if (empty($extType)) $extType = 'component';
|
||||
|
||||
// -- Build type prefix --------------------------------------------------------
|
||||
$typePrefix = '';
|
||||
switch ($extType) {
|
||||
case 'plugin': $typePrefix = "plg_{$extFolder}_"; break;
|
||||
case 'module': $typePrefix = 'mod_'; break;
|
||||
case 'component': $typePrefix = 'com_'; break;
|
||||
case 'template': $typePrefix = 'tpl_'; break;
|
||||
case 'library': $typePrefix = 'lib_'; break;
|
||||
case 'package': $typePrefix = 'pkg_'; break;
|
||||
}
|
||||
|
||||
// -- Export to GITHUB_OUTPUT if requested -------------------------------------
|
||||
if ($githubOutput) {
|
||||
$ghOutput = getenv('GITHUB_OUTPUT');
|
||||
$lines = [
|
||||
"ext_element={$extElement}",
|
||||
"ext_name={$extName}",
|
||||
"ext_type={$extType}",
|
||||
"ext_folder={$extFolder}",
|
||||
"type_prefix={$typePrefix}",
|
||||
];
|
||||
if ($ghOutput) {
|
||||
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
|
||||
fwrite(STDERR, "Exported " . count($lines) . " fields to GITHUB_OUTPUT\n");
|
||||
} else {
|
||||
foreach ($lines as $line) echo "{$line}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// -- Stability suffix map -----------------------------------------------------
|
||||
$stabilitySuffixMap = [
|
||||
'stable' => '',
|
||||
'rc' => '-rc',
|
||||
'beta' => '-beta',
|
||||
'alpha' => '-alpha',
|
||||
'development' => '-dev',
|
||||
];
|
||||
|
||||
$stabilityTagMap = [
|
||||
'stable' => 'stable',
|
||||
'rc' => 'rc',
|
||||
'beta' => 'beta',
|
||||
'alpha' => 'alpha',
|
||||
'development' => 'dev',
|
||||
];
|
||||
|
||||
// -- Build update entries -----------------------------------------------------
|
||||
$releaseTag = $stabilityTagMap[$stability] ?? $stability;
|
||||
|
||||
// For the primary entry: apply suffix if not stable
|
||||
$primarySuffix = $stabilitySuffixMap[$stability] ?? '';
|
||||
$primaryVersion = $version . $primarySuffix;
|
||||
|
||||
$downloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$releaseTag}/{$typePrefix}{$extElement}-{$primaryVersion}.zip";
|
||||
$infoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$releaseTag}";
|
||||
|
||||
// Build client tag
|
||||
$clientTag = '';
|
||||
if (!empty($extClient)) {
|
||||
$clientTag = " <client>{$extClient}</client>";
|
||||
} elseif ($extType === 'module' || $extType === 'plugin') {
|
||||
$clientTag = ' <client>site</client>';
|
||||
}
|
||||
|
||||
// Build folder tag
|
||||
$folderTag = '';
|
||||
if (!empty($extFolder) && $extType === 'plugin') {
|
||||
$folderTag = " <folder>{$extFolder}</folder>";
|
||||
}
|
||||
|
||||
// PHP minimum tag
|
||||
$phpTag = '';
|
||||
if (!empty($phpMinimum)) {
|
||||
$phpTag = " <php_minimum>{$phpMinimum}</php_minimum>";
|
||||
}
|
||||
|
||||
// SHA tag
|
||||
$shaTag = '';
|
||||
if (!empty($sha)) {
|
||||
$shaTag = " <sha256>{$sha}</sha256>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a single <update> entry for a given stability tag
|
||||
*/
|
||||
function buildEntry(
|
||||
string $tagName,
|
||||
string $entryVersion,
|
||||
string $entryDownloadUrl,
|
||||
string $extName,
|
||||
string $extElement,
|
||||
string $extType,
|
||||
string $clientTag,
|
||||
string $folderTag,
|
||||
string $infoUrl,
|
||||
string $targetPlatform,
|
||||
string $phpTag,
|
||||
string $shaTag
|
||||
): string {
|
||||
$lines = [];
|
||||
$lines[] = ' <update>';
|
||||
$lines[] = " <name>{$extName}</name>";
|
||||
$lines[] = " <description>{$extName} update</description>";
|
||||
$lines[] = " <element>{$extElement}</element>";
|
||||
$lines[] = " <type>{$extType}</type>";
|
||||
$lines[] = " <version>{$entryVersion}</version>";
|
||||
if (!empty($clientTag)) $lines[] = $clientTag;
|
||||
if (!empty($folderTag)) $lines[] = $folderTag;
|
||||
$lines[] = " <tags><tag>{$tagName}</tag></tags>";
|
||||
$lines[] = " <infourl title=\"{$extName}\">{$infoUrl}</infourl>";
|
||||
$lines[] = ' <downloads>';
|
||||
$lines[] = " <downloadurl type=\"full\" format=\"zip\">{$entryDownloadUrl}</downloadurl>";
|
||||
$lines[] = ' </downloads>';
|
||||
if (!empty($shaTag)) $lines[] = $shaTag;
|
||||
$lines[] = " {$targetPlatform}";
|
||||
if (!empty($phpTag)) $lines[] = $phpTag;
|
||||
$lines[] = ' <maintainer>Moko Consulting</maintainer>';
|
||||
$lines[] = ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>';
|
||||
$lines[] = ' </update>';
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
// -- Determine which channels to write ----------------------------------------
|
||||
// Stable cascades to all channels; pre-releases only write their level and below
|
||||
// Each channel gets its own suffixed version:
|
||||
// development -> 04.01.00-dev
|
||||
// alpha -> 04.01.00-alpha
|
||||
// beta -> 04.01.00-beta
|
||||
// rc -> 04.01.00-rc
|
||||
// stable -> 04.01.00
|
||||
$allChannels = ['development', 'alpha', 'beta', 'rc', 'stable'];
|
||||
$stabilityIndex = array_search($stability === 'development' ? 'development' : $stability, $allChannels);
|
||||
if ($stabilityIndex === false) $stabilityIndex = 4; // default to stable
|
||||
|
||||
// Write entries for this stability and all below it
|
||||
$entries = [];
|
||||
for ($i = 0; $i <= $stabilityIndex; $i++) {
|
||||
$channelName = $allChannels[$i];
|
||||
$channelSuffix = $stabilitySuffixMap[$channelName] ?? '';
|
||||
$channelVersion = $version . $channelSuffix;
|
||||
$channelTag = $stabilityTagMap[$channelName] ?? $channelName;
|
||||
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$channelTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
|
||||
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$channelTag}";
|
||||
|
||||
$entries[] = buildEntry(
|
||||
$channelName,
|
||||
$channelVersion,
|
||||
$channelDownloadUrl,
|
||||
$extName,
|
||||
$extElement,
|
||||
$extType,
|
||||
$clientTag,
|
||||
$folderTag,
|
||||
$channelInfoUrl,
|
||||
$targetPlatform,
|
||||
$phpTag,
|
||||
$shaTag
|
||||
);
|
||||
}
|
||||
|
||||
// -- Write updates.xml --------------------------------------------------------
|
||||
$year = date('Y');
|
||||
$output = <<<XML
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) {$year} Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: {$primaryVersion}
|
||||
-->
|
||||
|
||||
<updates>
|
||||
XML;
|
||||
$output .= "\n" . implode("\n", $entries) . "\n</updates>\n";
|
||||
|
||||
$dest = $outputFile ?? "{$root}/updates.xml";
|
||||
file_put_contents($dest, $output);
|
||||
|
||||
$channelCount = count($entries);
|
||||
echo "updates.xml: {$primaryVersion} ({$channelCount} channel(s), stability={$stability})\n";
|
||||
echo "Output: {$dest}\n";
|
||||
exit(0);
|
||||
@@ -10,6 +10,13 @@
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* PATH: /cli/version_set_platform.php
|
||||
* BRIEF: Set version in platform-specific files (Dolibarr $this->version, Joomla <version>)
|
||||
*
|
||||
* Usage:
|
||||
* php version_set_platform.php --path . --version 04.01.00
|
||||
* php version_set_platform.php --path . --version 04.01.00 --stability alpha
|
||||
*
|
||||
* When --stability is set to anything other than "stable", the suffix is
|
||||
* appended to the version (e.g. 04.01.00-dev, 04.01.00-alpha, 04.01.00-rc).
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
@@ -17,10 +24,13 @@ declare(strict_types=1);
|
||||
$path = '.';
|
||||
$version = null;
|
||||
$branch = null;
|
||||
$stability = 'stable';
|
||||
|
||||
foreach ($argv as $i => $arg) {
|
||||
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
|
||||
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
|
||||
if ($arg === '--branch' && isset($argv[$i + 1])) $branch = $argv[$i + 1];
|
||||
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
|
||||
}
|
||||
|
||||
// Auto-detect branch from git or GitHub env
|
||||
@@ -32,10 +42,26 @@ if ($branch === null) {
|
||||
}
|
||||
|
||||
if ($version === null) {
|
||||
fwrite(STDERR, "Usage: version_set_platform.php --path . --version development\n");
|
||||
fwrite(STDERR, "Usage: version_set_platform.php --path . --version 04.01.00 [--stability dev]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Append stability suffix for non-stable releases
|
||||
$stabilitySuffixMap = [
|
||||
'stable' => '',
|
||||
'development' => '-dev',
|
||||
'dev' => '-dev',
|
||||
'alpha' => '-alpha',
|
||||
'beta' => '-beta',
|
||||
'rc' => '-rc',
|
||||
'release-candidate' => '-rc',
|
||||
];
|
||||
$suffix = $stabilitySuffixMap[$stability] ?? '';
|
||||
if ($suffix !== '' && !str_ends_with($version, $suffix)) {
|
||||
$version .= $suffix;
|
||||
echo "Version with stability suffix: {$version}\n";
|
||||
}
|
||||
|
||||
$root = realpath($path) ?: $path;
|
||||
|
||||
// Detect platform
|
||||
|
||||
Reference in New Issue
Block a user