Files
moko-platform/validate/check_version_consistency.php
Jonathan Miller 4cc3f5bee4
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (pull_request) Successful in 6s
Generic: Repo Health / Scripts governance (pull_request) Successful in 6s
Generic: Repo Health / Repository health (push) Successful in 14s
Generic: Repo Health / Repository health (pull_request) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been skipped
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been skipped
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
style: fix all PHPCS PSR-12 violations across 74 files (7539 → 0 errors)
- Convert tabs to spaces (3,413 violations)
- Fix line endings, trailing whitespace, brace placement
- Break lines exceeding 150-char absolute limit
- Replace heredoc tab closers with spaces
- Fix empty elseif, forbidden function calls
- Update phpcs.xml: exclude rules inappropriate for CLI scripts
  (SideEffects, MissingNamespace, MultipleClasses, HeaderOrder,
  empty catch blocks)

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-24 17:07:51 -05:00

243 lines
10 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Validate
* INGROUP: MokoStandards
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /validate/check_version_consistency.php
* BRIEF: Validates that version numbers are consistent across all critical repository files
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoEnterprise\CliFramework;
/**
* Checks that the version recorded in composer.json matches VERSION headers
* and badges in README.md, CHANGELOG.md, CONTRIBUTING.md, workflow files,
* and PHP Enterprise library files.
*/
class CheckVersionConsistency extends CliFramework
{
protected function configure(): void
{
$this->setDescription('Validates version consistency across all critical repository files');
$this->addArgument('--path', 'Repository root path to check', '.');
}
protected function run(): int
{
$path = rtrim((string) $this->getArgument('--path'), '/\\');
$composerFile = $path . '/composer.json';
// ── Resolve expected version ──────────────────────────────────────────
$this->section('Resolving expected version');
$expected = null;
if (is_file($composerFile)) {
$data = json_decode((string) file_get_contents($composerFile), true);
if (isset($data['version'])) {
$expected = (string) $data['version'];
$this->status(true, "Expected version (composer.json): {$expected}");
} else {
$this->status(false, 'composer.json', 'missing "version" key');
}
} else {
$this->status(false, 'composer.json', 'file not found — falling back to README.md');
}
// Fallback: extract version from README.md VERSION header
if ($expected === null) {
$readmeFile = $path . '/README.md';
if (is_file($readmeFile)) {
$readme = (string) file_get_contents($readmeFile);
if (preg_match('/VERSION:\s*(\d{2}\.\d{2}\.\d{2})/', $readme, $m)) {
$expected = $m[1];
$this->status(true, "Expected version (README.md): {$expected}");
} else {
$this->status(false, 'README.md', 'no VERSION header found');
return 2;
}
} else {
$this->status(false, 'README.md', 'file not found');
return 2;
}
}
// ── Check critical root files ─────────────────────────────────────────
$this->section('Checking critical files');
$criticalChecks = [
'README.md' => ['/VERSION:\s*(\d{2}\.\d{2}\.\d{2})/', '/MokoStandards-(\d{2}\.\d{2}\.\d{2})/'],
'CHANGELOG.md' => ['/VERSION:\s*(\d{2}\.\d{2}\.\d{2})/'],
'CONTRIBUTING.md' => ['/VERSION:\s*(\d{2}\.\d{2}\.\d{2})/'],
];
$issues = [];
foreach ($criticalChecks as $filename => $patterns) {
$file = $path . '/' . $filename;
if (!is_file($file)) {
$this->status(false, $filename, 'file not found');
$issues[] = $filename;
continue;
}
$content = (string) file_get_contents($file);
$filePassed = true;
foreach ($patterns as $pattern) {
preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[1] as $match) {
if ($match[0] !== $expected) {
$line = substr_count(substr($content, 0, (int) $match[1]), "\n") + 1;
$this->status(false, $filename, "line {$line}: found {$match[0]}, expected {$expected}");
$issues[] = $filename;
$filePassed = false;
}
}
}
if ($filePassed) {
$this->status(true, $filename);
}
}
// ── Check workflow files ──────────────────────────────────────────────
$this->section('Checking workflow files');
// Check both .github/workflows and .gitea/workflows
$workflowFiles = [];
foreach (['.github/workflows', '.mokogitea/workflows'] as $wfDir) {
$dir = $path . '/' . $wfDir;
if (is_dir($dir)) {
$workflowFiles = array_merge($workflowFiles, glob($dir . '/*.yml') ?: []);
}
}
$total = count($workflowFiles);
foreach ($workflowFiles as $i => $file) {
$this->progress($i + 1, $total, basename($file));
$content = (string) file_get_contents($file);
$filePassed = true;
preg_match_all('/#\s*VERSION:\s*(\d{2}\.\d{2}\.\d{2})/', $content, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[1] as $match) {
if ($match[0] !== $expected) {
$this->progress($i + 1, $total, '', true);
$rel = str_replace($path . '/', '', $file);
$this->status(false, $rel, "found {$match[0]}, expected {$expected}");
$issues[] = $rel;
$filePassed = false;
}
}
}
$this->progress($total, $total, '', true);
// ── Check PHP Enterprise library files ────────────────────────────────
$this->section('Checking PHP source files');
$phpFiles = $this->findPhpFiles($path . '/lib/Enterprise');
$phpTotal = count($phpFiles);
foreach ($phpFiles as $i => $file) {
$this->progress($i + 1, $phpTotal, basename($file));
$content = (string) file_get_contents($file);
$filePassed = true;
preg_match_all('/\* VERSION:\s*(\d{2}\.\d{2}\.\d{2})/', $content, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[1] as $match) {
if ($match[0] !== $expected) {
$this->progress($i + 1, $phpTotal, '', true);
$rel = str_replace($path . '/', '', $file);
$this->status(false, $rel, "found {$match[0]}, expected {$expected}");
$issues[] = $rel;
$filePassed = false;
}
}
}
$this->progress($phpTotal, $phpTotal, '', true);
// ── Check Terraform definition files ─────────────────────────────────
// Each .tf file has TWO version locations that must both match:
// - Block-comment header: * Version: XX.XX.XX
// - HCL metadata field: version = "XX.XX.XX"
$this->section('Checking Terraform definition files');
$defFiles = glob($path . '/definitions/default/*.tf') ?: [];
$defTotal = count($defFiles);
foreach ($defFiles as $i => $file) {
$this->progress($i + 1, $defTotal, basename($file));
$content = (string) file_get_contents($file);
$filePassed = true;
$rel = str_replace($path . '/', '', $file);
// Block-comment header version
preg_match_all('/\*\s*Version:\s*(\d{2}\.\d{2}\.\d{2})/', $content, $headerMatches, PREG_OFFSET_CAPTURE);
foreach ($headerMatches[1] as $match) {
if ($match[0] !== $expected) {
$this->progress($i + 1, $defTotal, '', true);
$this->status(false, $rel, "header Version: found {$match[0]}, expected {$expected}");
$issues[] = $rel;
$filePassed = false;
}
}
// HCL metadata version field
preg_match_all('/^\s*version\s*=\s*"(\d{2}\.\d{2}\.\d{2})"/m', $content, $hclMatches, PREG_OFFSET_CAPTURE);
foreach ($hclMatches[1] as $match) {
if ($match[0] !== $expected) {
$this->progress($i + 1, $defTotal, '', true);
$this->status(false, $rel, "HCL version = found {$match[0]}, expected {$expected}");
$issues[] = $rel;
$filePassed = false;
}
}
if ($filePassed) {
$this->status(true, $rel);
}
}
$this->progress($defTotal, $defTotal, '', true);
// ── Summary ───────────────────────────────────────────────────────────
$totalChecked = count($criticalChecks) + $total + $phpTotal + $defTotal;
$totalFailed = count(array_unique($issues));
$this->printSummary($totalChecked - $totalFailed, $totalFailed, $this->elapsed());
return $totalFailed === 0 ? 0 : 1;
}
/**
* Recursively find all PHP files under a directory.
*
* @return list<string>
*/
private function findPhpFiles(string $dir): array
{
if (!is_dir($dir)) {
return [];
}
$files = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$files[] = $file->getPathname();
}
}
return $files;
}
}
$script = new CheckVersionConsistency('check_version_consistency', 'Validates version consistency across repository files');
exit($script->execute());