diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml
index 251deb5b76..97c3bec0db 100644
--- a/.mokogitea/manifest.xml
+++ b/.mokogitea/manifest.xml
@@ -4,7 +4,7 @@
MokoGitea
MokoConsulting
Moko fork of Gitea - adding project board REST API endpoints and custom enhancements
- 06.15.00
+ 06.13.02
v1.26.1+MOKO
GNU General Public License v3
diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml
index 23d9c11732..cb47eab79e 100644
--- a/.mokogitea/workflows/issue-branch.yml
+++ b/.mokogitea/workflows/issue-branch.yml
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokoplatform.Automation
-# VERSION: 06.15.00
+# VERSION: 06.13.02
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml
index bc53b7fe2b..24c47e0828 100644
--- a/.mokogitea/workflows/pre-release.yml
+++ b/.mokogitea/workflows/pre-release.yml
@@ -8,4 +8,245 @@
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
-# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
\ No newline at end of file
+# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
+
+name: "Universal: Pre-Release"
+
+on:
+ push:
+ branches:
+ - dev
+ - 'fix/**'
+ - 'patch/**'
+ - 'hotfix/**'
+ - 'bugfix/**'
+ - 'chore/**'
+ - alpha
+ - beta
+ - rc
+ workflow_dispatch:
+ inputs:
+ stability:
+ description: 'Pre-release channel'
+ required: true
+ type: choice
+ options:
+ - development
+ - alpha
+ - beta
+ - release-candidate
+
+permissions:
+ contents: write
+
+env:
+ GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
+ GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
+ GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
+
+jobs:
+ build:
+ name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
+ runs-on: release
+ if: >-
+ github.event_name == 'workflow_dispatch' ||
+ github.event_name == 'push'
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.MOKOGITEA_TOKEN }}
+ ref: ${{ github.ref_name }}
+
+ - name: Setup moko-platform tools
+ env:
+ MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
+ MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
+ run: |
+ # Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
+ if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
+ echo Using pre-installed /opt/moko-platform
+ echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
+ else
+ echo Falling back to fresh clone
+ if ! command -v composer > /dev/null 2>&1; 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
+ fi
+ rm -rf /tmp/moko-platform-api
+ CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
+ git clone --depth 1 --branch main --quiet $CLONE_URL /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
+ fi
+
+ - name: Detect platform
+ id: platform
+ run: |
+ # Auto-detect and update platform if not set in manifest
+ php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
+ php ${MOKO_CLI}/manifest_read.php --path . --github-output
+
+ - name: Resolve metadata and bump version
+ id: meta
+ run: |
+ # Auto-detect stability from branch name on push, or use input on dispatch
+ if [ "${{ github.event_name }}" = "push" ]; then
+ case "${{ github.ref_name }}" in
+ rc) STABILITY="release-candidate" ;;
+ alpha) STABILITY="alpha" ;;
+ beta) STABILITY="beta" ;;
+ *) STABILITY="development" ;;
+ esac
+ else
+ STABILITY="${{ inputs.stability || 'development' }}"
+ fi
+
+ case "$STABILITY" in
+ development) SUFFIX="-dev"; TAG="development" ;;
+ alpha) SUFFIX="-alpha"; TAG="alpha" ;;
+ beta) SUFFIX="-beta"; TAG="beta" ;;
+ release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
+ esac
+
+ # Bump version via CLI: patch for dev/alpha/beta, minor for RC
+ case "$STABILITY" in
+ release-candidate) BUMP="minor" ;;
+ *) BUMP="patch" ;;
+ esac
+
+ php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
+
+ # Set stability suffix and verify consistency
+ VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
+ VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
+
+ php ${MOKO_CLI}/version_set_platform.php \
+ --path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
+ php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
+
+ # Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
+ php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
+
+ # Append suffix for output
+ 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://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]"
+ git push origin HEAD 2>&1
+ }
+
+ # Auto-detect element via manifest_element.php
+ php ${MOKO_CLI}/manifest_element.php \
+ --path . --version "$VERSION" --stability "$STABILITY" \
+ --repo "${GITEA_REPO}" --github-output
+
+ # Read back element outputs
+ 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}.zip"
+
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
+ echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
+ echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
+ echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
+ echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
+
+ echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
+
+ - name: Create release
+ id: release
+ run: |
+ TAG="${{ steps.meta.outputs.tag }}"
+ VERSION="${{ steps.meta.outputs.version }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ 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
+
+ - name: Update release notes from CHANGELOG.md
+ run: |
+ TAG="${{ steps.meta.outputs.tag }}"
+ VERSION="${{ steps.meta.outputs.version }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+
+ # Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
+ if [ -f "CHANGELOG.md" ]; then
+ NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
+ [ -z "$NOTES" ] && NOTES="Release ${VERSION}"
+ else
+ NOTES="Release ${VERSION}"
+ fi
+
+ # Update release body via API
+ RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
+ "${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
+
+ if [ -n "$RELEASE_ID" ]; then
+ python3 -c "
+ import json, urllib.request
+ body = open('/dev/stdin').read()
+ payload = json.dumps({'body': body}).encode()
+ req = urllib.request.Request(
+ '${API_BASE}/releases/${RELEASE_ID}',
+ data=payload, method='PATCH',
+ headers={
+ 'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
+ 'Content-Type': 'application/json'
+ })
+ urllib.request.urlopen(req)
+ " <<< "$NOTES"
+ echo "Release notes updated from CHANGELOG.md"
+ fi
+
+ - name: Build package and upload
+ id: package
+ run: |
+ VERSION="${{ steps.meta.outputs.version }}"
+ TAG="${{ steps.meta.outputs.tag }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ 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
+
+ # updates.xml is generated dynamically by MokoGitea license server
+ # No need to build, commit, or sync updates.xml from workflows
+
+ - name: "Delete lesser pre-release channels (cascade)"
+ continue-on-error: true
+ run: |
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
+
+ php ${MOKO_CLI}/release_cascade.php \
+ --stability "${{ steps.meta.outputs.stability }}" \
+ --token "${TOKEN}" \
+ --api-base "${API_BASE}"
+
+ - name: Summary
+ if: always()
+ run: |
+ VERSION="${{ steps.meta.outputs.version }}"
+ STABILITY="${{ steps.meta.outputs.stability }}"
+ ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
+ SHA256="${{ steps.package.outputs.sha256_zip }}"
+ echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
+ echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
+ echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
diff --git a/mcp-mokogitea-api b/mcp-mokogitea-api
new file mode 160000
index 0000000000..dbaf91546e
--- /dev/null
+++ b/mcp-mokogitea-api
@@ -0,0 +1 @@
+Subproject commit dbaf91546e0fb44614cf8d914b7a78e679dc1efe
diff --git a/services/updateserver/joomla.go b/services/updateserver/joomla.go
index 73fd674613..bac8f634e2 100644
--- a/services/updateserver/joomla.go
+++ b/services/updateserver/joomla.go
@@ -383,12 +383,22 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
infoURL = meta.SupportURL
}
- // Joomla element uses string values per the update server spec.
- // Joomla's XML parser maps these to client_id internally (0/1).
- client := "site"
+ // Joomla element must match the client_id stored in #__extensions.
+ // Joomla's update finder matches by (element, type, client_id, folder) —
+ // a mismatch causes extension_id=0 and the update never shows.
+ //
+ // Joomla hardcodes client_id per extension type in the installer adapters:
+ // component → client_id=1 (ComponentAdapter.php:900)
+ // package → client_id=0 (PackageAdapter.php:548)
+ // plugin → client_id=0 (PluginAdapter.php:492)
+ // library → client_id=0 (LibraryAdapter.php:420)
+ // file → client_id=0 (FileAdapter.php:422)
+ // module → client_id from manifest (0=site, 1=admin)
+ // template → client_id from manifest (0=site, 1=admin)
+ client := "site" // default: client_id=0
switch extType {
- case "package", "component", "library", "file":
- client = "administrator"
+ case "component":
+ client = "administrator" // client_id=1
}
u := xmlUpdate{