From 5353cc7603ee6cff26bc4db67e8a16427f4d92f1 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sat, 4 Jul 2026 18:25:19 +0100 Subject: [PATCH 1/7] chore(editorconfig): update indent style for YAML files --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index 07c6fd18b..a80b3ab21 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,6 +15,7 @@ indent_size = 4 [*.yml] indent_size = 2 +indent_style = space [*.conf] indent_style = space From bdfc3fe5450e0cfcdc63bb4ec86866ffd9e214a0 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 5 Jul 2026 04:22:10 +0100 Subject: [PATCH 2/7] refactor: release workflow for immutable tag and release publishing Fixes #54 where packagist complains the release tag has been modified after the release. To fix we need to completely change how we do releases and auto version bump. We now manually trigger the workflow, input the version tag, run the auto version bump and changelog update jobs, and then create the release and tag pragmatically after committing. - Refactor release pipeline to completely change how we do releases and auto version bump. Allowing the workflow to be triggered manually (instead of on-release) with inputs to set the release tag version and publish the release. - Introduced `tag_name`, `target_branch` with default as master, and `prerelease` boolean inputs. - Introduced `publish_only` and `release_sha` inputs to enable the skipping of a version and changelog updates and jump directly to publishing the release. - Fixed the release tag behaviour and removed the updating of tag commits. - Added a proper publish release job with the creation of immutable tags and releases. - Publish releases from resolved release commit SHA to keep tag/release contents aligned both on GitHub and Packagist. - Updated versioning validation and enforce semver release tags prefixed with a v. - Require both version.php and CHANGELOG.md updates for every release and validate them before creating a PR. --- .github/workflows/release.yml | 757 ++++++++++++++++++++++------------ 1 file changed, 489 insertions(+), 268 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffaa92b7f..750d6c9d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,283 +1,504 @@ -on: - release: - types: [prereleased, released] +name: Prepare And Publish Release -env: - TAG_NAME: ${{ github.ref_name }} - TARGET_BRANCH: ${{ github.event.release.target_commitish }} +on: + workflow_dispatch: + inputs: + tag_name: + description: "Release tag (e.g. v3.4.5)" + required: true + type: string + target_branch: + description: "Branch to release from" + required: false + default: "master" + type: string + prerelease: + description: "Mark GitHub release as prerelease" + required: false + default: false + type: boolean + publish_only: + description: "Skip preparation and publish from an existing release commit" + required: false + default: false + type: boolean + release_sha: + description: "Optional commit SHA to publish from in publish-only mode (defaults to target branch HEAD)" + required: false + type: string permissions: contents: write pull-requests: write actions: read -name: Release Version Update - jobs: - validate-release-version: - name: Validate Release Version + prepare-release: + if: inputs.publish_only != true + name: Prepare Release runs-on: ubuntu-latest - if: github.event_name == 'release' outputs: - version: ${{ steps.parse-version.outputs.version }} - is-valid: ${{ steps.parse-version.outputs.is-valid }} - + release_sha: ${{ steps.resolve_release_sha.outputs.release_sha }} + env: + TAG_NAME: ${{ inputs.tag_name }} + TARGET_BRANCH: ${{ inputs.target_branch }} steps: - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Parse and Validate Version - id: parse-version - run: | - TAG_NAME="${{ env.TAG_NAME }}" - - # Remove 'v' prefix if present and validate semver format - if [[ $TAG_NAME =~ ^v?([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?)$ ]]; then - VERSION=${BASH_REMATCH[1]} - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "is-valid=true" >> $GITHUB_OUTPUT - echo "✅ Valid version format: $VERSION" - else - echo "is-valid=false" >> $GITHUB_OUTPUT - echo "❌ Invalid version format: $TAG_NAME" - echo "Version must follow semver format (e.g., v1.0.0, 1.0.0, v1.0.0-beta.1)" - exit 1 + - name: Checkout target branch + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Parse and validate version + id: parse_version + run: | + set -euo pipefail + + TAG_NAME="${{ env.TAG_NAME }}" + + # Validate semver format and require the release tag to start with 'v'. + if [[ "$TAG_NAME" =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?)$ ]]; then + VERSION="${BASH_REMATCH[1]}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "✅ Valid semver tag: $TAG_NAME" + else + echo "❌ Invalid version format: $TAG_NAME" + echo "ℹ️ Version must start with 'v' and follow semver (e.g., v1.0.0, v1.0.0-beta.1)" + exit 1 + fi + + - name: Check local tag availability + run: | + set -euo pipefail + + if git rev-parse -q --verify "refs/tags/${{ env.TAG_NAME }}" >/dev/null; then + echo "⚠️ Tag ${{ env.TAG_NAME }} already exists locally." + exit 1 + fi + + - name: Check remote tag availability + run: | + set -euo pipefail + + if git ls-remote --exit-code --tags origin "refs/tags/${{ env.TAG_NAME }}" >/dev/null 2>&1; then + echo "⚠️ Tag ${{ env.TAG_NAME }} already exists on origin." + echo "Published tags are immutable. Choose a new tag." + exit 1 + fi + + - name: Setup git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Update version file + run: | + set -euo pipefail + + VERSION="${{ steps.parse_version.outputs.version }}" + CURRENT_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) + + if [[ "$VERSION" == "$CURRENT_VERSION" ]]; then + echo "ℹ️ version.php already set to $VERSION" + else + sed -i "s/\$version = '[^']*';/\$version = '$VERSION';/" cli/version.php + echo "✅ Updated version.php: $CURRENT_VERSION -> $VERSION" + fi + + - name: Detect changelog file + id: changelog_file + run: | + set -euo pipefail + + # Check if CHANGELOG.md exists + if [[ -f "CHANGELOG.md" ]]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "❌ CHANGELOG.md not found. Release requires changelog updates." + exit 1 + fi + + - name: Detect unreleased changelog heading + id: changelog_unreleased + if: steps.changelog_file.outputs.exists == 'true' + run: | + set -euo pipefail + + # Check if Unreleased section exists (with or without a trailing URL) + if grep -qP "^## \[Unreleased\]" CHANGELOG.md; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "❌ No [Unreleased] section found in CHANGELOG.md." + echo "Release requires converting [Unreleased] into the release heading." + exit 1 + fi + + - name: Update changelog release heading + if: steps.changelog_unreleased.outputs.exists == 'true' + run: | + set -euo pipefail + + VERSION="${{ steps.parse_version.outputs.version }}" + REPO="${{ github.repository }}" + CURRENT_DATE=$(date +%Y-%m-%d) + + # Replace the entire [Unreleased] heading line (including any trailing URL) + # with a versioned release heading that links to the tree at the tag. + RELEASE_URL="${{ github.server_url }}/${REPO}/tree/${{ env.TAG_NAME }}" + NEW_HEADING="## [${VERSION}](${RELEASE_URL}) - ${CURRENT_DATE}" + + sed -i "s|^## \[Unreleased\].*|${NEW_HEADING}|" CHANGELOG.md + echo "✅ Updated CHANGELOG.md [Unreleased] heading to [$VERSION]" + + - name: Verify version metadata + run: | + set -euo pipefail + + VERSION="${{ steps.parse_version.outputs.version }}" + FILE_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) + + if [[ "$FILE_VERSION" != "$VERSION" ]]; then + echo "❌ version.php mismatch: expected $VERSION, found $FILE_VERSION" + exit 1 + fi + + - name: Verify changelog metadata + run: | + set -euo pipefail + + VERSION="${{ steps.parse_version.outputs.version }}" + + if ! grep -qE "^## \[$VERSION\]" CHANGELOG.md; then + echo "❌ CHANGELOG.md does not contain a release heading for [$VERSION]" + echo "❌ Refusing to release because changelog metadata is incomplete." + exit 1 + fi + + - name: Verify required release file changes + id: git_check + run: | + set -euo pipefail + + VERSION_CHANGED=true + CHANGELOG_CHANGED=true + + if git diff --quiet -- cli/version.php; then + VERSION_CHANGED=false + fi + + if git diff --quiet -- CHANGELOG.md; then + CHANGELOG_CHANGED=false + fi + + if [[ "$VERSION_CHANGED" != "true" ]]; then + echo "❌ version.php was not changed." + echo "❌ A release requires a version bump." + exit 1 + fi + + if [[ "$CHANGELOG_CHANGED" != "true" ]]; then + echo "❌ CHANGELOG.md was not changed." + echo "❌ A release requires a changelog update." + exit 1 + fi + + echo "changes=true" >> "$GITHUB_OUTPUT" + echo "📝 Required release changes detected:" + git status --porcelain -- cli/version.php CHANGELOG.md + + - name: Prepare release branch name + if: steps.git_check.outputs.changes == 'true' + id: release_branch + run: | + set -euo pipefail + + # Create a release branch name based on the tag name + BRANCH_NAME="release/${{ env.TAG_NAME }}" + echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + + - name: Check release branch doesn't already exist remotely + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail + + BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" + + if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then + echo "❌ Branch $BRANCH_NAME already exists on origin" + echo "Delete the existing branch or use a new tag name." + exit 1 + fi + + echo "✅ Release branch name ($BRANCH_NAME) is available" + + - name: Create release branch + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail + + BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" + + # Create and checkout new branch + git checkout -b "$BRANCH_NAME" || { + echo "❌ Failed to create release branch: $BRANCH_NAME" + exit 1 + } + echo "✅ Created release branch: $BRANCH_NAME" + + - name: Commit release update changes + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail + + # Stage and commit changes + git add cli/version.php CHANGELOG.md || { + echo "❌ Failed to stage files" + exit 1 + } + + git commit -m "chore: version bump and update changelog for ${{ env.TAG_NAME }}" || { + echo "❌ Failed to commit changes" + exit 1 + } + echo "✅ Committed changes" + + - name: Push to remote + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail + # Push to remote + git push -u origin "${{ steps.release_branch.outputs.branch_name }}" || { + echo "❌ Failed to push release branch to remote: ${{ steps.release_branch.outputs.branch_name }}" + exit 1 + } + echo "✅ Pushed release branch to remote" + + - name: Create pull request + if: steps.git_check.outputs.changes == 'true' + id: create_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" + + echo "Creating pull request on $BRANCH_NAME branch into ${{ env.TARGET_BRANCH }}" + + # Determine release type for PR title and body + RELEASE_TYPE="release" + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + RELEASE_TYPE="pre-release" + fi + + # Define PR details + # ^ capitalizes the first letter + PR_TITLE="${RELEASE_TYPE^} ${{ env.TAG_NAME }}" + PR_BODY="Automated version bump and changelog update for $RELEASE_TYPE ${{ env.TAG_NAME }}." + PR_LABELS="auto version bump,release" + + # Create pull request and get the returned URL + PR_URL=$(gh pr create \ + --base "${{ env.TARGET_BRANCH }}" \ + --head "$BRANCH_NAME" \ + --title "$PR_TITLE" \ + --body "$PR_BODY" \ + --label "$PR_LABELS") || { + echo "❌ Failed to create pull request" + exit 1 + } + echo "✅ Pull request created successfully: $PR_URL" + + # Extract PR number from URL and store as output + PR_NUMBER=$(echo "$PR_URL" | grep -oP '\\d+$') + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" + + + - name: Verify repository auto-merge is enabled + if: steps.git_check.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + # Check if auto-merge is allowed for the repository using GitHub GraphQL API + AUTO_MERGE_ALLOWED=$(gh api graphql \ + -f query='query($owner:String!, $repo:String!){ repository(owner:$owner, name:$repo){ autoMergeAllowed } }' \ + -f owner='${{ github.repository_owner }}' \ + -f repo='${{ github.event.repository.name }}' \ + --jq '.data.repository.autoMergeAllowed') + + if [[ "$AUTO_MERGE_ALLOWED" != "true" ]]; then + echo "❌ Repository auto-merge is disabled." + echo "ℹ️ Enable auto-merge in repository settings, or manually merge the release PR and rerun with publish_only=true." + exit 1 + fi + + - name: Queue pull request for auto-merge + if: steps.git_check.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + # Merge PR using auto-merge + gh pr merge "${{ steps.create_pr.outputs.pr_url }}" --auto --squash || { + echo "❌ Failed to queue pull request for auto-merge" + exit 1 + } + echo "✅ Pull request queued for auto-merge" + + - name: Wait for PR merge + if: steps.git_check.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + PR_NUMBER="${{ steps.create_pr.outputs.pr_number }}" + + echo "⏳ Waiting for pull request #$PR_NUMBER to be merged..." + + # Wait for PR to be merged (timeout after 10 minutes) + TIMEOUT=600 + ELAPSED=0 + INTERVAL=5 + + while [[ $ELAPSED -lt $TIMEOUT ]]; do + # Get PR state and merge status in one call + PR_DATA=$(gh pr view "$PR_NUMBER" --json state,mergedAt) + PR_STATE=$(echo "$PR_DATA" | jq -r '.state') + PR_MERGED_AT=$(echo "$PR_DATA" | jq -r '.mergedAt') + + if [[ "$PR_STATE" == "MERGED" || "$PR_MERGED_AT" != "null" ]]; then + echo "✅ Pull request successfully merged" + exit 0 fi - update-version: - name: Update Version - needs: validate-release-version + echo "⏳ Still waiting... (${ELAPSED}s elapsed)" + sleep "$INTERVAL" + ELAPSED=$((ELAPSED + INTERVAL)) + done + + echo "❌ Timeout waiting for pull request to merge" + exit 1 + + - name: Resolve release commit SHA + id: resolve_release_sha + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + if [[ "${{ steps.git_check.outputs.changes }}" == "true" ]]; then + RELEASE_SHA=$(gh pr view "${{ steps.create_pr.outputs.pr_number }}" --json mergeCommit --jq '.mergeCommit.oid') + else + git fetch origin "${{ env.TARGET_BRANCH }}" + RELEASE_SHA=$(git rev-parse "origin/${{ env.TARGET_BRANCH }}") + fi + + echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" + echo "✅ Resolved release commit SHA: $RELEASE_SHA" + + resolve-release-sha-publish-only: + if: inputs.publish_only == true + name: Resolve Release SHA (Publish Only) runs-on: ubuntu-latest - if: needs.validate-release-version.outputs.is-valid == 'true' - + outputs: + release_sha: ${{ steps.resolve_publish_only_sha.outputs.release_sha }} + env: + TARGET_BRANCH: ${{ inputs.target_branch }} steps: - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - name: Setup Git - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Update Version in cli/version.php - run: | - VERSION="${{ needs.validate-release-version.outputs.version }}" - - # If this is a prerelease, remove any prerelease tags to get base version - if [ "${{ github.event.action }}" = "prereleased" ]; then - BASE_VERSION=$(echo "$VERSION" | sed 's/-.*$//') - echo "🔄 Prerelease detected: using base version $BASE_VERSION instead of $VERSION" - VERSION="$BASE_VERSION" - fi - - CURRENT_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) - - if [ "$VERSION" = "$CURRENT_VERSION" ]; then - echo "ℹ️ cli/version.php already has version $VERSION, skipping update" - else - echo "📦 Updating cli/version.php from $CURRENT_VERSION to $VERSION" - sed -i "s/\$version = '[^']*';/\$version = '$VERSION';/" cli/version.php - echo "✅ Successfully updated version to $VERSION" - fi - - - name: Update Changelog Release Heading - run: | - VERSION="${{ needs.validate-release-version.outputs.version }}" - TAG_NAME="${{ env.TAG_NAME }}" - REPO="${{ github.repository }}" - CURRENT_DATE=$(date +%Y-%m-%d) - - # Check if CHANGELOG.md exists - if [ ! -f "CHANGELOG.md" ]; then - echo "⚠️ CHANGELOG.md not found, skipping changelog update" - exit 0 - fi - - # Check if Unreleased section exists (with or without a trailing URL) - if ! grep -qP "^## \[Unreleased\]" CHANGELOG.md; then - echo "ℹ️ No [Unreleased] section found in CHANGELOG.md, skipping update" - exit 0 - fi - - # Replace the entire [Unreleased] heading line (including any trailing URL) with - # a versioned release heading that links to the tree at the tag. - RELEASE_URL="${{ github.server_url }}/${REPO}/tree/${TAG_NAME}" - NEW_HEADING="## [${VERSION}](${RELEASE_URL}) - ${CURRENT_DATE}" - - sed -i "s|^## \[Unreleased\].*|${NEW_HEADING}|" CHANGELOG.md - - echo "✅ Updated CHANGELOG.md:" - echo " [Unreleased] → [${VERSION}]" - echo " Date: ${CURRENT_DATE}" - echo " URL: ${RELEASE_URL}" - - - name: Check for Changes - id: git-check - run: | - # Check for any modified or untracked files - if [ -n "$(git status --porcelain)" ]; then - echo "changes=true" >> $GITHUB_OUTPUT - echo "📝 Changes detected:" - git status --porcelain - else - echo "changes=false" >> $GITHUB_OUTPUT - echo "ℹ️ No changes to commit" - fi - - - name: Create New Branch - if: steps.git-check.outputs.changes == 'true' - id: create-branch - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -e # Exit on any error - BRANCH_NAME="release/${{ env.TAG_NAME }}" - echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT - - # Check if branch already exists remotely - if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then - echo "❌ Cannot create $BRANCH_NAME branch as it already exists. This may indicate:" - echo " - A previous workflow run failed partway through" - echo " - The branch was manually created" - echo "Please manually delete the branch or check for an existing PR and resolve manually." - exit 1 - fi - - # Create and checkout new branch - git checkout -b "$BRANCH_NAME" || { - echo "❌ Failed to create branch $BRANCH_NAME" - exit 1 - } - echo "✅ Created $BRANCH_NAME branch" - - - name: Commit Changes - if: steps.git-check.outputs.changes == 'true' - id: commit-changes - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -e # Exit on any error - - # Stage and commit changes - git add cli/version.php CHANGELOG.md || { - echo "❌ Failed to stage files" - exit 1 - } - - git commit -m "chore: version bump and update changelog" || { - echo "❌ Failed to commit changes" - exit 1 - } - echo "✅ Committed changes" - - # Push to remote - BRANCH_NAME="${{ steps.create-branch.outputs.branch-name }}" - git push origin "$BRANCH_NAME" || { - echo "❌ Failed to push changes to remote" - exit 1 - } - echo "✅ Pushed changes to remote on $BRANCH_NAME branch" - - - name: Create Pull Request - if: steps.git-check.outputs.changes == 'true' - id: create-pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -e # Exit on any error - BRANCH_NAME="${{ steps.create-branch.outputs.branch-name }}" - - echo "Creating pull request on $BRANCH_NAME branch into ${{ env.TARGET_BRANCH }}" - - # Set PR title and body based on release type - RELEASE_TYPE="release" - if [ "${{ github.event.action }}" = "prereleased" ]; then - RELEASE_TYPE="pre-release" - fi - - # Define PR details - # ^ capitalises the first letter - PR_TITLE="${RELEASE_TYPE^} ${{ env.TAG_NAME }}" - PR_BODY="Automated version bump for $RELEASE_TYPE ${{ env.TAG_NAME }}." - PR_LABELS="auto version bump,release" - - # Create pull request - PR_URL=$(gh pr create \ - --base "${{ env.TARGET_BRANCH }}" \ - --head "$BRANCH_NAME" \ - --title "$PR_TITLE" \ - --body "$PR_BODY" \ - --label "$PR_LABELS") || { - echo "❌ Failed to create pull request" - exit 1 - } - echo "✅ Pull request created successfully: $PR_URL" - - # Extract PR number from URL and store as output - PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$') - echo "pr-number=$PR_NUMBER" >> $GITHUB_OUTPUT - echo "pr-url=$PR_URL" >> $GITHUB_OUTPUT - - # Enable GitHub PR auto-merge - gh pr merge "$PR_URL" --auto --squash || { - echo "❌ Failed to enable auto-merge" - echo "This may be due to:" - echo " - Auto-merge not being enabled in repository settings" - echo " - Required status checks not configured" - echo " - Insufficient permissions" - exit 1 - } - echo "✅ Enabled auto-merge for pull request" - - - name: Wait for PR Merge - if: steps.git-check.outputs.changes == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - PR_NUMBER="${{ steps.create-pr.outputs.pr-number }}" - - echo "⏳ Waiting for pull request #$PR_NUMBER to be merged..." - - # Wait for PR to be merged (timeout after 5 minutes) - TIMEOUT=300 - ELAPSED=0 - INTERVAL=5 - - while [ $ELAPSED -lt $TIMEOUT ]; do - # Get PR state and merge status in one call - PR_DATA=$(gh pr view "$PR_NUMBER" --json state,mergedAt 2>/dev/null || echo '{"state":"UNKNOWN","mergedAt":null}') - PR_STATE=$(echo "$PR_DATA" | jq -r '.state') - PR_MERGED_AT=$(echo "$PR_DATA" | jq -r '.mergedAt') - - if [ "$PR_STATE" = "MERGED" ] || [ "$PR_MERGED_AT" != "null" ]; then - echo "✅ Pull request successfully merged" - break - fi - - sleep $INTERVAL - ELAPSED=$((ELAPSED + INTERVAL)) - echo "⏳ Still waiting... (${ELAPSED}s elapsed)" - done - - if [ $ELAPSED -ge $TIMEOUT ]; then - echo "❌ Timeout waiting for pull request to merge" - exit 1 - fi - - - name: Update Tag Reference - if: steps.git-check.outputs.changes == 'true' - run: | - # Fetch the latest changes from target branch - git fetch origin ${{ env.TARGET_BRANCH }} - git checkout ${{ env.TARGET_BRANCH }} - git pull origin ${{ env.TARGET_BRANCH }} - - # Move the existing tag to point to the new commit (preserves GitHub release relationship) - git tag ${{ env.TAG_NAME }} -f - git push origin ${{ env.TAG_NAME }} -f - echo "✅ Updated tag ${{ env.TAG_NAME }} to point to release commit" + - name: Checkout target branch + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Resolve release commit SHA + id: resolve_publish_only_sha + run: | + set -euo pipefail + + INPUT_SHA="${{ inputs.release_sha }}" + + if [[ -n "$INPUT_SHA" ]]; then + RELEASE_SHA="$INPUT_SHA" + else + git fetch origin "${{ env.TARGET_BRANCH }}" + RELEASE_SHA=$(git rev-parse "origin/${{ env.TARGET_BRANCH }}") + fi + + git cat-file -e "$RELEASE_SHA^{commit}" || { + echo "❌ Could not resolve a valid commit SHA for publish-only mode" + exit 1 + } + + echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" + echo "✅ Publish-only release SHA resolved: $RELEASE_SHA" + + publish-release: + name: Publish Release + runs-on: ubuntu-latest + if: always() && (needs.prepare-release.result == 'success' || needs.resolve-release-sha-publish-only.result == 'success') + needs: + - prepare-release + - resolve-release-sha-publish-only + env: + TAG_NAME: ${{ inputs.tag_name }} + TARGET_BRANCH: ${{ inputs.target_branch }} + RELEASE_SHA: ${{ inputs.publish_only && needs.resolve-release-sha-publish-only.outputs.release_sha || needs.prepare-release.outputs.release_sha }} + steps: + - name: Checkout target branch + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Fetch target branch + run: | + set -euo pipefail + git fetch origin "${{ env.TARGET_BRANCH }}" + + - name: Verify release commit exists locally + run: | + set -euo pipefail + git cat-file -e "${{ env.RELEASE_SHA }}^{commit}" + echo "✅ Release commit exists locally: ${{ env.RELEASE_SHA }}" + + - name: Create immutable tag + run: | + set -euo pipefail + git tag "${{ env.TAG_NAME }}" "${{ env.RELEASE_SHA }}" + echo "✅ Created immutable tag locally: ${{ env.TAG_NAME }}" + + - name: Push immutable tag + run: | + set -euo pipefail + git push origin "${{ env.TAG_NAME }}" + echo "✅ Pushed immutable tag: ${{ env.TAG_NAME }}" + + - name: Publish GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + RELEASE_ARGS=() + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + RELEASE_ARGS+=(--prerelease) + fi + + gh release create "${{ env.TAG_NAME }}" \ + --target "${{ env.RELEASE_SHA }}" \ + --generate-notes \ + "${RELEASE_ARGS[@]}" + + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + echo "✅ Published prerelease: ${{ env.TAG_NAME }}" + else + echo "✅ Published release: ${{ env.TAG_NAME }}" + fi From 34d111ce720756f75c7f75d2076d8f82cc3b7f99 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 5 Jul 2026 04:40:28 +0100 Subject: [PATCH 3/7] fix: formatting --- .github/workflows/release.yml | 791 +++++++++++++++++----------------- 1 file changed, 395 insertions(+), 396 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 750d6c9d3..fab6027bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,363 +43,362 @@ jobs: TAG_NAME: ${{ inputs.tag_name }} TARGET_BRANCH: ${{ inputs.target_branch }} steps: - - name: Checkout target branch - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ inputs.target_branch }} - fetch-depth: 0 - - - name: Parse and validate version - id: parse_version - run: | - set -euo pipefail - - TAG_NAME="${{ env.TAG_NAME }}" - - # Validate semver format and require the release tag to start with 'v'. - if [[ "$TAG_NAME" =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?)$ ]]; then - VERSION="${BASH_REMATCH[1]}" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "✅ Valid semver tag: $TAG_NAME" - else - echo "❌ Invalid version format: $TAG_NAME" - echo "ℹ️ Version must start with 'v' and follow semver (e.g., v1.0.0, v1.0.0-beta.1)" - exit 1 - fi + - name: Checkout target branch + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Parse and validate version + id: parse_version + run: | + set -euo pipefail + + TAG_NAME="${{ env.TAG_NAME }}" + + # Validate semver format and require the release tag to start with 'v'. + if [[ "$TAG_NAME" =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?)$ ]]; then + VERSION="${BASH_REMATCH[1]}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "✅ Valid semver tag: $TAG_NAME" + else + echo "❌ Invalid version format: $TAG_NAME" + echo "ℹ️ Version must start with 'v' and follow semver (e.g., v1.0.0, v1.0.0-beta.1)" + exit 1 + fi - - name: Check local tag availability - run: | - set -euo pipefail + - name: Check local tag availability + run: | + set -euo pipefail - if git rev-parse -q --verify "refs/tags/${{ env.TAG_NAME }}" >/dev/null; then - echo "⚠️ Tag ${{ env.TAG_NAME }} already exists locally." - exit 1 - fi + if git rev-parse -q --verify "refs/tags/${{ env.TAG_NAME }}" >/dev/null; then + echo "⚠️ Tag ${{ env.TAG_NAME }} already exists locally." + exit 1 + fi - - name: Check remote tag availability - run: | - set -euo pipefail + - name: Check remote tag availability + run: | + set -euo pipefail - if git ls-remote --exit-code --tags origin "refs/tags/${{ env.TAG_NAME }}" >/dev/null 2>&1; then - echo "⚠️ Tag ${{ env.TAG_NAME }} already exists on origin." - echo "Published tags are immutable. Choose a new tag." - exit 1 - fi - - - name: Setup git identity - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Update version file - run: | - set -euo pipefail - - VERSION="${{ steps.parse_version.outputs.version }}" - CURRENT_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) - - if [[ "$VERSION" == "$CURRENT_VERSION" ]]; then - echo "ℹ️ version.php already set to $VERSION" - else - sed -i "s/\$version = '[^']*';/\$version = '$VERSION';/" cli/version.php - echo "✅ Updated version.php: $CURRENT_VERSION -> $VERSION" - fi - - - name: Detect changelog file - id: changelog_file - run: | - set -euo pipefail - - # Check if CHANGELOG.md exists - if [[ -f "CHANGELOG.md" ]]; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "❌ CHANGELOG.md not found. Release requires changelog updates." - exit 1 - fi - - - name: Detect unreleased changelog heading - id: changelog_unreleased - if: steps.changelog_file.outputs.exists == 'true' - run: | - set -euo pipefail - - # Check if Unreleased section exists (with or without a trailing URL) - if grep -qP "^## \[Unreleased\]" CHANGELOG.md; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "❌ No [Unreleased] section found in CHANGELOG.md." - echo "Release requires converting [Unreleased] into the release heading." - exit 1 - fi - - - name: Update changelog release heading - if: steps.changelog_unreleased.outputs.exists == 'true' - run: | - set -euo pipefail + if git ls-remote --exit-code --tags origin "refs/tags/${{ env.TAG_NAME }}" >/dev/null 2>&1; then + echo "⚠️ Tag ${{ env.TAG_NAME }} already exists on origin." + echo "Published tags are immutable. Choose a new tag." + exit 1 + fi - VERSION="${{ steps.parse_version.outputs.version }}" - REPO="${{ github.repository }}" - CURRENT_DATE=$(date +%Y-%m-%d) + - name: Setup git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # Replace the entire [Unreleased] heading line (including any trailing URL) - # with a versioned release heading that links to the tree at the tag. - RELEASE_URL="${{ github.server_url }}/${REPO}/tree/${{ env.TAG_NAME }}" - NEW_HEADING="## [${VERSION}](${RELEASE_URL}) - ${CURRENT_DATE}" + - name: Update version file + run: | + set -euo pipefail - sed -i "s|^## \[Unreleased\].*|${NEW_HEADING}|" CHANGELOG.md - echo "✅ Updated CHANGELOG.md [Unreleased] heading to [$VERSION]" + VERSION="${{ steps.parse_version.outputs.version }}" + CURRENT_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) - - name: Verify version metadata - run: | - set -euo pipefail + if [[ "$VERSION" == "$CURRENT_VERSION" ]]; then + echo "ℹ️ version.php already set to $VERSION" + else + sed -i "s/\$version = '[^']*';/\$version = '$VERSION';/" cli/version.php + echo "✅ Updated version.php: $CURRENT_VERSION -> $VERSION" + fi - VERSION="${{ steps.parse_version.outputs.version }}" - FILE_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) + - name: Detect changelog file + id: changelog_file + run: | + set -euo pipefail + + # Check if CHANGELOG.md exists + if [[ -f "CHANGELOG.md" ]]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "❌ CHANGELOG.md not found. Release requires changelog updates." + exit 1 + fi - if [[ "$FILE_VERSION" != "$VERSION" ]]; then - echo "❌ version.php mismatch: expected $VERSION, found $FILE_VERSION" - exit 1 - fi + - name: Detect unreleased changelog heading + id: changelog_unreleased + if: steps.changelog_file.outputs.exists == 'true' + run: | + set -euo pipefail + + # Check if Unreleased section exists (with or without a trailing URL) + if grep -qP "^## \[Unreleased\]" CHANGELOG.md; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "❌ No [Unreleased] section found in CHANGELOG.md." + echo "Release requires converting [Unreleased] into the release heading." + exit 1 + fi - - name: Verify changelog metadata - run: | - set -euo pipefail + - name: Update changelog release heading + if: steps.changelog_unreleased.outputs.exists == 'true' + run: | + set -euo pipefail - VERSION="${{ steps.parse_version.outputs.version }}" + VERSION="${{ steps.parse_version.outputs.version }}" + REPO="${{ github.repository }}" + CURRENT_DATE=$(date +%Y-%m-%d) - if ! grep -qE "^## \[$VERSION\]" CHANGELOG.md; then - echo "❌ CHANGELOG.md does not contain a release heading for [$VERSION]" - echo "❌ Refusing to release because changelog metadata is incomplete." - exit 1 - fi + # Replace the entire [Unreleased] heading line (including any trailing URL) + # with a versioned release heading that links to the tree at the tag. + RELEASE_URL="${{ github.server_url }}/${REPO}/tree/${{ env.TAG_NAME }}" + NEW_HEADING="## [${VERSION}](${RELEASE_URL}) - ${CURRENT_DATE}" - - name: Verify required release file changes - id: git_check - run: | - set -euo pipefail + sed -i "s|^## \[Unreleased\].*|${NEW_HEADING}|" CHANGELOG.md + echo "✅ Updated CHANGELOG.md [Unreleased] heading to [$VERSION]" - VERSION_CHANGED=true - CHANGELOG_CHANGED=true + - name: Verify version metadata + run: | + set -euo pipefail - if git diff --quiet -- cli/version.php; then - VERSION_CHANGED=false - fi + VERSION="${{ steps.parse_version.outputs.version }}" + FILE_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) - if git diff --quiet -- CHANGELOG.md; then - CHANGELOG_CHANGED=false - fi + if [[ "$FILE_VERSION" != "$VERSION" ]]; then + echo "❌ version.php mismatch: expected $VERSION, found $FILE_VERSION" + exit 1 + fi - if [[ "$VERSION_CHANGED" != "true" ]]; then - echo "❌ version.php was not changed." - echo "❌ A release requires a version bump." - exit 1 - fi + - name: Verify changelog metadata + run: | + set -euo pipefail - if [[ "$CHANGELOG_CHANGED" != "true" ]]; then - echo "❌ CHANGELOG.md was not changed." - echo "❌ A release requires a changelog update." - exit 1 - fi + VERSION="${{ steps.parse_version.outputs.version }}" - echo "changes=true" >> "$GITHUB_OUTPUT" - echo "📝 Required release changes detected:" - git status --porcelain -- cli/version.php CHANGELOG.md + if ! grep -qE "^## \[$VERSION\]" CHANGELOG.md; then + echo "❌ CHANGELOG.md does not contain a release heading for [$VERSION]" + echo "❌ Refusing to release because changelog metadata is incomplete." + exit 1 + fi - - name: Prepare release branch name - if: steps.git_check.outputs.changes == 'true' - id: release_branch - run: | - set -euo pipefail + - name: Verify required release file changes + id: git_check + run: | + set -euo pipefail - # Create a release branch name based on the tag name - BRANCH_NAME="release/${{ env.TAG_NAME }}" - echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" + VERSION_CHANGED=true + CHANGELOG_CHANGED=true - - name: Check release branch doesn't already exist remotely - if: steps.git_check.outputs.changes == 'true' - run: | - set -euo pipefail + if git diff --quiet -- cli/version.php; then + VERSION_CHANGED=false + fi - BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" + if git diff --quiet -- CHANGELOG.md; then + CHANGELOG_CHANGED=false + fi - if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then - echo "❌ Branch $BRANCH_NAME already exists on origin" - echo "Delete the existing branch or use a new tag name." - exit 1 - fi + if [[ "$VERSION_CHANGED" != "true" ]]; then + echo "❌ version.php was not changed." + echo "❌ A release requires a version bump." + exit 1 + fi - echo "✅ Release branch name ($BRANCH_NAME) is available" + if [[ "$CHANGELOG_CHANGED" != "true" ]]; then + echo "❌ CHANGELOG.md was not changed." + echo "❌ A release requires a changelog update." + exit 1 + fi - - name: Create release branch - if: steps.git_check.outputs.changes == 'true' - run: | - set -euo pipefail + echo "changes=true" >> "$GITHUB_OUTPUT" + echo "📝 Required release changes detected:" + git status --porcelain -- cli/version.php CHANGELOG.md - BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" + - name: Prepare release branch name + if: steps.git_check.outputs.changes == 'true' + id: release_branch + run: | + set -euo pipefail - # Create and checkout new branch - git checkout -b "$BRANCH_NAME" || { - echo "❌ Failed to create release branch: $BRANCH_NAME" - exit 1 - } - echo "✅ Created release branch: $BRANCH_NAME" + # Create a release branch name based on the tag name + BRANCH_NAME="release/${{ env.TAG_NAME }}" + echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" - - name: Commit release update changes - if: steps.git_check.outputs.changes == 'true' - run: | - set -euo pipefail + - name: Check release branch doesn't already exist remotely + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail - # Stage and commit changes - git add cli/version.php CHANGELOG.md || { - echo "❌ Failed to stage files" - exit 1 - } + BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" - git commit -m "chore: version bump and update changelog for ${{ env.TAG_NAME }}" || { - echo "❌ Failed to commit changes" - exit 1 - } - echo "✅ Committed changes" - - - name: Push to remote - if: steps.git_check.outputs.changes == 'true' - run: | - set -euo pipefail - # Push to remote - git push -u origin "${{ steps.release_branch.outputs.branch_name }}" || { - echo "❌ Failed to push release branch to remote: ${{ steps.release_branch.outputs.branch_name }}" - exit 1 - } - echo "✅ Pushed release branch to remote" - - - name: Create pull request - if: steps.git_check.outputs.changes == 'true' - id: create_pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" - - echo "Creating pull request on $BRANCH_NAME branch into ${{ env.TARGET_BRANCH }}" - - # Determine release type for PR title and body - RELEASE_TYPE="release" - if [[ "${{ inputs.prerelease }}" == "true" ]]; then - RELEASE_TYPE="pre-release" - fi - - # Define PR details - # ^ capitalizes the first letter - PR_TITLE="${RELEASE_TYPE^} ${{ env.TAG_NAME }}" - PR_BODY="Automated version bump and changelog update for $RELEASE_TYPE ${{ env.TAG_NAME }}." - PR_LABELS="auto version bump,release" - - # Create pull request and get the returned URL - PR_URL=$(gh pr create \ - --base "${{ env.TARGET_BRANCH }}" \ - --head "$BRANCH_NAME" \ - --title "$PR_TITLE" \ - --body "$PR_BODY" \ - --label "$PR_LABELS") || { - echo "❌ Failed to create pull request" - exit 1 - } - echo "✅ Pull request created successfully: $PR_URL" - - # Extract PR number from URL and store as output - PR_NUMBER=$(echo "$PR_URL" | grep -oP '\\d+$') - echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" - - - - name: Verify repository auto-merge is enabled - if: steps.git_check.outputs.changes == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - # Check if auto-merge is allowed for the repository using GitHub GraphQL API - AUTO_MERGE_ALLOWED=$(gh api graphql \ - -f query='query($owner:String!, $repo:String!){ repository(owner:$owner, name:$repo){ autoMergeAllowed } }' \ - -f owner='${{ github.repository_owner }}' \ - -f repo='${{ github.event.repository.name }}' \ - --jq '.data.repository.autoMergeAllowed') - - if [[ "$AUTO_MERGE_ALLOWED" != "true" ]]; then - echo "❌ Repository auto-merge is disabled." - echo "ℹ️ Enable auto-merge in repository settings, or manually merge the release PR and rerun with publish_only=true." - exit 1 - fi - - - name: Queue pull request for auto-merge - if: steps.git_check.outputs.changes == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - # Merge PR using auto-merge - gh pr merge "${{ steps.create_pr.outputs.pr_url }}" --auto --squash || { - echo "❌ Failed to queue pull request for auto-merge" - exit 1 - } - echo "✅ Pull request queued for auto-merge" - - - name: Wait for PR merge - if: steps.git_check.outputs.changes == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - PR_NUMBER="${{ steps.create_pr.outputs.pr_number }}" - - echo "⏳ Waiting for pull request #$PR_NUMBER to be merged..." - - # Wait for PR to be merged (timeout after 10 minutes) - TIMEOUT=600 - ELAPSED=0 - INTERVAL=5 - - while [[ $ELAPSED -lt $TIMEOUT ]]; do - # Get PR state and merge status in one call - PR_DATA=$(gh pr view "$PR_NUMBER" --json state,mergedAt) - PR_STATE=$(echo "$PR_DATA" | jq -r '.state') - PR_MERGED_AT=$(echo "$PR_DATA" | jq -r '.mergedAt') - - if [[ "$PR_STATE" == "MERGED" || "$PR_MERGED_AT" != "null" ]]; then - echo "✅ Pull request successfully merged" - exit 0 + if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then + echo "❌ Branch $BRANCH_NAME already exists on origin" + echo "Delete the existing branch or use a new tag name." + exit 1 fi - echo "⏳ Still waiting... (${ELAPSED}s elapsed)" - sleep "$INTERVAL" - ELAPSED=$((ELAPSED + INTERVAL)) - done + echo "✅ Release branch name ($BRANCH_NAME) is available" + + - name: Create release branch + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail + + BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" + + # Create and checkout new branch + git checkout -b "$BRANCH_NAME" || { + echo "❌ Failed to create release branch: $BRANCH_NAME" + exit 1 + } + echo "✅ Created release branch: $BRANCH_NAME" + + - name: Commit release update changes + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail + + # Stage and commit changes + git add cli/version.php CHANGELOG.md || { + echo "❌ Failed to stage files" + exit 1 + } + + git commit -m "chore: version bump and update changelog for ${{ env.TAG_NAME }}" || { + echo "❌ Failed to commit changes" + exit 1 + } + echo "✅ Committed changes" + + - name: Push to remote + if: steps.git_check.outputs.changes == 'true' + run: | + set -euo pipefail + # Push to remote + git push -u origin "${{ steps.release_branch.outputs.branch_name }}" || { + echo "❌ Failed to push release branch to remote: ${{ steps.release_branch.outputs.branch_name }}" + exit 1 + } + echo "✅ Pushed release branch to remote" + + - name: Create pull request + if: steps.git_check.outputs.changes == 'true' + id: create_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + BRANCH_NAME="${{ steps.release_branch.outputs.branch_name }}" + + echo "Creating pull request on $BRANCH_NAME branch into ${{ env.TARGET_BRANCH }}" + + # Determine release type for PR title and body + RELEASE_TYPE="release" + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + RELEASE_TYPE="pre-release" + fi - echo "❌ Timeout waiting for pull request to merge" - exit 1 + # Define PR details + # ^ capitalizes the first letter + PR_TITLE="${RELEASE_TYPE^} ${{ env.TAG_NAME }}" + PR_BODY="Automated version bump and changelog update for $RELEASE_TYPE ${{ env.TAG_NAME }}." + PR_LABELS="auto version bump,release" + + # Create pull request and get the returned URL + PR_URL=$(gh pr create \ + --base "${{ env.TARGET_BRANCH }}" \ + --head "$BRANCH_NAME" \ + --title "$PR_TITLE" \ + --body "$PR_BODY" \ + --label "$PR_LABELS") || { + echo "❌ Failed to create pull request" + exit 1 + } + echo "✅ Pull request created successfully: $PR_URL" + + # Extract PR number from URL and store as output + PR_NUMBER=$(echo "$PR_URL" | grep -oP '\\d+$') + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" + + - name: Verify repository auto-merge is enabled + if: steps.git_check.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + # Check if auto-merge is allowed for the repository using GitHub GraphQL API + AUTO_MERGE_ALLOWED=$(gh api graphql \ + -f query='query($owner:String!, $repo:String!){ repository(owner:$owner, name:$repo){ autoMergeAllowed } }' \ + -f owner='${{ github.repository_owner }}' \ + -f repo='${{ github.event.repository.name }}' \ + --jq '.data.repository.autoMergeAllowed') + + if [[ "$AUTO_MERGE_ALLOWED" != "true" ]]; then + echo "❌ Repository auto-merge is disabled." + echo "ℹ️ Enable auto-merge in repository settings, or manually merge the release PR and rerun with publish_only=true." + exit 1 + fi - - name: Resolve release commit SHA - id: resolve_release_sha - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail + - name: Queue pull request for auto-merge + if: steps.git_check.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + # Merge PR using auto-merge + gh pr merge "${{ steps.create_pr.outputs.pr_url }}" --auto --squash || { + echo "❌ Failed to queue pull request for auto-merge" + exit 1 + } + echo "✅ Pull request queued for auto-merge" + + - name: Wait for PR merge + if: steps.git_check.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + PR_NUMBER="${{ steps.create_pr.outputs.pr_number }}" + + echo "⏳ Waiting for pull request #$PR_NUMBER to be merged..." + + # Wait for PR to be merged (timeout after 10 minutes) + TIMEOUT=600 + ELAPSED=0 + INTERVAL=5 + + while [[ $ELAPSED -lt $TIMEOUT ]]; do + # Get PR state and merge status in one call + PR_DATA=$(gh pr view "$PR_NUMBER" --json state,mergedAt) + PR_STATE=$(echo "$PR_DATA" | jq -r '.state') + PR_MERGED_AT=$(echo "$PR_DATA" | jq -r '.mergedAt') + + if [[ "$PR_STATE" == "MERGED" || "$PR_MERGED_AT" != "null" ]]; then + echo "✅ Pull request successfully merged" + exit 0 + fi + + echo "⏳ Still waiting... (${ELAPSED}s elapsed)" + sleep "$INTERVAL" + ELAPSED=$((ELAPSED + INTERVAL)) + done + + echo "❌ Timeout waiting for pull request to merge" + exit 1 - if [[ "${{ steps.git_check.outputs.changes }}" == "true" ]]; then - RELEASE_SHA=$(gh pr view "${{ steps.create_pr.outputs.pr_number }}" --json mergeCommit --jq '.mergeCommit.oid') - else - git fetch origin "${{ env.TARGET_BRANCH }}" - RELEASE_SHA=$(git rev-parse "origin/${{ env.TARGET_BRANCH }}") - fi + - name: Resolve release commit SHA + id: resolve_release_sha + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + if [[ "${{ steps.git_check.outputs.changes }}" == "true" ]]; then + RELEASE_SHA=$(gh pr view "${{ steps.create_pr.outputs.pr_number }}" --json mergeCommit --jq '.mergeCommit.oid') + else + git fetch origin "${{ env.TARGET_BRANCH }}" + RELEASE_SHA=$(git rev-parse "origin/${{ env.TARGET_BRANCH }}") + fi - echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" - echo "✅ Resolved release commit SHA: $RELEASE_SHA" + echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" + echo "✅ Resolved release commit SHA: $RELEASE_SHA" resolve-release-sha-publish-only: if: inputs.publish_only == true @@ -410,34 +409,34 @@ jobs: env: TARGET_BRANCH: ${{ inputs.target_branch }} steps: - - name: Checkout target branch - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ inputs.target_branch }} - fetch-depth: 0 - - - name: Resolve release commit SHA - id: resolve_publish_only_sha - run: | - set -euo pipefail - - INPUT_SHA="${{ inputs.release_sha }}" - - if [[ -n "$INPUT_SHA" ]]; then - RELEASE_SHA="$INPUT_SHA" - else - git fetch origin "${{ env.TARGET_BRANCH }}" - RELEASE_SHA=$(git rev-parse "origin/${{ env.TARGET_BRANCH }}") - fi + - name: Checkout target branch + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Resolve release commit SHA + id: resolve_publish_only_sha + run: | + set -euo pipefail + + INPUT_SHA="${{ inputs.release_sha }}" + + if [[ -n "$INPUT_SHA" ]]; then + RELEASE_SHA="$INPUT_SHA" + else + git fetch origin "${{ env.TARGET_BRANCH }}" + RELEASE_SHA=$(git rev-parse "origin/${{ env.TARGET_BRANCH }}") + fi - git cat-file -e "$RELEASE_SHA^{commit}" || { - echo "❌ Could not resolve a valid commit SHA for publish-only mode" - exit 1 - } + git cat-file -e "$RELEASE_SHA^{commit}" || { + echo "❌ Could not resolve a valid commit SHA for publish-only mode" + exit 1 + } - echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" - echo "✅ Publish-only release SHA resolved: $RELEASE_SHA" + echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" + echo "✅ Publish-only release SHA resolved: $RELEASE_SHA" publish-release: name: Publish Release @@ -451,54 +450,54 @@ jobs: TARGET_BRANCH: ${{ inputs.target_branch }} RELEASE_SHA: ${{ inputs.publish_only && needs.resolve-release-sha-publish-only.outputs.release_sha || needs.prepare-release.outputs.release_sha }} steps: - - name: Checkout target branch - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ inputs.target_branch }} - fetch-depth: 0 - - - name: Fetch target branch - run: | - set -euo pipefail - git fetch origin "${{ env.TARGET_BRANCH }}" - - - name: Verify release commit exists locally - run: | - set -euo pipefail - git cat-file -e "${{ env.RELEASE_SHA }}^{commit}" - echo "✅ Release commit exists locally: ${{ env.RELEASE_SHA }}" - - - name: Create immutable tag - run: | - set -euo pipefail - git tag "${{ env.TAG_NAME }}" "${{ env.RELEASE_SHA }}" - echo "✅ Created immutable tag locally: ${{ env.TAG_NAME }}" - - - name: Push immutable tag - run: | - set -euo pipefail - git push origin "${{ env.TAG_NAME }}" - echo "✅ Pushed immutable tag: ${{ env.TAG_NAME }}" - - - name: Publish GitHub release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - RELEASE_ARGS=() - if [[ "${{ inputs.prerelease }}" == "true" ]]; then - RELEASE_ARGS+=(--prerelease) - fi - - gh release create "${{ env.TAG_NAME }}" \ - --target "${{ env.RELEASE_SHA }}" \ - --generate-notes \ - "${RELEASE_ARGS[@]}" - - if [[ "${{ inputs.prerelease }}" == "true" ]]; then - echo "✅ Published prerelease: ${{ env.TAG_NAME }}" - else - echo "✅ Published release: ${{ env.TAG_NAME }}" - fi + - name: Checkout target branch + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Fetch target branch + run: | + set -euo pipefail + git fetch origin "${{ env.TARGET_BRANCH }}" + + - name: Verify release commit exists locally + run: | + set -euo pipefail + git cat-file -e "${{ env.RELEASE_SHA }}^{commit}" + echo "✅ Release commit exists locally: ${{ env.RELEASE_SHA }}" + + - name: Create immutable tag + run: | + set -euo pipefail + git tag "${{ env.TAG_NAME }}" "${{ env.RELEASE_SHA }}" + echo "✅ Created immutable tag locally: ${{ env.TAG_NAME }}" + + - name: Push immutable tag + run: | + set -euo pipefail + git push origin "${{ env.TAG_NAME }}" + echo "✅ Pushed immutable tag: ${{ env.TAG_NAME }}" + + - name: Publish GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + RELEASE_ARGS=() + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + RELEASE_ARGS+=(--prerelease) + fi + + gh release create "${{ env.TAG_NAME }}" \ + --target "${{ env.RELEASE_SHA }}" \ + --generate-notes \ + "${RELEASE_ARGS[@]}" + + if [[ "${{ inputs.prerelease }}" == "true" ]]; then + echo "✅ Published prerelease: ${{ env.TAG_NAME }}" + else + echo "✅ Published release: ${{ env.TAG_NAME }}" + fi From 640ded49cc9e7ffcf9beaac18dbef5e1c5d505e5 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 5 Jul 2026 05:14:09 +0100 Subject: [PATCH 4/7] fix: release tag validation being skipped in publish only mode. When using publish only mode, the release tag validation is skipped because it's a step in the skippable prepare-release job. But we still need the validation for publish only mode. Fixed by: - Moved the release tag validation into a separate required job (validate-release-tag) that always runs no matter the mode. - Refactored code to use the new validate-release-tag job outputs, and require it in the publish-release job. --- .github/workflows/release.yml | 37 ++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fab6027bc..828b0d1e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,12 +33,11 @@ permissions: actions: read jobs: - prepare-release: - if: inputs.publish_only != true - name: Prepare Release + validate-release-tag: + name: Validate Release Tag runs-on: ubuntu-latest outputs: - release_sha: ${{ steps.resolve_release_sha.outputs.release_sha }} + version: ${{ steps.parse_version.outputs.version }} env: TAG_NAME: ${{ inputs.tag_name }} TARGET_BRANCH: ${{ inputs.target_branch }} @@ -87,6 +86,24 @@ jobs: exit 1 fi + prepare-release: + if: inputs.publish_only != true + name: Prepare Release + runs-on: ubuntu-latest + needs: validate-release-tag + outputs: + release_sha: ${{ steps.resolve_release_sha.outputs.release_sha }} + env: + TAG_NAME: ${{ inputs.tag_name }} + TARGET_BRANCH: ${{ inputs.target_branch }} + steps: + - name: Checkout target branch + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + - name: Setup git identity run: | git config user.name "github-actions[bot]" @@ -96,7 +113,7 @@ jobs: run: | set -euo pipefail - VERSION="${{ steps.parse_version.outputs.version }}" + VERSION="${{ needs.validate-release-tag.outputs.version }}" CURRENT_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) if [[ "$VERSION" == "$CURRENT_VERSION" ]]; then @@ -139,7 +156,7 @@ jobs: run: | set -euo pipefail - VERSION="${{ steps.parse_version.outputs.version }}" + VERSION="${{ needs.validate-release-tag.outputs.version }}" REPO="${{ github.repository }}" CURRENT_DATE=$(date +%Y-%m-%d) @@ -155,7 +172,7 @@ jobs: run: | set -euo pipefail - VERSION="${{ steps.parse_version.outputs.version }}" + VERSION="${{ needs.validate-release-tag.outputs.version }}" FILE_VERSION=$(grep -oP "(?<=\\\$version = ')[^']+" cli/version.php) if [[ "$FILE_VERSION" != "$VERSION" ]]; then @@ -167,7 +184,7 @@ jobs: run: | set -euo pipefail - VERSION="${{ steps.parse_version.outputs.version }}" + VERSION="${{ needs.validate-release-tag.outputs.version }}" if ! grep -qE "^## \[$VERSION\]" CHANGELOG.md; then echo "❌ CHANGELOG.md does not contain a release heading for [$VERSION]" @@ -404,6 +421,7 @@ jobs: if: inputs.publish_only == true name: Resolve Release SHA (Publish Only) runs-on: ubuntu-latest + needs: validate-release-tag outputs: release_sha: ${{ steps.resolve_publish_only_sha.outputs.release_sha }} env: @@ -441,8 +459,9 @@ jobs: publish-release: name: Publish Release runs-on: ubuntu-latest - if: always() && (needs.prepare-release.result == 'success' || needs.resolve-release-sha-publish-only.result == 'success') + if: always() && needs.validate-release-tag.result == 'success' && (needs.prepare-release.result == 'success' || needs.resolve-release-sha-publish-only.result == 'success') needs: + - validate-release-tag - prepare-release - resolve-release-sha-publish-only env: From 9c760f19e7b2e71c4b03350dc33aadbe356e41d9 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 5 Jul 2026 05:30:41 +0100 Subject: [PATCH 5/7] fix: correct regex for extracting PR number from URL --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 828b0d1e3..9791ab17d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -328,7 +328,7 @@ jobs: echo "✅ Pull request created successfully: $PR_URL" # Extract PR number from URL and store as output - PR_NUMBER=$(echo "$PR_URL" | grep -oP '\\d+$') + PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$') echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" From 3e0c9f741ccf7145a17a2dc56803509fb3ec92d5 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 5 Jul 2026 06:42:42 +0100 Subject: [PATCH 6/7] fix: validate remote tag state for publish-only mode - Added checks to ensure that existing tags are immutable and point to the expected release commit. - Implemented conditional steps to handle tag validation based on the `publish_only` input. --- .github/workflows/release.yml | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9791ab17d..34035d70f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,6 +68,7 @@ jobs: fi - name: Check local tag availability + if: inputs.publish_only != true run: | set -euo pipefail @@ -77,6 +78,7 @@ jobs: fi - name: Check remote tag availability + if: inputs.publish_only != true run: | set -euo pipefail @@ -487,13 +489,56 @@ jobs: git cat-file -e "${{ env.RELEASE_SHA }}^{commit}" echo "✅ Release commit exists locally: ${{ env.RELEASE_SHA }}" + - name: Detect remote tag state + id: remote_tag + run: | + set -euo pipefail + + TAG_SHA=$(git ls-remote --tags origin "refs/tags/${{ env.TAG_NAME }}" | awk '{print $1}') + + # Check if the tag exists remotely and output the result + if [[ -n "$TAG_SHA" ]]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "tag_sha=$TAG_SHA" >> "$GITHUB_OUTPUT" + echo "ℹ️ Remote tag already exists: ${{ env.TAG_NAME }} ($TAG_SHA)" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "tag_sha=" >> "$GITHUB_OUTPUT" + echo "ℹ️ Remote tag does not exist yet: ${{ env.TAG_NAME }}" + fi + + - name: Validate remote tag state for normal mode + if: inputs.publish_only != true && steps.remote_tag.outputs.exists == 'true' + run: | + set -euo pipefail + + echo "❌ Tag ${{ env.TAG_NAME }} already exists on origin." + echo "Published tags are immutable. Choose a new tag." + exit 1 + + - name: Validate existing tag for publish-only mode + if: inputs.publish_only == true && steps.remote_tag.outputs.exists == 'true' + run: | + set -euo pipefail + + # Check if the existing tag points to the expected release commit + if [[ "${{ steps.remote_tag.outputs.tag_sha }}" != "${{ env.RELEASE_SHA }}" ]]; then + echo "❌ Existing tag ${{ env.TAG_NAME }} points to ${{ steps.remote_tag.outputs.tag_sha }}, expected ${{ env.RELEASE_SHA }}" + echo "ℹ️ Refusing to continue to avoid publishing from a mismatched immutable tag." + exit 1 + fi + + echo "✅ Existing immutable tag already points to the expected commit" + - name: Create immutable tag + if: steps.remote_tag.outputs.exists != 'true' run: | set -euo pipefail git tag "${{ env.TAG_NAME }}" "${{ env.RELEASE_SHA }}" echo "✅ Created immutable tag locally: ${{ env.TAG_NAME }}" - name: Push immutable tag + if: steps.remote_tag.outputs.exists != 'true' run: | set -euo pipefail git push origin "${{ env.TAG_NAME }}" From 4799b436a2917e7088a40bc5720f788db0508240 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 5 Jul 2026 06:51:50 +0100 Subject: [PATCH 7/7] fix: rename `tag_name` input to `release_tag` to make it self documenting - Changed all instances of the input `tag_name` to `release_tag`. - Changed the env variable name `TAG_NAME` to `RELEASE_TAG`. --- .github/workflows/release.yml | 58 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34035d70f..bb06b89d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Prepare And Publish Release on: workflow_dispatch: inputs: - tag_name: + release_tag: description: "Release tag (e.g. v3.4.5)" required: true type: string @@ -39,7 +39,7 @@ jobs: outputs: version: ${{ steps.parse_version.outputs.version }} env: - TAG_NAME: ${{ inputs.tag_name }} + RELEASE_TAG: ${{ inputs.release_tag }} TARGET_BRANCH: ${{ inputs.target_branch }} steps: - name: Checkout target branch @@ -54,15 +54,15 @@ jobs: run: | set -euo pipefail - TAG_NAME="${{ env.TAG_NAME }}" + RELEASE_TAG="${{ env.RELEASE_TAG }}" # Validate semver format and require the release tag to start with 'v'. - if [[ "$TAG_NAME" =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?)$ ]]; then + if [[ "$RELEASE_TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?)$ ]]; then VERSION="${BASH_REMATCH[1]}" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "✅ Valid semver tag: $TAG_NAME" + echo "✅ Valid semver tag: $RELEASE_TAG" else - echo "❌ Invalid version format: $TAG_NAME" + echo "❌ Invalid version format: $RELEASE_TAG" echo "ℹ️ Version must start with 'v' and follow semver (e.g., v1.0.0, v1.0.0-beta.1)" exit 1 fi @@ -72,8 +72,8 @@ jobs: run: | set -euo pipefail - if git rev-parse -q --verify "refs/tags/${{ env.TAG_NAME }}" >/dev/null; then - echo "⚠️ Tag ${{ env.TAG_NAME }} already exists locally." + if git rev-parse -q --verify "refs/tags/${{ env.RELEASE_TAG }}" >/dev/null; then + echo "⚠️ Tag ${{ env.RELEASE_TAG }} already exists locally." exit 1 fi @@ -82,8 +82,8 @@ jobs: run: | set -euo pipefail - if git ls-remote --exit-code --tags origin "refs/tags/${{ env.TAG_NAME }}" >/dev/null 2>&1; then - echo "⚠️ Tag ${{ env.TAG_NAME }} already exists on origin." + if git ls-remote --exit-code --tags origin "refs/tags/${{ env.RELEASE_TAG }}" >/dev/null 2>&1; then + echo "⚠️ Tag ${{ env.RELEASE_TAG }} already exists on origin." echo "Published tags are immutable. Choose a new tag." exit 1 fi @@ -96,7 +96,7 @@ jobs: outputs: release_sha: ${{ steps.resolve_release_sha.outputs.release_sha }} env: - TAG_NAME: ${{ inputs.tag_name }} + RELEASE_TAG: ${{ inputs.release_tag }} TARGET_BRANCH: ${{ inputs.target_branch }} steps: - name: Checkout target branch @@ -164,7 +164,7 @@ jobs: # Replace the entire [Unreleased] heading line (including any trailing URL) # with a versioned release heading that links to the tree at the tag. - RELEASE_URL="${{ github.server_url }}/${REPO}/tree/${{ env.TAG_NAME }}" + RELEASE_URL="${{ github.server_url }}/${REPO}/tree/${{ env.RELEASE_TAG }}" NEW_HEADING="## [${VERSION}](${RELEASE_URL}) - ${CURRENT_DATE}" sed -i "s|^## \[Unreleased\].*|${NEW_HEADING}|" CHANGELOG.md @@ -233,7 +233,7 @@ jobs: set -euo pipefail # Create a release branch name based on the tag name - BRANCH_NAME="release/${{ env.TAG_NAME }}" + BRANCH_NAME="release/${{ env.RELEASE_TAG }}" echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" - name: Check release branch doesn't already exist remotely @@ -276,7 +276,7 @@ jobs: exit 1 } - git commit -m "chore: version bump and update changelog for ${{ env.TAG_NAME }}" || { + git commit -m "chore: version bump and update changelog for ${{ env.RELEASE_TAG }}" || { echo "❌ Failed to commit changes" exit 1 } @@ -313,8 +313,8 @@ jobs: # Define PR details # ^ capitalizes the first letter - PR_TITLE="${RELEASE_TYPE^} ${{ env.TAG_NAME }}" - PR_BODY="Automated version bump and changelog update for $RELEASE_TYPE ${{ env.TAG_NAME }}." + PR_TITLE="${RELEASE_TYPE^} ${{ env.RELEASE_TAG }}" + PR_BODY="Automated version bump and changelog update for $RELEASE_TYPE ${{ env.RELEASE_TAG }}." PR_LABELS="auto version bump,release" # Create pull request and get the returned URL @@ -467,7 +467,7 @@ jobs: - prepare-release - resolve-release-sha-publish-only env: - TAG_NAME: ${{ inputs.tag_name }} + RELEASE_TAG: ${{ inputs.release_tag }} TARGET_BRANCH: ${{ inputs.target_branch }} RELEASE_SHA: ${{ inputs.publish_only && needs.resolve-release-sha-publish-only.outputs.release_sha || needs.prepare-release.outputs.release_sha }} steps: @@ -494,17 +494,17 @@ jobs: run: | set -euo pipefail - TAG_SHA=$(git ls-remote --tags origin "refs/tags/${{ env.TAG_NAME }}" | awk '{print $1}') + TAG_SHA=$(git ls-remote --tags origin "refs/tags/${{ env.RELEASE_TAG }}" | awk '{print $1}') # Check if the tag exists remotely and output the result if [[ -n "$TAG_SHA" ]]; then echo "exists=true" >> "$GITHUB_OUTPUT" echo "tag_sha=$TAG_SHA" >> "$GITHUB_OUTPUT" - echo "ℹ️ Remote tag already exists: ${{ env.TAG_NAME }} ($TAG_SHA)" + echo "ℹ️ Remote tag already exists: ${{ env.RELEASE_TAG }} ($TAG_SHA)" else echo "exists=false" >> "$GITHUB_OUTPUT" echo "tag_sha=" >> "$GITHUB_OUTPUT" - echo "ℹ️ Remote tag does not exist yet: ${{ env.TAG_NAME }}" + echo "ℹ️ Remote tag does not exist yet: ${{ env.RELEASE_TAG }}" fi - name: Validate remote tag state for normal mode @@ -512,7 +512,7 @@ jobs: run: | set -euo pipefail - echo "❌ Tag ${{ env.TAG_NAME }} already exists on origin." + echo "❌ Tag ${{ env.RELEASE_TAG }} already exists on origin." echo "Published tags are immutable. Choose a new tag." exit 1 @@ -523,7 +523,7 @@ jobs: # Check if the existing tag points to the expected release commit if [[ "${{ steps.remote_tag.outputs.tag_sha }}" != "${{ env.RELEASE_SHA }}" ]]; then - echo "❌ Existing tag ${{ env.TAG_NAME }} points to ${{ steps.remote_tag.outputs.tag_sha }}, expected ${{ env.RELEASE_SHA }}" + echo "❌ Existing tag ${{ env.RELEASE_TAG }} points to ${{ steps.remote_tag.outputs.tag_sha }}, expected ${{ env.RELEASE_SHA }}" echo "ℹ️ Refusing to continue to avoid publishing from a mismatched immutable tag." exit 1 fi @@ -534,15 +534,15 @@ jobs: if: steps.remote_tag.outputs.exists != 'true' run: | set -euo pipefail - git tag "${{ env.TAG_NAME }}" "${{ env.RELEASE_SHA }}" - echo "✅ Created immutable tag locally: ${{ env.TAG_NAME }}" + git tag "${{ env.RELEASE_TAG }}" "${{ env.RELEASE_SHA }}" + echo "✅ Created immutable tag locally: ${{ env.RELEASE_TAG }}" - name: Push immutable tag if: steps.remote_tag.outputs.exists != 'true' run: | set -euo pipefail - git push origin "${{ env.TAG_NAME }}" - echo "✅ Pushed immutable tag: ${{ env.TAG_NAME }}" + git push origin "${{ env.RELEASE_TAG }}" + echo "✅ Pushed immutable tag: ${{ env.RELEASE_TAG }}" - name: Publish GitHub release env: @@ -555,13 +555,13 @@ jobs: RELEASE_ARGS+=(--prerelease) fi - gh release create "${{ env.TAG_NAME }}" \ + gh release create "${{ env.RELEASE_TAG }}" \ --target "${{ env.RELEASE_SHA }}" \ --generate-notes \ "${RELEASE_ARGS[@]}" if [[ "${{ inputs.prerelease }}" == "true" ]]; then - echo "✅ Published prerelease: ${{ env.TAG_NAME }}" + echo "✅ Published prerelease: ${{ env.RELEASE_TAG }}" else - echo "✅ Published release: ${{ env.TAG_NAME }}" + echo "✅ Published release: ${{ env.RELEASE_TAG }}" fi