From c7b6f98f933692f5eeba0b0c84db1ba24442010e Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 18 Jun 2026 15:53:43 -0500 Subject: [PATCH 1/3] feat: add joomla_metadata_validate CLI command (#257) Validates MokoGitea repo metadata against the actual Joomla extension manifest XML to catch update delivery mismatches before production. Checks: - package_type matches - Element name derived correctly (prefix + lowercase + clean) - Display name matches tag - Version consistency (ignoring -dev/-rc suffixes) - PHP minimum matches composer.json - Description match (informational) Supports: - Local mode: reads .mokogitea/manifest.xml + Joomla XML from disk - API mode: fetches metadata via Gitea API (--token) - CI mode: --ci flag exits 1 on errors - JSON output: --json for workflow integration Handles all Joomla types: package, component, module, plugin, template, library, file. Replicates Joomla's InputFilter::clean('cmd') for element name derivation. Refs mokoplatform #257 --- cli/joomla_metadata_validate.php | 515 +++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 cli/joomla_metadata_validate.php diff --git a/cli/joomla_metadata_validate.php b/cli/joomla_metadata_validate.php new file mode 100644 index 0000000..dac1cc9 --- /dev/null +++ b/cli/joomla_metadata_validate.php @@ -0,0 +1,515 @@ +#!/usr/bin/env php + + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * FILE INFORMATION + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/joomla_metadata_validate.php + * VERSION: 09.30.00 + * BRIEF: Validate MokoGitea repo metadata against Joomla extension manifest XML + */ + +declare(strict_types=1); + +require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; + +use MokoEnterprise\CliFramework; + +class JoomlaMetadataValidateCli extends CliFramework +{ + /** Joomla element prefix map — must match MokoGitea's cleanJoomlaElement() */ + private const JOOMLA_PREFIX = [ + 'package' => 'pkg_', + 'component' => 'com_', + 'module' => 'mod_', + 'template' => 'tpl_', + 'library' => 'lib_', + 'file' => 'file_', + ]; + + protected function configure(): void + { + $this->setDescription('Validate MokoGitea repo metadata against Joomla extension manifest XML'); + $this->addArgument('--path', 'Repo root path (default: current directory)', '.'); + $this->addArgument('--token', 'Gitea API token (or GITEA_TOKEN env)', ''); + $this->addArgument('--org', 'Gitea org', 'MokoConsulting'); + $this->addArgument('--repo', 'Repo name (auto-detected from git if empty)', ''); + $this->addArgument('--api-base', 'Gitea API base URL', 'https://git.mokoconsulting.tech/api/v1'); + $this->addArgument('--ci', 'CI mode: exit 1 on any error', false); + $this->addArgument('--json', 'Output as JSON', false); + } + + protected function run(): int + { + $path = realpath($this->getArgument('--path')) ?: $this->getArgument('--path'); + $token = $this->getArgument('--token') ?: getenv('GITEA_TOKEN') ?: ''; + $org = $this->getArgument('--org'); + $repoName = $this->getArgument('--repo'); + $apiBase = rtrim($this->getArgument('--api-base'), '/'); + $ciMode = (bool) $this->getArgument('--ci'); + $jsonMode = (bool) $this->getArgument('--json'); + + if (!is_dir($path)) { + $this->log('ERROR', "Path does not exist: {$path}"); + return 1; + } + + if ($repoName === '') { + $repoName = $this->detectRepoName($path); + } + + // ── Step 1: Find the Joomla extension manifest XML ────────── + $joomlaXml = $this->findJoomlaManifest($path); + + if ($joomlaXml === null) { + $this->log('ERROR', 'No Joomla extension manifest XML found'); + return 1; + } + + $this->log('INFO', "Joomla manifest: {$joomlaXml['path']}"); + + // ── Step 2: Load MokoGitea metadata ───────────────────────── + $metadata = $this->loadMetadata($path, $org, $repoName, $token, $apiBase); + + if ($metadata === null) { + $this->log('ERROR', 'Could not load MokoGitea metadata'); + return 1; + } + + // ── Step 3: Compare ───────────────────────────────────────── + $results = $this->compare($metadata, $joomlaXml, $path); + + // ── Step 4: Output ────────────────────────────────────────── + if ($jsonMode) { + echo json_encode([ + 'repo' => $repoName, + 'results' => $results, + ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; + } else { + $this->printResults($repoName, $results); + } + + $errors = count(array_filter($results, fn($r) => $r['status'] === 'error')); + + return ($ciMode && $errors > 0) ? 1 : 0; + } + + // ================================================================= + // Find Joomla manifest XML + // ================================================================= + + private function findJoomlaManifest(string $root): ?array + { + // Search common locations for a Joomla extension manifest + $candidates = []; + + // Package manifest: source/pkg_*.xml + foreach (glob("{$root}/source/pkg_*.xml") as $file) { + $candidates[] = $file; + } + + // Component manifest: source/packages/com_*/[name].xml + foreach (glob("{$root}/source/packages/com_*/*.xml") as $file) { + $basename = basename($file); + // Skip access.xml, config.xml, etc. + if (in_array($basename, ['access.xml', 'config.xml'], true)) { + continue; + } + $candidates[] = $file; + } + + // Direct source/*.xml + foreach (glob("{$root}/source/*.xml") as $file) { + if (basename($file) !== 'pkg_mokosuitebackup.xml') { + // Already caught above + } + $candidates[] = $file; + } + + // src/ fallback + foreach (glob("{$root}/src/pkg_*.xml") as $file) { + $candidates[] = $file; + } + + // Find the first one that has + foreach (array_unique($candidates) as $file) { + $content = file_get_contents($file); + if ($content === false) { + continue; + } + + if (preg_match('/]*type=["\']([^"\']+)["\']/', $content, $typeMatch)) { + $xml = @simplexml_load_string($content); + if ($xml === false) { + continue; + } + + $type = strtolower($typeMatch[1]); + $relPath = str_replace($root . '/', '', $file); + $relPath = str_replace($root . '\\', '', $relPath); + + return [ + 'path' => $relPath, + 'type' => $type, + 'xml' => $xml, + ]; + } + } + + return null; + } + + // ================================================================= + // Load metadata (from .mokogitea/manifest.xml or API) + // ================================================================= + + private function loadMetadata(string $root, string $org, string $repoName, string $token, string $apiBase): ?array + { + // Try local .mokogitea/manifest.xml first + $localManifest = "{$root}/.mokogitea/manifest.xml"; + + if (is_file($localManifest)) { + $xml = @simplexml_load_file($localManifest); + + if ($xml !== false) { + $identity = $xml->identity ?? $xml; + $governance = $xml->governance ?? $xml; + $build = $xml->build ?? $xml; + + return [ + 'name' => (string) ($identity->name ?? ''), + 'display_name' => (string) ($identity->{'display-name'} ?? ''), + 'description' => (string) ($identity->description ?? ''), + 'version' => (string) ($identity->version ?? ''), + 'platform' => (string) ($governance->platform ?? ''), + 'package_type' => (string) ($build->{'package-type'} ?? ''), + 'language' => (string) ($build->language ?? ''), + 'entry_point' => (string) ($build->{'entry-point'} ?? ''), + 'source' => 'local', + ]; + } + } + + // Fall back to API + if ($token !== '') { + $url = "{$apiBase}/repos/{$org}/{$repoName}/manifest"; + $ctx = stream_context_create([ + 'http' => [ + 'header' => "Authorization: token {$token}\r\nAccept: application/json\r\n", + 'timeout' => 10, + ], + ]); + + $body = @file_get_contents($url, false, $ctx); + + if ($body !== false) { + $data = json_decode($body, true); + if (is_array($data)) { + $data['source'] = 'api'; + return $data; + } + } + } + + return null; + } + + // ================================================================= + // Compare metadata against Joomla manifest + // ================================================================= + + private function compare(array $metadata, array $joomlaXml, string $root): array + { + $results = []; + $xml = $joomlaXml['xml']; + $type = $joomlaXml['type']; + + // 1. Extension type vs package_type + $metaType = $this->normalizePackageType($metadata['package_type'] ?? ''); + $results[] = [ + 'field' => 'package_type', + 'metadata' => $metaType, + 'joomla' => $type, + 'status' => ($metaType === $type) ? 'ok' : 'error', + 'message' => ($metaType === $type) + ? "matches " + : "metadata has \"{$metaType}\" but Joomla manifest has \"{$type}\"", + ]; + + // 2. Element name + $metaName = strtolower($metadata['name'] ?? ''); + $metaElement = $this->deriveElement($metaType, $metaName); + $joomlaElement = $this->extractJoomlaElement($xml, $type); + + $elementMatch = ($metaElement === $joomlaElement); + $results[] = [ + 'field' => 'element', + 'metadata' => $metaElement, + 'joomla' => $joomlaElement, + 'status' => $elementMatch ? 'ok' : 'error', + 'message' => $elementMatch + ? "derived correctly" + : "metadata derives \"{$metaElement}\" but Joomla uses \"{$joomlaElement}\"", + ]; + + // 3. Display name + $metaDisplay = $metadata['display_name'] ?? ''; + $joomlaName = (string) ($xml->name ?? ''); + + if ($metaDisplay !== '' && $joomlaName !== '') { + $displayMatch = ($metaDisplay === $joomlaName); + $results[] = [ + 'field' => 'display_name', + 'metadata' => $metaDisplay, + 'joomla' => $joomlaName, + 'status' => $displayMatch ? 'ok' : 'warn', + 'message' => $displayMatch + ? 'matches' + : "metadata has \"{$metaDisplay}\" but Joomla has \"{$joomlaName}\"", + ]; + } + + // 4. Version + $metaVersion = $metadata['version'] ?? ''; + $joomlaVersion = (string) ($xml->version ?? ''); + + if ($metaVersion !== '' && $joomlaVersion !== '') { + // Strip dev/rc suffixes for comparison (CI bumps these) + $metaBase = preg_replace('/-(dev|rc|alpha|beta)\d*$/', '', $metaVersion); + $joomlaBase = preg_replace('/-(dev|rc|alpha|beta)\d*$/', '', $joomlaVersion); + $versionMatch = ($metaBase === $joomlaBase); + + $results[] = [ + 'field' => 'version', + 'metadata' => $metaVersion, + 'joomla' => $joomlaVersion, + 'status' => $versionMatch ? 'ok' : 'warn', + 'message' => $versionMatch + ? 'matches (base version)' + : "metadata has \"{$metaVersion}\" but Joomla has \"{$joomlaVersion}\"", + ]; + } + + // 5. PHP minimum (from composer.json) + $composerPhp = $this->readComposerPhpRequirement($root); + $metaPhp = $metadata['php_minimum'] ?? ''; + + if ($composerPhp !== '' && $metaPhp !== '') { + $phpMatch = ($metaPhp === $composerPhp); + $results[] = [ + 'field' => 'php_minimum', + 'metadata' => $metaPhp, + 'joomla' => $composerPhp . ' (composer.json)', + 'status' => $phpMatch ? 'ok' : 'warn', + 'message' => $phpMatch + ? 'matches composer.json' + : "metadata has \"{$metaPhp}\" but composer.json requires \"{$composerPhp}\"", + ]; + } + + // 6. Description + $metaDesc = $metadata['description'] ?? ''; + $joomlaDesc = (string) ($xml->description ?? ''); + + // Joomla descriptions are often language keys, skip those + if ($metaDesc !== '' && $joomlaDesc !== '' && !str_starts_with($joomlaDesc, 'COM_') && !str_starts_with($joomlaDesc, 'PKG_')) { + $descMatch = ($metaDesc === $joomlaDesc); + $results[] = [ + 'field' => 'description', + 'metadata' => substr($metaDesc, 0, 60) . (strlen($metaDesc) > 60 ? '...' : ''), + 'joomla' => substr($joomlaDesc, 0, 60) . (strlen($joomlaDesc) > 60 ? '...' : ''), + 'status' => $descMatch ? 'ok' : 'info', + 'message' => $descMatch ? 'matches' : 'descriptions differ (informational)', + ]; + } + + return $results; + } + + // ================================================================= + // Helpers + // ================================================================= + + /** + * Normalize package_type — map MokoGitea types to Joomla types. + */ + private function normalizePackageType(string $type): string + { + return match (strtolower($type)) { + 'joomla-extension' => 'package', // legacy mapping + default => strtolower($type), + }; + } + + /** + * Derive the Joomla element name from type + name. + * Replicates MokoGitea's cleanJoomlaElement() + prefix logic. + */ + private function deriveElement(string $type, string $name): string + { + // Clean: lowercase, strip non-alphanumeric except . _ - + $clean = strtolower($name); + $clean = preg_replace('/[^a-z0-9._-]/', '', $clean); + + $prefix = self::JOOMLA_PREFIX[$type] ?? ''; + + return $prefix . $clean; + } + + /** + * Extract the element name from a Joomla manifest XML. + * Follows the same logic as Joomla's InstallerAdapter::getElement(). + */ + private function extractJoomlaElement(\SimpleXMLElement $xml, string $type): string + { + switch ($type) { + case 'package': + $packagename = (string) ($xml->packagename ?? ''); + if ($packagename !== '') { + return 'pkg_' . strtolower(preg_replace('/[^a-zA-Z0-9._-]/', '', $packagename)); + } + break; + + case 'component': + $element = (string) ($xml->element ?? ''); + if ($element !== '') { + $element = strtolower($element); + return str_starts_with($element, 'com_') ? $element : 'com_' . $element; + } + $name = (string) ($xml->name ?? ''); + $name = strtolower(preg_replace('/[^a-zA-Z0-9._-]/', '', $name)); + return str_starts_with($name, 'com_') ? $name : 'com_' . $name; + + case 'module': + $element = (string) ($xml->element ?? ''); + if ($element !== '') { + return strtolower($element); + } + break; + + case 'plugin': + // Plugins derive element from the file attribute + if (isset($xml->files)) { + foreach ($xml->files->children() as $file) { + $plugin = (string) ($file->attributes()->plugin ?? ''); + if ($plugin !== '') { + return strtolower($plugin); + } + } + } + break; + + case 'library': + $libname = (string) ($xml->libraryname ?? ''); + if ($libname !== '') { + return strtolower($libname); + } + break; + } + + // Fallback: use tag + $name = (string) ($xml->name ?? ''); + return strtolower(preg_replace('/[^a-zA-Z0-9._-]/', '', $name)); + } + + /** + * Read PHP version requirement from composer.json. + */ + private function readComposerPhpRequirement(string $root): string + { + $composerFile = "{$root}/composer.json"; + + if (!is_file($composerFile)) { + return ''; + } + + $data = json_decode(file_get_contents($composerFile), true); + + if (!is_array($data)) { + return ''; + } + + $phpReq = $data['require']['php'] ?? ''; + + // Extract version number from constraint like ">=8.1" + if (preg_match('/(\d+\.\d+)/', $phpReq, $m)) { + return $m[1]; + } + + return ''; + } + + private function detectRepoName(string $root): string + { + $gitConfig = "{$root}/.git/config"; + + if (!file_exists($gitConfig)) { + return basename($root); + } + + $content = file_get_contents($gitConfig); + + if (preg_match('/url\s*=\s*.*\/([^\/\s]+?)(?:\.git)?\s*$/m', $content, $m)) { + return $m[1]; + } + + return basename($root); + } + + // ================================================================= + // Output + // ================================================================= + + private function printResults(string $repoName, array $results): void + { + $errors = count(array_filter($results, fn($r) => $r['status'] === 'error')); + $warns = count(array_filter($results, fn($r) => $r['status'] === 'warn')); + $oks = count(array_filter($results, fn($r) => $r['status'] === 'ok')); + + $this->log('INFO', "Validating {$repoName} Joomla metadata...\n"); + + foreach ($results as $r) { + $icon = match ($r['status']) { + 'ok' => "\xE2\x9C\x93", // ✓ + 'error' => "\xE2\x9C\x97", // ✗ + 'warn' => "\xE2\x9A\xA0", // ⚠ + default => "\xE2\x84\xB9", // ℹ + }; + + $line = sprintf( + " %s %-16s %s", + $icon, + $r['field'], + $r['message'] + ); + + $this->log( + match ($r['status']) { + 'error' => 'ERROR', + 'warn' => 'WARN', + 'ok' => 'OK', + default => 'INFO', + }, + $line + ); + } + + echo "\n"; + + if ($errors > 0) { + $this->log('ERROR', "{$errors} error(s) — update delivery will fail"); + } elseif ($warns > 0) { + $this->log('WARN', "All critical checks passed, {$warns} warning(s)"); + } else { + $this->log('OK', "All {$oks} checks passed"); + } + } +} + +$app = new JoomlaMetadataValidateCli(); +exit($app->execute()); -- 2.52.0 From a51f0bfb2fc0a5da30208c801dc8c268116dbf1e Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Fri, 19 Jun 2026 03:13:31 -0500 Subject: [PATCH 2/3] fix: rename package_type to extension_type, remove display_name validation (#259) - API endpoint updated from /manifest to /metadata - Removed dead .mokogitea/manifest.xml local file fallback - display_name is now server-computed, no longer validated - package_type renamed to extension_type throughout --- cli/joomla_metadata_validate.php | 63 +++++--------------------------- 1 file changed, 10 insertions(+), 53 deletions(-) diff --git a/cli/joomla_metadata_validate.php b/cli/joomla_metadata_validate.php index dac1cc9..90fa2c3 100644 --- a/cli/joomla_metadata_validate.php +++ b/cli/joomla_metadata_validate.php @@ -165,39 +165,13 @@ class JoomlaMetadataValidateCli extends CliFramework } // ================================================================= - // Load metadata (from .mokogitea/manifest.xml or API) + // Load metadata (from API) // ================================================================= private function loadMetadata(string $root, string $org, string $repoName, string $token, string $apiBase): ?array { - // Try local .mokogitea/manifest.xml first - $localManifest = "{$root}/.mokogitea/manifest.xml"; - - if (is_file($localManifest)) { - $xml = @simplexml_load_file($localManifest); - - if ($xml !== false) { - $identity = $xml->identity ?? $xml; - $governance = $xml->governance ?? $xml; - $build = $xml->build ?? $xml; - - return [ - 'name' => (string) ($identity->name ?? ''), - 'display_name' => (string) ($identity->{'display-name'} ?? ''), - 'description' => (string) ($identity->description ?? ''), - 'version' => (string) ($identity->version ?? ''), - 'platform' => (string) ($governance->platform ?? ''), - 'package_type' => (string) ($build->{'package-type'} ?? ''), - 'language' => (string) ($build->language ?? ''), - 'entry_point' => (string) ($build->{'entry-point'} ?? ''), - 'source' => 'local', - ]; - } - } - - // Fall back to API if ($token !== '') { - $url = "{$apiBase}/repos/{$org}/{$repoName}/manifest"; + $url = "{$apiBase}/repos/{$org}/{$repoName}/metadata"; $ctx = stream_context_create([ 'http' => [ 'header' => "Authorization: token {$token}\r\nAccept: application/json\r\n", @@ -229,10 +203,10 @@ class JoomlaMetadataValidateCli extends CliFramework $xml = $joomlaXml['xml']; $type = $joomlaXml['type']; - // 1. Extension type vs package_type - $metaType = $this->normalizePackageType($metadata['package_type'] ?? ''); + // 1. Extension type + $metaType = $this->normalizeExtensionType($metadata['extension_type'] ?? ''); $results[] = [ - 'field' => 'package_type', + 'field' => 'extension_type', 'metadata' => $metaType, 'joomla' => $type, 'status' => ($metaType === $type) ? 'ok' : 'error', @@ -257,24 +231,7 @@ class JoomlaMetadataValidateCli extends CliFramework : "metadata derives \"{$metaElement}\" but Joomla uses \"{$joomlaElement}\"", ]; - // 3. Display name - $metaDisplay = $metadata['display_name'] ?? ''; - $joomlaName = (string) ($xml->name ?? ''); - - if ($metaDisplay !== '' && $joomlaName !== '') { - $displayMatch = ($metaDisplay === $joomlaName); - $results[] = [ - 'field' => 'display_name', - 'metadata' => $metaDisplay, - 'joomla' => $joomlaName, - 'status' => $displayMatch ? 'ok' : 'warn', - 'message' => $displayMatch - ? 'matches' - : "metadata has \"{$metaDisplay}\" but Joomla has \"{$joomlaName}\"", - ]; - } - - // 4. Version + // 3. Version $metaVersion = $metadata['version'] ?? ''; $joomlaVersion = (string) ($xml->version ?? ''); @@ -295,7 +252,7 @@ class JoomlaMetadataValidateCli extends CliFramework ]; } - // 5. PHP minimum (from composer.json) + // 4. PHP minimum (from composer.json) $composerPhp = $this->readComposerPhpRequirement($root); $metaPhp = $metadata['php_minimum'] ?? ''; @@ -312,7 +269,7 @@ class JoomlaMetadataValidateCli extends CliFramework ]; } - // 6. Description + // 5. Description $metaDesc = $metadata['description'] ?? ''; $joomlaDesc = (string) ($xml->description ?? ''); @@ -336,9 +293,9 @@ class JoomlaMetadataValidateCli extends CliFramework // ================================================================= /** - * Normalize package_type — map MokoGitea types to Joomla types. + * Normalize extension_type — map MokoGitea types to Joomla types. */ - private function normalizePackageType(string $type): string + private function normalizeExtensionType(string $type): string { return match (strtolower($type)) { 'joomla-extension' => 'package', // legacy mapping -- 2.52.0 From 1b113af0687df357b3c623c3cde071a5e3d1b1c3 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Fri, 19 Jun 2026 08:14:01 +0000 Subject: [PATCH 3/3] chore(version): pre-release bump to 09.25.04-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- README.md | 2 +- cli/branch_rename.php | 2 +- cli/bulk_workflow_push.php | 2 +- cli/bulk_workflow_trigger.php | 2 +- cli/client_dashboard.php | 2 +- cli/client_inventory.php | 2 +- cli/client_provision.php | 2 +- cli/grafana_dashboard.php | 2 +- cli/joomla_build.php | 2 +- cli/joomla_metadata_validate.php | 2 +- cli/manifest_licensing.php | 2 +- cli/manifest_read.php | 2 +- cli/platform_detect.php | 2 +- cli/release_cascade.php | 2 +- cli/release_publish.php | 2 +- cli/scaffold_client.php | 2 +- cli/updates_xml_sync.php | 2 +- cli/version_auto_bump.php | 2 +- cli/version_check.php | 2 +- cli/wiki_sync.php | 2 +- cli/workflow_sync.php | 2 +- deploy/backup-before-deploy.php | 2 +- deploy/deploy-dolibarr.php | 2 +- deploy/health-check.php | 2 +- deploy/rollback-joomla.php | 2 +- deploy/sync-joomla.php | 2 +- tests/Unit/VersionBumpTest.php | 2 +- tests/Unit/VersionReadTest.php | 4 ++-- validate/check_file_integrity.php | 2 +- 30 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index be48abe..3819480 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: moko-platform.Automation -# VERSION: 09.25.03 +# VERSION: 09.25.04 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/README.md b/README.md index a4b1b82..49fdc19 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DEFGROUP: MokoPlatform.Root INGROUP: MokoPlatform REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform PATH: /README.md -VERSION: 09.25.03 +VERSION: 09.25.04 BRIEF: Project overview and documentation --> diff --git a/cli/branch_rename.php b/cli/branch_rename.php index fdc7c40..89190e4 100644 --- a/cli/branch_rename.php +++ b/cli/branch_rename.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/branch_rename.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old) */ diff --git a/cli/bulk_workflow_push.php b/cli/bulk_workflow_push.php index 06e7281..5d9c8d1 100644 --- a/cli/bulk_workflow_push.php +++ b/cli/bulk_workflow_push.php @@ -12,7 +12,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/bulk_workflow_push.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Push a workflow file to all governed repos via the Gitea Contents API */ diff --git a/cli/bulk_workflow_trigger.php b/cli/bulk_workflow_trigger.php index 426a264..bf5e08d 100644 --- a/cli/bulk_workflow_trigger.php +++ b/cli/bulk_workflow_trigger.php @@ -12,7 +12,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/bulk_workflow_trigger.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Trigger a workflow across multiple repos at once */ diff --git a/cli/client_dashboard.php b/cli/client_dashboard.php index 73d62ad..5c6e1fd 100644 --- a/cli/client_dashboard.php +++ b/cli/client_dashboard.php @@ -12,7 +12,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/client_dashboard.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Generate unified client dashboard HTML */ diff --git a/cli/client_inventory.php b/cli/client_inventory.php index 34679c0..4df49ca 100644 --- a/cli/client_inventory.php +++ b/cli/client_inventory.php @@ -12,7 +12,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/client_inventory.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Discover and list all client-waas repos with their server configuration status */ diff --git a/cli/client_provision.php b/cli/client_provision.php index 44c66df..ff960c7 100644 --- a/cli/client_provision.php +++ b/cli/client_provision.php @@ -12,7 +12,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/client_provision.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Provision a new client environment end-to-end */ diff --git a/cli/grafana_dashboard.php b/cli/grafana_dashboard.php index e15ad34..9c9f0ab 100644 --- a/cli/grafana_dashboard.php +++ b/cli/grafana_dashboard.php @@ -12,7 +12,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/grafana_dashboard.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Manage Grafana dashboards via API */ diff --git a/cli/joomla_build.php b/cli/joomla_build.php index e758d3b..61a0df8 100644 --- a/cli/joomla_build.php +++ b/cli/joomla_build.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/joomla_build.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Build a Joomla extension ZIP from manifest — all types supported * NOTE: Called by pre-release and auto-release workflows. */ diff --git a/cli/joomla_metadata_validate.php b/cli/joomla_metadata_validate.php index 90fa2c3..b7b07d8 100644 --- a/cli/joomla_metadata_validate.php +++ b/cli/joomla_metadata_validate.php @@ -10,7 +10,7 @@ * INGROUP: mokoplatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform * PATH: /cli/joomla_metadata_validate.php - * VERSION: 09.30.00 + * VERSION: 09.25.04 * BRIEF: Validate MokoGitea repo metadata against Joomla extension manifest XML */ diff --git a/cli/manifest_licensing.php b/cli/manifest_licensing.php index 565a7e1..3058b66 100644 --- a/cli/manifest_licensing.php +++ b/cli/manifest_licensing.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/manifest_licensing.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests */ diff --git a/cli/manifest_read.php b/cli/manifest_read.php index 441366f..6bcfa13 100644 --- a/cli/manifest_read.php +++ b/cli/manifest_read.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/manifest_read.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Parse .manifest.xml and output requested field(s) for CI consumption */ diff --git a/cli/platform_detect.php b/cli/platform_detect.php index 7892ed4..0b19eff 100644 --- a/cli/platform_detect.php +++ b/cli/platform_detect.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/platform_detect.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Auto-detect repository platform type and optionally update manifest */ diff --git a/cli/release_cascade.php b/cli/release_cascade.php index 2b64134..a760acd 100644 --- a/cli/release_cascade.php +++ b/cli/release_cascade.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/release_cascade.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: DEPRECATED — cascade behavior removed. Each release stream is independent. */ diff --git a/cli/release_publish.php b/cli/release_publish.php index 9e4b368..cb22975 100644 --- a/cli/release_publish.php +++ b/cli/release_publish.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/release_publish.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Publish a release and create copies for all lesser stability streams. */ diff --git a/cli/scaffold_client.php b/cli/scaffold_client.php index efc2466..7df9e2c 100644 --- a/cli/scaffold_client.php +++ b/cli/scaffold_client.php @@ -12,7 +12,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/scaffold_client.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings */ diff --git a/cli/updates_xml_sync.php b/cli/updates_xml_sync.php index db20af5..31ee8d0 100644 --- a/cli/updates_xml_sync.php +++ b/cli/updates_xml_sync.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/updates_xml_sync.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Sync updates.xml to target branches via Gitea API * NOTE: Called by pre-release and auto-release workflows after updates.xml * is modified on the current branch. Pushes the file to other branches diff --git a/cli/version_auto_bump.php b/cli/version_auto_bump.php index 4282140..c028360 100644 --- a/cli/version_auto_bump.php +++ b/cli/version_auto_bump.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/version_auto_bump.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash */ diff --git a/cli/version_check.php b/cli/version_check.php index ec2af82..fe84c9d 100644 --- a/cli/version_check.php +++ b/cli/version_check.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/version_check.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Validate version consistency across README, manifests, and sub-packages */ diff --git a/cli/wiki_sync.php b/cli/wiki_sync.php index 4c68879..34c5f6b 100644 --- a/cli/wiki_sync.php +++ b/cli/wiki_sync.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/wiki_sync.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Sync select wiki pages from moko-platform to all template repos */ diff --git a/cli/workflow_sync.php b/cli/workflow_sync.php index f22b352..96bec10 100644 --- a/cli/workflow_sync.php +++ b/cli/workflow_sync.php @@ -10,7 +10,7 @@ * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/workflow_sync.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform */ diff --git a/deploy/backup-before-deploy.php b/deploy/backup-before-deploy.php index db7c54f..d2cea7a 100644 --- a/deploy/backup-before-deploy.php +++ b/deploy/backup-before-deploy.php @@ -12,7 +12,7 @@ * INGROUP: MokoPlatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /deploy/backup-before-deploy.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Snapshot Joomla directories before deployment for rollback capability */ diff --git a/deploy/deploy-dolibarr.php b/deploy/deploy-dolibarr.php index d10d782..9972774 100644 --- a/deploy/deploy-dolibarr.php +++ b/deploy/deploy-dolibarr.php @@ -12,7 +12,7 @@ * INGROUP: MokoPlatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /deploy/deploy-dolibarr.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync */ diff --git a/deploy/health-check.php b/deploy/health-check.php index b7fbcc3..af50850 100644 --- a/deploy/health-check.php +++ b/deploy/health-check.php @@ -12,7 +12,7 @@ * INGROUP: MokoPlatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /deploy/health-check.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Post-deploy health check — verify a Joomla site is responding correctly */ diff --git a/deploy/rollback-joomla.php b/deploy/rollback-joomla.php index 5650bfa..a5ff9bc 100644 --- a/deploy/rollback-joomla.php +++ b/deploy/rollback-joomla.php @@ -12,7 +12,7 @@ * INGROUP: MokoPlatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /deploy/rollback-joomla.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot */ diff --git a/deploy/sync-joomla.php b/deploy/sync-joomla.php index fb4d970..027ec79 100644 --- a/deploy/sync-joomla.php +++ b/deploy/sync-joomla.php @@ -12,7 +12,7 @@ * INGROUP: MokoPlatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /deploy/sync-joomla.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Sync Joomla site directories between two servers via rsync over SSH */ diff --git a/tests/Unit/VersionBumpTest.php b/tests/Unit/VersionBumpTest.php index e129061..250fa35 100644 --- a/tests/Unit/VersionBumpTest.php +++ b/tests/Unit/VersionBumpTest.php @@ -63,7 +63,7 @@ class VersionBumpTest extends TestCase { file_put_contents( "{$this->tmpDir}/README.md", - "\nSome content\n" + "\nSome content\n" ); $this->execute(); diff --git a/tests/Unit/VersionReadTest.php b/tests/Unit/VersionReadTest.php index bbba14a..a7060c1 100644 --- a/tests/Unit/VersionReadTest.php +++ b/tests/Unit/VersionReadTest.php @@ -34,7 +34,7 @@ class VersionReadTest extends TestCase { file_put_contents( "{$this->tmpDir}/README.md", - "# Test\n\n" + "# Test\n\n" ); $this->assertSame('02.03.04', trim($this->runScript())); @@ -68,7 +68,7 @@ class VersionReadTest extends TestCase { file_put_contents( "{$this->tmpDir}/README.md", - "\n" + "\n" ); mkdir("{$this->tmpDir}/src", 0755, true); file_put_contents( diff --git a/validate/check_file_integrity.php b/validate/check_file_integrity.php index 3fe5c5d..f5a60a0 100644 --- a/validate/check_file_integrity.php +++ b/validate/check_file_integrity.php @@ -12,7 +12,7 @@ * INGROUP: MokoPlatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /validate/check_file_integrity.php - * VERSION: 09.25.03 + * VERSION: 09.25.04 * BRIEF: Compare deployed files on a remote server against the local repository to detect drift */ -- 2.52.0