From b32556fdef65c57085af09a1441f3394748c8248 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 29 Jun 2026 12:03:14 -0500 Subject: [PATCH] =?UTF-8?q?feat(cli):=20theme=5Fvars=5Fcheck=20governance?= =?UTF-8?q?=20=E2=80=94=20forbidden=20files,=20topics,=20protection,=20ele?= =?UTF-8?q?ment=20naming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Forbidden committed files: .mcp.json/.mcp_*.json, sftp-config*.json, *.min.css/*.min.js, TODO.md, .claude/. - Required repo topics (joomla, client-waas, mokoonyx) via Gitea API. - main branch protection enforced via Gitea API. - naming convention (file_mokoonyx_*) for file packages. Authored-by: Moko Consulting --- cli/theme_vars_check.php | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/cli/theme_vars_check.php b/cli/theme_vars_check.php index 8f14cc0..433157d 100644 --- a/cli/theme_vars_check.php +++ b/cli/theme_vars_check.php @@ -88,6 +88,42 @@ foreach ($requiredFiles as $label => $file) { is_file($file) ? $ok("$label present") : $fail("missing required file: $label"); } +// 1b) Forbidden committed files ------------------------------------------ +echo "\n=== Forbidden files ===\n"; +$scan = static function (string $dir, array $skip) use (&$scan): array { + $out = []; + foreach (scandir($dir) ?: [] as $e) { + if ($e === '.' || $e === '..') { continue; } + $p = "$dir/$e"; + if (is_dir($p)) { + if (in_array($e, $skip, true)) { continue; } + $out = array_merge($out, $scan($p, $skip)); + } else { + $out[] = $p; + } + } + return $out; +}; +$forbidden = []; +if (is_dir("$root/.claude")) { $forbidden[] = '.claude/'; } +foreach ($scan($root, ['.git', 'vendor', 'node_modules']) as $f) { + $b = basename($f); + $rel = ltrim(str_replace('\\', '/', substr($f, strlen($root))), '/'); + if ($b === '.mcp.json' || $b === 'TODO.md' + || fnmatch('.mcp_*.json', $b) || fnmatch('sftp-config*.json', $b) + || fnmatch('*.min.css', $b) || fnmatch('*.min.js', $b)) { + $forbidden[] = $rel; + } +} +$forbidden = array_values(array_unique($forbidden)); +if ($forbidden) { + $shown = array_slice($forbidden, 0, 10); + $more = count($forbidden) - count($shown); + $fail('forbidden file(s) committed: ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : '')); +} else { + $ok('no forbidden files committed'); +} + // 2) CSS variables — required set derived from the MokoOnyx standard theme echo "\n=== CSS variables vs MokoOnyx standard theme ===\n"; if ($ref === '') { @@ -138,6 +174,15 @@ if (is_file($manifest)) { isset($xml->dlid) ? $ok(' license-key field present') : $fail(' is missing'); + // Element naming convention (MokoOnyx client theme file package) + $type = trim((string) ($xml['type'] ?? '')); + $element = isset($xml->element) ? trim((string) $xml->element) : ''; + if ($type === 'file' && $element !== '') { + strpos($element, 'file_mokoonyx_') === 0 + ? $ok("element naming ok ($element)") + : $fail("element '$element' should start with 'file_mokoonyx_' for a MokoOnyx client theme package"); + } + // Required manifest fields foreach (['name', 'element', 'author', 'creationDate'] as $field) { (isset($xml->$field) && trim((string) $xml->$field) !== '') @@ -204,8 +249,27 @@ if ($apiBase !== '' && $repoSlug !== '' && $token !== '') { $topics = $data['topics'] ?? []; (is_array($topics) && count($topics) > 0) ? $ok(count($topics) . ' topic(s) set') : $fail('repo has no topics'); + + // Required topics + $requiredTopics = ['joomla', 'client-waas', 'mokoonyx']; + $haveTopics = array_map('strtolower', is_array($topics) ? $topics : []); + $missingTopics = array_values(array_diff($requiredTopics, $haveTopics)); + $missingTopics + ? $fail('missing required topic(s): ' . implode(', ', $missingTopics)) + : $ok('required topics present (' . implode(', ', $requiredTopics) . ')'); + ((string) ($data['default_branch'] ?? '')) === 'main' ? $ok('default branch is main') : $fail("default branch is '" . ($data['default_branch'] ?? '') . "' (expected main)"); + + // Branch protection on main + $bresp = @file_get_contents("$apiBase/repos/$repoSlug/branches/main", false, $ctx); + $bdata = $bresp !== false ? json_decode($bresp, true) : null; + if (is_array($bdata) && array_key_exists('protected', $bdata)) { + $bdata['protected'] ? $ok('main branch is protected') + : $fail('main branch is not protected'); + } else { + $note('could not read branch protection state for main'); + } } } else { echo "\n=== Repository metadata (Gitea API) ===\n"; -- 2.52.0