1d87be7d5e
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
- Update REPO: from MokoStandards-API to moko-platform in 125 files - Fix wrong org path (mokoconsulting-tech → MokoConsulting) in 10 files - Fix SPDX-LICENSE-IDENTIFIER case in 2 template files - Add missing REPO: field to 3 files Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
178 lines
7.4 KiB
PHP
178 lines
7.4 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: MokoStandards.Templates.Scripts.Validate
|
|
* INGROUP: MokoStandards.Templates
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
* PATH: /templates/scripts/validate/dolibarr_module.php
|
|
* BRIEF: Validate a Dolibarr module repository against MokoStandards requirements
|
|
* NOTE: Deployed to bin/validate_module.php in governed Dolibarr module repos.
|
|
* Run: php bin/validate_module.php [--path DIR] [--verbose] [--json]
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
// Deployed to bin/validate_module.php in org repos — vendor/ is one level up (repo root)
|
|
require_once __DIR__ . '/../vendor/autoload.php';
|
|
|
|
use MokoEnterprise\CliFramework;
|
|
|
|
/**
|
|
* Validates a Dolibarr module repository against MokoStandards requirements.
|
|
*
|
|
* Checks performed:
|
|
* - Required directories (src/, src/core/modules/, langs/en_US/, img/)
|
|
* - Required files (README.md, CHANGELOG.md, LICENSE, composer.json)
|
|
* - Module descriptor exists and declares a valid version
|
|
* - SPDX-License-Identifier header present in all PHP source files
|
|
* - No tab characters in YAML/JSON config files
|
|
* - No Windows path separators in PHP source
|
|
*/
|
|
class ValidateDolibarrModule extends CliFramework
|
|
{
|
|
protected function configure(): void
|
|
{
|
|
$this->setDescription('Validate a Dolibarr module repository against MokoStandards requirements');
|
|
$this->addArgument('--path', 'Repository root to validate', '.');
|
|
}
|
|
|
|
protected function run(): int
|
|
{
|
|
$path = rtrim((string) $this->getArgument('--path'), '/\\');
|
|
$passed = 0;
|
|
$failed = 0;
|
|
|
|
if (!is_dir($path)) {
|
|
$this->error("Path does not exist: {$path}");
|
|
}
|
|
|
|
// ── Required directories ──────────────────────────────────────────
|
|
$this->section('Required directories');
|
|
foreach ([
|
|
'src',
|
|
'src/core/modules',
|
|
'langs/en_US',
|
|
'img',
|
|
] as $dir) {
|
|
$ok = is_dir("{$path}/{$dir}");
|
|
$this->status($ok, $dir . '/');
|
|
$ok ? $passed++ : $failed++;
|
|
}
|
|
|
|
// ── Required files ────────────────────────────────────────────────
|
|
$this->section('Required files');
|
|
foreach ([
|
|
'README.md',
|
|
'CHANGELOG.md',
|
|
'LICENSE',
|
|
'composer.json',
|
|
] as $file) {
|
|
$ok = file_exists("{$path}/{$file}");
|
|
$this->status($ok, $file);
|
|
$ok ? $passed++ : $failed++;
|
|
}
|
|
|
|
// ── Module descriptor ─────────────────────────────────────────────
|
|
$this->section('Module descriptor');
|
|
$descriptors = glob("{$path}/src/core/modules/mod*.class.php") ?: [];
|
|
if (empty($descriptors)) {
|
|
$this->status(false, 'mod*.class.php exists in src/core/modules/');
|
|
$failed++;
|
|
} else {
|
|
$this->status(true, 'mod*.class.php exists: ' . basename($descriptors[0]));
|
|
$passed++;
|
|
|
|
// Check $this->version is declared
|
|
$contents = (string) file_get_contents($descriptors[0]);
|
|
$hasVersion = (bool) preg_match('/\$this\s*->\s*version\s*=/', $contents);
|
|
$this->status($hasVersion, '$this->version declared in descriptor');
|
|
$hasVersion ? $passed++ : $failed++;
|
|
|
|
// Check module number is declared
|
|
$hasNumber = (bool) preg_match('/\$this\s*->\s*numero\s*=/', $contents);
|
|
$this->status($hasNumber, '$this->numero declared in descriptor');
|
|
$hasNumber ? $passed++ : $failed++;
|
|
}
|
|
|
|
// ── SPDX headers ──────────────────────────────────────────────────
|
|
$this->section('SPDX-License-Identifier headers');
|
|
$phpFiles = $this->findFiles($path . '/src', '*.php');
|
|
$missingHeader = 0;
|
|
foreach ($phpFiles as $file) {
|
|
$head = file_get_contents($file, false, null, 0, 512) ?: '';
|
|
if (!str_contains($head, 'SPDX-License-Identifier')) {
|
|
$missingHeader++;
|
|
$this->log('WARN', 'Missing SPDX header: ' . substr($file, strlen($path) + 1));
|
|
}
|
|
}
|
|
$ok = $missingHeader === 0;
|
|
$this->status($ok, "All PHP files have SPDX header ({$missingHeader} missing)");
|
|
$ok ? $passed++ : $failed++;
|
|
|
|
// ── No tabs in config files ───────────────────────────────────────
|
|
$this->section('No tab characters in config files');
|
|
$configFiles = array_merge(
|
|
$this->findFiles($path, '*.yml'),
|
|
$this->findFiles($path, '*.yaml'),
|
|
$this->findFiles($path, '*.json')
|
|
);
|
|
$tabFiles = 0;
|
|
foreach ($configFiles as $file) {
|
|
if (str_contains((string) file_get_contents($file), "\t")) {
|
|
$tabFiles++;
|
|
$this->log('WARN', 'Tab found: ' . substr($file, strlen($path) + 1));
|
|
}
|
|
}
|
|
$ok = $tabFiles === 0;
|
|
$this->status($ok, "No tabs in YAML/JSON ({$tabFiles} files affected)");
|
|
$ok ? $passed++ : $failed++;
|
|
|
|
// ── No backslash paths in PHP ─────────────────────────────────────
|
|
$this->section('No Windows path separators in PHP source');
|
|
$phpAll = $this->findFiles($path . '/src', '*.php');
|
|
$bsFiles = 0;
|
|
foreach ($phpAll as $file) {
|
|
if (preg_match('/["\'](?:[A-Za-z]:)?\\\\[^\\\\]/', (string) file_get_contents($file))) {
|
|
$bsFiles++;
|
|
$this->log('WARN', 'Backslash path: ' . substr($file, strlen($path) + 1));
|
|
}
|
|
}
|
|
$ok = $bsFiles === 0;
|
|
$this->status($ok, "No backslash paths ({$bsFiles} files affected)");
|
|
$ok ? $passed++ : $failed++;
|
|
|
|
// ── Summary ───────────────────────────────────────────────────────
|
|
$this->printSummary($passed, $failed, $this->elapsed());
|
|
|
|
return $failed > 0 ? 1 : 0;
|
|
}
|
|
|
|
/** Recursively find files matching a glob pattern under a directory. */
|
|
private function findFiles(string $dir, string $pattern): array
|
|
{
|
|
if (!is_dir($dir)) {
|
|
return [];
|
|
}
|
|
$results = [];
|
|
$iter = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)
|
|
);
|
|
foreach ($iter as $file) {
|
|
if ($file->isFile() && fnmatch($pattern, $file->getFilename())) {
|
|
$results[] = $file->getPathname();
|
|
}
|
|
}
|
|
return $results;
|
|
}
|
|
}
|
|
|
|
$script = new ValidateDolibarrModule('validate_module', 'Validate a Dolibarr module repository against MokoStandards requirements');
|
|
exit($script->execute());
|