chore: update .github/workflows/update-server.yml from MokoStandards

This commit is contained in:
2026-04-09 10:51:29 -05:00
parent dca8c56ec4
commit 3aa6ab2a58

View File

@@ -6,8 +6,8 @@
# DEFGROUP: GitHub.Workflow # DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Joomla # INGROUP: MokoStandards.Joomla
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/joomla/update-server.yml.template # PATH: /templates/workflows/joomla/update-server.yml
# VERSION: 04.06.00 # VERSION: 04.05.13
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries # BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
# #
# Writes updates.xml with multiple <update> entries: # Writes updates.xml with multiple <update> entries:
@@ -20,12 +20,9 @@
name: Update Joomla Update Server XML Feed name: Update Joomla Update Server XML Feed
on: on:
pull_request: push:
types: [closed]
branches: branches:
- 'dev/**' - 'dev/**'
- 'alpha/**'
- 'beta/**'
- 'rc/**' - 'rc/**'
paths: paths:
- 'src/**' - 'src/**'
@@ -33,14 +30,12 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
stability: stability:
description: 'Stability tag' description: 'Stability tag (development, rc, stable)'
required: true required: true
default: 'development' default: 'development'
type: choice type: choice
options: options:
- development - development
- alpha
- beta
- rc - rc
- stable - stable
@@ -54,8 +49,6 @@ jobs:
update-xml: update-xml:
name: Update updates.xml name: Update updates.xml
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -82,64 +75,33 @@ jobs:
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
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 alpha/beta/rc branches (not dev — dev bumps manually)
if [[ "$BRANCH" != dev/* ]]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
BUMPED=$(php /tmp/mokostandards/api/cli/version_bump.php --path . 2>/dev/null || true)
if [ -n "$BUMPED" ]; then
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
git add -A
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>" 2>/dev/null || true
git push 2>/dev/null || true
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
STABILITY="${{ inputs.stability }}" STABILITY="${{ inputs.stability }}"
elif [[ "$BRANCH" == rc/* ]]; then elif [[ "$BRANCH" == rc/* ]]; then
STABILITY="rc" STABILITY="rc"
elif [[ "$BRANCH" == beta/* ]]; then
STABILITY="beta"
elif [[ "$BRANCH" == alpha/* ]]; then
STABILITY="alpha"
elif [[ "$BRANCH" == dev/* ]]; then elif [[ "$BRANCH" == dev/* ]]; then
STABILITY="development" STABILITY="development"
else else
STABILITY="stable" STABILITY="stable"
fi fi
# Parse manifest (portable — no grep -P) # Parse manifest
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
echo "No Joomla manifest found — skipping" echo "No Joomla manifest found — skipping"
exit 0 exit 0
fi fi
# Extract fields using sed (works on all runners) EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
EXT_NAME=$(sed -n 's/.*<name>\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_CLIENT=$(sed -n 's/.*<extension[^>]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
EXT_VERSION=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
# Fallbacks
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
# Templates and modules don't have <element> — derive from <name>
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
fi
# Use manifest version if README version is empty
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml)
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/") [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
CLIENT_TAG="" CLIENT_TAG=""
@@ -154,117 +116,127 @@ jobs:
# Version suffix for non-stable # Version suffix for non-stable
DISPLAY_VERSION="$VERSION" DISPLAY_VERSION="$VERSION"
case "$STABILITY" in [ "$STABILITY" = "rc" ] && DISPLAY_VERSION="${VERSION}-rc"
development) DISPLAY_VERSION="${VERSION}-dev" ;; [ "$STABILITY" = "development" ] && DISPLAY_VERSION="${VERSION}-dev"
alpha) DISPLAY_VERSION="${VERSION}-alpha" ;;
beta) DISPLAY_VERSION="${VERSION}-beta" ;;
rc) DISPLAY_VERSION="${VERSION}-rc" ;;
esac
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
RELEASE_TAG="v${MAJOR}"
# Each stability level has its own release tag
case "$STABILITY" in
development) RELEASE_TAG="development" ;;
alpha) RELEASE_TAG="alpha" ;;
beta) RELEASE_TAG="beta" ;;
rc) RELEASE_TAG="release-candidate" ;;
*) RELEASE_TAG="v${MAJOR}" ;;
esac
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
INFO_URL="https://github.com/${REPO}" INFO_URL="https://github.com/${REPO}"
# ── Build install packages (ZIP + tar.gz) ─────────────────── # ── Build install-ready ZIP ─────────────────────────────────
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
EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
cd "$SOURCE_DIR" cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES zip -r "/tmp/${PACKAGE_NAME}" .
cd .. cd ..
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
--exclude='.ftpignore' --exclude='sftp-config*' \
--exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1) SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
# Ensure release exists # Ensure draft release exists for this major
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \ gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \
gh release create "$RELEASE_TAG" --title "${RELEASE_TAG} (${DISPLAY_VERSION})" --notes "${STABILITY} release" --prerelease --target main 2>/dev/null || true gh release create "$RELEASE_TAG" --title "v${MAJOR}" --notes "Development release" --draft --target main 2>/dev/null || true
# Upload both formats # Upload ZIP to the major release
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true
gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY echo "Package: ${PACKAGE_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
else else
SHA256="" SHA256=""
fi fi
# ── Build the new entry ─────────────────────────────────────── # ── Build the new entry ───────────────────────────────────────
NEW_ENTRY="" NEW_ENTRY=$(cat <<XMLEOF
NEW_ENTRY="${NEW_ENTRY} <update>\n" <update>
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n" <name>${EXT_NAME}</name>
NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} (${STABILITY})</description>\n" <description>${EXT_NAME} (${STABILITY})</description>
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n" <element>${EXT_ELEMENT}</element>
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n" <type>${EXT_TYPE}</type>
NEW_ENTRY="${NEW_ENTRY} <version>${DISPLAY_VERSION}</version>\n" <version>${DISPLAY_VERSION}</version>
[ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n" $([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}")
[ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n" $([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}")
NEW_ENTRY="${NEW_ENTRY} <tags>\n" <tags>
NEW_ENTRY="${NEW_ENTRY} <tag>${STABILITY}</tag>\n" <tag>${STABILITY}</tag>
NEW_ENTRY="${NEW_ENTRY} </tags>\n" </tags>
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n" <infourl title="${EXT_NAME}">${INFO_URL}</infourl>
NEW_ENTRY="${NEW_ENTRY} <downloads>\n" <downloads>
TAR_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz" <downloadurl type="full" format="zip">${DOWNLOAD_URL}</downloadurl>
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n" </downloads>
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n" $([ -n "$SHA256" ] && echo " <sha256>sha256:${SHA256}</sha256>")
NEW_ENTRY="${NEW_ENTRY} </downloads>\n" ${TARGET_PLATFORM}
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>sha256:${SHA256}</sha256>\n" $([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}")
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n" <maintainer>Moko Consulting</maintainer>
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n" <maintainerurl>https://mokoconsulting.tech</maintainerurl>
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n" </update>
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n" XMLEOF
NEW_ENTRY="${NEW_ENTRY} </update>" )
# ── Write new entry to temp file ───────────────────────────────
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
# ── Merge into updates.xml ───────────────────────────────────── # ── Merge into updates.xml ─────────────────────────────────────
if [ ! -f "updates.xml" ]; then if [ ! -f "updates.xml" ]; then
# Create fresh
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' '<updates>' >> updates.xml printf '%s\n' '<updates>' >> updates.xml
cat /tmp/new_entry.xml >> updates.xml echo "$NEW_ENTRY" >> updates.xml
printf '\n%s\n' '</updates>' >> updates.xml printf '%s\n' '</updates>' >> updates.xml
else else
# Remove existing entry for this stability, insert new one # Remove existing entry for this stability, add new one
printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py # Use python for reliable XML manipulation
printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py python3 -c "
printf 'with open("/tmp/new_entry.xml") as f: new_entry = f.read()\n' >> /tmp/merge_xml.py import re, sys
printf 'pattern = r" <update>.*?<tag>" + re.escape(stability) + r"</tag>.*?</update>\\n?"\n' >> /tmp/merge_xml.py
printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py with open('updates.xml', 'r') 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
printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py # Remove existing entry with this stability tag
python3 /tmp/merge_xml.py 2>/dev/null || { pattern = r' <update>.*?<tag>${STABILITY}</tag>.*?</update>\n?'
# Fallback: rebuild keeping other stability entries content = re.sub(pattern, '', content, flags=re.DOTALL)
# Insert new entry before </updates>
new_entry = '''${NEW_ENTRY}'''
content = content.replace('</updates>', new_entry + '\n</updates>')
# Clean up empty lines
content = re.sub(r'\n{3,}', '\n\n', content)
with open('updates.xml', 'w') as f:
f.write(content)
" 2>/dev/null || {
# Fallback: just rewrite the whole file if python fails
# Keep existing stable entry if present
STABLE_ENTRY=""
if [ "$STABILITY" != "stable" ] && grep -q '<tag>stable</tag>' updates.xml; then
STABLE_ENTRY=$(sed -n '/<update>/,/<\/update>/{ /<tag>stable<\/tag>/,/<\/update>/p; /<update>/,/<tag>stable<\/tag>/p }' updates.xml | sort -u)
fi
RC_ENTRY=""
if [ "$STABILITY" != "rc" ] && grep -q '<tag>rc</tag>' updates.xml; then
RC_ENTRY=$(python3 -c "
import re
with open('updates.xml') as f: c = f.read()
m = re.search(r'(<update>.*?<tag>rc</tag>.*?</update>)', c, re.DOTALL)
if m: print(m.group(1))
" 2>/dev/null || true)
fi
DEV_ENTRY=""
if [ "$STABILITY" != "development" ] && grep -q '<tag>development</tag>' updates.xml; then
DEV_ENTRY=$(python3 -c "
import re
with open('updates.xml') as f: c = f.read()
m = re.search(r'(<update>.*?<tag>development</tag>.*?</update>)', c, re.DOTALL)
if m: print(m.group(1))
" 2>/dev/null || true)
fi
{ {
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
printf '%s\n' '<updates>' printf '%s\n' '<updates>'
for TAG in stable rc development; do [ -n "$STABLE_ENTRY" ] && echo "$STABLE_ENTRY"
[ "$TAG" = "${STABILITY}" ] && continue [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
if grep -q "<tag>${TAG}</tag>" updates.xml 2>/dev/null; then [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
sed -n "/<update>/,/<\/update>/{ /<tag>${TAG}<\/tag>/p; }" updates.xml echo "$NEW_ENTRY"
fi printf '%s\n' '</updates>'
done } > updates.xml
cat /tmp/new_entry.xml
printf '\n%s\n' '</updates>'
} > /tmp/updates_new.xml
mv /tmp/updates_new.xml updates.xml
} }
fi fi
@@ -278,64 +250,6 @@ jobs:
git push git push
} }
- name: SFTP deploy to dev server
if: contains(github.ref, 'dev/')
env:
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
# ── Permission check: admin or maintain role required ──────
ACTOR="${{ github.actor }}"
REPO="${{ github.repository }}"
PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \
--jq '.permission' 2>/dev/null || \
gh api "repos/${REPO}/collaborators/${ACTOR}" \
--jq '.role' 2>/dev/null || echo "read")
case "$PERMISSION" in
admin|maintain|write) ;;
*)
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
exit 0
;;
esac
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && exit 0
PORT="${DEV_PORT:-22}"
REMOTE="${DEV_PATH%/}"
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
if [ -n "$DEV_KEY" ]; then
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
fi
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
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
php /tmp/mokostandards/api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
- name: Summary
if: always()
run: |
echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY