From 7e4d88eea59519db5842701e4ef51fb600a8a5a7 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 29 Jun 2026 11:36:05 -0500 Subject: [PATCH] feat(cli): add theme_vars_check.php for client MokoOnyx themes Validates a client theme package: required CSS variables defined in both light.custom.css and dark.custom.css, required files present, and templateDetails.xml sanity (version, dynamic update server not raw/branch, dlid). Standalone (no CliFramework dependency). Authored-by: Moko Consulting --- cli/theme_vars_check.php | 141 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 cli/theme_vars_check.php diff --git a/cli/theme_vars_check.php b/cli/theme_vars_check.php new file mode 100644 index 0000000..47a091e --- /dev/null +++ b/cli/theme_vars_check.php @@ -0,0 +1,141 @@ +#!/usr/bin/env php + + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * FILE INFORMATION + * DEFGROUP: mokocli.CLI + * INGROUP: mokocli + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli + * PATH: /cli/theme_vars_check.php + * BRIEF: Verify a client MokoOnyx theme defines all required CSS variables + * (light + dark) and passes basic theme-package sanity checks. + * + * Standalone (no CliFramework dependency) so it runs even when the shared + * framework autoloader is unavailable. + * + * Usage: php theme_vars_check.php --path . [--github-output] [--strict] + */ + +declare(strict_types=1); + +$opts = getopt('', ['path:', 'github-output', 'strict']); +$path = $opts['path'] ?? '.'; +if (!is_string($path) || $path === '') { $path = '.'; } +$gh = array_key_exists('github-output', $opts); +$root = realpath($path); +if ($root === false) { $root = $path; } + +// Locate the package source directory (src/ preferred, source/ legacy). +$src = is_dir("$root/src") ? "$root/src" : (is_dir("$root/source") ? "$root/source" : null); +if ($src === null) { + fwrite(STDERR, "ERROR: no src/ or source/ directory under $root\n"); + exit(1); +} + +$errors = 0; +$summary = []; +$fail = function (string $msg) use (&$errors, &$summary): void { + echo " [FAIL] $msg\n"; + $summary[] = "FAIL: $msg"; + $errors++; +}; +$ok = function (string $msg): void { echo " [ok] $msg\n"; }; + +$cssDir = "$src/media/templates/site/mokoonyx/css"; +$themeDir = "$cssDir/theme"; +$light = "$themeDir/light.custom.css"; +$dark = "$themeDir/dark.custom.css"; +$manifest = "$src/templateDetails.xml"; + +echo "MokoOnyx theme validation: $src\n\n"; + +// 1) Required files ------------------------------------------------------- +echo "=== Required files ===\n"; +$requiredFiles = [ + 'templateDetails.xml' => $manifest, + 'theme/light.custom.css' => $light, + 'theme/dark.custom.css' => $dark, + 'user.css' => "$cssDir/user.css", +]; +foreach ($requiredFiles as $label => $file) { + is_file($file) ? $ok("$label present") : $fail("missing required file: $label"); +} + +// 2) Required CSS variables (must be defined in BOTH light and dark) ------ +$required = [ + // accent + Bootstrap contextual colors + '--color-primary', '--primary', '--secondary', '--success', '--info', + '--warning', '--danger', '--light', '--dark', + // rgb triplets (utilities rely on these) + '--primary-rgb', '--secondary-rgb', '--success-rgb', '--info-rgb', + '--warning-rgb', '--danger-rgb', '--light-rgb', '--dark-rgb', + // 5.3 emphasis + subtle tokens (theme-aware utilities) + '--primary-text-emphasis', '--success-text-emphasis', '--info-text-emphasis', + '--warning-text-emphasis', '--danger-text-emphasis', + '--primary-bg-subtle', '--success-bg-subtle', '--info-bg-subtle', + '--warning-bg-subtle', '--danger-bg-subtle', + // body + links + '--body-bg', '--body-color', '--body-bg-rgb', '--body-color-rgb', + '--color-link', '--color-hover', +]; + +echo "\n=== MokoOnyx CSS variables (light + dark) ===\n"; +foreach (['light' => $light, 'dark' => $dark] as $mode => $file) { + if (!is_file($file)) { continue; } // already reported as missing file + $css = (string) file_get_contents($file); + $missing = []; + foreach ($required as $var) { + if (!preg_match('/' . preg_quote($var, '/') . '\s*:/', $css)) { + $missing[] = $var; + } + } + if ($missing) { + $fail("$mode mode is missing " . count($missing) . ' variable(s): ' . implode(', ', $missing)); + } else { + $ok("$mode mode defines all " . count($required) . ' required variables'); + } +} + +// 3) Manifest sanity ------------------------------------------------------ +echo "\n=== Manifest (templateDetails.xml) ===\n"; +if (is_file($manifest)) { + $xml = @simplexml_load_file($manifest); + if ($xml === false) { + $fail('templateDetails.xml is not well-formed XML'); + } else { + $version = isset($xml->version) ? trim((string) $xml->version) : ''; + $version !== '' ? $ok("version $version") : $fail(' is missing or empty'); + + $server = isset($xml->updateservers->server) ? trim((string) $xml->updateservers->server) : ''; + if ($server === '') { + $fail(' is missing'); + } elseif (strpos($server, '/raw/branch/') !== false) { + $fail('update server uses a legacy raw/branch URL; use the dynamic MokoGitea feed (…///updates.xml)'); + } else { + $ok("update server: $server"); + } + + isset($xml->dlid) ? $ok(' license-key field present') + : $fail(' is missing'); + } +} + +// Output / exit ----------------------------------------------------------- +echo "\n"; +if ($gh && ($f = getenv('GITHUB_STEP_SUMMARY'))) { + $md = "## MokoOnyx Theme Validation\n\n"; + $md .= $errors === 0 + ? "All checks passed.\n" + : implode("\n", array_map(static fn ($l) => "- $l", $summary)) . "\n"; + @file_put_contents($f, $md, FILE_APPEND); +} + +if ($errors > 0) { + echo "FAILED: $errors issue(s) found.\n"; + exit(1); +} +echo "All MokoOnyx theme validation checks passed.\n"; +exit(0);