Public Access
d206dd5afb
Update bin/moko banner, help text, and file headers. Update bin/validate-module output and headers.
172 lines
6.5 KiB
PHP
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);
|