Files
MokoCLI/bin/validate-module
T
Jonathan Miller d206dd5afb fix: rename MokoStandards to MokoCLI in CLI help text and output (#265)
Update bin/moko banner, help text, and file headers.
Update bin/validate-module output and headers.
2026-06-20 11:24:42 -05:00

172 lines
6.5 KiB
PHP

#!/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: MokoCLI.CLI
* INGROUP: MokoCLI
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
* PATH: /bin/validate-module
* VERSION: 04.01.00
* BRIEF: Validates a Dolibarr module structure — checks descriptor, version, module ID, required files
*
* USAGE
* vendor/bin/validate-module --path . (from a governed Dolibarr repo)
* php bin/validate-module --path /path/to/repo (standalone)
*/
declare(strict_types=1);
// Find autoloader — works from vendor/bin/ symlink or direct execution
$autoloaders = [
__DIR__ . '/../vendor/autoload.php', // direct: bin/validate-module
__DIR__ . '/../autoload.php', // symlink: vendor/bin/validate-module → ../../bin/validate-module
__DIR__ . '/../../autoload.php', // alternative vendor layout
];
foreach ($autoloaders as $autoloader) {
if (file_exists($autoloader)) {
require_once $autoloader;
break;
}
}
// Resolve --path argument
$path = '.';
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
}
$path = realpath($path) ?: $path;
$errors = [];
$warnings = [];
$passed = [];
// ── 1. Check src/ directory exists ──────────────────────────────────────
if (!is_dir("{$path}/src")) {
$errors[] = "Missing src/ directory";
} else {
$passed[] = "src/ directory exists";
}
// ── 2. Check module descriptor exists ───────────────────────────────────
$modFiles = glob("{$path}/src/core/modules/mod*.class.php") ?: [];
if (empty($modFiles)) {
$modFiles = glob("{$path}/core/modules/mod*.class.php") ?: [];
}
if (empty($modFiles)) {
$errors[] = "No module descriptor found (src/core/modules/mod*.class.php)";
} else {
$modFile = $modFiles[0];
$modContent = file_get_contents($modFile);
$passed[] = "Module descriptor: " . basename($modFile);
// ── 3. Check module number (numero) ─────────────────────────────────
if (preg_match('/\$this->numero\s*=\s*(\d+)/', $modContent, $m)) {
$numero = (int) $m[1];
if ($numero === 0) {
$errors[] = "Module number (\$this->numero) is 0 — request one via issue template";
} else {
$passed[] = "Module number: {$numero}";
}
} else {
$errors[] = "Module number (\$this->numero) not found in descriptor";
}
// ── 4. Check version field ──────────────────────────────────────────
if (preg_match('/\$this->version\s*=\s*[\'"]([^\'"]+)[\'"]/', $modContent, $m)) {
$version = $m[1];
$passed[] = "Module version: {$version}";
if ($version === 'development') {
$warnings[] = "Module version is 'development' — auto-release will set the real version on merge to main";
}
} else {
$errors[] = "Module version (\$this->version) not found in descriptor";
}
// ── 5. Check url_last_version ────────────────────────────────────────
if (preg_match('/\$this->url_last_version\s*=\s*[\'"]([^\'"]+)[\'"]/', $modContent, $m)) {
$url = $m[1];
$passed[] = "Update URL: {$url}";
// Detect current branch
$branch = trim((string) @shell_exec('git rev-parse --abbrev-ref HEAD 2>/dev/null'));
if ($branch === 'main' || $branch === 'master') {
if (!str_contains($url, '/main/update.txt')) {
$errors[] = "On main: url_last_version must point to /main/update.txt";
} else {
$passed[] = "Update URL correctly points to main";
}
} elseif (!empty($branch) && $branch !== 'HEAD') {
if (str_contains($url, '/main/')) {
$warnings[] = "url_last_version points to main (current branch: {$branch}) — auto-release handles this on merge";
}
}
} else {
$warnings[] = "url_last_version not set — see docs/update-server.md";
}
}
// ── 5. Check README.md exists and has VERSION ───────────────────────────
$readme = null;
foreach (['README.md', 'src/README.md'] as $candidate) {
if (file_exists("{$path}/{$candidate}")) {
$readme = $candidate;
break;
}
}
if ($readme === null) {
$warnings[] = "No README.md found";
} else {
$readmeContent = file_get_contents("{$path}/{$readme}");
if (preg_match('/VERSION:\s*(\d{2}\.\d{2}\.\d{2})/', $readmeContent, $m)) {
$passed[] = "README version: {$m[1]}";
} else {
$warnings[] = "README.md missing VERSION in FILE INFORMATION block";
}
}
// ── 6. Check CHANGELOG exists ───────────────────────────────────────────
$hasChangelog = file_exists("{$path}/CHANGELOG.md") || file_exists("{$path}/src/ChangeLog.md");
if ($hasChangelog) {
$passed[] = "Changelog present";
} else {
$warnings[] = "No CHANGELOG.md or ChangeLog.md found";
}
// ── 7. Check LICENSE exists ─────────────────────────────────────────────
if (file_exists("{$path}/LICENSE")) {
$passed[] = "LICENSE present";
} else {
$errors[] = "Missing LICENSE file";
}
// ── Output ──────────────────────────────────────────────────────────────
echo "MokoCLI Module Validation" . PHP_EOL;
echo str_repeat("─", 50) . PHP_EOL;
echo PHP_EOL;
foreach ($passed as $p) {
echo " ✅ {$p}" . PHP_EOL;
}
foreach ($warnings as $w) {
echo " ⚠️ {$w}" . PHP_EOL;
}
foreach ($errors as $e) {
echo " ❌ {$e}" . PHP_EOL;
}
echo PHP_EOL;
$total = count($passed) + count($warnings) + count($errors);
echo "Results: {$total} checks — " . count($passed) . " passed, " . count($warnings) . " warnings, " . count($errors) . " errors" . PHP_EOL;
exit(count($errors) > 0 ? 1 : 0);