diff --git a/.github/workflows/release_from_version.yml b/.github/workflows/release_from_version.yml index bc75f57..03ff0e8 100644 --- a/.github/workflows/release_from_version.yml +++ b/.github/workflows/release_from_version.yml @@ -1,7 +1,58 @@ +# +# Copyright (C) 2025 Moko Consulting +# +# This file is part of a Moko Consulting project. +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# FILE INFORMATION +# DEFGROUP: GitHub.Workflow +# INGROUP: MokoStandards.Release +# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia +# PATH: /.github/workflows/release_from_version.yml +# VERSION: 01.00.00 +# BRIEF: Enterprise release pipeline for promoting dev branches, building Joomla artifacts, publishing prereleases, and optionally squashing to main. +# NOTE: Designed for Joomla and Dolibarr projects following MokoStandards governance. +# +# name: Release from Version Branch Pipeline on: workflow_dispatch: + inputs: + promote_to_version: + description: "Promote dev/ to version/" + required: true + default: true + type: boolean + delete_dev_branch: + description: "Delete dev/ after promotion" + required: true + default: true + type: boolean + squash_to_main: + description: "Squash merge version/ into main" + required: true + default: false + type: boolean + delete_version_branch: + description: "Delete version/ after squash merge to main" + required: true + default: false + type: boolean concurrency: group: release-from-dev-${{ github.ref_name }} @@ -29,11 +80,17 @@ jobs: BRANCH="${GITHUB_REF_NAME}" echo "Invoked from branch: $BRANCH" - echo "$BRANCH" | grep -E '^dev/[0-9]+\.[0-9]+\.[0-9]+$' + echo "${BRANCH}" | grep -E '^(dev|version)/[0-9]+\.[0-9]+\.[0-9]+$' VERSION="${BRANCH#dev/}" - DEV_BRANCH="dev/$VERSION" - VERSION_BRANCH="version/$VERSION" + VERSION="${VERSION#version/}" + DEV_BRANCH="dev/${VERSION}" + VERSION_BRANCH="version/${VERSION}" + + # If invoked from an existing version/ branch, treat it as already promoted + if echo "${BRANCH}" | grep -qE '^version/'; then + VERSION_BRANCH="${BRANCH}" + fi TODAY_UTC="$(date -u +%Y-%m-%d)" echo "version=$VERSION" >> "$GITHUB_OUTPUT" @@ -42,6 +99,7 @@ jobs: echo "today_utc=$TODAY_UTC" >> "$GITHUB_OUTPUT" promote_branch: + if: ${{ github.event.inputs.promote_to_version == 'true' && startsWith(github.ref_name, 'dev/') }} name: 01 Promote dev to version branch runs-on: ubuntu-latest needs: guard @@ -92,7 +150,11 @@ jobs: git checkout -B "$DST" "origin/$SRC" git push origin "$DST" - git push origin --delete "$SRC" + if [ "${{ github.event.inputs.delete_dev_branch }}" = "true" ]; then + git push origin --delete "${SRC}" + else + echo "Dev branch retention enabled. Skipping deletion of ${SRC}." + fi echo "Promotion complete: $SRC -> $DST" @@ -250,42 +312,50 @@ jobs: echo "SHA256: $SHA" # If a template exists, instantiate it + # Preferred canonical template location: docs/templates/ if [ -f "docs/templates/template_update.xml" ]; then - cp -f "docs/templates/template_update.xml" "update.xml" + 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 "update.xml" ]; then - echo "ERROR: update.xml not found and docs/templates/template_update.xml not found." - exit 1 + if [ ! -f "updates.xml" ]; then + # Backward compatibility: allow repos that still keep updates.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" update.xml || true - sed -i "s#{{DATE}}#${TODAY}#g" update.xml || true - sed -i "s#{{DOWNLOADURL}}#${DOWNLOAD_URL}#g" update.xml || true - sed -i "s#{{SHA256}}#${SHA}#g" update.xml || true - sed -i "s#{{ZIP}}#${ZIP}#g" update.xml || true + 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" update.xml || true - sed -i "s#[^<]*#${SHA}#g" update.xml || true - sed -i "s#[^<]*#${SHA}#g" update.xml || true - sed -i "s#[^<]*#${VERSION}#g" update.xml || true - sed -i "s#[^<]*#${TODAY}#g" update.xml || true + 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 "update.xml updated." + echo "updates.xml updated." - name: Commit update.xml changes (and any related date deltas) to version branch run: | set -euo pipefail if git diff --quiet; then - echo "No update.xml changes detected. No commit required." + echo "No updates.xml changes detected. No commit required." exit 0 fi git add -A - git commit -m "chore(release): update update.xml for ${{ needs.guard.outputs.version }}" + git commit -m "chore(release): update updates.xml for ${{ needs.guard.outputs.version }}" git push origin "HEAD:${{ needs.guard.outputs.version_branch }}" - name: Create and push annotated tag after final release commit @@ -326,7 +396,7 @@ jobs: path: | dist/*.zip dist/SHA256SUMS.txt - update.xml + updates.xml RELEASE_NOTES.md retention-days: 30 @@ -346,5 +416,66 @@ jobs: body_path: RELEASE_NOTES.md files: | dist/*.zip - update.xml + updates.xml dist/SHA256SUMS.txt + + squash_to_main: + name: 04 Optional squash merge version branch to main + runs-on: ubuntu-latest + needs: + - guard + - build_update_and_release + + if: ${{ github.event.inputs.squash_to_main == 'true' }} + + permissions: + contents: write + + steps: + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + - name: Configure Git identity + run: | + 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_WORKSPACE}" + + - name: Fetch version branch + run: | + set -euo pipefail + git fetch origin --prune + + - name: Squash merge version branch into main + run: | + set -euo pipefail + + VERSION="${{ needs.guard.outputs.version }}" + VBRANCH="origin/${{ needs.guard.outputs.version_branch }}" + + # Governance control: if main is protected from direct pushes, this will fail by design. + # Enforce PR-based merge in that scenario. + + git checkout main + git merge --squash "${VBRANCH}" + + if git diff --cached --quiet; then + echo "No changes to merge from ${VBRANCH}." + exit 0 + fi + + git commit -m "chore(release): squash ${VERSION} into main" + git push origin "HEAD:main" + + - name: Optional delete version branch after squash + run: | + set -euo pipefail + if [ "${{ github.event.inputs.delete_version_branch }}" = "true" ]; then + git push origin --delete "${{ needs.guard.outputs.version_branch }}" + else + echo "Version branch retention enabled. Skipping deletion." + fi