|
| 1 | +name: Release |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + tags: |
| 6 | + - "v*" |
| 7 | + |
| 8 | +permissions: |
| 9 | + contents: write |
| 10 | + |
| 11 | +concurrency: |
| 12 | + group: release-${{ github.ref }} |
| 13 | + cancel-in-progress: false |
| 14 | + |
| 15 | +jobs: |
| 16 | + publish: |
| 17 | + name: Publish ${{ github.ref_name }} |
| 18 | + runs-on: ubuntu-latest |
| 19 | + env: |
| 20 | + CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} |
| 21 | + CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} |
| 22 | + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} |
| 23 | + RELEASE_TAG: ${{ github.ref_name }} |
| 24 | + steps: |
| 25 | + - name: Checkout repository |
| 26 | + uses: actions/checkout@v6 |
| 27 | + with: |
| 28 | + fetch-depth: 0 |
| 29 | + |
| 30 | + - name: Set up Temurin JDK 21 |
| 31 | + uses: actions/setup-java@v5 |
| 32 | + with: |
| 33 | + distribution: temurin |
| 34 | + java-version: "21" |
| 35 | + cache: maven |
| 36 | + server-id: central |
| 37 | + server-username: CENTRAL_TOKEN_USERNAME |
| 38 | + server-password: CENTRAL_TOKEN_PASSWORD |
| 39 | + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} |
| 40 | + gpg-passphrase: GPG_PASSPHRASE |
| 41 | + |
| 42 | + - name: Validate tag format and project version |
| 43 | + run: | |
| 44 | + set -euo pipefail |
| 45 | +
|
| 46 | + if ! printf '%s\n' "${RELEASE_TAG}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then |
| 47 | + echo "Release tag must match vX.Y.Z. Got: ${RELEASE_TAG}" >&2 |
| 48 | + exit 1 |
| 49 | + fi |
| 50 | +
|
| 51 | + release_version="${RELEASE_TAG#v}" |
| 52 | + pom_version="$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version | tail -n 1)" |
| 53 | +
|
| 54 | + if [ "${pom_version}" != "${release_version}" ]; then |
| 55 | + echo "Tag version ${release_version} does not match pom.xml version ${pom_version}." >&2 |
| 56 | + exit 1 |
| 57 | + fi |
| 58 | +
|
| 59 | + echo "RELEASE_VERSION=${release_version}" >> "${GITHUB_ENV}" |
| 60 | +
|
| 61 | + - name: Ensure tag commit belongs to main or master |
| 62 | + run: | |
| 63 | + set -euo pipefail |
| 64 | +
|
| 65 | + git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main || true |
| 66 | + git fetch --no-tags origin +refs/heads/master:refs/remotes/origin/master || true |
| 67 | +
|
| 68 | + release_sha="$(git rev-list -n 1 "${RELEASE_TAG}")" |
| 69 | + release_branch="" |
| 70 | +
|
| 71 | + for branch in main master; do |
| 72 | + if git show-ref --verify --quiet "refs/remotes/origin/${branch}" && \ |
| 73 | + git merge-base --is-ancestor "${release_sha}" "refs/remotes/origin/${branch}"; then |
| 74 | + release_branch="${branch}" |
| 75 | + break |
| 76 | + fi |
| 77 | + done |
| 78 | +
|
| 79 | + if [ -z "${release_branch}" ]; then |
| 80 | + echo "Release tag ${RELEASE_TAG} must point to a commit reachable from origin/main or origin/master." >&2 |
| 81 | + exit 1 |
| 82 | + fi |
| 83 | +
|
| 84 | + echo "RELEASE_BRANCH=${release_branch}" >> "${GITHUB_ENV}" |
| 85 | +
|
| 86 | + - name: Resolve imported GPG key id |
| 87 | + run: | |
| 88 | + set -euo pipefail |
| 89 | +
|
| 90 | + key_id="$(gpg --list-secret-keys --keyid-format LONG | awk '/^sec/{split($2, parts, "/"); print parts[2]; exit}')" |
| 91 | + if [ -z "${key_id}" ]; then |
| 92 | + echo "Unable to resolve imported GPG key id." >&2 |
| 93 | + exit 1 |
| 94 | + fi |
| 95 | +
|
| 96 | + echo "GPG_KEY_ID=${key_id}" >> "${GITHUB_ENV}" |
| 97 | +
|
| 98 | + - name: Verify release build |
| 99 | + run: mvn -B -ntp clean verify |
| 100 | + |
| 101 | + - name: Generate release notes |
| 102 | + run: ./scripts/generate-release-notes.sh "${RELEASE_TAG}" "https://github.com/${GITHUB_REPOSITORY}" > release-notes.md |
| 103 | + |
| 104 | + - name: Publish to Maven Central |
| 105 | + run: mvn -B -ntp -P release -DskipTests -Dgpg.keyname="${GPG_KEY_ID}" deploy |
| 106 | + |
| 107 | + - name: Upload release artifacts for diagnostics |
| 108 | + if: always() |
| 109 | + uses: actions/upload-artifact@v4 |
| 110 | + with: |
| 111 | + name: release-${{ github.ref_name }} |
| 112 | + path: | |
| 113 | + release-notes.md |
| 114 | + target/*.jar |
| 115 | + target/*.asc |
| 116 | + target/central-publishing/** |
| 117 | + if-no-files-found: warn |
| 118 | + |
| 119 | + - name: Publish GitHub Release |
| 120 | + env: |
| 121 | + GH_TOKEN: ${{ github.token }} |
| 122 | + run: | |
| 123 | + set -euo pipefail |
| 124 | + shopt -s nullglob |
| 125 | +
|
| 126 | + assets=(target/*.jar target/*.asc) |
| 127 | +
|
| 128 | + gh release create "${RELEASE_TAG}" "${assets[@]}" \ |
| 129 | + --verify-tag \ |
| 130 | + --fail-on-no-commits \ |
| 131 | + --latest \ |
| 132 | + --title "${RELEASE_TAG}" \ |
| 133 | + --notes-file release-notes.md |
0 commit comments