From 16bb5eed13d3fe564a20e09e74cf25ff824631dd Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 18 Dec 2025 19:13:34 -0600 Subject: [PATCH] Update release_from_version.yml --- .github/workflows/release_from_version.yml | 272 ++++++++++++++++----- 1 file changed, 215 insertions(+), 57 deletions(-) diff --git a/.github/workflows/release_from_version.yml b/.github/workflows/release_from_version.yml index 788124f..bc04431 100644 --- a/.github/workflows/release_from_version.yml +++ b/.github/workflows/release_from_version.yml @@ -3,17 +3,23 @@ name: Release from Version Branch Pipeline on: workflow_dispatch: +concurrency: + group: release-from-dev-${{ github.ref_name }} + cancel-in-progress: false + permissions: - contents: write + contents: read jobs: guard: - name: 00 Guard – enforce dev version branch + name: 00 Guard and derive release metadata runs-on: ubuntu-latest outputs: version: ${{ steps.extract.outputs.version }} + dev_branch: ${{ steps.extract.outputs.dev_branch }} version_branch: ${{ steps.extract.outputs.version_branch }} + today_utc: ${{ steps.extract.outputs.today_utc }} steps: - name: Validate calling branch and extract version @@ -23,54 +29,82 @@ jobs: BRANCH="${GITHUB_REF_NAME}" echo "Invoked from branch: $BRANCH" - echo "$BRANCH" | grep -E '^dev/[0-9]+\.[0-9]+\.[0-9]+$' VERSION="${BRANCH#dev/}" + DEV_BRANCH="dev/$VERSION" VERSION_BRANCH="version/$VERSION" + TODAY_UTC="$(date -u +%Y-%m-%d)" echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "dev_branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT" echo "version_branch=$VERSION_BRANCH" >> "$GITHUB_OUTPUT" + echo "today_utc=$TODAY_UTC" >> "$GITHUB_OUTPUT" promote_branch: - name: 01 Promote dev to version + name: 01 Promote dev to version branch runs-on: ubuntu-latest needs: guard + permissions: + contents: write + steps: - name: Checkout dev branch uses: actions/checkout@v4 with: - ref: dev/${{ needs.guard.outputs.version }} + ref: ${{ needs.guard.outputs.dev_branch }} 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: Promote branch + - name: Enforce branch promotion preconditions run: | set -euo pipefail - SRC="dev/${{ needs.guard.outputs.version }}" + SRC="${{ needs.guard.outputs.dev_branch }}" DST="${{ needs.guard.outputs.version_branch }}" - git fetch origin + git fetch origin --prune - if git show-ref --verify --quiet "refs/remotes/origin/$DST"; then - echo "ERROR: $DST already exists." + if ! git show-ref --verify --quiet "refs/remotes/origin/$SRC"; then + echo "ERROR: origin/$SRC not found." exit 1 fi + if git show-ref --verify --quiet "refs/remotes/origin/$DST"; then + echo "ERROR: origin/$DST already exists." + exit 1 + fi + + - name: Promote dev branch to version branch + run: | + set -euo pipefail + + SRC="${{ needs.guard.outputs.dev_branch }}" + DST="${{ needs.guard.outputs.version_branch }}" + git checkout -B "$DST" "origin/$SRC" git push origin "$DST" + git push origin --delete "$SRC" - update_dates: - name: 02 Normalize release dates + echo "Promotion complete: $SRC -> $DST" + + normalize_dates: + name: 02 Normalize dates on version branch runs-on: ubuntu-latest - needs: promote_branch + needs: + - guard + - promote_branch + + permissions: + contents: write steps: - name: Checkout version branch @@ -81,52 +115,93 @@ jobs: - 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: Update release dates + - name: Validate repository release prerequisites + run: | + set -euo pipefail + test -d src || (echo "ERROR: src directory missing." && exit 1) + 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]." + exit 1 + fi + + - name: Update dates using repo script when available, otherwise apply baseline updates run: | set -euo pipefail - TODAY="$(date -u +%Y-%m-%d)" + TODAY="${{ needs.guard.outputs.today_utc }}" VERSION="${{ needs.guard.outputs.version }}" + echo "Release version: $VERSION" + echo "Release date (UTC): $TODAY" + if [ -f scripts/update_dates.sh ]; then chmod +x scripts/update_dates.sh scripts/update_dates.sh "$TODAY" "$VERSION" else + echo "scripts/update_dates.sh not found. Applying baseline date normalization." + find . -type f -name "*.xml" \ -not -path "./.git/*" \ - -exec sed -i "s#[^<]*#${TODAY}#g" {} \; + -print0 | while IFS= read -r -d '' f; do + sed -i "s#[^<]*#${TODAY}#g" "$f" || true + sed -i "s#[^<]*#${TODAY}#g" "$f" || true + sed -i "s#[^<]*#${TODAY}#g" "$f" || true + done - if [ -f CHANGELOG.md ]; then - sed -i -E "s#^(## \\[${VERSION}\\]) [0-9]{4}-[0-9]{2}-[0-9]{2}#\\1 ${TODAY}#g" CHANGELOG.md - fi + sed -i -E "s#^(## \\[${VERSION}\\]) [0-9]{4}-[0-9]{2}-[0-9]{2}#\\1 ${TODAY}#g" CHANGELOG.md || true fi - - name: Commit date updates + - name: Commit and push date updates run: | + set -euo pipefail + if git diff --quiet; then - echo "No date changes detected." + echo "No date changes detected. No commit required." exit 0 fi git add -A - git commit -m "chore(release): normalize release dates" - git push origin HEAD + git commit -m "chore(release): normalize dates for ${{ needs.guard.outputs.version }}" + git push origin "HEAD:${{ needs.guard.outputs.version_branch }}" - build_zip: - name: 03 Build ZIP + build_update_and_release: + name: 03 Build Joomla ZIP, update update.xml, prerelease runs-on: ubuntu-latest - needs: update_dates + needs: + - guard + - normalize_dates + + permissions: + contents: write + id-token: write + + environment: + name: release steps: - name: Checkout version branch uses: actions/checkout@v4 with: ref: ${{ needs.guard.outputs.version_branch }} + fetch-depth: 0 - - name: Build ZIP from src + - 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: Build Joomla compliant ZIP from src + id: build run: | set -euo pipefail @@ -134,59 +209,142 @@ jobs: REPO="${{ github.event.repository.name }}" ZIP="${REPO}-${VERSION}.zip" - if [ ! -d src ]; then - echo "ERROR: src directory missing." - exit 1 - fi + test -d src || (echo "ERROR: src directory missing." && exit 1) mkdir -p dist + + # Joomla compliant packaging: src contents at ZIP root (no nested src folder) cd src zip -r "../dist/$ZIP" . cd .. - - name: Upload ZIP artifact - uses: actions/upload-artifact@v4 - with: - name: release-zip - path: dist/*.zip - retention-days: 7 + echo "zip_name=$ZIP" >> "$GITHUB_OUTPUT" + ls -la dist - prerelease: - name: 04 Create prerelease - runs-on: ubuntu-latest - needs: build_zip + - 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 - steps: - - name: Checkout version branch - uses: actions/checkout@v4 - with: - ref: ${{ needs.guard.outputs.version_branch }} + - name: Update update.xml with download URL and sha256 + run: | + set -euo pipefail - - name: Download ZIP artifact - uses: actions/download-artifact@v4 - with: - name: release-zip - path: dist + VERSION="${{ needs.guard.outputs.version }}" + TODAY="${{ needs.guard.outputs.today_utc }}" + ZIP="${{ steps.build.outputs.zip_name }}" + SHA="${{ steps.sha.outputs.sha256 }}" - - name: Generate release notes + 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 "templates/update_template.xml" ]; then + cp -f "templates/update_template.xml" "update.xml" + fi + + if [ ! -f "update.xml" ]; then + echo "ERROR: update.xml not found and templates/update_template.xml not found." + exit 1 + 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 + + # 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 + + echo "update.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." + exit 0 + fi + + git add -A + git commit -m "chore(release): update update.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 run: | set -euo pipefail VERSION="${{ needs.guard.outputs.version }}" - if [ -f CHANGELOG.md ]; then - awk "/^## \\[$VERSION\\]/{flag=1;next}/^## \\[/{flag=0}flag" CHANGELOG.md > RELEASE_NOTES.md || true + 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 "Prerelease $VERSION" > RELEASE_NOTES.md + echo "ERROR: Release notes extraction failed for $VERSION." + exit 1 fi - - name: Create GitHub prerelease + printf "\n\nAssets:\n- %s\n- update.xml\n- SHA256SUMS.txt\n" "${{ steps.build.outputs.zip_name }}" >> RELEASE_NOTES.md + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: release-assets + path: | + dist/*.zip + dist/SHA256SUMS.txt + update.xml + RELEASE_NOTES.md + retention-days: 30 + + - name: Attest build provenance + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + dist/*.zip + dist/SHA256SUMS.txt + + - name: Create GitHub prerelease and attach assets uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.guard.outputs.version }} name: Prerelease ${{ needs.guard.outputs.version }} prerelease: true body_path: RELEASE_NOTES.md - files: dist/*.zip + files: | + dist/*.zip + update.xml + dist/SHA256SUMS.txt