Public Access
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4bb08073b | |||
| b32556fdef | |||
| c57a7e4b96 | |||
| 5846ccc3e8 | |||
| 6ba0208571 | |||
| 759b04e088 | |||
| 4dfa2d9722 | |||
| 590ad7337a | |||
| c607966e83 | |||
| 97f2226288 | |||
| b1f2c391cb | |||
| 7e4d88eea5 | |||
| 9fdd6c5cf9 | |||
| 113af457d9 | |||
| abd67f6c43 | |||
| d3dce5d24e | |||
| c9592d8e78 | |||
| 6bf6251705 |
@@ -7,7 +7,7 @@
|
|||||||
# INGROUP: mokocli.Release
|
# INGROUP: mokocli.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
||||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||||
# VERSION: 05.00.00
|
# VERSION: 05.01.00
|
||||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||||
#
|
#
|
||||||
# +=======================================================================+
|
# +=======================================================================+
|
||||||
@@ -75,6 +75,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Setup mokocli tools
|
||||||
env:
|
env:
|
||||||
@@ -173,6 +174,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Configure git for bot pushes
|
- name: Configure git for bot pushes
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -13,6 +13,12 @@
|
|||||||
name: "Generic: Project CI"
|
name: "Generic: Project CI"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
@@ -29,12 +29,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Rename branch
|
- name: Rename branch
|
||||||
|
env:
|
||||||
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
set -euo pipefail
|
||||||
|
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
||||||
|
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
||||||
|
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
||||||
|
fi
|
||||||
SUFFIX="${BRANCH#rc/}"
|
SUFFIX="${BRANCH#rc/}"
|
||||||
DEV_BRANCH="dev/${SUFFIX}"
|
DEV_BRANCH="dev/${SUFFIX}"
|
||||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
|
||||||
|
|
||||||
# Create dev/ branch from rc/ branch
|
# Create dev/ branch from rc/ branch
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||||
@@ -42,25 +50,22 @@ jobs:
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
||||||
"${API}" 2>/dev/null || true)
|
"${API}" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ "$STATUS" = "201" ]; then
|
if [ "$STATUS" = "201" ]; then
|
||||||
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
else
|
else
|
||||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
|
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Delete rc/ branch
|
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
||||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
-H "Authorization: token ${TOKEN}" \
|
-H "Authorization: token ${TOKEN}" \
|
||||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ "$STATUS" = "204" ]; then
|
if [ "$STATUS" = "204" ]; then
|
||||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
else
|
else
|
||||||
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
|
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ BRIEF: Release changelog
|
|||||||
|
|
||||||
## [09.41.00] --- 2026-06-27
|
## [09.41.00] --- 2026-06-27
|
||||||
|
|
||||||
|
## [09.41.00] --- 2026-06-27
|
||||||
|
|
||||||
## [09.41.00] --- 2026-06-25
|
## [09.41.00] --- 2026-06-25
|
||||||
|
|
||||||
## [09.41.00] --- 2026-06-25
|
## [09.41.00] --- 2026-06-25
|
||||||
|
|||||||
@@ -0,0 +1,294 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* 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: 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 . --reference /tmp/mokoonyx [--github-output]
|
||||||
|
* [--api-base <url> --repo <owner/repo> --token <token>]
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$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; }
|
||||||
|
|
||||||
|
$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"; };
|
||||||
|
$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";
|
||||||
|
$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' => $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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 === '') {
|
||||||
|
$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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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('<version> is missing or empty');
|
||||||
|
|
||||||
|
$server = isset($xml->updateservers->server) ? trim((string) $xml->updateservers->server) : '';
|
||||||
|
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');
|
||||||
|
} else {
|
||||||
|
$ok("update server: $server");
|
||||||
|
}
|
||||||
|
|
||||||
|
isset($xml->dlid) ? $ok('<dlid> license-key field present')
|
||||||
|
: $fail('<dlid prefix="dlid=" suffix=""/> 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) !== '')
|
||||||
|
? $ok("<$field> present")
|
||||||
|
: $fail("<$field> is missing or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// scriptfile (if declared) must exist
|
||||||
|
if (isset($xml->scriptfile)) {
|
||||||
|
$sf = trim((string) $xml->scriptfile);
|
||||||
|
is_file("$src/$sf") ? $ok("scriptfile '$sf' present")
|
||||||
|
: $fail("<scriptfile> references '$sf' which is not in src/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fileset integrity — every referenced file/folder must exist in src/
|
||||||
|
if (isset($xml->fileset)) {
|
||||||
|
$missingFs = [];
|
||||||
|
foreach ($xml->fileset->files as $files) {
|
||||||
|
$folder = trim((string) ($files['folder'] ?? ''));
|
||||||
|
$baseDir = $folder !== '' ? "$src/$folder" : $src;
|
||||||
|
foreach ($files->filename as $fn) {
|
||||||
|
$rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fn);
|
||||||
|
if (!is_file("$baseDir/" . trim((string) $fn))) { $missingFs[] = $rel; }
|
||||||
|
}
|
||||||
|
foreach ($files->folder as $fd) {
|
||||||
|
$rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fd) . '/';
|
||||||
|
if (!is_dir("$baseDir/" . trim((string) $fd))) { $missingFs[] = $rel; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($missingFs) {
|
||||||
|
$shown = array_slice($missingFs, 0, 10);
|
||||||
|
$more = count($missingFs) - count($shown);
|
||||||
|
$fail('fileset references ' . count($missingFs) . ' missing path(s): '
|
||||||
|
. implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||||
|
} else {
|
||||||
|
$ok('all fileset paths exist in src/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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";
|
||||||
|
$note('API args not provided; skipping repo metadata check');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
Reference in New Issue
Block a user