#!/usr/bin/env php * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: moko-platform.CLI * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/release_validate.php * BRIEF: Pre-release validation -- version consistency, required files, manifest checks */ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; use MokoEnterprise\CliFramework; class ReleaseValidateCli extends CliFramework { private int $pass = 0; private int $fail = 0; private int $warn = 0; private array $results = []; protected function configure(): void { $this->setDescription('Pre-release validation -- version consistency, required files, manifest checks'); $this->addArgument('--path', 'Repository root', '.'); $this->addArgument('--version', 'Expected version string', null); $this->addArgument('--platform', 'joomla|dolibarr|generic', null); $this->addArgument('--output-summary', 'Write markdown to $GITHUB_STEP_SUMMARY', false); $this->addArgument('--github-output', 'Export counts to $GITHUB_OUTPUT', false); } protected function run(): int { $path = $this->getArgument('--path'); $version = $this->getArgument('--version'); $platform = $this->getArgument('--platform'); $outputSummary = (bool) $this->getArgument('--output-summary'); $githubOutput = (bool) $this->getArgument('--github-output'); if ($version === null) { $this->log('ERROR', "Usage: release_validate.php --path . --version XX.YY.ZZ [--platform joomla]"); return 1; } $root = realpath($path) ?: $path; if ($platform === null) { $manifestXml = "{$root}/.mokogitea/manifest.xml"; if (file_exists($manifestXml)) { $mContent = file_get_contents($manifestXml); if (preg_match('/([^<]+)<\/platform>/', $mContent, $pm)) { $platform = trim($pm[1]); } } if (in_array($platform, ['waas-component'], true)) { $platform = 'joomla'; } if (in_array($platform, ['crm-module'], true)) { $platform = 'dolibarr'; } if ($platform === null) { $platform = 'generic'; } } $hasSource = is_dir("{$root}/src") || is_dir("{$root}/htdocs"); $this->addVResult('Source directory', $hasSource ? 'PASS' : 'WARN', $hasSource ? 'src/ or htdocs/ found' : 'No src/ or htdocs/ directory'); if (!file_exists("{$root}/README.md")) { $this->addVResult('README.md', 'FAIL', 'Not found'); } else { $readme = file_get_contents("{$root}/README.md"); $this->addVResult('README.md version', (preg_match('/VERSION:\s*' . preg_quote($version, '/') . '/', $readme) || strpos($readme, $version) !== false) ? 'PASS' : 'FAIL', (preg_match('/VERSION:\s*' . preg_quote($version, '/') . '/', $readme) || strpos($readme, $version) !== false) ? "`{$version}` found" : "`{$version}` not found"); } if (!file_exists("{$root}/CHANGELOG.md")) { $this->addVResult('CHANGELOG.md', 'WARN', 'Not found'); } else { $cl = file_get_contents("{$root}/CHANGELOG.md"); $this->addVResult('CHANGELOG.md version', preg_match('/^##\s.*' . preg_quote($version, '/') . '/m', $cl) ? 'PASS' : 'WARN', preg_match('/^##\s.*' . preg_quote($version, '/') . '/m', $cl) ? "Section found" : "No section header"); } $licenseFound = false; foreach (['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'] as $lf) { if (file_exists("{$root}/{$lf}")) { $licenseFound = true; break; } } $this->addVResult('LICENSE', $licenseFound ? 'PASS' : 'FAIL', $licenseFound ? 'Found' : 'Not found'); if ($platform === 'joomla') { $manifest = null; foreach (["{$root}/src", $root] as $dir) { if (!is_dir($dir)) continue; foreach (glob("{$dir}/*.xml") as $xmlFile) { $content = file_get_contents($xmlFile); if (strpos($content, 'addVResult('XML manifest', 'FAIL', 'No Joomla manifest found'); } else { if (preg_match('/([^<]+)<\/version>/', file_get_contents($manifest), $m)) { $mVer = trim($m[1]); $this->addVResult('Manifest version', $mVer === $version ? 'PASS' : 'FAIL', $mVer === $version ? "`{$mVer}` matches" : "`{$mVer}` != `{$version}`"); } else { $this->addVResult('Manifest version', 'FAIL', 'No tag'); } } if (!file_exists("{$root}/updates.xml")) { $this->addVResult('updates.xml', 'WARN', 'Not found'); } else { $ux = file_get_contents("{$root}/updates.xml"); $this->addVResult('updates.xml version', preg_match('/' . preg_quote($version, '/') . '<\/version>/', $ux) ? 'PASS' : 'FAIL', preg_match('/' . preg_quote($version, '/') . '<\/version>/', $ux) ? "`{$version}` found" : "`{$version}` not found"); } } elseif ($platform === 'dolibarr') { $modFile = null; foreach (['src', 'htdocs'] as $sd) { $matches = glob("{$root}/{$sd}/mod*.class.php"); if (!empty($matches)) { $modFile = $matches[0]; break; } } if ($modFile === null) { $this->addVResult('Dolibarr mod file', 'FAIL', 'No mod*.class.php found'); } else { $mc = file_get_contents($modFile); $this->addVResult('Dolibarr version', preg_match("/\\\$this->version\s*=\s*'" . preg_quote($version, '/') . "'/", $mc) ? 'PASS' : 'FAIL', preg_match("/\\\$this->version\s*=\s*'" . preg_quote($version, '/') . "'/", $mc) ? "`{$version}` matches" : "`{$version}` not found"); } } if (file_exists("{$root}/composer.json")) { $composer = json_decode(file_get_contents("{$root}/composer.json"), true); if (isset($composer['version'])) { $this->addVResult('composer.json version', $composer['version'] === $version ? 'PASS' : 'WARN', $composer['version'] === $version ? "`{$version}` matches" : "`{$composer['version']}` != `{$version}`"); } } $table = "| Check | Result | Details |\n|-------|--------|--------|\n"; foreach ($this->results as $r) { $table .= "| {$r['check']} | {$r['status']} | {$r['details']} |\n"; } $table .= "\n**Validation: {$this->pass} passed, {$this->fail} failed, {$this->warn} warnings**\n"; echo $table; if ($outputSummary) { $summaryFile = getenv('GITHUB_STEP_SUMMARY'); if ($summaryFile) { file_put_contents($summaryFile, "## Pre-Release Sanity Checks ({$platform})\n\n{$table}\n", FILE_APPEND); } } if ($githubOutput) { $ghOutput = getenv('GITHUB_OUTPUT'); if ($ghOutput) { file_put_contents($ghOutput, "validation_pass={$this->pass}\nvalidation_fail={$this->fail}\nvalidation_warn={$this->warn}\nvalidation_platform={$platform}\n", FILE_APPEND); } } return $this->fail > 0 ? 1 : 0; } private function addVResult(string $check, string $status, string $details): void { $this->results[] = ['check' => $check, 'status' => $status, 'details' => $details]; if ($status === 'PASS') { $this->pass++; } elseif ($status === 'FAIL') { $this->fail++; } elseif ($status === 'WARN') { $this->warn++; } } } $app = new ReleaseValidateCli(); exit($app->execute());