feat: add release workflow + migrate MokoCassiopeia styles on install
- .gitea/workflows/release.yml: auto-bump, build ZIP, Gitea release, per-channel updates.xml targeting (same as MokoCassiopeia) - script.php: on install, detect MokoCassiopeia styles, create matching MokoOnyx style copies with same params, set default, copy user files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
517
.gitea/workflows/release.yml
Normal file
517
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# This file is part of a Moko Consulting project.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoOnyx.Release
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||||
|
# PATH: /.gitea/workflows/release.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Joomla release — build ZIP, publish to Gitea, mirror to GitHub
|
||||||
|
|
||||||
|
name: Create Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '[0-9][0-9].[0-9][0-9].[0-9][0-9]'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Release version (e.g., 01.00.00)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
prerelease:
|
||||||
|
description: 'Mark as pre-release'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
stability:
|
||||||
|
description: 'Stability tag (development, alpha, beta, rc, stable)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'development'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_URL: https://git.mokoconsulting.tech
|
||||||
|
GITEA_ORG: MokoConsulting
|
||||||
|
GITEA_REPO: MokoOnyx
|
||||||
|
EXT_ELEMENT: mokoonyx
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Release Package
|
||||||
|
runs-on: release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
run: |
|
||||||
|
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
|
||||||
|
php -v
|
||||||
|
composer --version
|
||||||
|
|
||||||
|
- name: Get version and stability
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
STABILITY="${{ inputs.stability }}"
|
||||||
|
PRERELEASE="${{ inputs.prerelease }}"
|
||||||
|
else
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/}
|
||||||
|
STABILITY="stable"
|
||||||
|
PRERELEASE="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Derive suffix and tag from stability
|
||||||
|
case "$STABILITY" in
|
||||||
|
development) SUFFIX="-dev"; TAG_NAME="development" ;;
|
||||||
|
alpha) SUFFIX="-alpha"; TAG_NAME="alpha" ;;
|
||||||
|
beta) SUFFIX="-beta"; TAG_NAME="beta" ;;
|
||||||
|
rc) SUFFIX="-rc"; TAG_NAME="release-candidate" ;;
|
||||||
|
stable) SUFFIX=""; TAG_NAME="v${VERSION%%.*}" ;;
|
||||||
|
*) SUFFIX="-dev"; TAG_NAME="development" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
|
||||||
|
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "prerelease=${PRERELEASE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Building: ${ZIP_NAME} (${STABILITY})"
|
||||||
|
|
||||||
|
- name: Auto-bump patch version
|
||||||
|
id: bump
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
INPUT_VERSION: ${{ steps.meta.outputs.version }}
|
||||||
|
INPUT_STABILITY: ${{ steps.meta.outputs.stability }}
|
||||||
|
INPUT_SUFFIX: ${{ steps.meta.outputs.suffix }}
|
||||||
|
run: |
|
||||||
|
# Read current version from README.md
|
||||||
|
CURRENT=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
|
||||||
|
if [ -z "$CURRENT" ]; then
|
||||||
|
echo "No VERSION in README.md — using input version"
|
||||||
|
echo "version=${INPUT_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "zip_name=${EXT_ELEMENT}-${INPUT_VERSION}${INPUT_SUFFIX}.zip" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bump patch: XX.YY.ZZ → XX.YY.(ZZ+1)
|
||||||
|
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
|
||||||
|
MINOR=$(echo "$CURRENT" | cut -d. -f2)
|
||||||
|
PATCH=$(echo "$CURRENT" | cut -d. -f3)
|
||||||
|
NEW_PATCH=$(printf "%02d" $((10#$PATCH + 1)))
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
||||||
|
|
||||||
|
echo "Bumping: ${CURRENT} → ${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Update README.md
|
||||||
|
sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${NEW_VERSION}/" README.md
|
||||||
|
|
||||||
|
# Update templateDetails.xml / manifest
|
||||||
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
|
if [ -n "$MANIFEST" ]; then
|
||||||
|
sed -i "s|<version>${CURRENT}</version>|<version>${NEW_VERSION}</version>|" "$MANIFEST"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update only the matching stability channel in updates.xml
|
||||||
|
if [ -f "updates.xml" ]; then
|
||||||
|
export PY_OLD="$CURRENT" PY_NEW="$NEW_VERSION" PY_STABILITY="$INPUT_STABILITY"
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
import re, os
|
||||||
|
old = os.environ["PY_OLD"]
|
||||||
|
new = os.environ["PY_NEW"]
|
||||||
|
stability = os.environ["PY_STABILITY"]
|
||||||
|
with open("updates.xml") as f:
|
||||||
|
content = f.read()
|
||||||
|
pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(stability) + r"</tag>.*?</update>)"
|
||||||
|
match = re.search(pattern, content, re.DOTALL)
|
||||||
|
if match:
|
||||||
|
block = match.group(1)
|
||||||
|
updated = block.replace(old, new)
|
||||||
|
content = content.replace(block, updated)
|
||||||
|
with open("updates.xml", "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"Updated {stability} channel: {old} -> {new}")
|
||||||
|
PYEOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit bump
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git remote set-url origin "https://jmiller:${GA_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
git add -A
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore(version): bump ${CURRENT} → ${NEW_VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "zip_name=${EXT_ELEMENT}-${NEW_VERSION}${INPUT_SUFFIX}.zip" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create package
|
||||||
|
run: |
|
||||||
|
mkdir -p build/package
|
||||||
|
rsync -av \
|
||||||
|
--exclude='sftp-config*' \
|
||||||
|
--exclude='.ftpignore' \
|
||||||
|
--exclude='*.ppk' \
|
||||||
|
--exclude='*.pem' \
|
||||||
|
--exclude='*.key' \
|
||||||
|
--exclude='.env*' \
|
||||||
|
--exclude='*.local' \
|
||||||
|
src/ build/package/
|
||||||
|
|
||||||
|
- name: Build ZIP
|
||||||
|
id: zip
|
||||||
|
run: |
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
cd build/package
|
||||||
|
zip -r "../${ZIP_NAME}" .
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
|
||||||
|
SIZE=$(stat -c%s "${ZIP_NAME}")
|
||||||
|
|
||||||
|
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "size=${SIZE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "SHA-256: ${SHA256}"
|
||||||
|
echo "Size: ${SIZE} bytes"
|
||||||
|
|
||||||
|
# ── Gitea Release (PRIMARY) ──────────────────────────────────────
|
||||||
|
- name: "Gitea: Delete existing release"
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
# Find and delete existing release by tag (may not exist — ignore 404)
|
||||||
|
RELEASE_ID=$(curl -s -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/releases/tags/${TAG}" 2>/dev/null | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/releases/${RELEASE_ID}" || true
|
||||||
|
echo "Deleted existing release id=${RELEASE_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete existing tag
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/tags/${TAG}" 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: "Gitea: Create release"
|
||||||
|
id: gitea_release
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
PRERELEASE="${{ steps.meta.outputs.prerelease }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
# Build release body
|
||||||
|
BODY="## ${EXT_ELEMENT} ${VERSION} (${STABILITY})
|
||||||
|
|
||||||
|
### SHA-256
|
||||||
|
\`${SHA256}\`"
|
||||||
|
|
||||||
|
# Extract changelog if available
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
NOTES=$(awk "/## \[${VERSION}\]/,/## \[/{if(/## \[${VERSION}\]/)next;if(/## \[/)exit;print}" CHANGELOG.md)
|
||||||
|
if [ -n "$NOTES" ]; then
|
||||||
|
BODY="## ${EXT_ELEMENT} ${VERSION} (${STABILITY})
|
||||||
|
|
||||||
|
${NOTES}
|
||||||
|
|
||||||
|
### SHA-256
|
||||||
|
\`${SHA256}\`"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
IS_PRE="true"
|
||||||
|
if [ "$STABILITY" = "stable" ]; then
|
||||||
|
IS_PRE="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESULT=$(curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API}/releases" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg tag "$TAG" \
|
||||||
|
--arg target "${{ github.ref_name }}" \
|
||||||
|
--arg name "${EXT_ELEMENT} ${VERSION} ${STABILITY^}" \
|
||||||
|
--arg body "$BODY" \
|
||||||
|
--argjson pre "$IS_PRE" \
|
||||||
|
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: $pre}'
|
||||||
|
)")
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "$RESULT" | jq -r '.id')
|
||||||
|
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Gitea release created: id=${RELEASE_ID}, tag=${TAG}"
|
||||||
|
|
||||||
|
- name: "Gitea: Upload ZIP"
|
||||||
|
run: |
|
||||||
|
RELEASE_ID="${{ steps.gitea_release.outputs.release_id }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
||||||
|
--data-binary "@build/${ZIP_NAME}"
|
||||||
|
|
||||||
|
echo "Uploaded ${ZIP_NAME} to Gitea release ${RELEASE_ID}"
|
||||||
|
|
||||||
|
# ── GitHub Mirror (BACKUP) ───────────────────────────────────────
|
||||||
|
- name: "GitHub: Mirror release (stable/rc only)"
|
||||||
|
if: ${{ steps.meta.outputs.stability == 'stable' || steps.meta.outputs.stability == 'rc' }}
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
TOKEN="${{ secrets.GH_TOKEN }}"
|
||||||
|
GH_REPO="mokoconsulting-tech/${GITEA_REPO}"
|
||||||
|
GH_API="https://api.github.com/repos/${GH_REPO}"
|
||||||
|
|
||||||
|
IS_PRE="true"
|
||||||
|
[ "$STABILITY" = "stable" ] && IS_PRE="false"
|
||||||
|
|
||||||
|
# Delete existing release by tag
|
||||||
|
EXISTING=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${GH_API}/releases/tags/${TAG}" 2>/dev/null | jq -r '.id // empty')
|
||||||
|
if [ -n "$EXISTING" ]; then
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${GH_API}/releases/${EXISTING}" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete tag
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${GH_API}/git/refs/tags/${TAG}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create release
|
||||||
|
RELEASE_ID=$(curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${GH_API}/releases" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg tag "$TAG" \
|
||||||
|
--arg target "${{ github.sha }}" \
|
||||||
|
--arg name "${EXT_ELEMENT} ${VERSION} ${STABILITY^} (mirror)" \
|
||||||
|
--arg body "Mirror of Gitea release. SHA-256: \`${SHA256}\`" \
|
||||||
|
--argjson pre "$IS_PRE" \
|
||||||
|
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: $pre, draft: false}'
|
||||||
|
)" | jq -r '.id')
|
||||||
|
|
||||||
|
# Upload ZIP
|
||||||
|
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "null" ]; then
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
"https://uploads.github.com/repos/${GH_REPO}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
||||||
|
--data-binary "@build/${ZIP_NAME}"
|
||||||
|
echo "GitHub mirror: uploaded ${ZIP_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Update updates.xml ──────────────────────────────────────────
|
||||||
|
- name: "Update updates.xml for this channel"
|
||||||
|
run: |
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
|
if [ ! -f "updates.xml" ] || [ -z "$SHA256" ]; then
|
||||||
|
echo "No updates.xml or no SHA — skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \
|
||||||
|
PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \
|
||||||
|
PY_GITEA_ORG="$GITEA_ORG" PY_GITEA_REPO="$GITEA_REPO"
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
import re, os
|
||||||
|
|
||||||
|
stability = os.environ["PY_STABILITY"]
|
||||||
|
version = os.environ["PY_VERSION"]
|
||||||
|
sha256 = os.environ["PY_SHA256"]
|
||||||
|
zip_name = os.environ["PY_ZIP_NAME"]
|
||||||
|
tag = os.environ["PY_TAG"]
|
||||||
|
date = os.environ["PY_DATE"]
|
||||||
|
gitea_org = os.environ["PY_GITEA_ORG"]
|
||||||
|
gitea_repo = os.environ["PY_GITEA_REPO"]
|
||||||
|
|
||||||
|
# Map stability to the <tag> value in updates.xml
|
||||||
|
tag_map = {
|
||||||
|
"development": "development",
|
||||||
|
"alpha": "alpha",
|
||||||
|
"beta": "beta",
|
||||||
|
"rc": "rc",
|
||||||
|
"stable": "stable",
|
||||||
|
}
|
||||||
|
xml_tag = tag_map.get(stability, "development")
|
||||||
|
|
||||||
|
with open("updates.xml", "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Build regex to find the specific <update> block for this stability tag
|
||||||
|
# Use negative lookahead to avoid matching across multiple <update> blocks
|
||||||
|
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(xml_tag) + r"</tag>.*?</update>)"
|
||||||
|
match = re.search(block_pattern, content, re.DOTALL)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
print(f"No <update> block found for <tag>{xml_tag}</tag>")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
block = match.group(1)
|
||||||
|
original_block = block
|
||||||
|
|
||||||
|
# Update version
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Update SHA-256
|
||||||
|
block = re.sub(r"<sha256>[^<]*</sha256>", f"<sha256>{sha256}</sha256>", block)
|
||||||
|
|
||||||
|
# Update Gitea download URL
|
||||||
|
gitea_url = f"https://git.mokoconsulting.tech/{gitea_org}/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
||||||
|
block = re.sub(
|
||||||
|
r"(<downloadurl[^>]*>)https://git\.mokoconsulting\.tech/[^<]*(</downloadurl>)",
|
||||||
|
rf"\g<1>{gitea_url}\g<2>",
|
||||||
|
block
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update GitHub download URL only for RC and stable (others are Gitea-only)
|
||||||
|
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:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
print(f"Updated {xml_tag} channel: version={version}, sha={sha256[:16]}..., date={date}")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
- name: "Commit updates.xml to current branch and main"
|
||||||
|
run: |
|
||||||
|
if git diff --quiet updates.xml 2>/dev/null; then
|
||||||
|
echo "No changes to updates.xml"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||||
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git add updates.xml
|
||||||
|
git commit -m "chore: update ${STABILITY} SHA-256 for ${VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
|
||||||
|
# Set push URL with GA_TOKEN for authenticated pushes (branch protection requires jmiller)
|
||||||
|
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
# Push to current branch
|
||||||
|
git push || true
|
||||||
|
|
||||||
|
# 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 }}"
|
||||||
|
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}" \
|
||||||
|
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
||||||
|
|
||||||
|
if [ -n "$FILE_SHA" ]; then
|
||||||
|
# Base64-encode the updates.xml content from working tree (has updated SHA)
|
||||||
|
CONTENT=$(base64 -w0 updates.xml)
|
||||||
|
|
||||||
|
RESPONSE=$(curl -s -w "\n%{http_code}" -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: update ${STABILITY} channel to ${VERSION} on main [skip ci]" \
|
||||||
|
--arg branch "main" \
|
||||||
|
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
||||||
|
)")
|
||||||
|
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||||
|
ZIP_NAME="${{ steps.bump.outputs.zip_name }}"
|
||||||
|
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||||
|
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||||
|
|
||||||
|
echo "### Release Created" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Stability | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Tag | \`${TAG}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| SHA-256 | \`${SHA256}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Gitea | [Release](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${TAG}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -96,13 +96,14 @@ class Tpl_MokoonyxInstallerScript
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect MokoCassiopeia and migrate styles, params, and user files to MokoOnyx.
|
* Detect MokoCassiopeia and create matching MokoOnyx styles with the same params.
|
||||||
|
* Creates a MokoOnyx style copy for each MokoCassiopeia style.
|
||||||
*/
|
*/
|
||||||
private function migrateFromCassiopeia(): void
|
private function migrateFromCassiopeia(): void
|
||||||
{
|
{
|
||||||
$db = Factory::getDbo();
|
$db = Factory::getDbo();
|
||||||
|
|
||||||
// Check if MokoCassiopeia has any template styles
|
// Get all MokoCassiopeia styles
|
||||||
$query = $db->getQuery(true)
|
$query = $db->getQuery(true)
|
||||||
->select('*')
|
->select('*')
|
||||||
->from('#__template_styles')
|
->from('#__template_styles')
|
||||||
@@ -115,69 +116,74 @@ class Tpl_MokoonyxInstallerScript
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logMessage('MokoCassiopeia detected — migrating ' . count($oldStyles) . ' style(s).');
|
$this->logMessage('MokoCassiopeia detected — creating ' . count($oldStyles) . ' matching MokoOnyx style(s).');
|
||||||
|
|
||||||
|
// Get the installer-created default MokoOnyx style (to apply params to it)
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('id')
|
||||||
|
->from('#__template_styles')
|
||||||
|
->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
|
||||||
|
->where($db->quoteName('client_id') . ' = 0')
|
||||||
|
->order($db->quoteName('id') . ' ASC');
|
||||||
|
$defaultOnyxId = (int) $db->setQuery($query, 0, 1)->loadResult();
|
||||||
|
|
||||||
|
$firstStyle = true;
|
||||||
|
|
||||||
// 1. Copy template styles with params
|
|
||||||
foreach ($oldStyles as $oldStyle) {
|
foreach ($oldStyles as $oldStyle) {
|
||||||
$newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $oldStyle->title);
|
$newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $oldStyle->title);
|
||||||
$newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle);
|
$newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle);
|
||||||
|
|
||||||
// Check if MokoOnyx already has a style with this title
|
$params = is_string($oldStyle->params)
|
||||||
$check = $db->getQuery(true)
|
? str_replace(self::OLD_NAME, self::NEW_NAME, $oldStyle->params)
|
||||||
->select('COUNT(*)')
|
: $oldStyle->params;
|
||||||
->from('#__template_styles')
|
|
||||||
->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
|
|
||||||
->where($db->quoteName('title') . ' = ' . $db->quote($newTitle));
|
|
||||||
if ((int) $db->setQuery($check)->loadResult() > 0) {
|
|
||||||
// Update existing MokoOnyx style with MokoCassiopeia's params
|
|
||||||
$params = is_string($oldStyle->params)
|
|
||||||
? str_replace(self::OLD_NAME, self::NEW_NAME, $oldStyle->params)
|
|
||||||
: $oldStyle->params;
|
|
||||||
|
|
||||||
|
if ($firstStyle && $defaultOnyxId) {
|
||||||
|
// Update the installer-created default style with the first MokoCassiopeia style's params
|
||||||
$update = $db->getQuery(true)
|
$update = $db->getQuery(true)
|
||||||
->update('#__template_styles')
|
->update('#__template_styles')
|
||||||
->set($db->quoteName('params') . ' = ' . $db->quote($params))
|
->set($db->quoteName('params') . ' = ' . $db->quote($params))
|
||||||
->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
|
->set($db->quoteName('title') . ' = ' . $db->quote($newTitle))
|
||||||
->where($db->quoteName('title') . ' = ' . $db->quote($newTitle));
|
->where('id = ' . $defaultOnyxId);
|
||||||
$db->setQuery($update)->execute();
|
$db->setQuery($update)->execute();
|
||||||
$this->logMessage("Updated existing MokoOnyx style: {$newTitle}");
|
|
||||||
|
// Set as default if MokoCassiopeia was default
|
||||||
|
if ($oldStyle->home == 1) {
|
||||||
|
$db->setQuery(
|
||||||
|
$db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('home') . ' = 1')
|
||||||
|
->where('id = ' . $defaultOnyxId)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
$db->setQuery(
|
||||||
|
$db->getQuery(true)
|
||||||
|
->update('#__template_styles')
|
||||||
|
->set($db->quoteName('home') . ' = 0')
|
||||||
|
->where('id = ' . (int) $oldStyle->id)
|
||||||
|
)->execute();
|
||||||
|
|
||||||
|
$this->logMessage('Set MokoOnyx as default site template.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logMessage("Updated default MokoOnyx style with params: {$newTitle}");
|
||||||
|
$firstStyle = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new MokoOnyx style from MokoCassiopeia style
|
// For additional styles: create new MokoOnyx style copies
|
||||||
$newStyle = clone $oldStyle;
|
$newStyle = clone $oldStyle;
|
||||||
unset($newStyle->id);
|
unset($newStyle->id);
|
||||||
$newStyle->template = self::NEW_NAME;
|
$newStyle->template = self::NEW_NAME;
|
||||||
$newStyle->title = $newTitle;
|
$newStyle->title = $newTitle;
|
||||||
$newStyle->home = 0; // Don't set as default yet
|
$newStyle->home = 0;
|
||||||
|
$newStyle->params = $params;
|
||||||
|
|
||||||
if (is_string($newStyle->params)) {
|
try {
|
||||||
$newStyle->params = str_replace(self::OLD_NAME, self::NEW_NAME, $newStyle->params);
|
$db->insertObject('#__template_styles', $newStyle, 'id');
|
||||||
|
$this->logMessage("Created MokoOnyx style: {$newTitle}");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logMessage("Failed to create style {$newTitle}: " . $e->getMessage(), 'warning');
|
||||||
}
|
}
|
||||||
|
|
||||||
$db->insertObject('#__template_styles', $newStyle, 'id');
|
|
||||||
$newId = $newStyle->id;
|
|
||||||
|
|
||||||
// If the old style was the default, make the new one default
|
|
||||||
if ($oldStyle->home == 1) {
|
|
||||||
$db->setQuery(
|
|
||||||
$db->getQuery(true)
|
|
||||||
->update('#__template_styles')
|
|
||||||
->set($db->quoteName('home') . ' = 1')
|
|
||||||
->where('id = ' . (int) $newId)
|
|
||||||
)->execute();
|
|
||||||
|
|
||||||
$db->setQuery(
|
|
||||||
$db->getQuery(true)
|
|
||||||
->update('#__template_styles')
|
|
||||||
->set($db->quoteName('home') . ' = 0')
|
|
||||||
->where('id = ' . (int) $oldStyle->id)
|
|
||||||
)->execute();
|
|
||||||
|
|
||||||
$this->logMessage('Set MokoOnyx as default site template.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logMessage("Migrated style: {$oldStyle->title} → {$newTitle}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Copy user files (custom themes, user.css, user.js)
|
// 2. Copy user files (custom themes, user.css, user.js)
|
||||||
|
|||||||
Reference in New Issue
Block a user