From 23ffbba8b12163c79076b0491d34508a184b4b0b Mon Sep 17 00:00:00 2001
From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com>
Date: Tue, 23 Dec 2025 19:02:40 -0600
Subject: [PATCH] Update release_from_version.yml
---
.github/workflows/release_from_version.yml | 335 ++++++++++++++++++++-
1 file changed, 320 insertions(+), 15 deletions(-)
diff --git a/.github/workflows/release_from_version.yml b/.github/workflows/release_from_version.yml
index ea2331b..ba5f73d 100644
--- a/.github/workflows/release_from_version.yml
+++ b/.github/workflows/release_from_version.yml
@@ -60,6 +60,10 @@ concurrency:
permissions:
contents: read
+defaults:
+ run:
+ shell: bash
+
jobs:
guard:
name: 00 Guard and derive release metadata
@@ -69,6 +73,7 @@ jobs:
version: ${{ steps.extract.outputs.version }}
dev_branch: ${{ steps.extract.outputs.dev_branch }}
version_branch: ${{ steps.extract.outputs.version_branch }}
+ target_branch: ${{ steps.extract.outputs.target_branch }}
today_utc: ${{ steps.extract.outputs.today_utc }}
steps:
@@ -88,9 +93,17 @@ jobs:
VERSION_BRANCH="version/${VERSION}"
TODAY_UTC="$(date -u +%Y-%m-%d)"
+ PROMOTE_INPUT="${{ github.event.inputs.promote_to_version }}"
+ if [ "${PROMOTE_INPUT}" = "true" ]; then
+ TARGET_BRANCH="${VERSION_BRANCH}"
+ else
+ TARGET_BRANCH="${DEV_BRANCH}"
+ fi
+
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "dev_branch=${DEV_BRANCH}" >> "${GITHUB_OUTPUT}"
echo "version_branch=${VERSION_BRANCH}" >> "${GITHUB_OUTPUT}"
+ echo "target_branch=${TARGET_BRANCH}" >> "${GITHUB_OUTPUT}"
echo "today_utc=${TODAY_UTC}" >> "${GITHUB_OUTPUT}"
promote_branch:
@@ -155,14 +168,14 @@ jobs:
echo "Promotion complete: ${SRC} -> ${DST}"
normalize_dates:
- name: 02 Normalize dates on version branch
+ name: 02 Normalize dates on release working branch
runs-on: ubuntu-latest
needs:
- guard
- promote_branch
- # If promotion is disabled, run normalization directly on dev branch.
- if: ${{ always() }}
+ # Control: if promotion is requested, require promote_branch success.
+ if: ${{ (github.event.inputs.promote_to_version != 'true') || (needs.promote_branch.result == 'success') }}
permissions:
contents: write
@@ -171,7 +184,7 @@ jobs:
- name: Checkout release working branch
uses: actions/checkout@v4
with:
- ref: ${{ (github.event.inputs.promote_to_version == 'true' && startsWith(github.ref_name, 'dev/')) && needs.guard.outputs.version_branch || needs.guard.outputs.dev_branch }}
+ ref: ${{ needs.guard.outputs.target_branch }}
fetch-depth: 0
- name: Configure Git identity
@@ -188,8 +201,8 @@ jobs:
test -f CHANGELOG.md || (echo "ERROR: CHANGELOG.md missing." && exit 1)
VERSION="${{ needs.guard.outputs.version }}"
- if ! grep -qE "^## \\[$VERSION\\] " CHANGELOG.md; then
- echo "ERROR: CHANGELOG.md does not contain a heading for version [$VERSION]."
+ if ! grep -qE "^## \[${VERSION}\] " CHANGELOG.md; then
+ echo "ERROR: CHANGELOG.md does not contain a heading for version [${VERSION}]."
exit 1
fi
@@ -217,7 +230,7 @@ jobs:
sed -i "s#[^<]*#${TODAY}#g" "${f}" || true
done
- sed -i -E "s#^(## \\[${VERSION}\\]) [0-9]{4}-[0-9]{2}-[0-9]{2}#\ ${TODAY}#g" CHANGELOG.md || true
+ sed -i -E "s#^(## \[${VERSION}\]) [0-9]{4}-[0-9]{2}-[0-9]{2}#\1 ${TODAY}#g" CHANGELOG.md || true
fi
- name: Commit and push date updates
@@ -231,9 +244,7 @@ jobs:
git add -A
git commit -m "chore(release): normalize dates for ${{ needs.guard.outputs.version }}"
-
- TARGET="${{ (github.event.inputs.promote_to_version == 'true' && startsWith(github.ref_name, 'dev/')) && needs.guard.outputs.version_branch || needs.guard.outputs.dev_branch }}"
- git push origin "HEAD:${TARGET}"
+ git push origin "HEAD:${{ needs.guard.outputs.target_branch }}"
build_update_and_release:
name: 03 Build Joomla ZIP, update updates.xml, prerelease
@@ -246,14 +257,11 @@ jobs:
contents: write
id-token: write
- environment:
- name: release
-
steps:
- name: Checkout release working branch
uses: actions/checkout@v4
with:
- ref: ${{ (github.event.inputs.promote_to_version == 'true' && startsWith(github.ref_name, 'dev/')) && needs.guard.outputs.version_branch || needs.guard.outputs.dev_branch }}
+ ref: ${{ needs.guard.outputs.target_branch }}
fetch-depth: 0
- name: Configure Git identity
@@ -261,4 +269,301 @@ jobs:
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git config --global --add safe.directory "${GITHUB
+ git config --global --add safe.directory "${GITHUB_WORKSPACE}"
+
+ - name: Build Joomla compliant ZIP (template, component, module, plugin)
+ id: build
+ run: |
+ set -euo pipefail
+
+ VERSION="${{ needs.guard.outputs.version }}"
+ REPO="${{ github.event.repository.name }}"
+
+ test -d src || (echo "ERROR: src directory missing." && exit 1)
+
+ mkdir -p dist
+
+ # Determine the Joomla extension root inside src.
+ # Rules:
+ # - If src contains a single top-level directory, prefer that directory as the extension root.
+ # - Otherwise, use src as the extension root.
+ # - The extension root must contain a manifest XML at its root.
+ ROOT="src"
+ TOP_COUNT="$(find src -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')"
+ if [ "${TOP_COUNT}" = "1" ]; then
+ ONLY_DIR="$(find src -mindepth 1 -maxdepth 1 -type d -print -quit)"
+ ROOT="${ONLY_DIR}"
+ fi
+
+ echo "Candidate extension root: ${ROOT}"
+
+ # Locate the manifest XML at the root of ROOT.
+ # Priority:
+ # - templateDetails.xml for templates
+ # - otherwise, any *.xml containing a Joomla root element
+ MANIFEST=""
+ if [ -f "${ROOT}/templateDetails.xml" ]; then
+ MANIFEST="${ROOT}/templateDetails.xml"
+ else
+ while IFS= read -r -d '' f; do
+ if grep -qE ' element."
+ exit 1
+ fi
+
+ echo "Manifest: ${MANIFEST}"
+
+ # Derive extension type for logging and compliance checks.
+ EXT_TYPE="$(grep -oE ']*type=\"[^\"]+\"' "${MANIFEST}" | head -n 1 | sed -E 's/.*type=\"([^\"]+)\".*/\/')"
+ if [ -z "${EXT_TYPE}" ]; then
+ EXT_TYPE="unknown"
+ fi
+ echo "Detected extension type: ${EXT_TYPE}"
+
+ # Basic Joomla compliance checks by type.
+ case "${EXT_TYPE}" in
+ template)
+ test -f "${ROOT}/templateDetails.xml" || (echo "ERROR: templateDetails.xml missing for template build." && exit 1)
+ ;;
+ component)
+ if ! ls "${ROOT}"/com_*.xml >/dev/null 2>&1; then
+ echo "WARNING: No com_*.xml manifest found at root. Using detected manifest anyway."
+ fi
+ ;;
+ module)
+ if ! ls "${ROOT}"/mod_*.xml >/dev/null 2>&1; then
+ echo "WARNING: No mod_*.xml manifest found at root. Using detected manifest anyway."
+ fi
+ ;;
+ plugin)
+ :
+ ;;
+ *)
+ echo "WARNING: Extension type could not be determined reliably. Proceeding with generic packaging."
+ ;;
+ esac
+
+ ZIP="${REPO}-${VERSION}.zip"
+
+ # Joomla install expectation: ZIP root is the extension root.
+ # Zip the CONTENTS of ROOT, not ROOT itself.
+ (cd "${ROOT}" && zip -r -X "../dist/${ZIP}" . \
+ -x "**/.git/**" \
+ -x "**/.github/**" \
+ -x "**/.DS_Store" \
+ -x "**/__MACOSX/**")
+
+ echo "zip_name=${ZIP}" >> "${GITHUB_OUTPUT}"
+ ls -la dist
+
+ - name: Compute SHA256 for ZIP
+ id: sha
+ run: |
+ set -euo pipefail
+ ZIP="${{ steps.build.outputs.zip_name }}"
+ SHA="$(sha256sum "dist/${ZIP}" | awk '{print $1}')"
+ echo "sha256=${SHA}" >> "${GITHUB_OUTPUT}"
+ printf "%s %s\n" "${SHA}" "${ZIP}" > dist/SHA256SUMS.txt
+ cat dist/SHA256SUMS.txt
+
+ - name: Update updates.xml with download URL and sha256
+ run: |
+ set -euo pipefail
+
+ VERSION="${{ needs.guard.outputs.version }}"
+ TODAY="${{ needs.guard.outputs.today_utc }}"
+ ZIP="${{ steps.build.outputs.zip_name }}"
+ SHA="${{ steps.sha.outputs.sha256 }}"
+
+ OWNER="${{ github.repository_owner }}"
+ REPO="${{ github.event.repository.name }}"
+
+ DOWNLOAD_URL="https://github.com/${OWNER}/${REPO}/releases/download/${VERSION}/${ZIP}"
+
+ echo "Version: ${VERSION}"
+ echo "Download URL: ${DOWNLOAD_URL}"
+ echo "SHA256: ${SHA}"
+
+ # If a template exists, instantiate it
+ if [ -f "docs/templates/template_update.xml" ]; then
+ cp -f "docs/templates/template_update.xml" "updates.xml"
+ elif [ -f "docs/templates/update_template.xml" ]; then
+ cp -f "docs/templates/update_template.xml" "updates.xml"
+ fi
+
+ if [ ! -f "updates.xml" ]; then
+ # Backward compatibility: allow repos that still keep update.xml
+ if [ -f "update.xml" ]; then
+ mv -f "update.xml" "updates.xml"
+ else
+ echo "ERROR: updates.xml not found and no template present in docs/templates."
+ exit 1
+ fi
+ fi
+
+ # Replace common placeholders if present
+ sed -i "s#{{VERSION}}#${VERSION}#g" updates.xml || true
+ sed -i "s#{{DATE}}#${TODAY}#g" updates.xml || true
+ sed -i "s#{{DOWNLOADURL}}#${DOWNLOAD_URL}#g" updates.xml || true
+ sed -i "s#{{SHA256}}#${SHA}#g" updates.xml || true
+ sed -i "s#{{ZIP}}#${ZIP}#g" updates.xml || true
+
+ # Also enforce canonical tag replacement inside common XML elements
+ sed -i "s#[^<]*#${DOWNLOAD_URL}#g" updates.xml || true
+ sed -i "s#[^<]*#${SHA}#g" updates.xml || true
+ sed -i "s#[^<]*#${SHA}#g" updates.xml || true
+ sed -i "s#[^<]*#${VERSION}#g" updates.xml || true
+ sed -i "s#[^<]*#${TODAY}#g" updates.xml || true
+
+ echo "updates.xml updated."
+
+ - name: Commit updates.xml changes (and any related date deltas)
+ run: |
+ set -euo pipefail
+
+ if git diff --quiet; then
+ echo "No updates.xml changes detected. No commit required."
+ exit 0
+ fi
+
+ git add -A
+ git commit -m "chore(release): update updates.xml for ${{ needs.guard.outputs.version }}"
+ git push origin "HEAD:${{ needs.guard.outputs.target_branch }}"
+
+ - name: Create and push annotated tag after final release commit
+ run: |
+ set -euo pipefail
+
+ VERSION="${{ needs.guard.outputs.version }}"
+
+ git fetch --tags
+
+ if git rev-parse -q --verify "refs/tags/${VERSION}" >/dev/null; then
+ echo "ERROR: Tag ${VERSION} already exists."
+ exit 1
+ fi
+
+ git tag -a "${VERSION}" -m "Prerelease ${VERSION}"
+ git push origin "refs/tags/${VERSION}"
+
+ - name: Generate release notes from CHANGELOG.md
+ run: |
+ set -euo pipefail
+
+ VERSION="${{ needs.guard.outputs.version }}"
+
+ awk "/^## \[${VERSION}\]/{flag=1;next}/^## \[/ {flag=0}flag" CHANGELOG.md > RELEASE_NOTES.md || true
+
+ if [ ! -s RELEASE_NOTES.md ]; then
+ echo "ERROR: Release notes extraction failed for ${VERSION}."
+ exit 1
+ fi
+
+ ZIP_ASSET="${{ steps.build.outputs.zip_name }}"
+ cat >> RELEASE_NOTES.md <