diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml
index 0745b52..614bebd 100644
--- a/.mokogitea/manifest.xml
+++ b/.mokogitea/manifest.xml
@@ -8,6 +8,7 @@
MokoOnyx
MokoConsulting
MokoOnyx - Joomla site template (successor to MokoCassiopeia)
+ 02.07.02
GNU General Public License v3
diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml
index 10a7e51..572facd 100644
--- a/.mokogitea/workflows/auto-bump.yml
+++ b/.mokogitea/workflows/auto-bump.yml
@@ -37,7 +37,7 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
- token: ${{ secrets.GA_TOKEN }}
+ token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
@@ -49,7 +49,7 @@ jobs:
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
- "https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
+ "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
@@ -63,10 +63,11 @@ jobs:
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true
[ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; }
- # Propagate to platform manifests
+ # Propagate to platform manifests with -dev suffix
php ${MOKO_CLI}/version_set_platform.php \
- --path . --version "$VERSION" --branch dev 2>/dev/null || true
+ --path . --version "$VERSION" --branch dev --stability dev 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
+ VERSION="${VERSION}-dev"
# Commit if anything changed
if git diff --quiet && git diff --cached --quiet; then
@@ -76,9 +77,9 @@ jobs:
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
- git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+ git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
- git commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \
+ git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
--author="gitea-actions[bot] "
git push origin dev
echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY
diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml
index 9544f96..8be9b71 100644
--- a/.mokogitea/workflows/auto-release.yml
+++ b/.mokogitea/workflows/auto-release.yml
@@ -63,12 +63,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
- token: ${{ secrets.GA_TOKEN }}
+ token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
env:
- MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
+ MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if ! command -v composer &> /dev/null; then
@@ -85,7 +85,7 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_promote.php \
--from auto --to release-candidate \
- --token "${{ secrets.GA_TOKEN }}" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${API_BASE}" \
--branch "${{ github.event.pull_request.head.ref || 'dev' }}"
@@ -95,7 +95,7 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_cascade.php \
--stability release-candidate \
- --token "${{ secrets.GA_TOKEN }}" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${API_BASE}"
- name: Summary
@@ -116,14 +116,20 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
- token: ${{ secrets.GA_TOKEN }}
+ token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
+ - name: Configure git for bot pushes
+ run: |
+ git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
+ git config --local user.name "gitea-actions[bot]"
+ git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+
- name: Setup moko-platform tools
env:
- MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
+ MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
- COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
+ COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}'
run: |
# Ensure PHP + Composer are available
if ! command -v composer &> /dev/null; then
@@ -155,6 +161,8 @@ jobs:
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
+ # Strip any pre-release suffix merged from dev (e.g. 01.02.20-dev → 01.02.20)
+ VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
MAJOR=$(echo "$VERSION" | cut -d. -f1)
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
@@ -167,7 +175,7 @@ jobs:
if: steps.version.outputs.skip != 'true'
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- RC_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ RC_JSON=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/release-candidate" 2>/dev/null || echo "{}")
RC_ID=$(echo "$RC_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
@@ -261,6 +269,18 @@ jobs:
# Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum
+ - name: "Step 4b: Promote and prune CHANGELOG"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.check.outputs.already_released != 'true'
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ MOKO_API="/tmp/moko-platform-api/cli"
+ if [ -f "CHANGELOG.md" ]; then
+ php ${MOKO_API}/changelog_promote.php --path . --version "$VERSION" 2>&1 || true
+ php ${MOKO_API}/changelog_prune.php --path . --keep 5 2>&1 || true
+ fi
+
- name: Commit release changes
if: >-
steps.version.outputs.skip != 'true' &&
@@ -271,10 +291,6 @@ jobs:
exit 0
fi
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
- # Set push URL with token for branch-protected repos
- git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git commit -m "chore(release): build ${VERSION} [skip ci]" \
--author="gitea-actions[bot] "
@@ -306,7 +322,7 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_promote.php \
--from release-candidate --to stable \
- --token "${{ secrets.GA_TOKEN }}" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${API_BASE}" \
--path . --branch main
echo "Promoted RC → stable (${VERSION})" >> $GITHUB_STEP_SUMMARY
@@ -322,7 +338,7 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_create.php \
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
- --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch main
echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
@@ -338,7 +354,7 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_package.php \
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
- --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# -- STEP 5: Write update stream (after build so SHA-256 is available) -----
@@ -349,9 +365,9 @@ jobs:
SHA256="${{ steps.package.outputs.sha256_zip }}"
# Fetch latest updates.xml from main so preserve logic has all channels
- GA_TOKEN="${{ secrets.GA_TOKEN }}"
+ GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
- curl -sf -H "Authorization: token ${GA_TOKEN}" \
+ curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/contents/updates.xml?ref=main" 2>/dev/null | \
python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
> updates.xml 2>/dev/null || true
@@ -366,9 +382,6 @@ jobs:
# Commit updates.xml if changed
if ! git diff --quiet updates.xml 2>/dev/null; then
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
- git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add updates.xml
git commit -m "chore: update stable channel ${VERSION} [skip ci]" \
--author="gitea-actions[bot] "
@@ -384,7 +397,7 @@ jobs:
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
php /tmp/moko-platform-api/cli/release_body_update.php \
--path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
- --token "${{ secrets.GA_TOKEN }}" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
2>&1 || true
echo "Release body updated" >> $GITHUB_STEP_SUMMARY
@@ -393,7 +406,7 @@ jobs:
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
- secrets.GH_TOKEN != ''
+ secrets.GITHUB_TOKEN != ''
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
@@ -402,8 +415,8 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
- --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
- --gh-token "${{ secrets.GH_TOKEN }}" --gh-repo "$GH_REPO" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
+ --gh-token "${{ secrets.GITHUB_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
@@ -411,14 +424,14 @@ jobs:
- name: "Step 10: Push main to GitHub mirror"
if: >-
steps.version.outputs.skip != 'true' &&
- secrets.GH_TOKEN != ''
+ secrets.GITHUB_TOKEN != ''
continue-on-error: true
run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
- git remote add github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
- git remote set-url github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
+ git remote add github "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
+ git remote set-url github "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
git fetch origin main --depth=1
git push github origin/main:refs/heads/main --force 2>/dev/null \
&& echo "main branch pushed to GitHub mirror" \
@@ -434,7 +447,7 @@ jobs:
php /tmp/moko-platform-api/cli/release_cascade.php \
--stability stable \
--version "${VERSION}" \
- --token "${{ secrets.GA_TOKEN }}" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${API_BASE}" 2>/dev/null || true
- name: "Step 11: Delete and recreate dev branch from main"
@@ -442,7 +455,7 @@ jobs:
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- TOKEN="${{ secrets.GA_TOKEN }}"
+ TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
@@ -454,25 +467,25 @@ jobs:
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
- echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
-
- - name: "Step 12: Create version branch from main"
- if: steps.version.outputs.skip != 'true'
- continue-on-error: true
- run: |
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- TOKEN="${{ secrets.GA_TOKEN }}"
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- BRANCH_NAME="version/${VERSION}"
- MAIN_SHA=$(git rev-parse HEAD)
-
- # Delete old version branch if it exists (same version re-release)
- curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
-
- # Create version/XX.YY.ZZ from main
- curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
-
- echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
+ echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
+
+ - name: "Step 12: Create version branch from main"
+ if: steps.version.outputs.skip != 'true'
+ continue-on-error: true
+ run: |
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ BRANCH_NAME="version/${VERSION}"
+ MAIN_SHA=$(git rev-parse HEAD)
+
+ # Delete old version branch if it exists (same version re-release)
+ curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
+
+ # Create version/XX.YY.ZZ from main
+ curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
+
+ echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
@@ -483,7 +496,7 @@ jobs:
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/version_reset_dev.php \
- --token "${{ secrets.GA_TOKEN }}" --api-base "${API_BASE}" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
diff --git a/.mokogitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml
index 7f26935..f7f0b3c 100644
--- a/.mokogitea/workflows/cascade-dev.yml
+++ b/.mokogitea/workflows/cascade-dev.yml
@@ -8,21 +8,21 @@
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/cascade-dev.yml.template
# VERSION: 02.00.00
-# BRIEF: Forward-merge main -> all open branches after every push to main
+# BRIEF: Forward-merge main → all open branches after every push to main
#
# +========================================================================+
-# | CASCADE MAIN -> ALL BRANCHES |
+# | CASCADE MAIN → ALL BRANCHES |
# +========================================================================+
# | |
# | Triggers on every push to main (PR merges, bot commits, etc.) |
# | |
# | 1. List all branches matching: dev, rc/*, beta/*, alpha/* |
-# | 2. For each: create PR (main -> branch), auto-merge if clean |
+# | 2. For each: create PR (main → branch), auto-merge if clean |
# | 3. On conflict: leave PR open for manual resolution |
# | |
# +========================================================================+
-name: "Universal: Cascade Main -> Dev"
+name: "Universal: Cascade Main → Dev"
on:
push:
@@ -42,7 +42,7 @@ permissions:
jobs:
cascade:
- name: Cascade main -> branches
+ name: Cascade main → branches
runs-on: ubuntu-latest
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
@@ -52,7 +52,7 @@ jobs:
- name: Discover target branches
id: branches
env:
- GA_TOKEN: ${{ secrets.GA_TOKEN }}
+ GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
@@ -61,7 +61,7 @@ jobs:
ALL_BRANCHES=""
while true; do
BATCH=$(curl -sS \
- -H "Authorization: token ${GA_TOKEN}" \
+ -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/branches?page=${PAGE}&limit=50" \
| jq -r '.[].name // empty')
[ -z "$BATCH" ] && break
@@ -83,17 +83,17 @@ jobs:
if [ -z "$TARGETS" ]; then
echo "targets=" >> "$GITHUB_OUTPUT"
- echo " No cascade target branches found"
+ echo "ℹ️ No cascade target branches found"
else
echo "targets=$TARGETS" >> "$GITHUB_OUTPUT"
COUNT=$(echo "$TARGETS" | wc -w)
- echo " Found ${COUNT} target branch(es): ${TARGETS}"
+ echo "📋 Found ${COUNT} target branch(es): ${TARGETS}"
fi
- name: Cascade to all target branches
if: steps.branches.outputs.targets != ''
env:
- GA_TOKEN: ${{ secrets.GA_TOKEN }}
+ GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
SHORT_SHA="${GITHUB_SHA:0:7}"
@@ -106,27 +106,27 @@ jobs:
for BRANCH in $TARGETS; do
echo ""
- echo " main -> ${BRANCH} "
+ echo "═══ main → ${BRANCH} ═══"
# Check if branch is already up to date
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
RESPONSE=$(curl -sS \
- -H "Authorization: token ${GA_TOKEN}" \
+ -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/compare/${ENCODED_BRANCH}...main")
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
if [ "$AHEAD" -eq 0 ]; then
- echo " Already up to date"
+ echo " ✅ Already up to date"
SKIPPED=$((SKIPPED + 1))
continue
fi
- echo " main is ${AHEAD} commit(s) ahead"
+ echo " ℹ️ main is ${AHEAD} commit(s) ahead"
# Check for existing cascade PR
EXISTING=$(curl -sS \
- -H "Authorization: token ${GA_TOKEN}" \
+ -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
@@ -134,16 +134,16 @@ jobs:
if [ "$EXISTING_COUNT" -gt 0 ]; then
PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number')
- echo " Reusing existing PR #${PR_NUMBER}"
+ echo " ℹ️ Reusing existing PR #${PR_NUMBER}"
else
# Create cascade PR
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
- -H "Authorization: token ${GA_TOKEN}" \
+ -H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
- \"title\": \"chore: cascade main -> ${BRANCH} (${SHORT_SHA}) [skip ci]\",
- \"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main -> Dev**.\",
+ \"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
+ \"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main → Dev**.\",
\"head\": \"main\",
\"base\": \"${BRANCH}\"
}" \
@@ -155,34 +155,34 @@ jobs:
if [ "$HTTP_CODE" != "201" ] || [ -z "$PR_NUMBER" ]; then
MSG=$(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)
- echo " Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}"
+ echo " ❌ Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}"
FAILED=$((FAILED + 1))
continue
fi
- echo " Created PR #${PR_NUMBER}"
+ echo " ✅ Created PR #${PR_NUMBER}"
fi
# Try auto-merge
PR_DATA=$(curl -sS \
- -H "Authorization: token ${GA_TOKEN}" \
+ -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/pulls/${PR_NUMBER}")
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
if [ "$MERGEABLE" != "true" ]; then
- echo " Conflicts -- PR #${PR_NUMBER} left open"
+ echo " ⚠️ Conflicts — PR #${PR_NUMBER} left open"
CONFLICTS=$((CONFLICTS + 1))
continue
fi
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
- -H "Authorization: token ${GA_TOKEN}" \
+ -H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"Do\": \"merge\",
- \"merge_message_field\": \"chore: cascade main -> ${BRANCH} [skip ci]\",
+ \"merge_message_field\": \"chore: cascade main → ${BRANCH} [skip ci]\",
\"delete_branch_after_merge\": false
}" \
"${API}/pulls/${PR_NUMBER}/merge")
@@ -190,23 +190,23 @@ jobs:
MERGE_HTTP=$(echo "$MERGE_RESPONSE" | tail -1)
if [ "$MERGE_HTTP" = "200" ] || [ "$MERGE_HTTP" = "204" ]; then
- echo " Merged -- ${BRANCH} is in sync"
+ echo " ✅ Merged — ${BRANCH} is in sync"
SUCCESS=$((SUCCESS + 1))
else
MERGE_BODY=$(echo "$MERGE_RESPONSE" | sed '$d')
- echo " Merge failed (HTTP ${MERGE_HTTP}) -- PR #${PR_NUMBER} left open"
+ echo " ⚠️ Merge failed (HTTP ${MERGE_HTTP}) — PR #${PR_NUMBER} left open"
CONFLICTS=$((CONFLICTS + 1))
fi
done
# Summary
echo ""
- echo ""
- echo " Merged: ${SUCCESS}"
- echo " Conflicts: ${CONFLICTS}"
- echo " Up to date: ${SKIPPED}"
- echo " Failed: ${FAILED}"
- echo ""
+ echo "════════════════════════════════════════"
+ echo " ✅ Merged: ${SUCCESS}"
+ echo " ⚠️ Conflicts: ${CONFLICTS}"
+ echo " ⏭️ Up to date: ${SKIPPED}"
+ echo " ❌ Failed: ${FAILED}"
+ echo "════════════════════════════════════════"
if [ "$FAILED" -gt 0 ]; then
exit 1
diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml
index c8012c7..29ca4d4 100644
--- a/.mokogitea/workflows/cleanup.yml
+++ b/.mokogitea/workflows/cleanup.yml
@@ -8,7 +8,7 @@
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/workflows/cleanup.yml
# VERSION: 01.00.00
-# BRIEF: Scheduled cleanup -- delete merged branches and old workflow runs
+# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
name: "Universal: Repository Cleanup"
@@ -33,17 +33,17 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- token: ${{ secrets.GA_TOKEN }}
+ token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Delete merged branches
env:
- GA_TOKEN: ${{ secrets.GA_TOKEN }}
+ GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API
- BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
+ BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0
@@ -56,7 +56,7 @@ jobs:
# Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}"
- curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
+ curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1))
fi
@@ -66,20 +66,20 @@ jobs:
- name: Clean old workflow runs
env:
- GA_TOKEN: ${{ secrets.GA_TOKEN }}
+ GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs
- RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
+ RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0
for RUN_ID in $RUNS; do
- curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
+ curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1))
done
diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml
index 6a672b8..e0fdd1d 100644
--- a/.mokogitea/workflows/gitleaks.yml
+++ b/.mokogitea/workflows/gitleaks.yml
@@ -8,7 +8,7 @@
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00
-# BRIEF: Secret scanning -- detect leaked credentials, API keys, and tokens
+# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
#
# +========================================================================+
# | SECRET SCANNING |
@@ -89,7 +89,7 @@ jobs:
run: |
REPO="${{ github.event.repository.name }}"
curl -sS \
- -H "Title: ${REPO} -- secrets detected in code" \
+ -H "Title: ${REPO} — secrets detected in code" \
-H "Tags: rotating_light,key" \
-H "Priority: urgent" \
-d "Gitleaks found potential secrets. Review and rotate credentials immediately." \
diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml
new file mode 100644
index 0000000..f084fe1
--- /dev/null
+++ b/.mokogitea/workflows/issue-branch.yml
@@ -0,0 +1,73 @@
+# Copyright (C) 2026 Moko Consulting
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# FILE INFORMATION
+# DEFGROUP: Gitea.Workflow
+# INGROUP: moko-platform.Automation
+# VERSION: 01.00.00
+# BRIEF: Auto-create feature branch when an issue is opened
+
+name: "Universal: Issue Branch"
+
+on:
+ issues:
+ types: [opened]
+
+permissions:
+ contents: write
+ issues: write
+
+env:
+ GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
+
+jobs:
+ create-branch:
+ name: Create feature branch
+ runs-on: ubuntu-latest
+ steps:
+ - name: Create branch and comment
+ run: |
+ TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
+ API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
+ ISSUE_NUM="${{ github.event.issue.number }}"
+ ISSUE_TITLE="${{ github.event.issue.title }}"
+
+ # Build slug from title: lowercase, replace non-alnum with dash, trim
+ SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
+ BRANCH="feature/${ISSUE_NUM}-${SLUG}"
+
+ # Check dev branch exists
+ DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \
+ -H "Authorization: token ${TOKEN}" \
+ "${API}/branches/dev" 2>/dev/null || echo "000")
+
+ if [ "${DEV_EXISTS}" != "200" ]; then
+ echo "No dev branch -- skipping"
+ exit 0
+ fi
+
+ # Create branch from dev
+ HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
+ -H "Authorization: token ${TOKEN}" \
+ -H "Content-Type: application/json" \
+ "${API}/branches" \
+ -d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000")
+
+ if [ "${HTTP}" = "201" ]; then
+ echo "Created branch: ${BRANCH}"
+
+ # Comment on issue with branch link
+ REPO_URL="${GITEA_URL}/${{ github.repository }}"
+ BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
+
+ curl -sf -X POST \
+ -H "Authorization: token ${TOKEN}" \
+ -H "Content-Type: application/json" \
+ "${API}/issues/${ISSUE_NUM}/comments" \
+ -d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
+
+ echo "Commented on issue #${ISSUE_NUM}"
+ else
+ echo "Failed to create branch (HTTP ${HTTP}) -- may already exist"
+ fi
diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml
index 35bfca2..df06523 100644
--- a/.mokogitea/workflows/pr-check.yml
+++ b/.mokogitea/workflows/pr-check.yml
@@ -8,7 +8,7 @@
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 05.00.00
-# BRIEF: PR gate -- branch policy + code validation before merge
+# BRIEF: PR gate — branch policy + code validation before merge
name: "Universal: PR Check"
@@ -24,7 +24,7 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
- # Branch Policy
+ # ── Branch Policy ──────────────────────────────────────────────────────
branch-policy:
name: Branch Policy
runs-on: ubuntu-latest
@@ -34,7 +34,7 @@ jobs:
HEAD="${{ github.head_ref }}"
BASE="${{ github.base_ref }}"
- echo "PR: ${HEAD} -> ${BASE}"
+ echo "PR: ${HEAD} → ${BASE}"
ALLOWED=true
REASON=""
@@ -85,18 +85,18 @@ jobs:
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
- echo "- \`feature/*\` -> \`dev\`" >> $GITHUB_STEP_SUMMARY
- echo "- \`fix/*\` -> \`dev\`" >> $GITHUB_STEP_SUMMARY
- echo "- \`hotfix/*\` -> \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
- echo "- \`dev\` -> \`main\`" >> $GITHUB_STEP_SUMMARY
- echo "- \`rc/*\` -> \`main\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
+ echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
exit 1
fi
- echo "Branch policy: OK (${HEAD} -> ${BASE})"
+ echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
- # Code Validation
+ # ── Code Validation ────────────────────────────────────────────────────
validate:
name: Validate PR
runs-on: ubuntu-latest
@@ -162,7 +162,7 @@ jobs:
echo "Dolibarr module: ${MOD_FILE}"
;;
*)
- echo "Generic platform -- no manifest validation"
+ echo "Generic platform — no manifest validation"
;;
esac
@@ -204,11 +204,11 @@ jobs:
steps:
- name: Trigger RC pre-release
env:
- GA_TOKEN: ${{ secrets.GA_TOKEN }}
+ GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
- curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
+ curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml
index f0b1d88..7920f53 100644
--- a/.mokogitea/workflows/pre-release.yml
+++ b/.mokogitea/workflows/pre-release.yml
@@ -50,11 +50,11 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- token: ${{ secrets.GA_TOKEN }}
+ token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Setup moko-platform tools
env:
- MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
+ MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if ! command -v composer &> /dev/null; then
@@ -87,16 +87,24 @@ jobs:
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
[ -z "$VERSION" ] && VERSION="00.00.01"
+ # Strip any existing suffix from version before applying stability
+ VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
+
php ${MOKO_CLI}/version_set_platform.php \
- --path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
+ --path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
# Verify version consistency across all files
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
+ # Update VERSION variable with suffix
+ if [ -n "$SUFFIX" ]; then
+ VERSION="${VERSION}${SUFFIX}"
+ fi
+
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
- git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+ git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
@@ -112,7 +120,7 @@ jobs:
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
- [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
+ [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
@@ -131,7 +139,7 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
- --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch dev --prerelease
- name: Build package and upload
@@ -142,7 +150,7 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
- --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
- name: Update updates.xml
@@ -199,7 +207,7 @@ jobs:
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- TOKEN="${{ secrets.GA_TOKEN }}"
+ TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml
index e272757..be52e37 100644
--- a/.mokogitea/workflows/repo-health.yml
+++ b/.mokogitea/workflows/repo-health.yml
@@ -49,22 +49,18 @@ env:
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
# Repo health policy
- REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,.gitea/workflows/
- REPO_OPTIONAL_FILES: SECURITY.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
+ REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/
+ REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
REPO_DISALLOWED_DIRS:
REPO_DISALLOWED_FILES: TODO.md,todo.md
- # Wiki-preferred documentation -- wiki is full credit, repo file is advisory
- # Format: filename:WikiPageName (Gitea wiki page slug)
- WIKI_PREFERRED_DOCS: CONTRIBUTING.md:Contributing,CODE_OF_CONDUCT.md:Code-of-Conduct,GOVERNANCE.md:Governance
-
# Extended checks toggles
EXTENDED_CHECKS: "true"
# File / directory variables
DOCS_INDEX: docs/docs-index.md
SCRIPT_DIR: scripts
- WORKFLOWS_DIR: .gitea/workflows
+ WORKFLOWS_DIR: .mokogitea/workflows
SHELLCHECK_PATTERN: '*.sh'
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -85,7 +81,7 @@ jobs:
- name: Check actor permission (admin only)
id: perm
env:
- TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
+ TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
REPO: ${{ github.repository }}
ACTOR: ${{ github.actor }}
run: |
@@ -94,7 +90,7 @@ jobs:
PERMISSION=unknown
METHOD=""
- # Hardcoded authorized users -- always allowed
+ # Hardcoded authorized users — always allowed
case "$ACTOR" in
jmiller|gitea-actions[bot])
ALLOWED=true
@@ -369,10 +365,6 @@ jobs:
- name: Repository health checks
env:
PROFILE_RAW: ${{ github.event.inputs.profile }}
- GA_TOKEN: ${{ secrets.GA_TOKEN }}
- GITHUB_TOKEN: ${{ github.token }}
- GITHUB_REPOSITORY: ${{ github.repository }}
- GITHUB_SERVER_URL: ${{ github.server_url }}
run: |
set -euo pipefail
@@ -530,63 +522,6 @@ jobs:
} >> "${GITHUB_STEP_SUMMARY}"
fi
- # -- Wiki-preferred documentation checks --
- # Docs that belong in the wiki; repo-file fallback is accepted but advisory
- wiki_findings=()
- wiki_ok=()
- wiki_file_fallback=()
- wiki_missing=()
-
- API_BASE="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}"
- WIKI_TOKEN="${WIKI_TOKEN:-${GA_TOKEN:-${GITHUB_TOKEN}}}"
- REPO_FULL="${GITHUB_REPOSITORY}"
-
- IFS=',' read -r -a wiki_docs <<< "${WIKI_PREFERRED_DOCS}"
- for entry in "${wiki_docs[@]}"; do
- file="${entry%%:*}"
- page="${entry##*:}"
-
- # Check wiki via Gitea API
- wiki_exists=false
- http_code=$(curl -sf -o /dev/null -w '%{http_code}' \
- -H "Authorization: token ${WIKI_TOKEN}" \
- "${API_BASE}/api/v1/repos/${REPO_FULL}/wiki/page/${page}" 2>/dev/null || echo "000")
- [ "${http_code}" = "200" ] && wiki_exists=true
-
- # Check repo file
- file_exists=false
- [ -f "${file}" ] && file_exists=true
-
- if [ "${wiki_exists}" = true ]; then
- wiki_ok+=("${file} -> wiki/${page}")
- elif [ "${file_exists}" = true ]; then
- wiki_file_fallback+=("${file}")
- content_warnings+=("${file} found in repo root (preferred location: wiki/${page})")
- else
- wiki_missing+=("${file} (wiki/${page})")
- missing_optional+=("${file} (not in wiki or repo)")
- fi
- done
-
- {
- printf '%s\n' '### Wiki-preferred documentation'
- printf '%s\n' '| Document | Location | Status |'
- printf '%s\n' '|---|---|---|'
- for item in "${wiki_ok[@]:-}"; do
- [ -z "${item}" ] && continue
- printf '%s\n' "| ${item%%->*}| Wiki | OK |"
- done
- for item in "${wiki_file_fallback[@]:-}"; do
- [ -z "${item}" ] && continue
- printf '%s\n' "| ${item} | Repo file | Advisory -- migrate to wiki |"
- done
- for item in "${wiki_missing[@]:-}"; do
- [ -z "${item}" ] && continue
- printf '%s\n' "| ${item%%(*}| Missing | Warning |"
- done
- printf '\n'
- } >> "${GITHUB_STEP_SUMMARY}"
-
# -- Joomla-specific checks --
joomla_findings=()
@@ -772,13 +707,6 @@ jobs:
printf '%s\n' '| Release variables | OK | Repository variables validation |'
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
- if [ "${#wiki_file_fallback[@]}" -gt 0 ]; then
- printf '%s\n' "| Wiki-preferred docs | Advisory | ${#wiki_file_fallback[@]} doc(s) in repo, prefer wiki |"
- elif [ "${#wiki_missing[@]}" -gt 0 ]; then
- printf '%s\n' "| Wiki-preferred docs | Warning | ${#wiki_missing[@]} doc(s) missing from wiki and repo |"
- else
- printf '%s\n' '| Wiki-preferred docs | OK | All docs found in wiki |'
- fi
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
if [ "${extended_enabled}" = 'true' ]; then
if [ "${#extended_findings[@]}" -gt 0 ]; then
diff --git a/.mokogitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml
index 8660a43..cd2eff0 100644
--- a/.mokogitea/workflows/update-server.yml
+++ b/.mokogitea/workflows/update-server.yml
@@ -4,18 +4,16 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Universal
+# INGROUP: moko-platform.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/update-server.yml
-# VERSION: 04.07.00
-# BRIEF: Update server XML feed with stable/rc/beta/alpha/dev entries (universal)
+# VERSION: 05.00.00
+# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
#
-# Writes updates.xml with multiple entries:
-# - stable on push to main (from auto-release)
-# - rc on push to rc/**
-# - development on push to dev or dev/**
+# Thin wrapper around moko-platform CLI tools.
+# Builds packages, updates updates.xml, and optionally deploys via SFTP.
#
-# Joomla filters by user's "Minimum Stability" setting.
+# Joomla filters update entries by the user's "Minimum Stability" setting.
name: "Update Server"
@@ -66,7 +64,7 @@ permissions:
jobs:
update-xml:
- name: Update updates.xml
+ name: Update Server
runs-on: release
if: >-
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
@@ -75,14 +73,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
- token: ${{ secrets.GA_TOKEN }}
+ token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Setup moko-platform tools
env:
- MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
+ MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
- COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
+ COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
@@ -97,28 +95,28 @@ jobs:
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
+ echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
- - name: Generate updates.xml entry
- id: update
+ - name: Detect platform
+ id: platform
+ run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
+
+ - name: Resolve stability and bump version
+ id: meta
run: |
BRANCH="${{ github.ref_name }}"
- REPO="${{ github.repository }}"
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
- # Auto-bump patch on all branches (dev, alpha, beta, rc)
+ # Configure git for bot pushes
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
- BUMPED=$(php /tmp/moko-platform/cli/version_bump.php --path . 2>/dev/null || true)
- if [ -n "$BUMPED" ]; then
- VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
- git add -A
- git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
- --author="gitea-actions[bot] " 2>/dev/null || true
- git push 2>/dev/null || true
- fi
+ git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- # Determine stability from branch or input
+ # Auto-bump patch version
+ php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
+
+ VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
+
+ # Determine stability from branch or manual input
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
STABILITY="${{ inputs.stability }}"
elif [[ "$BRANCH" == rc/* ]]; then
@@ -127,263 +125,96 @@ jobs:
STABILITY="beta"
elif [[ "$BRANCH" == alpha/* ]]; then
STABILITY="alpha"
- elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then
+ else
STABILITY="development"
- else
- STABILITY="stable"
fi
+ # Version suffix per stability stream
+ case "$STABILITY" in
+ development) SUFFIX="-dev"; TAG="development" ;;
+ alpha) SUFFIX="-alpha"; TAG="alpha" ;;
+ beta) SUFFIX="-beta"; TAG="beta" ;;
+ rc) SUFFIX="-rc"; TAG="release-candidate" ;;
+ *) SUFFIX=""; TAG="stable" ;;
+ esac
+
+ # Propagate version with stability suffix to all manifest files
+ php ${MOKO_CLI}/version_set_platform.php \
+ --path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
+ php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
+
+ # Re-read version (now includes suffix from version_set_platform)
+ if [ -n "$SUFFIX" ]; then
+ VERSION="${VERSION}${SUFFIX}"
+ fi
+
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
+ echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
+ echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
+ echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
- # Parse manifest (portable — no grep -P)
- MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '/dev/null | head -1)
- if [ -z "$MANIFEST" ]; then
- echo "No Joomla manifest found — skipping"
- exit 0
- fi
-
- # Extract fields using sed (works on all runners)
- EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
- EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
- EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- EXT_VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
- TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1)
- PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
-
- # Fallbacks
- [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
- [ -z "$EXT_TYPE" ] && EXT_TYPE="component"
-
- # Derive element if not in manifest: try XML filename, then repo name
- if [ -z "$EXT_ELEMENT" ]; then
- EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
- case "$EXT_ELEMENT" in
- templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
- esac
- fi
-
- # Use manifest version if README version is empty
- [ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
-
- [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/")
-
- # Joomla requires on ALL extension types for update matching
- if [ -n "$EXT_CLIENT" ]; then
- CLIENT_TAG="${EXT_CLIENT}"
- else
- CLIENT_TAG="site"
- fi
-
- FOLDER_TAG=""
- [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="${EXT_FOLDER}"
-
- PHP_TAG=""
- [ -n "$PHP_MINIMUM" ] && PHP_TAG="${PHP_MINIMUM}"
-
- # Version suffix for non-stable
- DISPLAY_VERSION="$VERSION"
- case "$STABILITY" in
- development) DISPLAY_VERSION="${VERSION}-dev" ;;
- alpha) DISPLAY_VERSION="${VERSION}-alpha" ;;
- beta) DISPLAY_VERSION="${VERSION}-beta" ;;
- rc) DISPLAY_VERSION="${VERSION}-rc" ;;
- esac
-
- MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
-
- # Each stability level has its own release tag
- case "$STABILITY" in
- development) RELEASE_TAG="development" ;;
- alpha) RELEASE_TAG="alpha" ;;
- beta) RELEASE_TAG="beta" ;;
- rc) RELEASE_TAG="release-candidate" ;;
- *) RELEASE_TAG="v${MAJOR}" ;;
- esac
-
- PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
- DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
- INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}"
-
- # -- Build install packages (ZIP + tar.gz) --------------------
- SOURCE_DIR="src"
- [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
- if [ -d "$SOURCE_DIR" ]; then
- EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
- TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
-
- cd "$SOURCE_DIR"
- zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES
- cd ..
- tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
- --exclude='.ftpignore' --exclude='sftp-config*' \
- --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
-
- SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
-
- # Ensure release exists on Gitea
- RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
- RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
-
- if [ -z "$RELEASE_ID" ]; then
- # Create release
- RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- -H "Content-Type: application/json" \
- "${API_BASE}/releases" \
- -d "$(python3 -c "import json; print(json.dumps({
- 'tag_name': '${RELEASE_TAG}',
- 'name': '${RELEASE_TAG} (${DISPLAY_VERSION})',
- 'body': '${STABILITY} release',
- 'prerelease': True,
- 'target_commitish': 'main'
- }))")" 2>/dev/null || true)
- RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
- fi
-
- if [ -n "$RELEASE_ID" ]; then
- # Delete existing assets with same name before uploading
- ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
- for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do
- ASSET_ID=$(echo "$ASSETS" | python3 -c "
- import sys,json
- assets = json.load(sys.stdin)
- for a in assets:
- if a['name'] == '${ASSET_FILE}':
- print(a['id']); break
- " 2>/dev/null || true)
- if [ -n "$ASSET_ID" ]; then
- curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
- fi
- done
-
- # Upload both formats
- curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- -H "Content-Type: application/octet-stream" \
- --data-binary @"/tmp/${PACKAGE_NAME}" \
- "${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true
-
- curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- -H "Content-Type: application/octet-stream" \
- --data-binary @"/tmp/${TAR_NAME}" \
- "${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
- fi
-
- echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
- else
- SHA256=""
- fi
-
- # -- Build the new entry (canonical format matching release.yml) --
- NEW_ENTRY=""
- NEW_ENTRY="${NEW_ENTRY} \n"
- NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n"
- NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME} ${STABILITY} build.\n"
- NEW_ENTRY="${NEW_ENTRY} ${EXT_ELEMENT}\n"
- NEW_ENTRY="${NEW_ENTRY} ${EXT_TYPE}\n"
- [ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n"
- [ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n"
- NEW_ENTRY="${NEW_ENTRY} ${VERSION}\n"
- NEW_ENTRY="${NEW_ENTRY} $(date +%Y-%m-%d)\n"
- NEW_ENTRY="${NEW_ENTRY} https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}\n"
- NEW_ENTRY="${NEW_ENTRY} \n"
- NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n"
- NEW_ENTRY="${NEW_ENTRY} \n"
- [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} ${SHA256}\n"
- NEW_ENTRY="${NEW_ENTRY} ${STABILITY}\n"
- NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n"
- NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n"
- NEW_ENTRY="${NEW_ENTRY} \n"
- [ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_MINIMUM}\n"
- NEW_ENTRY="${NEW_ENTRY} "
-
- # -- Write new entry to temp file --------------------------------
- printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
-
- # -- Merge into updates.xml ----------------------------------------
- # Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev
- CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development"
- TARGETS=""
- for entry in $CASCADE_MAP; do
- key="${entry%%:*}"
- vals="${entry#*:}"
- if [ "$key" = "${STABILITY}" ]; then
- TARGETS="$vals"
- break
- fi
- done
- [ -z "$TARGETS" ] && TARGETS="${STABILITY}"
-
- echo "Cascade: ${STABILITY} → ${TARGETS}"
-
- # Create updates.xml if missing
- if [ ! -f "updates.xml" ]; then
- printf '%s\n' "" > updates.xml
- printf '%s\n' "" >> updates.xml
- printf '%s\n' "" >> updates.xml
- printf '%s\n' "" >> updates.xml
- fi
-
- # Update existing blocks or create missing ones
- export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)"
- python3 << 'PYEOF'
- import re, os
-
- targets = os.environ["PY_TARGETS"].split(",")
- version = os.environ["PY_VERSION"]
- date = os.environ["PY_DATE"]
-
- with open("updates.xml") as f:
- content = f.read()
- with open("/tmp/new_entry.xml") as f:
- new_entry_template = f.read()
-
- for tag in targets:
- tag = tag.strip()
- # Build entry with this tag's name
- new_entry = re.sub(r"[^<]*", f"{tag}", new_entry_template)
-
- # Try to find existing block (handles both single-line and multi-line )
- block_pattern = r"((?:(?!).)*?" + re.escape(tag) + r".*?)"
- match = re.search(block_pattern, content, re.DOTALL)
-
- if match:
- # Update in place — replace entire block
- content = content.replace(match.group(1), new_entry.strip())
- print(f" UPDATED: {tag} → {version}")
- else:
- # Create — insert before
- content = content.replace("", "\n" + new_entry.strip() + "\n\n")
- print(f" CREATED: {tag} → {version}")
-
- # Clean up excessive blank lines
- content = re.sub(r"\n{3,}", "\n\n", content)
-
- with open("updates.xml", "w") as f:
- f.write(content)
- PYEOF
-
- # Commit
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
- git add updates.xml
+ # Commit version bump if changed
+ git add -A
git diff --cached --quiet || {
- git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \
+ git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
--author="gitea-actions[bot] "
git push
}
- # -- Sync updates.xml to main (for non-main branches) ----------------------
+ - name: Create release and upload package
+ id: package
+ run: |
+ VERSION="${{ steps.meta.outputs.version }}"
+ TAG="${{ steps.meta.outputs.tag }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+
+ # Create or update Gitea release
+ php ${MOKO_CLI}/release_create.php \
+ --path . --version "$VERSION" --tag "$TAG" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
+ --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
+
+ # Build package and upload
+ php ${MOKO_CLI}/release_package.php \
+ --path . --version "$VERSION" --tag "$TAG" \
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
+ --repo "${GITEA_REPO}" --output /tmp || true
+
+ - name: Update updates.xml
+ if: steps.platform.outputs.platform == 'joomla'
+ run: |
+ VERSION="${{ steps.meta.outputs.version }}"
+ STABILITY="${{ steps.meta.outputs.stability }}"
+ SHA256="${{ steps.package.outputs.sha256_zip }}"
+
+ if [ ! -f "updates.xml" ]; then
+ echo "No updates.xml — skipping"
+ exit 0
+ fi
+
+ SHA_FLAG=""
+ [ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
+
+ php ${MOKO_CLI}/updates_xml_build.php \
+ --path . --version "${VERSION}" --stability "${STABILITY}" \
+ --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
+ ${SHA_FLAG}
+
+ # Commit and push updates.xml
+ git add updates.xml
+ git diff --cached --quiet || {
+ git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
+ git push
+ }
+
- name: Sync updates.xml to main
- if: github.ref_name != 'main'
+ if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- GA_TOKEN="${{ secrets.GA_TOKEN }}"
+ GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
- FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
+ FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
@@ -394,27 +225,22 @@ jobs:
payload = json.dumps({
'content': content,
'sha': '${FILE_SHA}',
- 'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
+ 'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
'branch': 'main'
}).encode()
req = urllib.request.Request(
'${API_BASE}/contents/updates.xml',
data=payload, method='PUT',
headers={
- 'Authorization': 'token ${GA_TOKEN}',
+ 'Authorization': 'token ${GITEA_TOKEN}',
'Content-Type': 'application/json'
})
try:
urllib.request.urlopen(req)
print('updates.xml synced to main')
except Exception as e:
- print(f'ERROR: failed to sync updates.xml to main: {e}', file=sys.stderr)
- sys.exit(1)
- " \
- && echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
- || echo "::error::failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
- else
- echo "::error::could not get updates.xml SHA from main — file may not exist on main yet" >> $GITHUB_STEP_SUMMARY
+ print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
+ "
fi
- name: SFTP deploy to dev server
@@ -428,12 +254,11 @@ jobs:
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
run: |
- # -- Permission check: admin or maintain role required --------
+ # Permission check: admin or maintain role required
ACTOR="${{ github.actor }}"
- REPO="${{ github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
case "$PERMISSION" in
@@ -463,198 +288,24 @@ jobs:
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
fi
- PLATFORM=$(php /tmp/moko-platform/cli/platform_detect.php --path . 2>/dev/null || true)
- if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform/deploy/deploy-joomla.php" ]; then
- php /tmp/moko-platform/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
- elif [ -f "/tmp/moko-platform/deploy/deploy-sftp.php" ]; then
- php /tmp/moko-platform/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
+ PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
+ if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
+ php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
+ elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
+ php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
- - name: Validate updates.xml integrity
- run: |
- ERRORS=0
-
- if [ ! -f "updates.xml" ]; then
- echo "::error::updates.xml not found"
- exit 1
- fi
-
- # Well-formed XML
- if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('updates.xml')" 2>/dev/null; then
- echo "::error::updates.xml is not valid XML"
- ERRORS=$((ERRORS+1))
- fi
-
- python3 << 'PYEOF'
- import xml.etree.ElementTree as ET, sys, re, os
-
- tree = ET.parse("updates.xml")
- root = tree.getroot()
- updates = root.findall("update")
- errors = 0
- warnings = 0
- seen_tags = set()
-
- # All 5 channels MUST be present
- REQUIRED_CHANNELS = {"stable", "rc", "beta", "alpha", "dev"}
- VALID_TAGS = REQUIRED_CHANNELS | {"development"} # accept legacy alias
- REPO = os.environ.get("GITEA_REPO", "")
- ORG = os.environ.get("GITEA_ORG", "MokoConsulting")
- REPO_BASE = f"https://git.mokoconsulting.tech/{ORG}/"
-
- # Gitea release tag names per channel (Moko standard)
- RELEASE_TAG_MAP = {
- "stable": "stable",
- "rc": "release-candidate",
- "beta": "beta",
- "alpha": "alpha",
- "dev": "development",
- "development": "development",
- }
-
- # Joomla update XML required fields per
- # https://docs.joomla.org/Deploying_an_Update_Server
- REQUIRED_FIELDS = ["name", "element", "type", "version", "infourl"]
-
- for i, u in enumerate(updates):
- tag_el = u.find("tags/tag")
- tag = tag_el.text.strip() if tag_el is not None and tag_el.text else None
- label = f"Entry {i+1} ({tag or '?'})"
-
- # -- Required Joomla fields --
- for field in REQUIRED_FIELDS:
- el = u.find(field)
- if el is None or not (el.text or "").strip():
- print(f"::error::{label}: missing required <{field}>")
- errors += 1
-
- # -- --
- dl = u.find("downloads/downloadurl")
- if dl is None or not (dl.text or "").strip():
- print(f"::error::{label}: missing ")
- errors += 1
- else:
- dl_url = dl.text.strip()
- # Must point to org repo
- if REPO_BASE not in dl_url:
- print(f"::error::{label}: download URL not under {REPO_BASE}: {dl_url}")
- errors += 1
- # Must end in .zip
- if not dl_url.endswith(".zip"):
- print(f"::error::{label}: download URL must end in .zip: {dl_url}")
- errors += 1
- # Must use correct Gitea release tag in path
- if tag and tag in RELEASE_TAG_MAP:
- expected_tag = RELEASE_TAG_MAP[tag]
- if f"/download/{expected_tag}/" not in dl_url:
- print(f"::error::{label}: download URL should contain /download/{expected_tag}/ but got: {dl_url}")
- errors += 1
-
- # -- (required for Joomla to match update) --
- client = u.find("client")
- if client is None or not (client.text or "").strip():
- print(f"::error::{label}: missing (required for Joomla update matching)")
- errors += 1
-
- # -- --
- tp = u.find("targetplatform")
- if tp is None:
- print(f"::error::{label}: missing ")
- errors += 1
- else:
- tp_name = tp.get("name", "")
- tp_ver = tp.get("version", "")
- if tp_name != "joomla":
- print(f"::error::{label}: targetplatform name should be 'joomla', got '{tp_name}'")
- errors += 1
- if not tp_ver:
- print(f"::error::{label}: targetplatform missing version regex")
- errors += 1
- elif "5" not in tp_ver or "6" not in tp_ver:
- print(f"::warning::{label}: targetplatform version may not cover Joomla 5+6: {tp_ver}")
- warnings += 1
-
- # -- must be valid Joomla type --
- type_el = u.find("type")
- if type_el is not None and type_el.text:
- valid_types = {"component", "module", "plugin", "template", "library", "package", "file"}
- if type_el.text.strip() not in valid_types:
- print(f"::error::{label}: invalid type '{type_el.text}' (expected: {valid_types})")
- errors += 1
-
- # -- format (XX.YY.ZZ with optional suffix) --
- ver_el = u.find("version")
- if ver_el is not None and ver_el.text:
- if not re.match(r"^\d{2}\.\d{2}\.\d{2}(-\w+)?$", ver_el.text.strip()):
- print(f"::warning::{label}: version '{ver_el.text}' does not match XX.YY.ZZ format")
- warnings += 1
-
- # -- and --
- for field in ["maintainer", "maintainerurl"]:
- el = u.find(field)
- if el is None or not (el.text or "").strip():
- print(f"::warning::{label}: missing <{field}>")
- warnings += 1
-
- # -- Valid stability tag --
- if tag is None:
- print(f"::error::{label}: missing ")
- errors += 1
- elif tag not in VALID_TAGS:
- print(f"::error::{label}: invalid tag '{tag}' (expected: {VALID_TAGS})")
- errors += 1
-
- # -- Duplicate tag check --
- norm_tag = "dev" if tag == "development" else tag
- if norm_tag in seen_tags:
- print(f"::error::{label}: duplicate channel '{tag}'")
- errors += 1
- if norm_tag:
- seen_tags.add(norm_tag)
-
- # -- All 5 channels must exist --
- missing = REQUIRED_CHANNELS - seen_tags
- if missing:
- print(f"::error::Missing required update channels: {', '.join(sorted(missing))}")
- errors += 1
-
- # -- Version ordering: higher stability must not exceed dev version --
- channel_versions = {}
- for u in updates:
- tag_el = u.find("tags/tag")
- ver_el = u.find("version")
- if tag_el is not None and ver_el is not None and tag_el.text and ver_el.text:
- norm = "dev" if tag_el.text.strip() == "development" else tag_el.text.strip()
- # Strip suffix for comparison (01.00.18-dev -> 01.00.18)
- base_ver = re.sub(r"-\w+$", "", ver_el.text.strip())
- channel_versions[norm] = base_ver
-
- # Cascade check: dev >= alpha >= beta >= rc >= stable
- ORDER = ["dev", "alpha", "beta", "rc", "stable"]
- for j in range(1, len(ORDER)):
- current = ORDER[j]
- previous = ORDER[j - 1]
- if current in channel_versions and previous in channel_versions:
- if channel_versions[current] > channel_versions[previous]:
- print(f"::error::{current} version ({channel_versions[current]}) is ahead of {previous} ({channel_versions[previous]})")
- errors += 1
-
- # -- Summary --
- print(f"\nupdates.xml validation: {len(updates)} entries, {errors} error(s), {warnings} warning(s)")
- if errors > 0:
- sys.exit(1)
- PYEOF
-
- name: Summary
if: always()
run: |
- echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
+ VERSION="${{ steps.meta.outputs.version }}"
+ STABILITY="${{ steps.meta.outputs.stability }}"
+ DISPLAY="${{ steps.meta.outputs.display_version }}"
+ echo "## Update Server" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY
+ echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
diff --git a/src/media/css/template.css b/src/media/css/template.css
index 45af99b..129d4c4 100644
--- a/src/media/css/template.css
+++ b/src/media/css/template.css
@@ -23506,3 +23506,7 @@ padding: 0.25rem;
font-size: 0.8125rem;
}
}
+
+.fa-solid {
+ margin-right: 0.25rem;
+}
diff --git a/src/templateDetails.xml b/src/templateDetails.xml
index 8f3fab3..ba69ce9 100644
--- a/src/templateDetails.xml
+++ b/src/templateDetails.xml
@@ -36,7 +36,7 @@
MokoOnyx
- 02.07.00
+ 02.07.02-dev
script.php
2026-05-16
Jonathan Miller || Moko Consulting
@@ -366,3 +366,4 @@
+