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
- 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>
243 lines
10 KiB
PHP
Executable File
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());
|