b3d9ee8255
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 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 36s
Wrap all CLI tools in cli/, automation/, maintenance/, deploy/, and release/ in classes extending CliFramework. Replaces manual $argv parsing with configure()/addArgument(), moves logic into run(): int, and converts fwrite(STDERR,...) to $this->log(). Two CLIApp subclasses (generate_dolibarr_version_txt, generate_joomla_update_xml) converted to extend CliFramework directly. Every script now gets free --help, --verbose, --quiet, --dry-run, --json, --no-color, banners, coloured logging, and progress bars. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
180 lines
5.5 KiB
PHP
180 lines
5.5 KiB
PHP
#!/usr/bin/env php
|
|
<?php
|
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: moko-platform.CLI
|
|
* INGROUP: moko-platform
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
* PATH: /cli/theme_lint.php
|
|
* BRIEF: Lint theme files -- CSS syntax, image sizes, hardcoded URLs
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
|
|
|
use MokoEnterprise\CliFramework;
|
|
|
|
class ThemeLintCli extends CliFramework
|
|
{
|
|
protected function configure(): void
|
|
{
|
|
$this->setDescription('Lint theme files -- CSS syntax, image sizes, hardcoded URLs');
|
|
$this->addArgument('--path', 'Repository root', '.');
|
|
$this->addArgument('--max-image-kb', 'Maximum image file size in KB', '500');
|
|
$this->addArgument('--github-output', 'Export results to $GITHUB_OUTPUT', false);
|
|
$this->addArgument('--strict', 'Exit 1 on any warning', false);
|
|
}
|
|
|
|
protected function run(): int
|
|
{
|
|
$path = $this->getArgument('--path');
|
|
$maxImageKb = (int) $this->getArgument('--max-image-kb');
|
|
$ghOutput = (bool) $this->getArgument('--github-output');
|
|
$strict = (bool) $this->getArgument('--strict');
|
|
|
|
$root = realpath($path) ?: $path;
|
|
$errors = 0;
|
|
$warnings = 0;
|
|
|
|
$srcDir = null;
|
|
foreach (['src', 'htdocs'] as $d) {
|
|
if (is_dir("{$root}/{$d}")) { $srcDir = "{$root}/{$d}"; break; }
|
|
}
|
|
if ($srcDir === null) {
|
|
$this->log('ERROR', "No src/ or htdocs/ directory in {$root}");
|
|
return 1;
|
|
}
|
|
|
|
echo "Theme Lint: {$srcDir}\n\n";
|
|
|
|
echo "--- CSS Syntax ---\n";
|
|
$cssFiles = $this->findFiles($srcDir, '*.css');
|
|
$cssMinFiles = $this->findFiles($srcDir, '*.min.css');
|
|
$cssToCheck = array_diff($cssFiles, $cssMinFiles);
|
|
|
|
if (empty($cssToCheck)) {
|
|
echo " No CSS files to check\n";
|
|
} else {
|
|
foreach ($cssToCheck as $file) {
|
|
$content = file_get_contents($file);
|
|
$relPath = str_replace($root . '/', '', $file);
|
|
$openBraces = substr_count($content, '{');
|
|
$closeBraces = substr_count($content, '}');
|
|
if ($openBraces !== $closeBraces) {
|
|
echo " ERROR: {$relPath}: unmatched braces (open={$openBraces}, close={$closeBraces})\n";
|
|
$errors++;
|
|
}
|
|
if (preg_match_all('/\{[\s]*\}/', $content, $m)) {
|
|
$count = count($m[0]);
|
|
echo " WARN: {$relPath}: {$count} empty rule(s)\n";
|
|
$warnings++;
|
|
}
|
|
$importantCount = substr_count($content, '!important');
|
|
if ($importantCount > 10) {
|
|
echo " WARN: {$relPath}: {$importantCount} !important declarations (consider refactoring)\n";
|
|
$warnings++;
|
|
}
|
|
}
|
|
if ($errors === 0) {
|
|
echo " OK: " . count($cssToCheck) . " CSS file(s) checked\n";
|
|
}
|
|
}
|
|
|
|
echo "\n--- Image Sizes (max {$maxImageKb}KB) ---\n";
|
|
$imageExts = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.webp', '*.svg', '*.bmp'];
|
|
$images = [];
|
|
foreach ($imageExts as $ext) {
|
|
$images = array_merge($images, $this->findFiles($srcDir, $ext));
|
|
}
|
|
if (is_dir("{$root}/images")) {
|
|
foreach ($imageExts as $ext) {
|
|
$images = array_merge($images, $this->findFiles("{$root}/images", $ext));
|
|
}
|
|
}
|
|
|
|
$oversized = 0;
|
|
$totalSize = 0;
|
|
foreach ($images as $file) {
|
|
$size = filesize($file);
|
|
$totalSize += $size;
|
|
$relPath = str_replace($root . '/', '', $file);
|
|
$sizeKb = round($size / 1024);
|
|
if ($sizeKb > $maxImageKb) {
|
|
echo " WARN: {$relPath}: {$sizeKb}KB (exceeds {$maxImageKb}KB limit)\n";
|
|
$oversized++;
|
|
$warnings++;
|
|
}
|
|
}
|
|
|
|
$totalMb = round($totalSize / 1024 / 1024, 1);
|
|
echo " " . count($images) . " image(s), {$totalMb}MB total";
|
|
if ($oversized > 0) { echo ", {$oversized} oversized"; }
|
|
echo "\n";
|
|
|
|
echo "\n--- Hardcoded URLs ---\n";
|
|
$codeFiles = array_merge($this->findFiles($srcDir, '*.css'), $this->findFiles($srcDir, '*.js'));
|
|
$codeFiles = array_filter($codeFiles, function ($f) {
|
|
return !preg_match('/\.min\.(css|js)$/', $f);
|
|
});
|
|
$urlPatterns = [
|
|
'/https?:\/\/clarksvillefurs\.com/' => 'hardcoded production URL',
|
|
'/https?:\/\/[a-z]+\.dev\.mokoconsulting\.tech/' => 'hardcoded dev URL',
|
|
'/https?:\/\/localhost/' => 'localhost reference',
|
|
];
|
|
$urlIssues = 0;
|
|
foreach ($codeFiles as $file) {
|
|
$content = file_get_contents($file);
|
|
$relPath = str_replace($root . '/', '', $file);
|
|
foreach ($urlPatterns as $pattern => $desc) {
|
|
if (preg_match_all($pattern, $content, $matches)) {
|
|
$count = count($matches[0]);
|
|
echo " WARN: {$relPath}: {$count} {$desc}\n";
|
|
$urlIssues++;
|
|
$warnings++;
|
|
}
|
|
}
|
|
}
|
|
if ($urlIssues === 0) { echo " OK: No hardcoded URLs found\n"; }
|
|
|
|
echo "\n=== Summary ===\n";
|
|
echo "Errors: {$errors}\n";
|
|
echo "Warnings: {$warnings}\n";
|
|
|
|
if ($ghOutput) {
|
|
$ghFile = getenv('GITHUB_OUTPUT');
|
|
if ($ghFile) {
|
|
file_put_contents($ghFile, "lint_errors={$errors}\n", FILE_APPEND);
|
|
file_put_contents($ghFile, "lint_warnings={$warnings}\n", FILE_APPEND);
|
|
file_put_contents($ghFile, "lint_images=" . count($images) . "\n", FILE_APPEND);
|
|
file_put_contents($ghFile, "lint_css=" . count($cssToCheck) . "\n", FILE_APPEND);
|
|
}
|
|
}
|
|
|
|
if ($errors > 0) { return 1; }
|
|
if ($strict && $warnings > 0) { return 1; }
|
|
return 0;
|
|
}
|
|
|
|
private function findFiles(string $dir, string $pattern): array
|
|
{
|
|
$results = [];
|
|
if (!is_dir($dir)) { return $results; }
|
|
$iterator = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
|
|
);
|
|
foreach ($iterator as $file) {
|
|
if (fnmatch($pattern, $file->getFilename())) {
|
|
$results[] = $file->getPathname();
|
|
}
|
|
}
|
|
return $results;
|
|
}
|
|
}
|
|
|
|
$app = new ThemeLintCli();
|
|
exit($app->execute());
|