Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a59091e348 | |||
| ee3d58c20f | |||
| 2b137f9041 | |||
| 54a27c0a8f | |||
| 5754fae5a8 | |||
| ab3c0a3a8d | |||
| eb3689cff6 | |||
| 7338a3da2e | |||
| 0a0e1f11e0 | |||
| c3a3ab3f62 | |||
| 556ac85a63 | |||
| c1a145480c | |||
| ab7b6cfba1 | |||
| 2d6155d655 | |||
| 65215cdc4c | |||
| 8c87cf1e74 | |||
| 59d3524615 | |||
| 8058baef95 | |||
| df2efa4838 | |||
| 76bc91a383 | |||
| b53846f6f4 |
@@ -412,6 +412,12 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
sparse-checkout: automation/ci-issue-reporter.sh
|
||||||
|
sparse-checkout-cone-mode: false
|
||||||
|
|
||||||
- name: Check gate results
|
- name: Check gate results
|
||||||
run: |
|
run: |
|
||||||
{
|
{
|
||||||
@@ -437,3 +443,46 @@ jobs:
|
|||||||
echo "::error::One or more CI gates failed"
|
echo "::error::One or more CI gates failed"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: "File issues for failed gates"
|
||||||
|
if: >-
|
||||||
|
always() &&
|
||||||
|
(needs.code-quality.result == 'failure' ||
|
||||||
|
needs.tests.result == 'failure' ||
|
||||||
|
needs.self-health.result == 'failure' ||
|
||||||
|
needs.governance.result == 'failure' ||
|
||||||
|
needs.templates.result == 'failure')
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
run: |
|
||||||
|
chmod +x automation/ci-issue-reporter.sh
|
||||||
|
REPORTER="./automation/ci-issue-reporter.sh"
|
||||||
|
WF="Platform CI"
|
||||||
|
|
||||||
|
report_gate() {
|
||||||
|
local gate="$1" result="$2" details="$3"
|
||||||
|
if [ "$result" = "failure" ]; then
|
||||||
|
"$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
report_gate "Code Quality" \
|
||||||
|
"${{ needs.code-quality.result }}" \
|
||||||
|
"PHPCS (PSR-12), PHPStan, or PHP syntax checks failed. Run \`composer check\` locally to reproduce."
|
||||||
|
|
||||||
|
report_gate "Unit Tests" \
|
||||||
|
"${{ needs.tests.result }}" \
|
||||||
|
"PHPUnit tests failed on one or more PHP versions (8.1, 8.2, 8.3). Run \`vendor/bin/phpunit --testdox\` locally."
|
||||||
|
|
||||||
|
report_gate "Self-Health" \
|
||||||
|
"${{ needs.self-health.result }}" \
|
||||||
|
"Self-health score fell below the 80% threshold. Run \`php bin/moko health -- --path .\` locally."
|
||||||
|
|
||||||
|
report_gate "Governance" \
|
||||||
|
"${{ needs.governance.result }}" \
|
||||||
|
"Governance checks failed (license headers, secrets, or version consistency). Check the CI run summary for specifics."
|
||||||
|
|
||||||
|
report_gate "Template Integrity" \
|
||||||
|
"${{ needs.templates.result }}" \
|
||||||
|
"Workflow or gitignore templates failed YAML validation or are missing required entries."
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Automation
|
# INGROUP: moko-platform.Automation
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.25.00
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
|
|||||||
+3
-7
@@ -10,9 +10,11 @@ BRIEF: Release changelog
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
|
||||||
|
## [09.25.00] --- 2026-06-04
|
||||||
|
|
||||||
## [09.23] --- 2026-05-31
|
## [09.23] --- 2026-05-31
|
||||||
|
|
||||||
## [09.22] --- 2026-05-31
|
## [09.22] --- 2026-05-31
|
||||||
@@ -30,9 +32,3 @@ BRIEF: Release changelog
|
|||||||
## [09.21] --- 2026-05-30
|
## [09.21] --- 2026-05-30
|
||||||
|
|
||||||
## [09.20] --- 2026-05-30
|
## [09.20] --- 2026-05-30
|
||||||
|
|
||||||
## [09.19] --- 2026-05-30
|
|
||||||
|
|
||||||
## [09.18] --- 2026-05-30
|
|
||||||
|
|
||||||
## [09.17] --- 2026-05-30
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ DEFGROUP: MokoPlatform.Root
|
|||||||
INGROUP: MokoPlatform
|
INGROUP: MokoPlatform
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
PATH: /README.md
|
PATH: /README.md
|
||||||
VERSION: 09.23.00
|
VERSION: 09.25.00
|
||||||
BRIEF: Project overview and documentation
|
BRIEF: Project overview and documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================================
|
||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Automation.CI
|
||||||
|
# INGROUP: moko-platform.Automation
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /automation/ci-issue-reporter.sh
|
||||||
|
# VERSION: 09.23.00
|
||||||
|
# BRIEF: Creates or updates a Gitea issue when a CI gate fails.
|
||||||
|
# Deduplicates by searching open issues with the "ci-auto" label
|
||||||
|
# whose title matches the gate. If a matching issue exists, a comment
|
||||||
|
# is appended instead of opening a duplicate.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Defaults ────────────────────────────────────────────────────────────────
|
||||||
|
GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}"
|
||||||
|
GITEA_TOKEN="${GITEA_TOKEN:-}"
|
||||||
|
REPO="${GITHUB_REPOSITORY:-}"
|
||||||
|
RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
|
||||||
|
LABEL_NAME="ci-auto"
|
||||||
|
LABEL_COLOR="#e11d48"
|
||||||
|
|
||||||
|
GATE=""
|
||||||
|
DETAILS=""
|
||||||
|
SEVERITY="error"
|
||||||
|
WORKFLOW=""
|
||||||
|
|
||||||
|
# ── Parse arguments ─────────────────────────────────────────────────────────
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: ci-issue-reporter.sh --gate NAME --details TEXT [OPTIONS]
|
||||||
|
|
||||||
|
Required:
|
||||||
|
--gate CI gate name (e.g. "Code Quality", "Self-Health")
|
||||||
|
--details Human-readable failure description
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
--severity "error" (default) or "warning"
|
||||||
|
--workflow Workflow name for the issue title
|
||||||
|
--repo owner/repo (default: \$GITHUB_REPOSITORY)
|
||||||
|
--run-url URL to the CI run (auto-detected from env)
|
||||||
|
--token Gitea API token (default: \$GITEA_TOKEN)
|
||||||
|
--url Gitea base URL (default: \$GITEA_URL)
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--gate) GATE="$2"; shift 2 ;;
|
||||||
|
--details) DETAILS="$2"; shift 2 ;;
|
||||||
|
--severity) SEVERITY="$2"; shift 2 ;;
|
||||||
|
--workflow) WORKFLOW="$2"; shift 2 ;;
|
||||||
|
--repo) REPO="$2"; shift 2 ;;
|
||||||
|
--run-url) RUN_URL="$2"; shift 2 ;;
|
||||||
|
--token) GITEA_TOKEN="$2"; shift 2 ;;
|
||||||
|
--url) GITEA_URL="$2"; shift 2 ;;
|
||||||
|
-h|--help) usage ;;
|
||||||
|
*) echo "Unknown option: $1"; usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
|
||||||
|
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
|
||||||
|
[[ -z "$GITEA_TOKEN" ]] && { echo "ERROR: GITEA_TOKEN not set"; exit 1; }
|
||||||
|
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
|
||||||
|
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${REPO}"
|
||||||
|
|
||||||
|
# ── Build title ─────────────────────────────────────────────────────────────
|
||||||
|
if [[ -n "$WORKFLOW" ]]; then
|
||||||
|
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
|
||||||
|
else
|
||||||
|
TITLE="[CI] ${GATE} failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Ensure label exists ─────────────────────────────────────────────────────
|
||||||
|
ensure_label() {
|
||||||
|
local exists
|
||||||
|
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/labels" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$exists" == "200" ]]; then
|
||||||
|
# Check if label already exists
|
||||||
|
local found
|
||||||
|
found=$(curl -sf \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/labels" 2>/dev/null \
|
||||||
|
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
|
||||||
|
|
||||||
|
if [[ -z "$found" ]]; then
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/labels" \
|
||||||
|
-d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \
|
||||||
|
> /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Search for existing open issue ──────────────────────────────────────────
|
||||||
|
find_existing_issue() {
|
||||||
|
# URL-encode the gate name for the query
|
||||||
|
local query
|
||||||
|
query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g')
|
||||||
|
|
||||||
|
local response
|
||||||
|
response=$(curl -sf \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
|
||||||
|
2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
# Extract the first matching issue number
|
||||||
|
echo "$response" \
|
||||||
|
| grep -oP '"number":\s*\K[0-9]+' \
|
||||||
|
| head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Build issue body ────────────────────────────────────────────────────────
|
||||||
|
build_body() {
|
||||||
|
local severity_badge
|
||||||
|
if [[ "$SEVERITY" == "error" ]]; then
|
||||||
|
severity_badge="**Severity:** Error"
|
||||||
|
else
|
||||||
|
severity_badge="**Severity:** Warning"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<BODY
|
||||||
|
## CI Gate Failure: ${GATE}
|
||||||
|
|
||||||
|
${severity_badge}
|
||||||
|
**Workflow:** ${WORKFLOW:-unknown}
|
||||||
|
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||||
|
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||||
|
**Run:** [View CI run](${RUN_URL})
|
||||||
|
|
||||||
|
### Details
|
||||||
|
|
||||||
|
${DETAILS}
|
||||||
|
|
||||||
|
### Resolution
|
||||||
|
|
||||||
|
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Auto-created by [ci-issue-reporter](${GITEA_URL}/${REPO}/src/branch/main/automation/ci-issue-reporter.sh)*
|
||||||
|
BODY
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Build comment body (for existing issues) ────────────────────────────────
|
||||||
|
build_comment() {
|
||||||
|
cat <<COMMENT
|
||||||
|
### CI failure recurrence
|
||||||
|
|
||||||
|
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||||
|
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||||
|
**Run:** [View CI run](${RUN_URL})
|
||||||
|
|
||||||
|
${DETAILS}
|
||||||
|
COMMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
ensure_label
|
||||||
|
|
||||||
|
EXISTING=$(find_existing_issue)
|
||||||
|
|
||||||
|
if [[ -n "$EXISTING" ]]; then
|
||||||
|
# Append comment to existing issue
|
||||||
|
COMMENT_BODY=$(build_comment)
|
||||||
|
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
|
||||||
|
|
||||||
|
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/issues/${EXISTING}/comments" \
|
||||||
|
-d "${COMMENT_JSON}" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$HTTP" == "201" ]]; then
|
||||||
|
echo "Commented on existing issue #${EXISTING}"
|
||||||
|
else
|
||||||
|
echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Create new issue
|
||||||
|
ISSUE_BODY=$(build_body)
|
||||||
|
ISSUE_JSON=$(python3 -c "
|
||||||
|
import sys, json
|
||||||
|
body = sys.stdin.read()
|
||||||
|
print(json.dumps({
|
||||||
|
'title': sys.argv[1],
|
||||||
|
'body': body,
|
||||||
|
'labels': []
|
||||||
|
}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null)
|
||||||
|
|
||||||
|
# Create the issue
|
||||||
|
RESPONSE=$(curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/issues" \
|
||||||
|
-d "${ISSUE_JSON}" 2>/dev/null || echo "{}")
|
||||||
|
|
||||||
|
ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1)
|
||||||
|
|
||||||
|
if [[ -n "$ISSUE_NUM" ]]; then
|
||||||
|
# Apply label (separate call — more reliable across Gitea versions)
|
||||||
|
LABEL_ID=$(curl -sf \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/labels" 2>/dev/null \
|
||||||
|
| grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \
|
||||||
|
| head -1 || true)
|
||||||
|
|
||||||
|
if [[ -n "$LABEL_ID" ]]; then
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/issues/${ISSUE_NUM}/labels" \
|
||||||
|
-d "{\"labels\":[${LABEL_ID}]}" \
|
||||||
|
> /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Created issue #${ISSUE_NUM}: ${TITLE}"
|
||||||
|
else
|
||||||
|
echo "WARNING: Failed to create issue"
|
||||||
|
echo "Response: ${RESPONSE}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@@ -230,7 +230,8 @@ class PushFiles extends CliFramework
|
|||||||
{
|
{
|
||||||
// Read platform from repo's .mokogitea/manifest.xml via API
|
// Read platform from repo's .mokogitea/manifest.xml via API
|
||||||
try {
|
try {
|
||||||
$manifestData = $this->adapter->getFileContent($org, $repo, '.mokogitea/manifest.xml', 'main');
|
$fileInfo = $this->adapter->getFileContents($org, $repo, '.mokogitea/manifest.xml', 'main');
|
||||||
|
$manifestData = isset($fileInfo['content']) ? base64_decode($fileInfo['content']) : '';
|
||||||
if (!empty($manifestData)) {
|
if (!empty($manifestData)) {
|
||||||
$xml = @simplexml_load_string($manifestData);
|
$xml = @simplexml_load_string($manifestData);
|
||||||
if ($xml !== false) {
|
if ($xml !== false) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/branch_rename.php
|
* PATH: /cli/branch_rename.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old)
|
* BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/bulk_workflow_push.php
|
* PATH: /cli/bulk_workflow_push.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Push a workflow file to all governed repos via the Gitea Contents API
|
* BRIEF: Push a workflow file to all governed repos via the Gitea Contents API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/bulk_workflow_trigger.php
|
* PATH: /cli/bulk_workflow_trigger.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Trigger a workflow across multiple repos at once
|
* BRIEF: Trigger a workflow across multiple repos at once
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/client_dashboard.php
|
* PATH: /cli/client_dashboard.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Generate unified client dashboard HTML
|
* BRIEF: Generate unified client dashboard HTML
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/client_inventory.php
|
* PATH: /cli/client_inventory.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/client_provision.php
|
* PATH: /cli/client_provision.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Provision a new client environment end-to-end
|
* BRIEF: Provision a new client environment end-to-end
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/grafana_dashboard.php
|
* PATH: /cli/grafana_dashboard.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Manage Grafana dashboards via API
|
* BRIEF: Manage Grafana dashboards via API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/joomla_build.php
|
* PATH: /cli/joomla_build.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
||||||
* NOTE: Called by pre-release and auto-release workflows.
|
* NOTE: Called by pre-release and auto-release workflows.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/manifest_read.php
|
* PATH: /cli/manifest_read.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Parse .manifest.xml and output requested field(s) for CI consumption
|
* BRIEF: Parse .manifest.xml and output requested field(s) for CI consumption
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/release_cascade.php
|
* PATH: /cli/release_cascade.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: DEPRECATED — cascade behavior removed. Each release stream is independent.
|
* BRIEF: DEPRECATED — cascade behavior removed. Each release stream is independent.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+20
-1
@@ -230,12 +230,31 @@ class ReleasePackageCli extends CliFramework
|
|||||||
$subName = basename($pkgDir);
|
$subName = basename($pkgDir);
|
||||||
$subZipPath = "{$outputDir}/{$subName}.zip";
|
$subZipPath = "{$outputDir}/{$subName}.zip";
|
||||||
|
|
||||||
|
// If sub-package is a full repo checkout (e.g. git submodule),
|
||||||
|
// look for a src/ subdirectory containing a Joomla manifest XML
|
||||||
|
// and zip that instead of the repo root.
|
||||||
|
$subSourceDir = $pkgDir;
|
||||||
|
$srcCandidate = "{$pkgDir}/src";
|
||||||
|
if (is_dir($srcCandidate)) {
|
||||||
|
$srcManifests = array_merge(
|
||||||
|
glob("{$srcCandidate}/*.xml") ?: [],
|
||||||
|
glob("{$srcCandidate}/pkg_*.xml") ?: []
|
||||||
|
);
|
||||||
|
foreach ($srcManifests as $mf) {
|
||||||
|
if (strpos(file_get_contents($mf) ?: '', '<extension') !== false) {
|
||||||
|
$subSourceDir = $srcCandidate;
|
||||||
|
echo " Sub-package {$subName}: using src/ entry-point\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$subZip = new \ZipArchive();
|
$subZip = new \ZipArchive();
|
||||||
if ($subZip->open($subZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
if ($subZip->open($subZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||||
$this->log('ERROR', "Failed to create sub-package ZIP: {$subZipPath}");
|
$this->log('ERROR', "Failed to create sub-package ZIP: {$subZipPath}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$this->addDirToZip($subZip, $pkgDir, '', $this->excludePatterns);
|
$this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns);
|
||||||
$subZip->close();
|
$subZip->close();
|
||||||
|
|
||||||
$zip->addFile($subZipPath, "packages/{$subName}.zip");
|
$zip->addFile($subZipPath, "packages/{$subName}.zip");
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/release_publish.php
|
* PATH: /cli/release_publish.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/scaffold_client.php
|
* PATH: /cli/scaffold_client.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings
|
* BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/updates_xml_sync.php
|
* PATH: /cli/updates_xml_sync.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Sync updates.xml to target branches via Gitea API
|
* BRIEF: Sync updates.xml to target branches via Gitea API
|
||||||
* NOTE: Called by pre-release and auto-release workflows after updates.xml
|
* NOTE: Called by pre-release and auto-release workflows after updates.xml
|
||||||
* is modified on the current branch. Pushes the file to other branches
|
* is modified on the current branch. Pushes the file to other branches
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/version_auto_bump.php
|
* PATH: /cli/version_auto_bump.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -109,10 +109,18 @@ class VersionAutoBumpCli extends CliFramework
|
|||||||
echo "{$line}\n";
|
echo "{$line}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Read version
|
// Step 2: Read version (--quiet suppresses banner so only the version is output)
|
||||||
$versionOutput = [];
|
$versionOutput = [];
|
||||||
exec("{$php} {$cli}/version_read.php --path " . escapeshellarg($path) . " 2>&1", $versionOutput, $versionRc);
|
exec("{$php} {$cli}/version_read.php --path " . escapeshellarg($path) . " --quiet 2>&1", $versionOutput, $versionRc);
|
||||||
$version = trim($versionOutput[0] ?? '');
|
// Take the last non-empty line — the version is always the final output
|
||||||
|
$version = '';
|
||||||
|
foreach (array_reverse($versionOutput) as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
if (preg_match('/^\d{2}\.\d{2}\.\d{2}/', $line)) {
|
||||||
|
$version = $line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($version)) {
|
if (empty($version)) {
|
||||||
echo "No version found — skipping\n";
|
echo "No version found — skipping\n";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/version_check.php
|
* PATH: /cli/version_check.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ class VersionSetPlatformCli extends CliFramework
|
|||||||
// Strip any existing suffix(es) before applying the correct one
|
// Strip any existing suffix(es) before applying the correct one
|
||||||
$version = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $version);
|
$version = preg_replace('/(-(dev|alpha|beta|rc))+$/', '', $version);
|
||||||
|
|
||||||
|
// Validate version format — must be XX.YY.ZZ to prevent XML corruption
|
||||||
|
if (!preg_match('/^\d{2}\.\d{2}\.\d{2}$/', $version)) {
|
||||||
|
$this->log('ERROR', "Invalid version format: '{$version}' — expected XX.YY.ZZ");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Append stability suffix for non-stable releases
|
// Append stability suffix for non-stable releases
|
||||||
$stabilitySuffixMap = [
|
$stabilitySuffixMap = [
|
||||||
'stable' => '',
|
'stable' => '',
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
* INGROUP: moko-platform
|
* INGROUP: moko-platform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /cli/wiki_sync.php
|
* PATH: /cli/wiki_sync.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Sync select wiki pages from moko-platform to all template repos
|
* BRIEF: Sync select wiki pages from moko-platform to all template repos
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoPlatform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /deploy/backup-before-deploy.php
|
* PATH: /deploy/backup-before-deploy.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoPlatform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /deploy/deploy-dolibarr.php
|
* PATH: /deploy/deploy-dolibarr.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoPlatform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /deploy/health-check.php
|
* PATH: /deploy/health-check.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Post-deploy health check — verify a Joomla site is responding correctly
|
* BRIEF: Post-deploy health check — verify a Joomla site is responding correctly
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoPlatform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /deploy/rollback-joomla.php
|
* PATH: /deploy/rollback-joomla.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot
|
* BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoPlatform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /deploy/sync-joomla.php
|
* PATH: /deploy/sync-joomla.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Sync Joomla site directories between two servers via rsync over SSH
|
* BRIEF: Sync Joomla site directories between two servers via rsync over SSH
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,26 @@ abstract class CliFramework
|
|||||||
/** @var float Script start time for elapsed-time reporting. */
|
/** @var float Script start time for elapsed-time reporting. */
|
||||||
private float $startTime;
|
private float $startTime;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Display output — all decorative output goes to stderr
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write decorative/diagnostic output to stderr.
|
||||||
|
*
|
||||||
|
* All non-data output (banners, progress bars, section headers, status
|
||||||
|
* lines, log messages) MUST use this method so that stdout is reserved
|
||||||
|
* for machine-readable data. This ensures that shell captures like
|
||||||
|
* VERSION=$(php version_read.php --path .)
|
||||||
|
* only receive the actual data, not decorative text.
|
||||||
|
*
|
||||||
|
* @since 04.00.16
|
||||||
|
*/
|
||||||
|
protected function display(string $text): void
|
||||||
|
{
|
||||||
|
fwrite(STDERR, $text);
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Constructor
|
// Constructor
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -326,14 +346,14 @@ abstract class CliFramework
|
|||||||
protected function printHelp(): void
|
protected function printHelp(): void
|
||||||
{
|
{
|
||||||
$w = $this->termWidth();
|
$w = $this->termWidth();
|
||||||
echo $this->c(self::C_BOLD . self::C_CYAN, $this->scriptName);
|
$this->display($this->c(self::C_BOLD . self::C_CYAN, $this->scriptName));
|
||||||
if ($this->description !== '') {
|
if ($this->description !== '') {
|
||||||
echo ' — ' . $this->description;
|
$this->display(' — ' . $this->description);
|
||||||
}
|
}
|
||||||
echo "\n";
|
$this->display("\n");
|
||||||
echo $this->c(self::C_DIM, str_repeat(self::BOX_H, $w)) . "\n\n";
|
$this->display($this->c(self::C_DIM, str_repeat(self::BOX_H, $w)) . "\n\n");
|
||||||
echo $this->c(self::C_BOLD, 'Usage:') . " php {$this->scriptName}.php [options]\n\n";
|
$this->display($this->c(self::C_BOLD, 'Usage:') . " php {$this->scriptName}.php [options]\n\n");
|
||||||
echo $this->c(self::C_BOLD, 'Options:') . "\n";
|
$this->display($this->c(self::C_BOLD, 'Options:') . "\n");
|
||||||
|
|
||||||
$builtIn = [
|
$builtIn = [
|
||||||
'--help' => ['desc' => 'Show this help message', 'default' => null],
|
'--help' => ['desc' => 'Show this help message', 'default' => null],
|
||||||
@@ -348,16 +368,16 @@ abstract class CliFramework
|
|||||||
$hint = ($default !== null && $default !== false)
|
$hint = ($default !== null && $default !== false)
|
||||||
? $this->c(self::C_DIM, " (default: {$default})")
|
? $this->c(self::C_DIM, " (default: {$default})")
|
||||||
: '';
|
: '';
|
||||||
printf(
|
$this->display(sprintf(
|
||||||
" %s%-22s%s%s%s\n",
|
" %s%-22s%s%s%s\n",
|
||||||
self::C_CYAN,
|
self::C_CYAN,
|
||||||
$name,
|
$name,
|
||||||
self::C_RESET,
|
self::C_RESET,
|
||||||
$def['desc'],
|
$def['desc'],
|
||||||
$hint
|
$hint
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
echo "\n";
|
$this->display("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -378,23 +398,23 @@ abstract class CliFramework
|
|||||||
$titleLine = $this->padRight($titleStyled, $inner, strlen($titleRaw));
|
$titleLine = $this->padRight($titleStyled, $inner, strlen($titleRaw));
|
||||||
$descLine = ($desc !== '') ? $this->padRight(" {$desc}", $inner) : null;
|
$descLine = ($desc !== '') ? $this->padRight(" {$desc}", $inner) : null;
|
||||||
|
|
||||||
echo "\n";
|
$this->display("\n");
|
||||||
echo $this->c(
|
$this->display($this->c(
|
||||||
self::C_CYAN,
|
self::C_CYAN,
|
||||||
self::BOX_TL . str_repeat(self::BOX_H, $inner) . self::BOX_TR
|
self::BOX_TL . str_repeat(self::BOX_H, $inner) . self::BOX_TR
|
||||||
) . "\n";
|
) . "\n");
|
||||||
echo $this->c(self::C_CYAN, self::BOX_V)
|
$this->display($this->c(self::C_CYAN, self::BOX_V)
|
||||||
. $this->c(self::C_BOLD, $titleLine)
|
. $this->c(self::C_BOLD, $titleLine)
|
||||||
. $this->c(self::C_CYAN, self::BOX_V) . "\n";
|
. $this->c(self::C_CYAN, self::BOX_V) . "\n");
|
||||||
if ($descLine !== null) {
|
if ($descLine !== null) {
|
||||||
echo $this->c(self::C_CYAN, self::BOX_V)
|
$this->display($this->c(self::C_CYAN, self::BOX_V)
|
||||||
. $this->c(self::C_DIM, $descLine)
|
. $this->c(self::C_DIM, $descLine)
|
||||||
. $this->c(self::C_CYAN, self::BOX_V) . "\n";
|
. $this->c(self::C_CYAN, self::BOX_V) . "\n");
|
||||||
}
|
}
|
||||||
echo $this->c(
|
$this->display($this->c(
|
||||||
self::C_CYAN,
|
self::C_CYAN,
|
||||||
self::BOX_BL . str_repeat(self::BOX_H, $inner) . self::BOX_BR
|
self::BOX_BL . str_repeat(self::BOX_H, $inner) . self::BOX_BR
|
||||||
) . "\n\n";
|
) . "\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Print the dry-run notice box. */
|
/** Print the dry-run notice box. */
|
||||||
@@ -403,18 +423,18 @@ abstract class CliFramework
|
|||||||
$w = min($this->termWidth(), 70);
|
$w = min($this->termWidth(), 70);
|
||||||
$msg = ' ' . self::ICON_DRY . ' DRY-RUN MODE — no changes will be written ';
|
$msg = ' ' . self::ICON_DRY . ' DRY-RUN MODE — no changes will be written ';
|
||||||
$row = $this->padRight($msg, $w - 2);
|
$row = $this->padRight($msg, $w - 2);
|
||||||
echo $this->c(
|
$this->display($this->c(
|
||||||
self::C_YELLOW . self::C_BOLD,
|
self::C_YELLOW . self::C_BOLD,
|
||||||
self::BOX_TL . str_repeat(self::BOX_H, $w - 2) . self::BOX_TR
|
self::BOX_TL . str_repeat(self::BOX_H, $w - 2) . self::BOX_TR
|
||||||
) . "\n";
|
) . "\n");
|
||||||
echo $this->c(
|
$this->display($this->c(
|
||||||
self::C_YELLOW . self::C_BOLD,
|
self::C_YELLOW . self::C_BOLD,
|
||||||
self::BOX_V . $row . self::BOX_V
|
self::BOX_V . $row . self::BOX_V
|
||||||
) . "\n";
|
) . "\n");
|
||||||
echo $this->c(
|
$this->display($this->c(
|
||||||
self::C_YELLOW . self::C_BOLD,
|
self::C_YELLOW . self::C_BOLD,
|
||||||
self::BOX_BL . str_repeat(self::BOX_H, $w - 2) . self::BOX_BR
|
self::BOX_BL . str_repeat(self::BOX_H, $w - 2) . self::BOX_BR
|
||||||
) . "\n\n";
|
) . "\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -435,11 +455,11 @@ abstract class CliFramework
|
|||||||
$w = $this->termWidth();
|
$w = $this->termWidth();
|
||||||
$text = " {$title} ";
|
$text = " {$title} ";
|
||||||
$fill = max(0, $w - strlen($text) - 4);
|
$fill = max(0, $w - strlen($text) - 4);
|
||||||
echo "\n";
|
$this->display("\n");
|
||||||
echo $this->c(
|
$this->display($this->c(
|
||||||
self::C_CYAN,
|
self::C_CYAN,
|
||||||
str_repeat(self::BOX_H, 2) . $text . str_repeat(self::BOX_H, $fill)
|
str_repeat(self::BOX_H, 2) . $text . str_repeat(self::BOX_H, $fill)
|
||||||
) . "\n\n";
|
) . "\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Print a plain horizontal divider. */
|
/** Print a plain horizontal divider. */
|
||||||
@@ -449,7 +469,7 @@ abstract class CliFramework
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->clearProgress();
|
$this->clearProgress();
|
||||||
echo $this->c(self::C_DIM, str_repeat(self::BOX_H, $this->termWidth())) . "\n";
|
$this->display($this->c(self::C_DIM, str_repeat(self::BOX_H, $this->termWidth())) . "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -495,11 +515,7 @@ abstract class CliFramework
|
|||||||
|
|
||||||
$line = "{$ts} {$icon} {$badge} {$text}\n";
|
$line = "{$ts} {$icon} {$badge} {$text}\n";
|
||||||
|
|
||||||
if ($level === 'ERROR') {
|
$this->display($line);
|
||||||
fwrite(STDERR, $line);
|
|
||||||
} else {
|
|
||||||
echo $line;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Log a success message. */
|
/** Log a success message. */
|
||||||
@@ -564,7 +580,7 @@ abstract class CliFramework
|
|||||||
? ' ' . $this->c(self::C_DIM, "— {$detail}")
|
? ' ' . $this->c(self::C_DIM, "— {$detail}")
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
echo ' ' . $this->c($color . self::C_BOLD, $icon) . ' ' . $label . $suffix . "\n";
|
$this->display(' ' . $this->c($color . self::C_BOLD, $icon) . ' ' . $label . $suffix . "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -601,10 +617,10 @@ abstract class CliFramework
|
|||||||
$line = " [{$bar}] {$percent} {$counter}{$suffix}";
|
$line = " [{$bar}] {$percent} {$counter}{$suffix}";
|
||||||
|
|
||||||
if ($newline) {
|
if ($newline) {
|
||||||
echo "\r{$line}\n";
|
$this->display("\r{$line}\n");
|
||||||
$this->progressActive = false;
|
$this->progressActive = false;
|
||||||
} else {
|
} else {
|
||||||
echo "\r{$line}";
|
$this->display("\r{$line}");
|
||||||
$this->progressActive = true;
|
$this->progressActive = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -613,7 +629,7 @@ abstract class CliFramework
|
|||||||
protected function clearProgress(): void
|
protected function clearProgress(): void
|
||||||
{
|
{
|
||||||
if ($this->progressActive) {
|
if ($this->progressActive) {
|
||||||
echo "\r" . str_repeat(' ', $this->termWidth()) . "\r";
|
$this->display("\r" . str_repeat(' ', $this->termWidth()) . "\r");
|
||||||
$this->progressActive = false;
|
$this->progressActive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -644,8 +660,8 @@ abstract class CliFramework
|
|||||||
$maxKey = max(array_map('strlen', array_keys($rows)));
|
$maxKey = max(array_map('strlen', array_keys($rows)));
|
||||||
$inner = $maxKey + 20;
|
$inner = $maxKey + 20;
|
||||||
|
|
||||||
echo "\n";
|
$this->display("\n");
|
||||||
echo $this->c($color, self::BOX_TL . str_repeat(self::BOX_H, $inner) . self::BOX_TR) . "\n";
|
$this->display($this->c($color, self::BOX_TL . str_repeat(self::BOX_H, $inner) . self::BOX_TR) . "\n");
|
||||||
|
|
||||||
foreach ($rows as $label => $value) {
|
foreach ($rows as $label => $value) {
|
||||||
$valStr = (string) $value;
|
$valStr = (string) $value;
|
||||||
@@ -653,10 +669,10 @@ abstract class CliFramework
|
|||||||
$padding = $inner - strlen($label) - $valVis - 4;
|
$padding = $inner - strlen($label) - $valVis - 4;
|
||||||
$row = ' ' . $this->c(self::C_BOLD, $label)
|
$row = ' ' . $this->c(self::C_BOLD, $label)
|
||||||
. str_repeat(' ', max(1, $padding)) . $valStr . ' ';
|
. str_repeat(' ', max(1, $padding)) . $valStr . ' ';
|
||||||
echo $this->c($color, self::BOX_V) . $row . $this->c($color, self::BOX_V) . "\n";
|
$this->display($this->c($color, self::BOX_V) . $row . $this->c($color, self::BOX_V) . "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
echo $this->c($color, self::BOX_BL . str_repeat(self::BOX_H, $inner) . self::BOX_BR) . "\n\n";
|
$this->display($this->c($color, self::BOX_BL . str_repeat(self::BOX_H, $inner) . self::BOX_BR) . "\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -702,7 +718,7 @@ abstract class CliFramework
|
|||||||
$this->clearProgress();
|
$this->clearProgress();
|
||||||
$badge = $this->c(self::C_BOLD . self::C_MAGENTA, "Step {$current}/{$total}");
|
$badge = $this->c(self::C_BOLD . self::C_MAGENTA, "Step {$current}/{$total}");
|
||||||
$arrow = $this->c(self::C_DIM, self::ICON_INFO);
|
$arrow = $this->c(self::C_DIM, self::ICON_INFO);
|
||||||
echo "\n{$badge} {$arrow} {$title}\n";
|
$this->display("\n{$badge} {$arrow} {$title}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -964,13 +980,13 @@ abstract class CliFramework
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Header.
|
// Header.
|
||||||
echo $sep . "\n";
|
$this->display($sep . "\n");
|
||||||
$headerLine = '|';
|
$headerLine = '|';
|
||||||
foreach ($headers as $i => $h) {
|
foreach ($headers as $i => $h) {
|
||||||
$headerLine .= ' ' . $this->c(self::C_BOLD, str_pad($h, $widths[$i])) . ' |';
|
$headerLine .= ' ' . $this->c(self::C_BOLD, str_pad($h, $widths[$i])) . ' |';
|
||||||
}
|
}
|
||||||
echo $headerLine . "\n";
|
$this->display($headerLine . "\n");
|
||||||
echo $sep . "\n";
|
$this->display($sep . "\n");
|
||||||
|
|
||||||
// Rows.
|
// Rows.
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
@@ -978,9 +994,9 @@ abstract class CliFramework
|
|||||||
foreach ($row as $i => $cell) {
|
foreach ($row as $i => $cell) {
|
||||||
$line .= ' ' . str_pad((string) $cell, $widths[$i]) . ' |';
|
$line .= ' ' . str_pad((string) $cell, $widths[$i]) . ' |';
|
||||||
}
|
}
|
||||||
echo $line . "\n";
|
$this->display($line . "\n");
|
||||||
}
|
}
|
||||||
echo $sep . "\n";
|
$this->display($sep . "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +1,56 @@
|
|||||||
<!--
|
# Update Server — Dolibarr Modules
|
||||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
|
|
||||||
This file is part of a Moko Consulting project.
|
## Overview
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
MokoGitea provides a built-in Update Server that can serve Dolibarr-compatible JSON update feeds from repository releases. **No static feed file is needed in the repository.**
|
||||||
|
|
||||||
# FILE INFORMATION
|
|
||||||
DEFGROUP: {{REPO_NAME}}.Documentation
|
|
||||||
INGROUP: MokoPlatform.Templates
|
|
||||||
REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}
|
|
||||||
PATH: /docs/update-server.md
|
|
||||||
VERSION: {{standards_version}}
|
|
||||||
BRIEF: How this module's update server file (update.txt) is managed
|
|
||||||
-->
|
|
||||||
|
|
||||||
# Dolibarr Update Server
|
|
||||||
|
|
||||||
[](https://git.mokoconsulting.tech/MokoConsulting/moko-platform)
|
|
||||||
|
|
||||||
This document explains how `update.txt` is automatically managed for this Dolibarr module.
|
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
Dolibarr checks for module updates by fetching a plain-text file from the URL in `$this->url_last_version` in the module descriptor (`src/core/modules/mod*.class.php`). The file must contain **only the version string** — no JSON, no XML, no trailing newline.
|
1. **Enable Update Server** in the repository's Settings > Advanced Settings
|
||||||
|
2. **Configure metadata** in Settings > Update Server (set platform to `dolibarr`)
|
||||||
|
3. **Create releases** with tagged module archives
|
||||||
|
4. MokoGitea serves the update feed at `/{owner}/{repo}/updates/dolibarr.json`
|
||||||
|
|
||||||
### Automatic Generation
|
## Feed URL
|
||||||
|
|
||||||
| Event | Workflow | `update.txt` Content | `$this->version` |
|
|
||||||
|-------|----------|---------------------|-------------------|
|
|
||||||
| Merge to `main` | `auto-release.yml` | `XX.YY.ZZ` (real version) | Real version |
|
|
||||||
| Push to `dev/**` | `deploy-dev.yml` | `development` | `development` |
|
|
||||||
| Push to `rc/**` | `deploy-dev.yml` | `XX.YY.ZZ-rc` | RC version |
|
|
||||||
|
|
||||||
### Module Descriptor
|
|
||||||
|
|
||||||
The `url_last_version` in your module descriptor should point to:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/update.txt
|
https://git.mokoconsulting.tech/{owner}/{repo}/updates/dolibarr.json
|
||||||
```
|
```
|
||||||
|
|
||||||
This is set automatically by `version_set_platform.php` during the build pipeline. **Never manually edit `$this->version` or `$this->url_last_version`** — the workflows handle it.
|
## Release Naming Convention
|
||||||
|
|
||||||
### Branch Lifecycle
|
Release assets should follow:
|
||||||
|
|
||||||
```
|
```
|
||||||
dev/XX.YY.ZZ → rc/XX.YY.ZZ → main → version/XX
|
{module_name}-{version}.zip
|
||||||
(development) (release candidate) (stable release) (frozen snapshot)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **Development** (`dev/**`): `update.txt` = `development`, `$this->version` = `development`
|
Examples:
|
||||||
2. **Release Candidate** (`rc/**`): `update.txt` = `XX.YY.ZZ-rc`, version set to RC
|
- `mokocrm-18.0.1.zip`
|
||||||
3. **Stable Release** (merge to `main`): `auto-release.yml` writes real version to `update.txt`, creates GitHub Release + tag, creates `version/XX` branch
|
- `mokodolisign-3.2.0.zip`
|
||||||
4. **Frozen Snapshot** (`version/XX`): immutable, never force-pushed
|
|
||||||
|
|
||||||
### Health Checks
|
## Update Server Settings
|
||||||
|
|
||||||
The `repo_health.yml` workflow verifies on every commit:
|
Configure these in Settings > Update Server:
|
||||||
|
|
||||||
- `update.txt` exists in the repository root
|
| Field | Description | Example |
|
||||||
- Module descriptor (`mod*.class.php`) exists in `src/core/modules/`
|
|-------|-------------|---------|
|
||||||
- `$this->numero` is set and non-zero
|
| Platform | Set to `dolibarr` | `dolibarr` |
|
||||||
- `$this->version` is not hardcoded (should be set by workflow)
|
| Extension Name | Dolibarr module directory name | `mokocrm` |
|
||||||
- `url_last_version` points to `update.txt` (not `update.json`)
|
| Display Name | Human-readable name | `Module - MokoCRM` |
|
||||||
- `url_last_version` references `/main/` branch on the main branch
|
| Extension Type | Usually `module` | `module` |
|
||||||
|
| Maintainer | Organization name | `Moko Consulting` |
|
||||||
|
| Support URL | Product support page | `https://mokoconsulting.tech/support/mokocrm` |
|
||||||
|
|
||||||
---
|
## Download Gating
|
||||||
|
|
||||||
*Managed by [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform). See [docs/workflows/update-server.md](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/blob/main/docs/workflows/update-server.md) for the full specification.*
|
Same three modes as Joomla: `none`, `prerelease`, `all`.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Dolibarr Update Server support is currently **disabled for all modules except MokoCRM**. Metadata is pre-configured and ready to enable when needed.
|
||||||
|
|
||||||
|
## What NOT to Do
|
||||||
|
|
||||||
|
- **Do NOT commit static feed files to the repository**
|
||||||
|
- **Do NOT use legacy update check mechanisms** — use the built-in feed
|
||||||
|
|||||||
@@ -1,122 +1,90 @@
|
|||||||
<!--
|
# Update Server — Joomla Extensions
|
||||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
|
|
||||||
This file is part of a Moko Consulting project.
|
## Overview
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
MokoGitea provides a built-in Update Server that dynamically generates Joomla-compatible update XML feeds from repository releases. **No static `updates.xml` file is needed in the repository.**
|
||||||
|
|
||||||
# FILE INFORMATION
|
|
||||||
DEFGROUP: {{REPO_NAME}}.Documentation
|
|
||||||
INGROUP: MokoPlatform.Templates
|
|
||||||
REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}
|
|
||||||
PATH: /docs/update-server.md
|
|
||||||
VERSION: {{standards_version}}
|
|
||||||
BRIEF: How this extension's Joomla update server file (updates.xml) is managed
|
|
||||||
-->
|
|
||||||
|
|
||||||
# Joomla Update Server
|
|
||||||
|
|
||||||
[](https://git.mokoconsulting.tech/MokoConsulting/moko-platform)
|
|
||||||
|
|
||||||
This document explains how `updates.xml` is automatically managed for this Joomla extension following the [Joomla Update Server specification](https://docs.joomla.org/Deploying_an_Update_Server).
|
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
Joomla checks for extension updates by fetching an XML file from the URL defined in the `<updateservers>` tag in the extension's XML manifest. moko-platform generates this file automatically.
|
1. **Enable Update Server** in the repository's Settings > Advanced Settings
|
||||||
|
2. **Configure metadata** in Settings > Update Server (extension name, type, target version, etc.)
|
||||||
|
3. **Create releases** with tagged assets (e.g. `pkg_mokowaas-02.19.00.zip`)
|
||||||
|
4. MokoGitea automatically serves the update feed at `/{owner}/{repo}/updates.xml`
|
||||||
|
|
||||||
### Automatic Generation
|
## Feed URL
|
||||||
|
|
||||||
| Event | Workflow | `<tag>` | `<version>` |
|
```
|
||||||
|-------|----------|---------|-------------|
|
https://git.mokoconsulting.tech/{owner}/{repo}/updates.xml
|
||||||
| Merge to `main` | `auto-release.yml` | `stable` | `XX.YY.ZZ` |
|
```
|
||||||
| Push to `dev/**` | `deploy-dev.yml` | `development` | `development` |
|
|
||||||
| Push to `rc/**` | `deploy-dev.yml` | `rc` | `XX.YY.ZZ-rc` |
|
|
||||||
|
|
||||||
### Generated XML Structure
|
This URL is what goes into your Joomla extension's `update_server` element in the manifest XML.
|
||||||
|
|
||||||
|
## Manifest Configuration
|
||||||
|
|
||||||
|
In your extension's manifest XML (`*.xml`), add:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<updateservers>
|
||||||
<updates>
|
<server type="extension" name="{Extension Name}">
|
||||||
<update>
|
https://git.mokoconsulting.tech/MokoConsulting/{RepoName}/updates.xml
|
||||||
<name>Extension Name</name>
|
</server>
|
||||||
<description>Extension Name update</description>
|
</updateservers>
|
||||||
<element>com_extensionname</element>
|
|
||||||
<type>component</type>
|
|
||||||
<version>01.02.03</version>
|
|
||||||
<client>site</client>
|
|
||||||
<folder>system</folder> <!-- plugins only -->
|
|
||||||
<tags>
|
|
||||||
<tag>stable</tag>
|
|
||||||
</tags>
|
|
||||||
<downloads>
|
|
||||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/.../releases/download/v01.02.03/com_ext-01.02.03.zip</downloadurl>
|
|
||||||
<downloadurl type="full" format="zip">https://github.com/.../releases/download/v01.02.03/com_ext-01.02.03.zip</downloadurl>
|
|
||||||
</downloads>
|
|
||||||
<targetplatform name="joomla" version="((5\.[0-9])|(6\.[0-9]))" />
|
|
||||||
<php_minimum>8.2</php_minimum> <!-- if present in manifest -->
|
|
||||||
<maintainer>Moko Consulting</maintainer>
|
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
|
||||||
</update>
|
|
||||||
</updates>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Metadata Source
|
## Release Naming Convention
|
||||||
|
|
||||||
All metadata is extracted from the extension's XML manifest (`src/*.xml`) at build time:
|
Release assets must follow this naming pattern for the feed generator to detect them:
|
||||||
|
|
||||||
| XML Element | Source | Notes |
|
|
||||||
|-------------|--------|-------|
|
|
||||||
| `<name>` | `<name>` in manifest | Extension display name |
|
|
||||||
| `<element>` | `<element>` in manifest | Must match installed extension identifier |
|
|
||||||
| `<type>` | `type` attribute on `<extension>` | `component`, `module`, `plugin`, `library`, `package`, `template` |
|
|
||||||
| `<client>` | `client` attribute on `<extension>` | `site` or `administrator` — **required for plugins and modules** |
|
|
||||||
| `<folder>` | `group` attribute on `<extension>` | Plugin group (e.g., `system`, `content`) — **required for plugins** |
|
|
||||||
| `<targetplatform>` | `<targetplatform>` in manifest | Falls back to Joomla 5.x / 6.x if not specified |
|
|
||||||
| `<php_minimum>` | `<php_minimum>` in manifest | Included only if present |
|
|
||||||
|
|
||||||
### Extension Manifest Setup
|
|
||||||
|
|
||||||
Your XML manifest must include an `<updateservers>` tag pointing to the `updates.xml` on the `main` branch:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<extension type="component" client="site" method="upgrade">
|
|
||||||
<name>My Extension</name>
|
|
||||||
<element>com_myextension</element>
|
|
||||||
<!-- ... -->
|
|
||||||
<updateservers>
|
|
||||||
<server type="extension" priority="1" name="My Extension Update Server (Gitea)">
|
|
||||||
https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/raw/branch/main/updates.xml
|
|
||||||
</server>
|
|
||||||
<server type="extension" priority="2" name="My Extension Update Server (GitHub)">
|
|
||||||
https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/updates.xml
|
|
||||||
</server>
|
|
||||||
</updateservers>
|
|
||||||
</extension>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Branch Lifecycle
|
|
||||||
|
|
||||||
```
|
```
|
||||||
dev/XX.YY.ZZ → rc/XX.YY.ZZ → main → version/XX
|
{extension_name}-{version}.zip
|
||||||
(development) (rc) (stable) (frozen snapshot)
|
{extension_name}-{version}.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **Development** (`dev/**`): `updates.xml` with `<tag>development</tag>`, download points to branch archive
|
Examples:
|
||||||
2. **Release Candidate** (`rc/**`): `updates.xml` with `<tag>rc</tag>`, version set to `XX.YY.ZZ-rc`
|
- `pkg_mokowaas-02.19.00.zip`
|
||||||
3. **Stable Release** (merge to `main`): `updates.xml` with `<tag>stable</tag>`, download points to Gitea Release asset (primary) + GitHub Release asset (mirror)
|
- `tpl_mokoonyx-02.19.00.zip`
|
||||||
4. **Frozen Snapshot** (`version/XX`): immutable, never force-pushed
|
- `mod_mokojoomhero-01.05.00.zip`
|
||||||
|
|
||||||
### Health Checks
|
## Update Server Settings
|
||||||
|
|
||||||
The `repo_health.yml` workflow verifies on every commit:
|
Configure these in Settings > Update Server:
|
||||||
|
|
||||||
- `updates.xml` exists in the repository root
|
| Field | Description | Example |
|
||||||
- XML manifest exists with `<extension>` tag
|
|-------|-------------|---------|
|
||||||
- `<version>`, `<name>`, `<author>`, `<namespace>` tags present
|
| Extension Name | Joomla element name | `pkg_mokowaas` |
|
||||||
- Extension `type` attribute is valid
|
| Display Name | Human-readable name | `Package - MokoWaaS` |
|
||||||
- Language `.ini` files exist
|
| Extension Type | package, plugin, template, module, component | `package` |
|
||||||
- `index.html` directory listing protection in `src/`, `src/admin/`, `src/site/`
|
| Target Version | Regex for compatible Joomla versions | `(5|6)\..*` |
|
||||||
|
| PHP Minimum | Minimum PHP version | `8.1` |
|
||||||
|
| Maintainer | Organization name | `Moko Consulting` |
|
||||||
|
| Maintainer URL | Organization website | `https://mokoconsulting.tech` |
|
||||||
|
| Support URL | Product support page | `https://mokoconsulting.tech/products/{alias}` |
|
||||||
|
| Info URL | Product information page | `https://mokoconsulting.tech/products/{alias}` |
|
||||||
|
|
||||||
---
|
## Download Gating
|
||||||
|
|
||||||
*Managed by [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform). See [docs/workflows/update-server.md](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/blob/main/docs/workflows/update-server.md) for the full specification.*
|
Three modes control who can download release assets:
|
||||||
|
|
||||||
|
| Mode | Behavior |
|
||||||
|
|------|----------|
|
||||||
|
| `none` | All downloads are public |
|
||||||
|
| `prerelease` | Pre-release downloads require a license key; stable releases are public |
|
||||||
|
| `all` | All downloads require a license key |
|
||||||
|
|
||||||
|
The update feed XML is **always public** — only the actual file downloads are gated.
|
||||||
|
|
||||||
|
## What NOT to Do
|
||||||
|
|
||||||
|
- **Do NOT commit `updates.xml` to the repository** — it is served dynamically
|
||||||
|
- **Do NOT use static update server workflows** — the old CI-generated approach is deprecated
|
||||||
|
- **Do NOT hardcode version numbers in feed URLs** — the feed auto-detects from releases
|
||||||
|
|
||||||
|
## Changelog Feed
|
||||||
|
|
||||||
|
A changelog XML is also served automatically at:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://git.mokoconsulting.tech/{owner}/{repo}/changelog.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
This is generated from release notes (markdown body of each release).
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
|
||||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
VERSION: {{VERSION}}
|
|
||||||
-->
|
|
||||||
|
|
||||||
<updates>
|
|
||||||
|
|
||||||
<!-- 1. DEVELOPMENT -->
|
|
||||||
<update>
|
|
||||||
<name>{{EXTENSION_NAME}}</name>
|
|
||||||
<description>{{EXTENSION_NAME}} development build — unstable.</description>
|
|
||||||
<element>{{EXTENSION_ELEMENT}}</element>
|
|
||||||
<type>{{EXTENSION_TYPE}}</type>
|
|
||||||
<folder>{{EXTENSION_FOLDER}}</folder>
|
|
||||||
<client>{{EXTENSION_CLIENT}}</client>
|
|
||||||
<version>{{VERSION}}</version>
|
|
||||||
<creationDate>{{DATE}}</creationDate>
|
|
||||||
<infourl title='{{EXTENSION_NAME}} Dev'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/development</infourl>
|
|
||||||
<downloads>
|
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/development/{{EXTENSION_ELEMENT}}-{{VERSION}}-dev.zip</downloadurl>
|
|
||||||
</downloads>
|
|
||||||
<sha256></sha256>
|
|
||||||
<tags><tag>development</tag></tags>
|
|
||||||
<maintainer>Moko Consulting</maintainer>
|
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
|
||||||
<targetplatform name='joomla' version='(5|6).*'/>
|
|
||||||
<php_minimum>8.1</php_minimum>
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<!-- 2. ALPHA -->
|
|
||||||
<update>
|
|
||||||
<name>{{EXTENSION_NAME}}</name>
|
|
||||||
<description>{{EXTENSION_NAME}} alpha build — early testing.</description>
|
|
||||||
<element>{{EXTENSION_ELEMENT}}</element>
|
|
||||||
<type>{{EXTENSION_TYPE}}</type>
|
|
||||||
<folder>{{EXTENSION_FOLDER}}</folder>
|
|
||||||
<client>{{EXTENSION_CLIENT}}</client>
|
|
||||||
<version>{{VERSION}}</version>
|
|
||||||
<creationDate>{{DATE}}</creationDate>
|
|
||||||
<infourl title='{{EXTENSION_NAME}} Alpha'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/alpha</infourl>
|
|
||||||
<downloads>
|
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/alpha/{{EXTENSION_ELEMENT}}-{{VERSION}}-alpha.zip</downloadurl>
|
|
||||||
</downloads>
|
|
||||||
<sha256></sha256>
|
|
||||||
<tags><tag>alpha</tag></tags>
|
|
||||||
<maintainer>Moko Consulting</maintainer>
|
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
|
||||||
<targetplatform name='joomla' version='(5|6).*'/>
|
|
||||||
<php_minimum>8.1</php_minimum>
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<!-- 3. BETA -->
|
|
||||||
<update>
|
|
||||||
<name>{{EXTENSION_NAME}}</name>
|
|
||||||
<description>{{EXTENSION_NAME}} beta build — feature complete, stability testing.</description>
|
|
||||||
<element>{{EXTENSION_ELEMENT}}</element>
|
|
||||||
<type>{{EXTENSION_TYPE}}</type>
|
|
||||||
<folder>{{EXTENSION_FOLDER}}</folder>
|
|
||||||
<client>{{EXTENSION_CLIENT}}</client>
|
|
||||||
<version>{{VERSION}}</version>
|
|
||||||
<creationDate>{{DATE}}</creationDate>
|
|
||||||
<infourl title='{{EXTENSION_NAME}} Beta'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/beta</infourl>
|
|
||||||
<downloads>
|
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/beta/{{EXTENSION_ELEMENT}}-{{VERSION}}-beta.zip</downloadurl>
|
|
||||||
</downloads>
|
|
||||||
<sha256></sha256>
|
|
||||||
<tags><tag>beta</tag></tags>
|
|
||||||
<maintainer>Moko Consulting</maintainer>
|
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
|
||||||
<targetplatform name='joomla' version='(5|6).*'/>
|
|
||||||
<php_minimum>8.1</php_minimum>
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<!-- 4. RC -->
|
|
||||||
<update>
|
|
||||||
<name>{{EXTENSION_NAME}}</name>
|
|
||||||
<description>{{EXTENSION_NAME}} release candidate — testing only.</description>
|
|
||||||
<element>{{EXTENSION_ELEMENT}}</element>
|
|
||||||
<type>{{EXTENSION_TYPE}}</type>
|
|
||||||
<folder>{{EXTENSION_FOLDER}}</folder>
|
|
||||||
<client>{{EXTENSION_CLIENT}}</client>
|
|
||||||
<version>{{VERSION}}</version>
|
|
||||||
<creationDate>{{DATE}}</creationDate>
|
|
||||||
<infourl title='{{EXTENSION_NAME}} RC'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/release-candidate</infourl>
|
|
||||||
<downloads>
|
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/release-candidate/{{EXTENSION_ELEMENT}}-{{VERSION}}-rc.zip</downloadurl>
|
|
||||||
</downloads>
|
|
||||||
<sha256></sha256>
|
|
||||||
<tags><tag>rc</tag></tags>
|
|
||||||
<maintainer>Moko Consulting</maintainer>
|
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
|
||||||
<targetplatform name='joomla' version='(5|6).*'/>
|
|
||||||
<php_minimum>8.1</php_minimum>
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<!-- 5. STABLE -->
|
|
||||||
<update>
|
|
||||||
<name>{{EXTENSION_NAME}}</name>
|
|
||||||
<description>{{EXTENSION_NAME}} — Moko Consulting Joomla extension.</description>
|
|
||||||
<element>{{EXTENSION_ELEMENT}}</element>
|
|
||||||
<type>{{EXTENSION_TYPE}}</type>
|
|
||||||
<folder>{{EXTENSION_FOLDER}}</folder>
|
|
||||||
<client>{{EXTENSION_CLIENT}}</client>
|
|
||||||
<version>{{VERSION}}</version>
|
|
||||||
<creationDate>{{DATE}}</creationDate>
|
|
||||||
<infourl title='{{EXTENSION_NAME}}'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/stable</infourl>
|
|
||||||
<downloads>
|
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/stable/{{EXTENSION_ELEMENT}}-{{VERSION}}.zip</downloadurl>
|
|
||||||
</downloads>
|
|
||||||
<sha256></sha256>
|
|
||||||
<tags><tag>stable</tag></tags>
|
|
||||||
<maintainer>Moko Consulting</maintainer>
|
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
|
||||||
<targetplatform name='joomla' version='(5|6).*'/>
|
|
||||||
<php_minimum>8.1</php_minimum>
|
|
||||||
</update>
|
|
||||||
|
|
||||||
</updates>
|
|
||||||
@@ -63,7 +63,7 @@ class VersionBumpTest extends TestCase
|
|||||||
{
|
{
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
"{$this->tmpDir}/README.md",
|
"{$this->tmpDir}/README.md",
|
||||||
"<!-- VERSION: 09.21.07 -->\nSome content\n"
|
"<!-- VERSION: 09.25.00 -->\nSome content\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->execute();
|
$this->execute();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class VersionReadTest extends TestCase
|
|||||||
{
|
{
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
"{$this->tmpDir}/README.md",
|
"{$this->tmpDir}/README.md",
|
||||||
"# Test\n<!-- VERSION: 09.21.07 -->\n"
|
"# Test\n<!-- VERSION: 09.25.00 -->\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertSame('02.03.04', trim($this->runScript()));
|
$this->assertSame('02.03.04', trim($this->runScript()));
|
||||||
@@ -68,7 +68,7 @@ class VersionReadTest extends TestCase
|
|||||||
{
|
{
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
"{$this->tmpDir}/README.md",
|
"{$this->tmpDir}/README.md",
|
||||||
"<!-- VERSION: 09.21.07 -->\n"
|
"<!-- VERSION: 09.25.00 -->\n"
|
||||||
);
|
);
|
||||||
mkdir("{$this->tmpDir}/src", 0755, true);
|
mkdir("{$this->tmpDir}/src", 0755, true);
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* INGROUP: MokoPlatform
|
* INGROUP: MokoPlatform
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
* PATH: /validate/check_file_integrity.php
|
* PATH: /validate/check_file_integrity.php
|
||||||
* VERSION: 09.23.00
|
* VERSION: 09.25.00
|
||||||
* BRIEF: Compare deployed files on a remote server against the local repository to detect drift
|
* BRIEF: Compare deployed files on a remote server against the local repository to detect drift
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user