Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2add41f69 | ||
|
|
254295311b |
323
.github/workflows/auto-release.yml
vendored
323
.github/workflows/auto-release.yml
vendored
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Release
|
# INGROUP: MokoStandards.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||||
# PATH: /templates/workflows/joomla/auto-release.yml.template
|
# PATH: /templates/workflows/joomla/auto-release.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum
|
# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum
|
||||||
@@ -22,15 +22,13 @@
|
|||||||
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
||||||
# | 5. Write updates.xml (Joomla update server XML) |
|
# | 5. Write updates.xml (Joomla update server XML) |
|
||||||
# | 6. Create git tag vXX.YY.ZZ |
|
# | 6. Create git tag vXX.YY.ZZ |
|
||||||
# | 7a. Patch: update existing Gitea Release for this minor |
|
# | 7a. Patch: update existing GitHub Release for this minor |
|
||||||
# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml |
|
# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml |
|
||||||
# | |
|
# | |
|
||||||
# | Every version change: archives main -> version/XX.YY branch |
|
# | Every version change: archives main -> version/XX.YY branch |
|
||||||
# | All patches release (including 00). Patch 00/01 = full pipeline. |
|
# | All patches release (including 00). Patch 00/01 = full pipeline. |
|
||||||
# | First release only (patch == 01): |
|
# | First release only (patch == 01): |
|
||||||
# | 7b. Create new Gitea Release |
|
# | 7b. Create new GitHub Release |
|
||||||
# | |
|
|
||||||
# | GitHub mirror: stable/rc releases only (continue-on-error) |
|
|
||||||
# | |
|
# | |
|
||||||
# +========================================================================+
|
# +========================================================================+
|
||||||
|
|
||||||
@@ -48,9 +46,6 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
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 }}
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -66,21 +61,19 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set authenticated push URL
|
||||||
|
run: git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoStandards tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
|
||||||
run: |
|
run: |
|
||||||
# Ensure PHP + Composer are available
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
if ! command -v composer &> /dev/null; then
|
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards-API.git" \
|
||||||
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
|
|
||||||
git clone --depth 1 --branch main --quiet \
|
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
|
||||||
/tmp/mokostandards-api
|
/tmp/mokostandards-api
|
||||||
cd /tmp/mokostandards-api
|
cd /tmp/mokostandards-api
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
@@ -106,8 +99,7 @@ jobs:
|
|||||||
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||||
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
||||||
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||||
echo "stability=stable" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
if [ "$PATCH" = "00" ] || [ "$PATCH" = "01" ]; then
|
if [ "$PATCH" = "00" ] || [ "$PATCH" = "01" ]; then
|
||||||
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
||||||
@@ -133,8 +125,11 @@ jobs:
|
|||||||
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
||||||
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
|
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# Tag and branch may persist across patch releases — never skip
|
if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then
|
||||||
|
echo "already_released=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
# -- SANITY CHECKS -------------------------------------------------------
|
# -- SANITY CHECKS -------------------------------------------------------
|
||||||
- name: "Sanity: Pre-release validation"
|
- name: "Sanity: Pre-release validation"
|
||||||
@@ -290,15 +285,9 @@ jobs:
|
|||||||
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||||
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||||
|
|
||||||
# Derive element if not in manifest:
|
# Templates/modules don't have <element> — derive from <name> (lowercased)
|
||||||
# 1. Try XML filename (e.g. mokowaas.xml → mokowaas)
|
|
||||||
# 2. Fall back to repo name (lowercased)
|
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
if [ -z "$EXT_ELEMENT" ]; then
|
||||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
||||||
# If filename is generic (templateDetails, manifest), use repo name
|
|
||||||
case "$EXT_ELEMENT" in
|
|
||||||
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build client tag: plugins and frontend modules need <client>site</client>
|
# Build client tag: plugins and frontend modules need <client>site</client>
|
||||||
@@ -317,7 +306,7 @@ jobs:
|
|||||||
|
|
||||||
# Build targetplatform (fallback to Joomla 5 if not in manifest)
|
# Build targetplatform (fallback to Joomla 5 if not in manifest)
|
||||||
if [ -z "$TARGET_PLATFORM" ]; then
|
if [ -z "$TARGET_PLATFORM" ]; then
|
||||||
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build php_minimum tag
|
# Build php_minimum tag
|
||||||
@@ -326,12 +315,11 @@ jobs:
|
|||||||
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip"
|
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
|
INFO_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/tag/v${VERSION}"
|
||||||
|
|
||||||
# -- Build update entry for a given stability tag
|
# -- Build stable entry to temp file ─────────────────────────
|
||||||
build_entry() {
|
{
|
||||||
local TAG_NAME="$1"
|
|
||||||
printf '%s\n' ' <update>'
|
printf '%s\n' ' <update>'
|
||||||
printf '%s\n' " <name>${EXT_NAME}</name>"
|
printf '%s\n' " <name>${EXT_NAME}</name>"
|
||||||
printf '%s\n' " <description>${EXT_NAME} update</description>"
|
printf '%s\n' " <description>${EXT_NAME} update</description>"
|
||||||
@@ -340,7 +328,9 @@ jobs:
|
|||||||
printf '%s\n' " <version>${VERSION}</version>"
|
printf '%s\n' " <version>${VERSION}</version>"
|
||||||
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
||||||
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
||||||
printf '%s\n' " <tags><tag>${TAG_NAME}</tag></tags>"
|
printf '%s\n' ' <tags>'
|
||||||
|
printf '%s\n' ' <tag>stable</tag>'
|
||||||
|
printf '%s\n' ' </tags>'
|
||||||
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
|
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
|
||||||
printf '%s\n' ' <downloads>'
|
printf '%s\n' ' <downloads>'
|
||||||
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
||||||
@@ -350,27 +340,35 @@ jobs:
|
|||||||
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
||||||
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
||||||
printf '%s\n' ' </update>'
|
printf '%s\n' ' </update>'
|
||||||
}
|
} > /tmp/stable_entry.xml
|
||||||
|
|
||||||
|
# -- Write updates.xml preserving dev/rc entries ──────────────
|
||||||
|
# Extract existing entries for other stability levels
|
||||||
|
# Order reflects release workflow: development → alpha → beta → rc → stable
|
||||||
|
if [ -f "updates.xml" ]; then
|
||||||
|
printf 'import re, sys\n' > /tmp/extract.py
|
||||||
|
printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py
|
||||||
|
printf 'tag = sys.argv[1]\n' >> /tmp/extract.py
|
||||||
|
printf 'm = re.search(r"( <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)", c, re.DOTALL)\n' >> /tmp/extract.py
|
||||||
|
printf 'if m: print(m.group(1))\n' >> /tmp/extract.py
|
||||||
|
fi
|
||||||
|
DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true)
|
||||||
|
ALPHA_ENTRY=$(python3 /tmp/extract.py alpha 2>/dev/null || true)
|
||||||
|
BETA_ENTRY=$(python3 /tmp/extract.py beta 2>/dev/null || true)
|
||||||
|
RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true)
|
||||||
|
|
||||||
# -- Write updates.xml with cascading channels
|
|
||||||
# Stable release updates ALL channels (development, alpha, beta, rc, stable)
|
|
||||||
{
|
{
|
||||||
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>"
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>"
|
|
||||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later"
|
|
||||||
printf '%s\n' " VERSION: ${VERSION}"
|
|
||||||
printf '%s\n' " -->"
|
|
||||||
printf '%s\n' ""
|
|
||||||
printf '%s\n' '<updates>'
|
printf '%s\n' '<updates>'
|
||||||
build_entry "development"
|
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
|
||||||
build_entry "alpha"
|
[ -n "$ALPHA_ENTRY" ] && echo "$ALPHA_ENTRY"
|
||||||
build_entry "beta"
|
[ -n "$BETA_ENTRY" ] && echo "$BETA_ENTRY"
|
||||||
build_entry "rc"
|
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
|
||||||
build_entry "stable"
|
cat /tmp/stable_entry.xml
|
||||||
printf '%s\n' '</updates>'
|
printf '%s\n' '</updates>'
|
||||||
} > updates.xml
|
} > updates.xml
|
||||||
|
|
||||||
echo "updates.xml: ${VERSION} (all channels updated to stable)" >> $GITHUB_STEP_SUMMARY
|
echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- Commit all changes ---------------------------------------------------
|
# -- Commit all changes ---------------------------------------------------
|
||||||
- name: Commit release changes
|
- name: Commit release changes
|
||||||
@@ -385,12 +383,10 @@ jobs:
|
|||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
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 add -A
|
||||||
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
git push -u origin HEAD
|
git push
|
||||||
|
|
||||||
# -- STEP 6: Create tag ---------------------------------------------------
|
# -- STEP 6: Create tag ---------------------------------------------------
|
||||||
- name: "Step 6: Create git tag"
|
- name: "Step 6: Create git tag"
|
||||||
@@ -410,75 +406,69 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- STEP 7: Create or update Gitea Release --------------------------------
|
# -- STEP 7: Create or update GitHub Release ------------------------------
|
||||||
- name: "Step 7: Gitea Release"
|
- name: "Step 7: GitHub Release"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true'
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
steps.check.outputs.tag_exists != 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
BRANCH="${{ steps.version.outputs.branch }}"
|
||||||
MAJOR="${{ steps.version.outputs.major }}"
|
MAJOR="${{ steps.version.outputs.major }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
|
|
||||||
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||||
|
echo "$NOTES" > /tmp/release_notes.md
|
||||||
|
|
||||||
# Check if the major release already exists
|
# Check if the major release already exists
|
||||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
|
||||||
EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -z "$EXISTING_ID" ]; then
|
if [ -z "$EXISTING" ]; then
|
||||||
# First release for this major
|
# First release for this major
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release create "$RELEASE_TAG" \
|
||||||
-H "Content-Type: application/json" \
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||||
"${API_BASE}/releases" \
|
--notes-file /tmp/release_notes.md \
|
||||||
-d "$(python3 -c "import json; print(json.dumps({
|
--target "$BRANCH"
|
||||||
'tag_name': '${RELEASE_TAG}',
|
|
||||||
'name': 'v${MAJOR} (latest: ${VERSION})',
|
|
||||||
'body': '''${NOTES}''',
|
|
||||||
'target_commitish': '${BRANCH}'
|
|
||||||
}))")"
|
|
||||||
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
# Append version notes to existing major release
|
# Append version notes to existing major release
|
||||||
CURRENT_BODY=$(echo "$EXISTING" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))" 2>/dev/null || true)
|
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
||||||
UPDATED_BODY="${CURRENT_BODY}
|
{
|
||||||
|
echo "$CURRENT_NOTES"
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo "### ${VERSION}"
|
||||||
|
echo ""
|
||||||
|
cat /tmp/release_notes.md
|
||||||
|
} > /tmp/updated_notes.md
|
||||||
|
|
||||||
---
|
gh release edit "$RELEASE_TAG" \
|
||||||
### ${VERSION}
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||||
|
--notes-file /tmp/updated_notes.md
|
||||||
${NOTES}"
|
|
||||||
|
|
||||||
curl -sf -X PATCH -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/releases/${EXISTING_ID}" \
|
|
||||||
-d "$(python3 -c "import json,sys; print(json.dumps({
|
|
||||||
'name': 'v${MAJOR} (latest: ${VERSION})',
|
|
||||||
'body': sys.stdin.read()
|
|
||||||
}))" <<< "$UPDATED_BODY")"
|
|
||||||
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
||||||
|
# Every patch builds an install-ready ZIP and uploads it to the minor release.
|
||||||
|
# Result: one Release per minor version with a ZIP for each patch.
|
||||||
- name: "Step 8: Build Joomla package and update checksum"
|
- name: "Step 8: Build Joomla package and update checksum"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true'
|
steps.version.outputs.skip != 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
|
|
||||||
# All ZIPs upload to the major release tag (vXX)
|
# All ZIPs upload to the major release tag (vXX)
|
||||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || {
|
||||||
"${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
|
|
||||||
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
}
|
||||||
|
|
||||||
# Find extension element name from manifest
|
# Find extension element name from manifest
|
||||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
||||||
@@ -512,109 +502,27 @@ jobs:
|
|||||||
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
||||||
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
||||||
|
|
||||||
# -- 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_NAME in "$ZIP_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_NAME}':
|
|
||||||
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 to release tag ----------------------------------
|
# -- Upload both to release tag ----------------------------------
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release upload "$RELEASE_TAG" "/tmp/${ZIP_NAME}" --clobber 2>/dev/null || true
|
||||||
-H "Content-Type: application/octet-stream" \
|
gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true
|
||||||
--data-binary @"/tmp/${ZIP_NAME}" \
|
|
||||||
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${ZIP_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
|
|
||||||
|
|
||||||
# -- Update updates.xml with both download formats ---------------
|
# -- Update updates.xml with both download formats ---------------
|
||||||
if [ -f "updates.xml" ]; then
|
if [ -f "updates.xml" ]; then
|
||||||
ZIP_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
ZIP_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
||||||
TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
TAR_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
||||||
|
|
||||||
# Use Python to update only the stable entry's downloads + sha256
|
# Replace downloads block with both formats + SHA
|
||||||
export PY_ZIP_URL="$ZIP_URL" PY_TAR_URL="$TAR_URL" PY_SHA="$SHA256_ZIP"
|
sed -i "s|<downloads>.*</downloads>|<downloads>\n <downloadurl type=\"full\" format=\"zip\">${ZIP_URL}</downloadurl>\n <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n </downloads>|" updates.xml 2>/dev/null || true
|
||||||
python3 << 'PYEOF'
|
if grep -q '<sha256>' updates.xml; then
|
||||||
import re, os
|
sed -i "s|<sha256>.*</sha256>|<sha256>${SHA256_ZIP}</sha256>|" updates.xml
|
||||||
|
else
|
||||||
|
sed -i "s|</downloads>|</downloads>\n <sha256>${SHA256_ZIP}</sha256>|" updates.xml
|
||||||
|
fi
|
||||||
|
|
||||||
with open("updates.xml") as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
zip_url = os.environ["PY_ZIP_URL"]
|
|
||||||
tar_url = os.environ["PY_TAR_URL"]
|
|
||||||
sha = os.environ["PY_SHA"]
|
|
||||||
|
|
||||||
# Find the stable update block and replace its downloads + sha256
|
|
||||||
def replace_stable(m):
|
|
||||||
block = m.group(0)
|
|
||||||
# Replace downloads block
|
|
||||||
new_downloads = (
|
|
||||||
" <downloads>\n"
|
|
||||||
f" <downloadurl type=\"full\" format=\"zip\">{zip_url}</downloadurl>\n"
|
|
||||||
" </downloads>"
|
|
||||||
)
|
|
||||||
block = re.sub(r' <downloads>.*?</downloads>', new_downloads, block, flags=re.DOTALL)
|
|
||||||
# Add or replace sha256
|
|
||||||
if '<sha256>' in block:
|
|
||||||
block = re.sub(r' <sha256>.*?</sha256>', f' <sha256>{sha}</sha256>', block)
|
|
||||||
else:
|
|
||||||
block = block.replace('</downloads>', f'</downloads>\n <sha256>{sha}</sha256>')
|
|
||||||
return block
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r' <update>.*?<tag>stable</tag>.*?</update>',
|
|
||||||
replace_stable,
|
|
||||||
content,
|
|
||||||
flags=re.DOTALL
|
|
||||||
)
|
|
||||||
|
|
||||||
with open("updates.xml", "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
|
||||||
git add updates.xml
|
git add updates.xml
|
||||||
git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \
|
git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true
|
||||||
git push || true
|
git push || true
|
||||||
|
|
||||||
# Sync updates.xml to main via direct API (always runs — may be on version/XX branch)
|
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
|
||||||
API="${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}"
|
|
||||||
|
|
||||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
|
||||||
|
|
||||||
if [ -n "$FILE_SHA" ]; then
|
|
||||||
CONTENT=$(base64 -w0 updates.xml)
|
|
||||||
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/contents/updates.xml" \
|
|
||||||
-d "$(jq -n \
|
|
||||||
--arg content "$CONTENT" \
|
|
||||||
--arg sha "$FILE_SHA" \
|
|
||||||
--arg msg "chore: sync updates.xml ${VERSION} [skip ci]" \
|
|
||||||
--arg branch "main" \
|
|
||||||
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
|
||||||
)" > /dev/null 2>&1 \
|
|
||||||
&& echo "updates.xml synced to main via API" \
|
|
||||||
|| echo "WARNING: failed to sync updates.xml to main"
|
|
||||||
else
|
|
||||||
echo "WARNING: could not get updates.xml SHA from main"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY
|
echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -624,50 +532,7 @@ jobs:
|
|||||||
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
|
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Download | [${PACKAGE_NAME}](https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
|
||||||
- name: "Step 9: Mirror release to GitHub"
|
|
||||||
if: >-
|
|
||||||
steps.version.outputs.skip != 'true' &&
|
|
||||||
steps.version.outputs.stability == 'stable' &&
|
|
||||||
secrets.GH_TOKEN != ''
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
|
||||||
MAJOR="${{ steps.version.outputs.major }}"
|
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
|
||||||
|
|
||||||
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
|
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
||||||
echo "$NOTES" > /tmp/release_notes.md
|
|
||||||
|
|
||||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".tag_name // empty" || true)
|
|
||||||
|
|
||||||
if [ -z "$EXISTING" ]; then
|
|
||||||
gh release create "$RELEASE_TAG" \
|
|
||||||
--repo "$GH_REPO" \
|
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
|
||||||
--notes-file /tmp/release_notes.md \
|
|
||||||
--target "$BRANCH" || true
|
|
||||||
else
|
|
||||||
gh release edit "$RELEASE_TAG" \
|
|
||||||
--repo "$GH_REPO" \
|
|
||||||
--title "v${MAJOR} (latest: ${VERSION})" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload assets to GitHub mirror
|
|
||||||
for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do
|
|
||||||
if [ -f "$PKG" ]; then
|
|
||||||
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".id // empty")
|
|
||||||
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# -- Summary --------------------------------------------------------------
|
# -- Summary --------------------------------------------------------------
|
||||||
- name: Pipeline Summary
|
- name: Pipeline Summary
|
||||||
@@ -688,5 +553,5 @@ jobs:
|
|||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Release | [View](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|||||||
79
.github/workflows/release.yml
vendored
79
.github/workflows/release.yml
vendored
@@ -375,7 +375,6 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Cascading channels: each stability updates itself and all lower levels
|
|
||||||
export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \
|
export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \
|
||||||
PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \
|
PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \
|
||||||
PY_GITEA_ORG="$GITEA_ORG" PY_GITEA_REPO="$GITEA_REPO"
|
PY_GITEA_ORG="$GITEA_ORG" PY_GITEA_REPO="$GITEA_REPO"
|
||||||
@@ -391,53 +390,73 @@ jobs:
|
|||||||
gitea_org = os.environ["PY_GITEA_ORG"]
|
gitea_org = os.environ["PY_GITEA_ORG"]
|
||||||
gitea_repo = os.environ["PY_GITEA_REPO"]
|
gitea_repo = os.environ["PY_GITEA_REPO"]
|
||||||
|
|
||||||
# Cascade map: each level updates itself + all lower levels
|
# Map stability to the <tag> value in updates.xml
|
||||||
cascade = {
|
tag_map = {
|
||||||
"stable": ["development", "alpha", "beta", "rc", "stable"],
|
"development": "development",
|
||||||
"rc": ["development", "alpha", "beta", "rc"],
|
"alpha": "alpha",
|
||||||
"beta": ["development", "alpha", "beta"],
|
"beta": "beta",
|
||||||
"alpha": ["development", "alpha"],
|
"rc": "rc",
|
||||||
"development": ["development"],
|
"stable": "stable",
|
||||||
}
|
}
|
||||||
targets = cascade.get(stability, [stability])
|
xml_tag = tag_map.get(stability, "development")
|
||||||
|
|
||||||
with open("updates.xml", "r") as f:
|
with open("updates.xml", "r") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
gitea_url = f"https://git.mokoconsulting.tech/{gitea_org}/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
# Build regex to find the specific <update> block for this stability tag
|
||||||
|
# Use negative lookahead to avoid matching across multiple <update> blocks
|
||||||
for xml_tag in targets:
|
|
||||||
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(xml_tag) + r"</tag>.*?</update>)"
|
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(xml_tag) + r"</tag>.*?</update>)"
|
||||||
match = re.search(block_pattern, content, re.DOTALL)
|
match = re.search(block_pattern, content, re.DOTALL)
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
print(f"No block for <tag>{xml_tag}</tag> — skipping")
|
print(f"No <update> block found for <tag>{xml_tag}</tag>")
|
||||||
continue
|
exit(0)
|
||||||
|
|
||||||
block = match.group(1)
|
block = match.group(1)
|
||||||
original = block
|
original_block = block
|
||||||
|
|
||||||
|
# Update version
|
||||||
block = re.sub(r"<version>[^<]*</version>", f"<version>{version}</version>", block)
|
block = re.sub(r"<version>[^<]*</version>", f"<version>{version}</version>", block)
|
||||||
|
|
||||||
|
# Update creation date
|
||||||
block = re.sub(r"<creationDate>[^<]*</creationDate>", f"<creationDate>{date}</creationDate>", block)
|
block = re.sub(r"<creationDate>[^<]*</creationDate>", f"<creationDate>{date}</creationDate>", block)
|
||||||
|
|
||||||
|
# Update or add SHA-256
|
||||||
if "<sha256>" in block:
|
if "<sha256>" in block:
|
||||||
block = re.sub(r"<sha256>[^<]*</sha256>", f"<sha256>{sha256}</sha256>", block)
|
block = re.sub(r"<sha256>[^<]*</sha256>", f"<sha256>{sha256}</sha256>", block)
|
||||||
else:
|
else:
|
||||||
block = block.replace("</downloads>", f"</downloads>\n <sha256>{sha256}</sha256>")
|
block = block.replace("</downloads>", f"</downloads>\n <sha256>{sha256}</sha256>")
|
||||||
|
|
||||||
|
# Update Gitea download URL
|
||||||
|
gitea_url = f"https://git.mokoconsulting.tech/{gitea_org}/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
||||||
block = re.sub(
|
block = re.sub(
|
||||||
r"(<downloadurl[^>]*>)https://git\.mokoconsulting\.tech/[^<]*(</downloadurl>)",
|
r"(<downloadurl[^>]*>)https://git\.mokoconsulting\.tech/[^<]*(</downloadurl>)",
|
||||||
rf"\g<1>{gitea_url}\g<2>",
|
rf"\g<1>{gitea_url}\g<2>",
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
|
|
||||||
content = content.replace(original, block)
|
# Update GitHub download URL only for RC and stable (others are Gitea-only)
|
||||||
print(f"Updated {xml_tag} channel")
|
if stability in ("rc", "stable"):
|
||||||
|
gh_url = f"https://github.com/mokoconsulting-tech/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
||||||
|
block = re.sub(
|
||||||
|
r"(<downloadurl[^>]*>)https://github\.com/[^<]*(</downloadurl>)",
|
||||||
|
rf"\g<1>{gh_url}\g<2>",
|
||||||
|
block
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Remove any GitHub download URL for dev/alpha/beta
|
||||||
|
block = re.sub(
|
||||||
|
r"\n\s*<downloadurl[^>]*>https://github\.com/[^<]*</downloadurl>",
|
||||||
|
"",
|
||||||
|
block
|
||||||
|
)
|
||||||
|
|
||||||
|
content = content.replace(original_block, block)
|
||||||
|
|
||||||
with open("updates.xml", "w") as f:
|
with open("updates.xml", "w") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
print(f"Cascaded {stability} → {', '.join(targets)}: v={version}, sha={sha256[:16]}...")
|
print(f"Updated {xml_tag} channel: version={version}, sha={sha256[:16]}..., date={date}")
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|
||||||
- name: "Commit updates.xml to current branch and main"
|
- name: "Commit updates.xml to current branch and main"
|
||||||
@@ -464,27 +483,39 @@ jobs:
|
|||||||
# Push to current branch
|
# Push to current branch
|
||||||
git push || true
|
git push || true
|
||||||
|
|
||||||
# Sync updates.xml to main via direct API
|
# Also update updates.xml on main via Gitea API (git push blocked by branch protection)
|
||||||
|
if [ "$CURRENT_BRANCH" != "main" ]; then
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
|
# Get current file SHA on main (required for update)
|
||||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
||||||
|
|
||||||
if [ -n "$FILE_SHA" ]; then
|
if [ -n "$FILE_SHA" ]; then
|
||||||
|
# Base64-encode the updates.xml content from working tree (has updated SHA)
|
||||||
CONTENT=$(base64 -w0 updates.xml)
|
CONTENT=$(base64 -w0 updates.xml)
|
||||||
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"${API}/contents/updates.xml" \
|
"${API}/contents/updates.xml" \
|
||||||
-d "$(jq -n \
|
-d "$(jq -n \
|
||||||
--arg content "$CONTENT" \
|
--arg content "$CONTENT" \
|
||||||
--arg sha "$FILE_SHA" \
|
--arg sha "$FILE_SHA" \
|
||||||
--arg msg "chore: sync updates.xml ${STABILITY} ${VERSION} [skip ci]" \
|
--arg msg "chore: update ${STABILITY} channel to ${VERSION} on main [skip ci]" \
|
||||||
--arg branch "main" \
|
--arg branch "main" \
|
||||||
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
||||||
)" > /dev/null 2>&1 \
|
)")
|
||||||
&& echo "updates.xml synced to main" \
|
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||||
|| echo "WARNING: failed to sync updates.xml to main"
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
||||||
|
echo "updates.xml synced to main via API (HTTP ${HTTP_CODE})"
|
||||||
|
else
|
||||||
|
echo "WARNING: failed to sync updates.xml to main (HTTP ${HTTP_CODE})"
|
||||||
|
echo "$RESPONSE" | head -5
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "WARNING: could not get file SHA for updates.xml on main"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
|
|||||||
268
.github/workflows/update-server.yml
vendored
268
.github/workflows/update-server.yml
vendored
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Joomla
|
# INGROUP: MokoStandards.Joomla
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||||
# PATH: /templates/workflows/joomla/update-server.yml.template
|
# PATH: /templates/workflows/joomla/update-server.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
|
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
|
||||||
@@ -13,27 +13,16 @@
|
|||||||
# Writes updates.xml with multiple <update> entries:
|
# Writes updates.xml with multiple <update> entries:
|
||||||
# - <tag>stable</tag> on push to main (from auto-release)
|
# - <tag>stable</tag> on push to main (from auto-release)
|
||||||
# - <tag>rc</tag> on push to rc/**
|
# - <tag>rc</tag> on push to rc/**
|
||||||
# - <tag>development</tag> on push to dev or dev/**
|
# - <tag>development</tag> on push to dev/**
|
||||||
#
|
#
|
||||||
# Joomla filters by user's "Minimum Stability" setting.
|
# Joomla filters by user's "Minimum Stability" setting.
|
||||||
|
|
||||||
name: Update Joomla Update Server XML Feed
|
name: Update Joomla Update Server XML Feed
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'dev'
|
|
||||||
- 'dev/**'
|
|
||||||
- 'alpha/**'
|
|
||||||
- 'beta/**'
|
|
||||||
- 'rc/**'
|
|
||||||
paths:
|
|
||||||
- 'src/**'
|
|
||||||
- 'htdocs/**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types: [closed]
|
||||||
branches:
|
branches:
|
||||||
- 'dev'
|
|
||||||
- 'dev/**'
|
- 'dev/**'
|
||||||
- 'alpha/**'
|
- 'alpha/**'
|
||||||
- 'beta/**'
|
- 'beta/**'
|
||||||
@@ -57,9 +46,6 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
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 }}
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -69,50 +55,46 @@ jobs:
|
|||||||
name: Update updates.xml
|
name: Update updates.xml
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: >-
|
if: >-
|
||||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoStandards tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
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
|
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards.git" \
|
||||||
fi
|
/tmp/mokostandards 2>/dev/null || true
|
||||||
git clone --depth 1 --branch main --quiet \
|
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
/tmp/mokostandards-api 2>/dev/null || true
|
|
||||||
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
|
||||||
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Generate updates.xml entry
|
- name: Generate updates.xml entry
|
||||||
id: update
|
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.ref_name }}"
|
BRANCH="${{ github.ref_name }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||||
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
|
||||||
|
|
||||||
# Auto-bump patch on all branches (dev, alpha, beta, rc)
|
# Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually)
|
||||||
|
if [[ "$BRANCH" != dev/* ]]; then
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "gitea-actions[bot]"
|
||||||
BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true)
|
BUMPED=$(php /tmp/mokostandards/api/cli/version_bump.php --path . 2>/dev/null || true)
|
||||||
if [ -n "$BUMPED" ]; then
|
if [ -n "$BUMPED" ]; then
|
||||||
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
||||||
git push 2>/dev/null || true
|
git push 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Determine stability from branch or input
|
# Determine stability from branch or input
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
@@ -123,14 +105,12 @@ jobs:
|
|||||||
STABILITY="beta"
|
STABILITY="beta"
|
||||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||||
STABILITY="alpha"
|
STABILITY="alpha"
|
||||||
elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then
|
elif [[ "$BRANCH" == dev/* ]]; then
|
||||||
STABILITY="development"
|
STABILITY="development"
|
||||||
else
|
else
|
||||||
STABILITY="stable"
|
STABILITY="stable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Parse manifest (portable — no grep -P)
|
# Parse manifest (portable — no grep -P)
|
||||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
if [ -z "$MANIFEST" ]; then
|
if [ -z "$MANIFEST" ]; then
|
||||||
@@ -152,18 +132,15 @@ jobs:
|
|||||||
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||||
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||||
|
|
||||||
# Derive element if not in manifest: try XML filename, then repo name
|
# Templates and modules don't have <element> — derive from <name>
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
if [ -z "$EXT_ELEMENT" ]; then
|
||||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
||||||
case "$EXT_ELEMENT" in
|
|
||||||
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use manifest version if README version is empty
|
# Use manifest version if README version is empty
|
||||||
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
|
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
|
||||||
|
|
||||||
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||||
|
|
||||||
CLIENT_TAG=""
|
CLIENT_TAG=""
|
||||||
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||||
@@ -196,10 +173,10 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
||||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||||
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}"
|
INFO_URL="https://github.com/${REPO}"
|
||||||
|
|
||||||
# -- Build install packages (ZIP + tar.gz) --------------------
|
# ── Build install packages (ZIP + tar.gz) ───────────────────
|
||||||
SOURCE_DIR="src"
|
SOURCE_DIR="src"
|
||||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
if [ -d "$SOURCE_DIR" ]; then
|
if [ -d "$SOURCE_DIR" ]; then
|
||||||
@@ -215,62 +192,20 @@ jobs:
|
|||||||
|
|
||||||
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||||
|
|
||||||
# Ensure release exists on Gitea
|
# Ensure release exists
|
||||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \
|
||||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
gh release create "$RELEASE_TAG" --title "${RELEASE_TAG} (${DISPLAY_VERSION})" --notes "${STABILITY} release" --prerelease --target 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)
|
|
||||||
|
|
||||||
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
|
# Upload both formats
|
||||||
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true
|
||||||
-H "Content-Type: application/octet-stream" \
|
gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true
|
||||||
--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
|
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
SHA256=""
|
SHA256=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -- Build the new entry -----------------------------------------
|
# ── Build the new entry ───────────────────────────────────────
|
||||||
NEW_ENTRY=""
|
NEW_ENTRY=""
|
||||||
NEW_ENTRY="${NEW_ENTRY} <update>\n"
|
NEW_ENTRY="${NEW_ENTRY} <update>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
|
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
|
||||||
@@ -285,76 +220,40 @@ jobs:
|
|||||||
NEW_ENTRY="${NEW_ENTRY} </tags>\n"
|
NEW_ENTRY="${NEW_ENTRY} </tags>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n"
|
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
|
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
|
||||||
|
TAR_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
|
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
|
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
|
||||||
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>${SHA256}</sha256>\n"
|
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>sha256:${SHA256}</sha256>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
|
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
|
||||||
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
|
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
||||||
NEW_ENTRY="${NEW_ENTRY} </update>"
|
NEW_ENTRY="${NEW_ENTRY} </update>"
|
||||||
|
|
||||||
# -- Write new entry to temp file --------------------------------
|
# ── Write new entry to temp file ───────────────────────────────
|
||||||
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
|
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
|
||||||
|
|
||||||
# -- Merge into updates.xml (only update this stability channel) -
|
# ── Merge into updates.xml ─────────────────────────────────────
|
||||||
# Cascading update: each stability level updates itself and all lower levels
|
|
||||||
# stable → all | rc → rc,beta,alpha,dev | beta → beta,alpha,dev | 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}"
|
|
||||||
|
|
||||||
if [ ! -f "updates.xml" ]; then
|
if [ ! -f "updates.xml" ]; then
|
||||||
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>" > updates.xml
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > updates.xml
|
||||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>" >> updates.xml
|
|
||||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later" >> updates.xml
|
|
||||||
printf '%s\n' " VERSION: ${VERSION}" >> updates.xml
|
|
||||||
printf '%s\n' " -->" >> updates.xml
|
|
||||||
printf '%s\n' "" >> updates.xml
|
|
||||||
printf '%s\n' '<updates>' >> updates.xml
|
printf '%s\n' '<updates>' >> updates.xml
|
||||||
cat /tmp/new_entry.xml >> updates.xml
|
cat /tmp/new_entry.xml >> updates.xml
|
||||||
printf '\n%s\n' '</updates>' >> updates.xml
|
printf '\n%s\n' '</updates>' >> updates.xml
|
||||||
else
|
else
|
||||||
# Replace each cascading channel with the new entry (different tag)
|
# Remove existing entry for this stability, insert new one
|
||||||
export PY_TARGETS="$TARGETS"
|
printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py
|
||||||
python3 << PYEOF
|
printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py
|
||||||
import re, os
|
printf 'with open("/tmp/new_entry.xml") as f: new_entry = f.read()\n' >> /tmp/merge_xml.py
|
||||||
targets = os.environ["PY_TARGETS"].split(",")
|
printf 'pattern = r" <update>.*?<tag>" + re.escape(stability) + r"</tag>.*?</update>\\n?"\n' >> /tmp/merge_xml.py
|
||||||
stability = "${STABILITY}"
|
printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py
|
||||||
with open("updates.xml") as f:
|
printf 'content = content.replace("</updates>", new_entry + "\\n</updates>")\n' >> /tmp/merge_xml.py
|
||||||
content = f.read()
|
printf 'content = re.sub(r"\\n{3,}", "\\n\\n", content)\n' >> /tmp/merge_xml.py
|
||||||
with open("/tmp/new_entry.xml") as f:
|
printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py
|
||||||
new_entry_template = f.read()
|
python3 /tmp/merge_xml.py 2>/dev/null || {
|
||||||
for tag in targets:
|
|
||||||
tag = tag.strip()
|
|
||||||
# Build entry with this tag
|
|
||||||
new_entry = re.sub(r"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
|
|
||||||
# Remove existing entry for this tag
|
|
||||||
pattern = r" <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>\n?"
|
|
||||||
content = re.sub(pattern, "", content, flags=re.DOTALL)
|
|
||||||
# Insert before </updates>
|
|
||||||
content = content.replace("</updates>", new_entry + "\n</updates>")
|
|
||||||
content = re.sub(r"\n{3,}", "\n\n", content)
|
|
||||||
with open("updates.xml", "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
PYEOF
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
# Fallback: rebuild keeping other stability entries
|
# Fallback: rebuild keeping other stability entries
|
||||||
{
|
{
|
||||||
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>"
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>"
|
|
||||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later"
|
|
||||||
printf '%s\n' " VERSION: ${VERSION}"
|
|
||||||
printf '%s\n' " -->"
|
|
||||||
printf '%s\n' ""
|
|
||||||
printf '%s\n' '<updates>'
|
printf '%s\n' '<updates>'
|
||||||
for TAG in stable rc development; do
|
for TAG in stable rc development; do
|
||||||
[ "$TAG" = "${STABILITY}" ] && continue
|
[ "$TAG" = "${STABILITY}" ] && continue
|
||||||
@@ -366,7 +265,7 @@ jobs:
|
|||||||
printf '\n%s\n' '</updates>'
|
printf '\n%s\n' '</updates>'
|
||||||
} > /tmp/updates_new.xml
|
} > /tmp/updates_new.xml
|
||||||
mv /tmp/updates_new.xml updates.xml
|
mv /tmp/updates_new.xml updates.xml
|
||||||
fi
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Commit
|
# Commit
|
||||||
@@ -379,55 +278,8 @@ jobs:
|
|||||||
git push
|
git push
|
||||||
}
|
}
|
||||||
|
|
||||||
# -- Sync updates.xml to main (for non-main branches) ----------------------
|
|
||||||
- name: Sync updates.xml to main
|
|
||||||
if: github.ref_name != 'main'
|
|
||||||
run: |
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
|
||||||
|
|
||||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_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
|
|
||||||
CONTENT=$(base64 -w0 updates.xml)
|
|
||||||
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/contents/updates.xml" \
|
|
||||||
-d "$(python3 -c "import json; print(json.dumps({
|
|
||||||
'content': '${CONTENT}',
|
|
||||||
'sha': '${FILE_SHA}',
|
|
||||||
'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
|
|
||||||
'branch': 'main'
|
|
||||||
}))")" > /dev/null 2>&1 \
|
|
||||||
&& echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
|
|
||||||
|| echo "WARNING: failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "WARNING: could not get updates.xml SHA from main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -- Mirror to GitHub (stable and rc only) --------------------------------
|
|
||||||
- name: Mirror release to GitHub
|
|
||||||
if: >-
|
|
||||||
(steps.update.outputs.stability == 'stable' || steps.update.outputs.stability == 'rc') &&
|
|
||||||
secrets.GH_TOKEN != ''
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
run: |
|
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
|
||||||
STABILITY="${{ steps.update.outputs.stability }}"
|
|
||||||
echo "GitHub mirror sync for ${STABILITY} — ${GH_REPO}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
# Mirror packages if they exist
|
|
||||||
for PKG in /tmp/*.zip /tmp/*.tar.gz; do
|
|
||||||
if [ -f "$PKG" ]; then
|
|
||||||
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/${RELEASE_TAG}" 2>/dev/null | jq -r ".id // empty")
|
|
||||||
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: SFTP deploy to dev server
|
- name: SFTP deploy to dev server
|
||||||
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
if: contains(github.ref, 'dev/')
|
||||||
env:
|
env:
|
||||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||||
@@ -436,15 +288,15 @@ jobs:
|
|||||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||||
|
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
# -- Permission check: admin or maintain role required --------
|
# ── Permission check: admin or maintain role required ──────
|
||||||
ACTOR="${{ github.actor }}"
|
ACTOR="${{ github.actor }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
|
||||||
|
2>/dev/null | jq -r '.permission' || \
|
||||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}" 2>/dev/null \
|
||||||
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
2>/dev/null | jq -r '.role' || echo "read")
|
||||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
|
||||||
case "$PERMISSION" in
|
case "$PERMISSION" in
|
||||||
admin|maintain|write) ;;
|
admin|maintain|write) ;;
|
||||||
*)
|
*)
|
||||||
@@ -472,11 +324,11 @@ jobs:
|
|||||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
|
||||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards/api/deploy/deploy-joomla.php" ]; then
|
||||||
php /tmp/mokostandards-api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
php /tmp/mokostandards/api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
elif [ -f "/tmp/mokostandards-api/deploy/deploy-sftp.php" ]; then
|
elif [ -f "/tmp/mokostandards/api/deploy/deploy-sftp.php" ]; then
|
||||||
php /tmp/mokostandards-api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
php /tmp/mokostandards/api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
fi
|
fi
|
||||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
478
README.md
478
README.md
@@ -9,7 +9,7 @@
|
|||||||
INGROUP: MokoCassiopeia.Documentation
|
INGROUP: MokoCassiopeia.Documentation
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia
|
||||||
FILE: ./README.md
|
FILE: ./README.md
|
||||||
VERSION: 03.10.24
|
VERSION: 03.10.23
|
||||||
BRIEF: Documentation for MokoCassiopeia template
|
BRIEF: Documentation for MokoCassiopeia template
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
**Retired — See [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)**
|
**Retired — See [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)**
|
||||||
|
|
||||||
[](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03)
|
[](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://www.joomla.org)
|
[](https://www.joomla.org)
|
||||||
[](https://www.php.net)
|
[](https://www.php.net)
|
||||||
@@ -28,13 +28,13 @@ MokoCassiopeia is a modern, lightweight enhancement layer built on top of Joomla
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Table of Contents
|
## 📑 Table of Contents
|
||||||
|
|
||||||
- [Features](#-features)
|
- [Features](#-features)
|
||||||
- [Requirements](#-requirements)
|
- [Requirements](#-requirements)
|
||||||
- [Installation](#-installation)
|
- [Installation](#-installation)
|
||||||
- [Quick Start](#-quick-start)
|
- [Quick Start](#-quick-start)
|
||||||
- [Configuration](#-configuration)
|
- [Configuration](#️-configuration)
|
||||||
- [Theme System](#-theme-system)
|
- [Theme System](#-theme-system)
|
||||||
- [Development](#-development)
|
- [Development](#-development)
|
||||||
- [Documentation](#-documentation)
|
- [Documentation](#-documentation)
|
||||||
@@ -46,19 +46,475 @@ MokoCassiopeia is a modern, lightweight enhancement layer built on top of Joomla
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Features
|
## ✨ Features
|
||||||
|
|
||||||
|
### Core Enhancements
|
||||||
|
|
||||||
- **Built on Cassiopeia**: Extends Joomla's default template with minimal overrides
|
- **Built on Cassiopeia**: Extends Joomla's default template with minimal overrides
|
||||||
- **Font Awesome 7**: Fully integrated into Joomla's asset manager with 2,000+ icons
|
- **Font Awesome 7**: Fully integrated into Joomla's asset manager with 2,000+ icons
|
||||||
- **Bootstrap 5**: Extended utility classes and responsive grid system
|
- **Bootstrap 5**: Extended utility classes and responsive grid system
|
||||||
|
- **No Template Overrides**: Clean installation that inherits all Cassiopeia defaults
|
||||||
|
- **Upgrade-Friendly**: Minimal modifications ensure smooth Joomla updates
|
||||||
|
|
||||||
|
### Advanced Theming
|
||||||
|
|
||||||
- **Dark Mode Support**: Built-in light/dark mode toggle with system preference detection
|
- **Dark Mode Support**: Built-in light/dark mode toggle with system preference detection
|
||||||
- **Google Tag Manager / GA4**: Optional analytics integrations
|
- **Color Palettes**: Standard, Alternative, and Custom color schemes
|
||||||
|
- **Theme Persistence**: User preferences saved via localStorage
|
||||||
|
- **Theme Control Options**: Switch, radio buttons, or hidden controls
|
||||||
|
- **Auto Dark Mode**: Optional automatic dark mode based on time/system settings
|
||||||
|
- **Meta Tags**: Automatic color-scheme and theme-color meta tags
|
||||||
|
|
||||||
|
### Developer Features
|
||||||
|
|
||||||
|
- **Custom Code Injection**: Add custom HTML to `<head>` start/end
|
||||||
|
- **Drawer Sidebars**: Configurable left/right drawer positions with custom icons
|
||||||
|
- **Font Options**: Local and web fonts (Roboto, Fira Sans, Noto Sans)
|
||||||
|
- **Sticky Header**: Optional sticky navigation
|
||||||
|
- **Back to Top**: Floating back-to-top button
|
||||||
|
|
||||||
|
### Analytics & Tracking
|
||||||
|
|
||||||
|
- **Google Tag Manager**: Optional GTM integration with container ID configuration
|
||||||
|
- **Google Analytics**: Optional GA4 integration with measurement ID
|
||||||
|
- **Privacy-Friendly**: All tracking features are optional and easily disabled
|
||||||
|
|
||||||
|
### Content Features
|
||||||
|
|
||||||
- **Table of Contents**: Automatic TOC generation for long articles
|
- **Table of Contents**: Automatic TOC generation for long articles
|
||||||
|
- Placement options: `toc-left` or `toc-right` layouts
|
||||||
## License
|
- Automatic heading extraction and navigation
|
||||||
|
- Responsive sidebar positioning
|
||||||
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Made with love by [Moko Consulting](https://mokoconsulting.tech)**
|
## 📋 Requirements
|
||||||
|
|
||||||
|
- **Joomla**: 4.4.x or 5.x
|
||||||
|
- **PHP**: 8.0 or higher
|
||||||
|
- **Database**: MySQL 5.7+ / MariaDB 10.2+ / PostgreSQL 11+
|
||||||
|
- **Browser Support**: Modern browsers (Chrome, Firefox, Safari, Edge)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
|
**Note**: MokoCassiopeia is a **standalone Joomla template extension** (not bundled as a package). Install it directly via Joomla's Extension Manager.
|
||||||
|
|
||||||
|
### Via Joomla Extension Manager
|
||||||
|
|
||||||
|
1. Download the latest `mokocassiopeia-{version}.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases)
|
||||||
|
2. In Joomla admin, navigate to **System → Install → Extensions**
|
||||||
|
3. Upload the ZIP file and click **Upload & Install**
|
||||||
|
4. Navigate to **System → Site Templates**
|
||||||
|
5. Set **MokoCassiopeia** as your default template
|
||||||
|
|
||||||
|
### Via Git (Development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia.git
|
||||||
|
cd MokoCassiopeia
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Development Guide](./docs/JOOMLA_DEVELOPMENT.md) for development setup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### 1. Install the Template
|
||||||
|
|
||||||
|
Install `mokocassiopeia.zip` via Joomla's Extension Manager.
|
||||||
|
|
||||||
|
### 2. Set as Default
|
||||||
|
|
||||||
|
Navigate to **System → Site Templates** and set **MokoCassiopeia** as default.
|
||||||
|
|
||||||
|
### 3. Configure Template Options
|
||||||
|
|
||||||
|
Go to **System → Site Templates → MokoCassiopeia** to configure:
|
||||||
|
|
||||||
|
- **Branding**: Upload logo, set site title/description
|
||||||
|
- **Theme**: Configure color schemes and dark mode
|
||||||
|
- **Layout**: Set container type (static/fluid), sticky header
|
||||||
|
- **Analytics**: Add GTM/GA4 tracking codes (optional)
|
||||||
|
- **Custom Code**: Inject custom HTML/CSS/JS
|
||||||
|
|
||||||
|
### 4. Test Dark Mode
|
||||||
|
|
||||||
|
The template includes a dark mode toggle. Test it by:
|
||||||
|
- Using the floating theme toggle button (bottom-right by default)
|
||||||
|
- Checking theme persistence across page loads
|
||||||
|
- Verifying system preference detection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Once installed and set as the default site template, MokoCassiopeia works out of the box with Joomla's standard content and module system. Key usage points:
|
||||||
|
|
||||||
|
- **Template Options** — Configure via **System → Site Templates → MokoCassiopeia** (theme colours, layout, analytics, favicon, drawers)
|
||||||
|
- **Custom Colour Schemes** — Copy `templates/mokocassiopeia/templates/light.custom.css` or `dark.custom.css` to `media/templates/site/mokocassiopeia/css/theme/` and select "Custom" in the Theme tab
|
||||||
|
- **Custom CSS/JS** — Create `media/templates/site/mokocassiopeia/css/user.css` or `js/user.js` for site-specific overrides that survive template updates
|
||||||
|
- **Module Overrides** — The template includes overrides for common Joomla modules with consistent title rendering, Bootstrap 5 styling, and Font Awesome 7 icons
|
||||||
|
- **Dark Mode** — Enabled by default with a floating toggle button; respects system preference and persists via localStorage
|
||||||
|
|
||||||
|
See [Configuration](#️-configuration) below for detailed parameter reference.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
### Global Parameters
|
||||||
|
|
||||||
|
Access template configuration via **System → Site Templates → MokoCassiopeia**.
|
||||||
|
|
||||||
|
#### Theme Tab
|
||||||
|
|
||||||
|
**General Settings:**
|
||||||
|
- **Theme Enabled**: Enable/disable theme system
|
||||||
|
- **Theme Control Type**: Switch (Light↔Dark), Radios (Light/Dark/System), or None
|
||||||
|
- **Default Choice**: System, Light, or Dark
|
||||||
|
- **Auto Dark Mode**: Automatic dark mode based on time
|
||||||
|
- **Meta Tags**: Enable color-scheme and theme-color meta tags
|
||||||
|
- **Bridge Bootstrap ARIA**: Sync theme with Bootstrap's data-bs-theme
|
||||||
|
|
||||||
|
**Variables & Palettes:**
|
||||||
|
- **Light Mode Palette**: Standard, Alternative, or Custom
|
||||||
|
- **Dark Mode Palette**: Standard, Alternative, or Custom
|
||||||
|
|
||||||
|
**Typography:**
|
||||||
|
- **Font Scheme**: Local (Roboto) or Web fonts (Fira Sans, Roboto+Noto Sans)
|
||||||
|
|
||||||
|
**Branding & Icons:**
|
||||||
|
- **Brand**: Enable/disable site branding
|
||||||
|
- **Logo File**: Upload custom logo (no default logo included)
|
||||||
|
- **Site Title**: Custom site title
|
||||||
|
- **Site Description**: Tagline/description
|
||||||
|
- **Font Awesome Kit**: Optional FA Pro kit code
|
||||||
|
|
||||||
|
**Header & Navigation:**
|
||||||
|
- **Sticky Header**: Enable fixed header on scroll
|
||||||
|
- **Back to Top**: Enable floating back-to-top button
|
||||||
|
|
||||||
|
**Theme Toggle UI:**
|
||||||
|
- **FAB Enabled**: Enable floating action button toggle
|
||||||
|
- **FAB Position**: Bottom-right, Bottom-left, Top-right, or Top-left
|
||||||
|
|
||||||
|
#### Advanced Tab
|
||||||
|
|
||||||
|
- **Layout**: Static or Fluid container
|
||||||
|
|
||||||
|
#### Google Tab
|
||||||
|
|
||||||
|
- **Google Tag Manager**: Enable and configure GTM container ID
|
||||||
|
- **Google Analytics**: Enable and configure GA4 measurement ID
|
||||||
|
|
||||||
|
#### Custom Code Tab
|
||||||
|
|
||||||
|
- **Custom Head Start**: HTML injected at start of `<head>`
|
||||||
|
- **Custom Head End**: HTML injected at end of `<head>`
|
||||||
|
|
||||||
|
#### Drawers Tab
|
||||||
|
|
||||||
|
- **Left Drawer Icon**: Font Awesome icon class (e.g., `fa-solid fa-chevron-right`)
|
||||||
|
- **Right Drawer Icon**: Font Awesome icon class (e.g., `fa-solid fa-chevron-left`)
|
||||||
|
|
||||||
|
### Custom Theme Palettes
|
||||||
|
|
||||||
|
MokoCassiopeia supports custom theme schemes:
|
||||||
|
|
||||||
|
1. **Copy template files** from `/templates/` directory:
|
||||||
|
- `light.custom.css` → `media/templates/site/mokocassiopeia/css/theme/light.custom.css`
|
||||||
|
- `dark.custom.css` → `media/templates/site/mokocassiopeia/css/theme/dark.custom.css`
|
||||||
|
2. **Customize** the CSS variables to match your brand colors
|
||||||
|
3. **Enable in Joomla**: System → Site Templates → MokoCassiopeia → Theme tab → Set palette to "Custom"
|
||||||
|
4. **Save** and view your site with custom colors
|
||||||
|
|
||||||
|
**Note:** Custom color files are excluded from version control (`.gitignore`) to prevent fork-specific customizations from being committed.
|
||||||
|
|
||||||
|
**Quick Example:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root[data-bs-theme="light"] {
|
||||||
|
--color-primary: #1e40af;
|
||||||
|
--color-link: #2563eb;
|
||||||
|
--color-hover: #1d4ed8;
|
||||||
|
--body-color: #1f2937;
|
||||||
|
--body-bg: #ffffff;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Complete Reference:** See [CSS Variables Documentation](./docs/CSS_VARIABLES.md) for all available variables and detailed usage examples.
|
||||||
|
|
||||||
|
### Table of Contents
|
||||||
|
|
||||||
|
Enable automatic TOC for articles:
|
||||||
|
|
||||||
|
1. Edit an article in Joomla admin
|
||||||
|
2. Navigate to **Options → Layout**
|
||||||
|
3. Select **toc-left** or **toc-right**
|
||||||
|
4. Save the article
|
||||||
|
|
||||||
|
The TOC will automatically generate from article headings (H2, H3, etc.) and appear as a sidebar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Theme System
|
||||||
|
|
||||||
|
### Dark Mode Features
|
||||||
|
|
||||||
|
- **Automatic Detection**: Respects user's system preferences
|
||||||
|
- **Manual Toggle**: Floating button or radio controls
|
||||||
|
- **Persistence**: Saves preference in localStorage
|
||||||
|
- **Smooth Transitions**: Animated theme switching
|
||||||
|
- **Comprehensive Support**: All components themed for dark mode
|
||||||
|
|
||||||
|
### Theme Control Types
|
||||||
|
|
||||||
|
1. **Switch**: Simple light/dark toggle button
|
||||||
|
2. **Radios**: Three options - Light, Dark, System
|
||||||
|
3. **None**: No visible control (respects system only)
|
||||||
|
|
||||||
|
### Meta Tags
|
||||||
|
|
||||||
|
When enabled, the template adds:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<meta name="color-scheme" content="light dark">
|
||||||
|
<meta name="theme-color" content="#1e3a8a" media="(prefers-color-scheme: dark)">
|
||||||
|
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Development
|
||||||
|
|
||||||
|
### For Contributors
|
||||||
|
|
||||||
|
**New to the project?** See [Quick Start Guide](./docs/QUICK_START.md) for a 5-minute setup.
|
||||||
|
|
||||||
|
### Development Resources
|
||||||
|
|
||||||
|
- **[Quick Start Guide](./docs/QUICK_START.md)** - Setup and first steps
|
||||||
|
- **[Joomla Development Guide](./docs/JOOMLA_DEVELOPMENT.md)** - Testing, quality checks, deployment
|
||||||
|
- **[Workflow Guide](./docs/WORKFLOW_GUIDE.md)** - Git workflow and branching
|
||||||
|
- **[Contributing Guide](./CONTRIBUTING.md)** - Contribution guidelines
|
||||||
|
- **[Roadmap](./docs/ROADMAP.md)** - Feature roadmap and planning
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
|
||||||
|
- **Pre-commit Hooks**: Automatic validation before commits
|
||||||
|
- **PHP CodeSniffer**: Code style validation (Joomla standards)
|
||||||
|
- **PHPStan**: Static analysis for PHP code
|
||||||
|
- **Codeception**: Testing framework
|
||||||
|
|
||||||
|
### Quick Development Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia.git
|
||||||
|
cd MokoCassiopeia
|
||||||
|
|
||||||
|
# Install development dependencies (if using Composer)
|
||||||
|
composer install --dev
|
||||||
|
|
||||||
|
# Run code quality checks
|
||||||
|
make validate # or manual commands
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building Template Package
|
||||||
|
|
||||||
|
See [Joomla Development Guide](./docs/JOOMLA_DEVELOPMENT.md) for packaging instructions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
### User Documentation
|
||||||
|
|
||||||
|
- **[README](./README.md)** - This file (overview and features)
|
||||||
|
- **[CHANGELOG](./CHANGELOG.md)** - Version history and changes
|
||||||
|
- **[Roadmap](./docs/ROADMAP.md)** - Planned features and timeline
|
||||||
|
|
||||||
|
### Developer Documentation
|
||||||
|
|
||||||
|
- **[Quick Start](./docs/QUICK_START.md)** - 5-minute developer setup
|
||||||
|
- **[Development Guide](./docs/JOOMLA_DEVELOPMENT.md)** - Comprehensive development guide
|
||||||
|
- **[Workflow Guide](./docs/WORKFLOW_GUIDE.md)** - Git workflow and processes
|
||||||
|
- **[CSS Variables Reference](./docs/CSS_VARIABLES.md)** - Complete CSS customization guide
|
||||||
|
- **[Documentation Index](./docs/README.md)** - All documentation links
|
||||||
|
|
||||||
|
### Governance
|
||||||
|
|
||||||
|
- **[Contributing](./CONTRIBUTING.md)** - How to contribute
|
||||||
|
- **[Code of Conduct](./CODE_OF_CONDUCT.md)** - Community standards
|
||||||
|
- **[Governance](./GOVERNANCE.md)** - Project governance model
|
||||||
|
- **[Security Policy](./SECURITY.md)** - Security reporting and procedures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Changelog
|
||||||
|
|
||||||
|
See the [CHANGELOG.md](./CHANGELOG.md) for detailed version history.
|
||||||
|
|
||||||
|
### Recent Releases
|
||||||
|
|
||||||
|
- **[03.06.03]** (2026-01-30) - README updates and TOC color variable improvements
|
||||||
|
- **[03.06.02]** (2026-01-30) - Complete rebrand to MokoCassiopeia, removed all overrides
|
||||||
|
- **[03.06.00]** (2026-01-28) - Version standardization
|
||||||
|
- **[03.05.01]** (2026-01-09) - Security and compliance improvements
|
||||||
|
- **[02.00.00]** (2025-08-30) - Dark mode toggle and improved theming
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💬 Support
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
- **Documentation**: Check this README and [docs folder](./docs/)
|
||||||
|
- **Issues**: Report bugs via [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/issues)
|
||||||
|
- **Discussions**: Ask questions in [GitHub Discussions](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/discussions)
|
||||||
|
- **Roadmap**: View planned features in [Roadmap](https://mokoconsulting.tech/support/joomla-cms/mokocassiopeia-roadmap)
|
||||||
|
|
||||||
|
### Reporting Bugs
|
||||||
|
|
||||||
|
Please include:
|
||||||
|
- Joomla version
|
||||||
|
- PHP version
|
||||||
|
- Template version
|
||||||
|
- Steps to reproduce
|
||||||
|
- Expected vs actual behavior
|
||||||
|
- Screenshots (if applicable)
|
||||||
|
|
||||||
|
### Security Issues
|
||||||
|
|
||||||
|
**Do not** report security vulnerabilities via public issues. See [SECURITY.md](./SECURITY.md) for reporting procedures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
||||||
|
|
||||||
|
### How to Contribute
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Make your changes
|
||||||
|
4. Run quality checks
|
||||||
|
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
6. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
7. Open a Pull Request
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
See [Workflow Guide](./docs/WORKFLOW_GUIDE.md) for detailed Git workflow.
|
||||||
|
|
||||||
|
### Customizations
|
||||||
|
|
||||||
|
For template customizations, use Joomla's built-in template settings (System → Site Templates → MokoCassiopeia → Custom Code tab) for HTML/CSS/JS customizations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Included Libraries
|
||||||
|
|
||||||
|
MokoCassiopeia includes the following third-party libraries to provide enhanced functionality:
|
||||||
|
|
||||||
|
### Bootstrap TOC
|
||||||
|
|
||||||
|
- **Version**: 1.0.1
|
||||||
|
- **Author**: Aidan Feldman
|
||||||
|
- **License**: MIT License
|
||||||
|
- **Source**: [GitHub Repository](https://github.com/afeld/bootstrap-toc)
|
||||||
|
- **Release**: [v1.0.1 Release](https://github.com/afeld/bootstrap-toc/releases/tag/v1.0.1)
|
||||||
|
- **Purpose**: Automatically generates a table of contents from article headings with scrollspy support
|
||||||
|
- **Location**: `src/media/vendor/bootstrap-toc/`
|
||||||
|
- **Integration**: Registered in `joomla.asset.json` as `vendor.bootstrap-toc` (CSS) and `vendor.bootstrap-toc.js` (JavaScript)
|
||||||
|
- **Usage**: Activated when using `toc-left` or `toc-right` article layouts
|
||||||
|
- **Features**:
|
||||||
|
- Automatic TOC generation from H1-H6 headings
|
||||||
|
- Hierarchical nested navigation
|
||||||
|
- Active state highlighting with scrollspy
|
||||||
|
- Responsive design (collapses on mobile)
|
||||||
|
- Smooth scrolling to sections
|
||||||
|
- Automatic unique ID generation for headings
|
||||||
|
- **Customizations**: CSS adapted to use Cassiopeia CSS variables for theme compatibility
|
||||||
|
|
||||||
|
### Font Awesome 7 Free
|
||||||
|
|
||||||
|
- **Version**: 7.0 (Free)
|
||||||
|
- **License**: Font Awesome Free License
|
||||||
|
- **Source**: [Font Awesome](https://fontawesome.com)
|
||||||
|
- **Purpose**: Provides 2,000+ vector icons for interface elements
|
||||||
|
- **Location**: `src/media/vendor/fa7free/`
|
||||||
|
- **Integration**: Fully integrated into Joomla's asset manager
|
||||||
|
- **Styles Available**: Solid, Regular, Brands
|
||||||
|
|
||||||
|
### Bootstrap 5
|
||||||
|
|
||||||
|
- **Version**: 5.x (via Joomla)
|
||||||
|
- **License**: MIT License
|
||||||
|
- **Source**: [Bootstrap](https://getbootstrap.com)
|
||||||
|
- **Purpose**: Provides responsive grid system and utility classes
|
||||||
|
- **Integration**: Inherited from Joomla's Cassiopeia template, extended with additional helpers
|
||||||
|
- **Components Used**: Grid, utilities, modal, dropdown, collapse, offcanvas, tooltip, popover, scrollspy
|
||||||
|
|
||||||
|
### Integration Method
|
||||||
|
|
||||||
|
All third-party libraries are:
|
||||||
|
- ✅ Properly licensed and attributed
|
||||||
|
- ✅ Registered in Joomla's Web Asset Manager (`joomla.asset.json`)
|
||||||
|
- ✅ Loaded on-demand to optimize performance
|
||||||
|
- ✅ Versioned and documented for maintenance
|
||||||
|
- ✅ Compatible with Joomla 4.4.x and 5.x
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.
|
||||||
|
|
||||||
|
### Third-Party Licenses
|
||||||
|
|
||||||
|
- **Joomla! CMS**: GPL-2.0-or-later
|
||||||
|
- **Cassiopeia Template**: GPL-2.0-or-later (Joomla Project)
|
||||||
|
- **Font Awesome 7 Free**: Font Awesome Free License
|
||||||
|
- **Bootstrap 5**: MIT License
|
||||||
|
- **Bootstrap TOC**: MIT License (A. Feld)
|
||||||
|
|
||||||
|
All third-party libraries and assets remain the property of their respective authors and are credited in source files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Links
|
||||||
|
|
||||||
|
- **Repository**: [GitHub](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia)
|
||||||
|
- **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/issues)
|
||||||
|
- **Discussions**: [GitHub Discussions](https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/discussions)
|
||||||
|
- **Roadmap**: [Full Roadmap](https://mokoconsulting.tech/support/joomla-cms/mokocassiopeia-roadmap)
|
||||||
|
- **Moko Consulting**: [Website](https://mokoconsulting.tech)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Metadata
|
||||||
|
|
||||||
|
- **Maintainer**: Moko Consulting Engineering
|
||||||
|
- **Author**: Jonathan Miller (@jmiller-moko)
|
||||||
|
- **Repository**: https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia
|
||||||
|
- **License**: GPL-3.0-or-later
|
||||||
|
- **Classification**: Public Open Source Standards
|
||||||
|
|
||||||
|
## 📝 Revision History
|
||||||
|
|
||||||
|
| Date | Version | Change Summary | Author |
|
||||||
|
| ---------- | -------- | ------------------------------------------------------------------------- | ------------------------------- |
|
||||||
|
| 2026-01-30 | 03.06.03 | Updated README title, fixed custom color variables instructions, improved TOC color scheme integration | Copilot Agent |
|
||||||
|
| 2026-01-30 | 03.06.02 | Regenerated README with comprehensive documentation and updated structure | Copilot Agent |
|
||||||
|
| 2026-01-30 | 03.06.02 | Complete rebrand to MokoCassiopeia, removed overrides | Copilot Agent |
|
||||||
|
| 2026-01-05 | 03.00.00 | Initial publication of template documentation and feature overview | Moko Consulting |
|
||||||
|
| 2026-01-05 | 03.00.00 | Fixed malformed markdown table formatting in revision history | Jonathan Miller (@jmiller-moko) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with ❤️ by [Moko Consulting](https://mokoconsulting.tech)**
|
||||||
|
|||||||
@@ -10,9 +10,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"ext-zip": "*",
|
"php": ">=8.1",
|
||||||
"mokoconsulting-tech/enterprise": "dev-version/04",
|
"ext-zip": "*"
|
||||||
"php": ">=8.1"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mokoconsulting-tech/enterprise": "^4.0"
|
"mokoconsulting-tech/enterprise": "^4.0"
|
||||||
|
|||||||
@@ -122,14 +122,11 @@ class Tpl_MokocassiopeiaInstallerScript implements InstallerScriptInterface
|
|||||||
// 5. Redirect update server to MokoOnyx
|
// 5. Redirect update server to MokoOnyx
|
||||||
$this->updateUpdateServer();
|
$this->updateUpdateServer();
|
||||||
|
|
||||||
// 6. Unlock MokoCassiopeia (allow uninstall) + lock MokoOnyx (prevent accidental uninstall)
|
// 6. Notify
|
||||||
$this->updateExtensionLocks();
|
|
||||||
|
|
||||||
// 7. Notify
|
|
||||||
$app->enqueueMessage(
|
$app->enqueueMessage(
|
||||||
'<strong>MokoOnyx has been installed as a replacement for MokoCassiopeia.</strong><br>'
|
'<strong>MokoOnyx has been installed as a replacement for MokoCassiopeia.</strong><br>'
|
||||||
. 'Your template settings have been migrated. MokoOnyx is now your active site template.<br>'
|
. 'Your template settings have been migrated. MokoOnyx is now your active site template.<br>'
|
||||||
. 'MokoCassiopeia has been unlocked — you can uninstall it from Extensions → Manage.',
|
. 'You can safely uninstall MokoCassiopeia from Extensions → Manage.',
|
||||||
'success'
|
'success'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -365,42 +362,6 @@ class Tpl_MokocassiopeiaInstallerScript implements InstallerScriptInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateExtensionLocks(): void
|
|
||||||
{
|
|
||||||
$db = Factory::getDbo();
|
|
||||||
|
|
||||||
// Unlock MokoCassiopeia — allow uninstall
|
|
||||||
try {
|
|
||||||
$query = $db->getQuery(true)
|
|
||||||
->update('#__extensions')
|
|
||||||
->set($db->quoteName('locked') . ' = 0')
|
|
||||||
->set($db->quoteName('protected') . ' = 0')
|
|
||||||
->where($db->quoteName('element') . ' = ' . $db->quote(self::OLD_NAME))
|
|
||||||
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
|
||||||
$db->setQuery($query)->execute();
|
|
||||||
if ($db->getAffectedRows() > 0) {
|
|
||||||
$this->log('Bridge: unlocked MokoCassiopeia (can be uninstalled).');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->log('Bridge: failed to unlock MokoCassiopeia: ' . $e->getMessage(), 'warning');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock MokoOnyx — prevent accidental uninstall
|
|
||||||
try {
|
|
||||||
$query = $db->getQuery(true)
|
|
||||||
->update('#__extensions')
|
|
||||||
->set($db->quoteName('locked') . ' = 1')
|
|
||||||
->where($db->quoteName('element') . ' = ' . $db->quote(self::NEW_NAME))
|
|
||||||
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
|
||||||
$db->setQuery($query)->execute();
|
|
||||||
if ($db->getAffectedRows() > 0) {
|
|
||||||
$this->log('Bridge: locked MokoOnyx (protected from uninstall).');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->log('Bridge: failed to lock MokoOnyx: ' . $e->getMessage(), 'warning');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Logging ────────────────────────────────────────────────────────
|
// ── Logging ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private function log(string $message, string $priority = 'info'): void
|
private function log(string $message, string $priority = 'info'): void
|
||||||
|
|||||||
67
updates.xml
67
updates.xml
@@ -1,28 +1,25 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
VERSION: 03.10.23
|
VERSION: 03.10.18
|
||||||
NOTE: This repository is RETIRED. All channels point to the same final stable release.
|
|
||||||
All future development is at https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<updates>
|
<updates>
|
||||||
|
|
||||||
<!-- All channels point to the same final release so every site sees the update -->
|
<!-- 1. DEVELOPMENT — dev → -->
|
||||||
|
|
||||||
<update>
|
<update>
|
||||||
<name>MokoCassiopeia</name>
|
<name>MokoCassiopeia</name>
|
||||||
<description>MokoCassiopeia is retired. Please install MokoOnyx instead.</description>
|
<description>MokoCassiopeia development build — unstable.</description>
|
||||||
<element>mokocassiopeia</element>
|
<element>mokocassiopeia</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>03.10.23</version>
|
<version>03.10.21</version>
|
||||||
<creationDate>2026-04-21</creationDate>
|
<creationDate>2026-04-21</creationDate>
|
||||||
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
|
<infourl title='MokoCassiopeia Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/development</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.10.23.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.10.21-dev.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>314ead3bafbaea370796b7ed9d8353ae9964becbf7ccf9be09e94229973440fc</sha256>
|
<sha256>2d21714719dd3e3d87228e1d021d5fc69a96a837a9ec2d5880da733eb28fa5d0</sha256>
|
||||||
<tags><tag>development</tag></tags>
|
<tags><tag>development</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
@@ -30,19 +27,20 @@
|
|||||||
<php_minimum>8.1</php_minimum>
|
<php_minimum>8.1</php_minimum>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<!-- 2. ALPHA — dev → alpha → -->
|
||||||
<update>
|
<update>
|
||||||
<name>MokoCassiopeia</name>
|
<name>MokoCassiopeia</name>
|
||||||
<description>MokoCassiopeia is retired. Please install MokoOnyx instead.</description>
|
<description>MokoCassiopeia alpha build — early testing.</description>
|
||||||
<element>mokocassiopeia</element>
|
<element>mokocassiopeia</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>03.10.23</version>
|
<version>03.10.13</version>
|
||||||
<creationDate>2026-04-21</creationDate>
|
<creationDate>2026-04-19</creationDate>
|
||||||
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
|
<infourl title='MokoCassiopeia Alpha'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/alpha</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.10.23.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>314ead3bafbaea370796b7ed9d8353ae9964becbf7ccf9be09e94229973440fc</sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>alpha</tag></tags>
|
<tags><tag>alpha</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
@@ -50,19 +48,20 @@
|
|||||||
<php_minimum>8.1</php_minimum>
|
<php_minimum>8.1</php_minimum>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<!-- 3. BETA — dev → alpha → beta → -->
|
||||||
<update>
|
<update>
|
||||||
<name>MokoCassiopeia</name>
|
<name>MokoCassiopeia</name>
|
||||||
<description>MokoCassiopeia is retired. Please install MokoOnyx instead.</description>
|
<description>MokoCassiopeia beta build — feature complete, stability testing.</description>
|
||||||
<element>mokocassiopeia</element>
|
<element>mokocassiopeia</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>03.10.23</version>
|
<version>03.10.13</version>
|
||||||
<creationDate>2026-04-21</creationDate>
|
<creationDate>2026-04-19</creationDate>
|
||||||
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
|
<infourl title='MokoCassiopeia Beta'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/beta</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.10.23.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>314ead3bafbaea370796b7ed9d8353ae9964becbf7ccf9be09e94229973440fc</sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>beta</tag></tags>
|
<tags><tag>beta</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
@@ -70,19 +69,20 @@
|
|||||||
<php_minimum>8.1</php_minimum>
|
<php_minimum>8.1</php_minimum>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<!-- 4. RC — dev → alpha → beta → rc → -->
|
||||||
<update>
|
<update>
|
||||||
<name>MokoCassiopeia</name>
|
<name>MokoCassiopeia</name>
|
||||||
<description>MokoCassiopeia is retired. Please install MokoOnyx instead.</description>
|
<description>MokoCassiopeia release candidate — testing only.</description>
|
||||||
<element>mokocassiopeia</element>
|
<element>mokocassiopeia</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>03.10.23</version>
|
<version>03.10.13</version>
|
||||||
<creationDate>2026-04-21</creationDate>
|
<creationDate>2026-04-19</creationDate>
|
||||||
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
|
<infourl title='MokoCassiopeia RC'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/release-candidate</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.10.23.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>314ead3bafbaea370796b7ed9d8353ae9964becbf7ccf9be09e94229973440fc</sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>rc</tag></tags>
|
<tags><tag>rc</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
@@ -90,19 +90,20 @@
|
|||||||
<php_minimum>8.1</php_minimum>
|
<php_minimum>8.1</php_minimum>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<!-- 5. STABLE — dev → alpha → beta → rc → version/XX → main -->
|
||||||
<update>
|
<update>
|
||||||
<name>MokoCassiopeia</name>
|
<name>MokoCassiopeia</name>
|
||||||
<description>MokoCassiopeia is retired. Please install MokoOnyx instead.</description>
|
<description>Moko Consulting's site template based on Cassiopeia.</description>
|
||||||
<element>mokocassiopeia</element>
|
<element>mokocassiopeia</element>
|
||||||
<type>template</type>
|
<type>template</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<version>03.10.23</version>
|
<version>03.10.13</version>
|
||||||
<creationDate>2026-04-21</creationDate>
|
<creationDate>2026-04-19</creationDate>
|
||||||
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
|
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.10.23.zip</downloadurl>
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.10.13.zip</downloadurl>
|
||||||
</downloads>
|
</downloads>
|
||||||
<sha256>314ead3bafbaea370796b7ed9d8353ae9964becbf7ccf9be09e94229973440fc</sha256>
|
<sha256></sha256>
|
||||||
<tags><tag>stable</tag></tags>
|
<tags><tag>stable</tag></tags>
|
||||||
<maintainer>Moko Consulting</maintainer>
|
<maintainer>Moko Consulting</maintainer>
|
||||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
|
|||||||
Reference in New Issue
Block a user