Public Access
feat(cli): derive required vars from MokoOnyx standard theme + repo metadata
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Platform: mokocli CI / Gate 1: Code Quality (pull_request) Failing after 1m3s
pr-check.yml / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: mokocli CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: mokocli CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: mokocli CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Platform: mokocli CI / Gate 1: Code Quality (pull_request) Failing after 1m3s
pr-check.yml / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: mokocli CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: mokocli CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: mokocli CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
- Read the required CSS variable set dynamically from the MokoOnyx standard theme (--reference checkout) instead of a hardcoded list, so it auto-syncs. Verifies client light/dark custom CSS defines every standard variable. - Add optional repo-metadata check via Gitea API (--api-base/--repo/--token): description, website, topics, default branch. - Drop unused --strict flag. Authored-by: Moko Consulting
This commit is contained in:
+98
-48
@@ -10,25 +10,29 @@
|
||||
* 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.
|
||||
* BRIEF: Validate a client MokoOnyx theme package — required CSS variables
|
||||
* (derived dynamically from the MokoOnyx standard theme) are defined in
|
||||
* the client's light/dark custom CSS, required files exist, the manifest
|
||||
* is sane, and (optionally) the repo's Gitea metadata is set.
|
||||
*
|
||||
* 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]
|
||||
* Usage:
|
||||
* php theme_vars_check.php --path . --reference /tmp/mokoonyx [--github-output]
|
||||
* [--api-base <url> --repo <owner/repo> --token <token>]
|
||||
*/
|
||||
|
||||
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);
|
||||
$opts = getopt('', ['path:', 'reference:', 'github-output', 'api-base:', 'repo:', 'token:']);
|
||||
$path = isset($opts['path']) && is_string($opts['path']) && $opts['path'] !== '' ? $opts['path'] : '.';
|
||||
$ref = isset($opts['reference']) && is_string($opts['reference']) ? $opts['reference'] : '';
|
||||
$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");
|
||||
@@ -42,60 +46,73 @@ $fail = function (string $msg) use (&$errors, &$summary): void {
|
||||
$summary[] = "FAIL: $msg";
|
||||
$errors++;
|
||||
};
|
||||
$ok = function (string $msg): void { echo " [ok] $msg\n"; };
|
||||
$ok = function (string $msg): void { echo " [ok] $msg\n"; };
|
||||
$note = function (string $msg): void { echo " [note] $msg\n"; };
|
||||
|
||||
/** Extract the set of CSS custom-property names DEFINED in a CSS string. */
|
||||
$definedVars = static function (string $css): array {
|
||||
// Matches "--name:" at a declaration position (not var(--name) uses).
|
||||
preg_match_all('/(?:^|[\s;{])(--[a-z0-9_-]+)\s*:/i', $css, $m);
|
||||
return array_values(array_unique(array_map('strtolower', $m[1] ?? [])));
|
||||
};
|
||||
|
||||
/** Find the MokoOnyx standard theme file for a mode inside a reference checkout. */
|
||||
$findStandard = static function (string $ref, string $mode): ?string {
|
||||
if ($ref === '') { return null; }
|
||||
foreach ([
|
||||
"$ref/source/media/css/theme/$mode.standard.css",
|
||||
"$ref/media/templates/site/mokoonyx/css/theme/$mode.standard.css",
|
||||
"$ref/$mode.standard.css",
|
||||
] as $cand) {
|
||||
if (is_file($cand)) { return $cand; }
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
$cssDir = "$src/media/templates/site/mokoonyx/css";
|
||||
$themeDir = "$cssDir/theme";
|
||||
$light = "$themeDir/light.custom.css";
|
||||
$dark = "$themeDir/dark.custom.css";
|
||||
$manifest = "$src/templateDetails.xml";
|
||||
$customs = ['light' => "$themeDir/light.custom.css", 'dark' => "$themeDir/dark.custom.css"];
|
||||
|
||||
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",
|
||||
'templateDetails.xml' => $manifest,
|
||||
'theme/light.custom.css' => $customs['light'],
|
||||
'theme/dark.custom.css' => $customs['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;
|
||||
// 2) CSS variables — required set derived from the MokoOnyx standard theme
|
||||
echo "\n=== CSS variables vs MokoOnyx standard theme ===\n";
|
||||
if ($ref === '') {
|
||||
$note('no --reference given; skipping variable parity check');
|
||||
} else {
|
||||
foreach ($customs as $mode => $customFile) {
|
||||
$std = $findStandard($ref, $mode);
|
||||
if ($std === null) {
|
||||
$fail("$mode: standard theme not found under reference '$ref'");
|
||||
continue;
|
||||
}
|
||||
if (!is_file($customFile)) {
|
||||
continue; // already reported missing
|
||||
}
|
||||
$required = $definedVars((string) file_get_contents($std));
|
||||
$defined = $definedVars((string) file_get_contents($customFile));
|
||||
$missing = array_values(array_diff($required, $defined));
|
||||
if ($missing) {
|
||||
$shown = array_slice($missing, 0, 15);
|
||||
$more = count($missing) - count($shown);
|
||||
$fail("$mode mode missing " . count($missing) . '/' . count($required)
|
||||
. ' standard variable(s): ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||
} else {
|
||||
$ok("$mode mode defines all " . count($required) . ' standard variables');
|
||||
}
|
||||
}
|
||||
if ($missing) {
|
||||
$fail("$mode mode is missing " . count($missing) . ' variable(s): ' . implode(', ', $missing));
|
||||
} else {
|
||||
$ok("$mode mode defines all " . count($required) . ' required variables');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +130,7 @@ if (is_file($manifest)) {
|
||||
if ($server === '') {
|
||||
$fail('<updateservers><server> is missing');
|
||||
} elseif (strpos($server, '/raw/branch/') !== false) {
|
||||
$fail('update server uses a legacy raw/branch URL; use the dynamic MokoGitea feed (…/<org>/<repo>/updates.xml)');
|
||||
$fail('update server uses a legacy raw/branch URL; use the dynamic MokoGitea feed');
|
||||
} else {
|
||||
$ok("update server: $server");
|
||||
}
|
||||
@@ -123,6 +140,39 @@ if (is_file($manifest)) {
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Repository metadata via Gitea API (optional) -------------------------
|
||||
$apiBase = isset($opts['api-base']) && is_string($opts['api-base']) ? rtrim($opts['api-base'], '/') : '';
|
||||
$repoSlug = isset($opts['repo']) && is_string($opts['repo']) ? trim($opts['repo']) : '';
|
||||
$token = isset($opts['token']) && is_string($opts['token']) ? trim($opts['token']) : '';
|
||||
if ($apiBase !== '' && $repoSlug !== '' && $token !== '') {
|
||||
echo "\n=== Repository metadata (Gitea API) ===\n";
|
||||
$url = "$apiBase/repos/$repoSlug";
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "Authorization: token $token\r\nAccept: application/json\r\n",
|
||||
'ignore_errors' => true,
|
||||
'timeout' => 15,
|
||||
]]);
|
||||
$resp = @file_get_contents($url, false, $ctx);
|
||||
$data = $resp !== false ? json_decode($resp, true) : null;
|
||||
if (!is_array($data) || !isset($data['name'])) {
|
||||
$fail("could not read repo metadata from Gitea API ($url)");
|
||||
} else {
|
||||
trim((string) ($data['description'] ?? '')) !== '' ? $ok('description set')
|
||||
: $fail('repo description is empty');
|
||||
trim((string) ($data['website'] ?? '')) !== '' ? $ok('website set: ' . $data['website'])
|
||||
: $fail('repo website is empty');
|
||||
$topics = $data['topics'] ?? [];
|
||||
(is_array($topics) && count($topics) > 0) ? $ok(count($topics) . ' topic(s) set')
|
||||
: $fail('repo has no topics');
|
||||
((string) ($data['default_branch'] ?? '')) === 'main' ? $ok('default branch is main')
|
||||
: $fail("default branch is '" . ($data['default_branch'] ?? '') . "' (expected main)");
|
||||
}
|
||||
} else {
|
||||
echo "\n=== Repository metadata (Gitea API) ===\n";
|
||||
$note('API args not provided; skipping repo metadata check');
|
||||
}
|
||||
|
||||
// Output / exit -----------------------------------------------------------
|
||||
echo "\n";
|
||||
if ($gh && ($f = getenv('GITHUB_STEP_SUMMARY'))) {
|
||||
|
||||
Reference in New Issue
Block a user