1- # This workflow automates the release process when a release is created on GitHub
2- # It uses Maven release plugin to manage versions and deploys to Maven Central
1+ # This workflow automates the release process when a draft release is created
2+ # on GitHub. It uses Maven release plugin to manage versions and deploys to
3+ # Maven Central
34#
45# Usage:
5- # 1. Create a new release in GitHub UI with tag format: vX.Y.Z (e.g., v1.2.0)
6+ # 1. Create a new draft release in GitHub UI with tag format: vX.Y.Z
7+ # (e.g., v1.2.0)
68# - Select the target branch (typically main)
79# 2. This workflow will automatically:
8- # - Use Maven release:prepare to update versions and create release commits
10+ # - Use Maven release:prepare to update versions and create release commit
911# - Use Maven release:perform to build and deploy artifacts to Maven Central
10- # - Move the tag to point to the actual release commit
12+ # - Create the tag to point to release commit
1113# - Push commits back to the originating branch
14+ # - Publish the GitHub release (mark draft as non-draft)
1215
1316name : Automated Release
1417
1518on :
1619 release :
17- types : [created] # Trigger when release is created
20+ types : [created] # Only fire when a release is created
1821
1922permissions :
20- contents : write # Required to push commits and update tags
23+ contents : write # Required to push commits, update tags and edit releases
2124
2225jobs :
2326 release :
27+ # Only run for draft releases; ignore non-draft created events
28+ if : github.event.release.draft
29+
2430 runs-on : ubuntu-latest
25-
31+
2632 steps :
2733 - name : Validate release tag format
2834 id : validate_tag
2935 run : |
3036 TAG_NAME="${{ github.event.release.tag_name }}"
3137 echo "Release tag: $TAG_NAME"
32-
38+
3339 # Validate tag format (should be vX.Y.Z)
3440 if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
3541 echo "::error::Invalid tag format. Expected format: vX.Y.Z (e.g., v1.2.0)"
3642 exit 1
3743 fi
38-
44+
3945 # Remove 'v' prefix to get the version number
4046 VERSION="${TAG_NAME#v}"
41- echo "version=${VERSION}" >> $GITHUB_OUTPUT
47+ echo "version=${VERSION}" >> " $GITHUB_OUTPUT"
4248 echo "Extracted version: $VERSION"
43-
49+
4450 - name : Determine target branch
4551 id : target_branch
4652 run : |
4753 # Use target_commitish to determine the originating branch
4854 TARGET="${{ github.event.release.target_commitish }}"
49-
55+
5056 # If target_commitish is empty or a SHA, default to main
5157 if [[ -z "$TARGET" ]] || [[ "$TARGET" =~ ^[0-9a-f]{40}$ ]]; then
5258 TARGET="main"
5359 fi
54-
55- echo "target_branch=${TARGET}" >> $GITHUB_OUTPUT
60+
61+ echo "target_branch=${TARGET}" >> " $GITHUB_OUTPUT"
5662 echo "Target branch: $TARGET"
57-
63+
5864 - name : Generate GitHub App Token
5965 id : generate_token
6066 uses : actions/create-github-app-token@v2
6167 with :
6268 app-id : ${{ secrets.APP_ID }}
6369 private-key : ${{ secrets.APP_PRIVATE_KEY }}
64-
70+
6571 - name : Checkout repository
66- uses : actions/checkout@v6
72+ uses : actions/checkout@v4
6773 with :
6874 ref : ${{ steps.target_branch.outputs.target_branch }}
6975 fetch-depth : 0
7076 token : ${{ steps.generate_token.outputs.token }}
71-
72- - name : Delete user-created tag
77+
78+ - name : Pre-build condition checks
7379 run : |
7480 TAG_NAME="${{ github.event.release.tag_name }}"
75-
76- # Delete the tag created by the user (will be recreated by release:prepare)
77- git tag -d "$TAG_NAME" || true
78- git push origin ":refs/tags/$TAG_NAME" || true
79-
80- echo "Deleted user-created tag $TAG_NAME"
81+
82+ echo "Performing pre-build condition checks..."
83+
84+ # Fetch latest state from remote
85+ git fetch origin --tags
86+
87+ # Check if a tag exists
88+ if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME$"; then
89+ echo "::error::Tag $TAG_NAME already exists on remote."
90+ exit 1
91+ fi
92+
93+ echo "Pre-build checks passed. No issues detected."
8194
8295 - name : Set up JDK 25
8396 uses : actions/setup-java@v5
@@ -95,24 +108,20 @@ jobs:
95108 server-password : MAVEN_PASSWORD
96109 gpg-private-key : ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
97110 gpg-passphrase : MAVEN_GPG_PASSPHRASE
98-
111+
99112 - name : Configure Git
100113 run : |
101114 git config user.name "github-actions[bot]"
102115 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
103-
116+
104117 - name : Run Maven release:prepare
105118 run : |
106119 VERSION="${{ steps.validate_tag.outputs.version }}"
107120 TAG_NAME="${{ github.event.release.tag_name }}"
108-
121+
109122 echo "Preparing release version: $VERSION"
110123 echo "Tag name: $TAG_NAME"
111-
112- # Run release:prepare with explicit release version
113- # Maven will automatically calculate the next development version
114- # Only prepare production modules, exclude all sample modules
115- # Pass -pl/-am to forked Maven invocations via -Darguments
124+
116125 ./mvnw -B release:prepare \
117126 -DreleaseVersion="${VERSION}" \
118127 -Dtag="${TAG_NAME}" \
@@ -122,40 +131,115 @@ jobs:
122131 MAVEN_USERNAME : ${{ secrets.OSSRH_USERNAME }}
123132 MAVEN_PASSWORD : ${{ secrets.OSSRH_TOKEN }}
124133 MAVEN_GPG_PASSPHRASE : ${{ secrets.MAVEN_GPG_PASSPHRASE }}
125-
134+
126135 - name : Run Maven release:perform
127136 run : |
128137 echo "Performing release and deploying to Maven Central"
129-
130- # Run release:perform to build and deploy
131- # Only release production modules, exclude all sample modules
132- # Pass -pl/-am to forked Maven invocations via -Darguments
138+
133139 ./mvnw -B release:perform \
134140 -DlocalCheckout=true \
135- -DeployAtEnd =true \
141+ -DdeployAtEnd =true \
136142 -Darguments="-pl xapi-model,xapi-client,xapi-model-spring-boot-starter -am"
137143 env :
138144 MAVEN_USERNAME : ${{ secrets.OSSRH_USERNAME }}
139145 MAVEN_PASSWORD : ${{ secrets.OSSRH_TOKEN }}
140146 MAVEN_GPG_PASSPHRASE : ${{ secrets.MAVEN_GPG_PASSPHRASE }}
141-
142- - name : Push changes to originating branch
147+
148+ - name : Push changes to originating branch and tag (with merge fallback)
143149 run : |
144150 TARGET_BRANCH="${{ steps.target_branch.outputs.target_branch }}"
145151 TAG_NAME="${{ github.event.release.tag_name }}"
146-
152+
147153 echo "Pushing changes to branch: $TARGET_BRANCH"
148-
149- # Push the commits created by release:prepare
150- if ! git push --force-with-lease origin "HEAD:${TARGET_BRANCH}"; then
151- echo "::error::Failed to push release commits to ${TARGET_BRANCH} due to branch divergence."
152- echo "The remote branch may have new commits. Please resolve the conflict manually:"
153- echo " 1. Fetch the latest changes: git fetch origin"
154- echo " 2. Rebase or merge as needed, then push again with --force-with-lease."
154+
155+ # First, try a normal fast-forward push
156+ if git push origin "HEAD:${TARGET_BRANCH}"; then
157+ echo "Fast-forward push to ${TARGET_BRANCH} succeeded."
158+ else
159+ echo "::warning::Fast-forward push to ${TARGET_BRANCH} failed. Trying merge fallback."
160+
161+ # Fetch latest state of the branch
162+ git fetch origin "${TARGET_BRANCH}"
163+
164+ # Merge origin/TARGET_BRANCH into our release HEAD.
165+ # If this conflicts, we bail out rather than trying to auto-resolve.
166+ if ! git merge --no-edit "origin/${TARGET_BRANCH}"; then
167+ echo "::error::Merge conflict detected - likely due to a race condition."
168+ echo ""
169+ echo "This typically happens when changes to POM files were merged to ${TARGET_BRANCH}"
170+ echo "while this release workflow was running."
171+ echo ""
172+ echo "Recommended resolution:"
173+ echo " 1. Delete this draft release in GitHub"
174+ echo " 2. Create a new release via the GitHub Release UI"
175+ echo ""
176+ echo "Note: Artifacts have been deployed to Maven Central."
177+ exit 1
178+ fi
179+
180+ # Now push the merge commit
181+ if git push origin "HEAD:${TARGET_BRANCH}"; then
182+ echo "Pushed merge commit to ${TARGET_BRANCH}."
183+ else
184+ echo "::error::Failed to push merge commit to ${TARGET_BRANCH} after merge."
185+ echo ""
186+ echo "This may be due to branch protection rules or another race condition."
187+ echo ""
188+ echo "Recommended resolution:"
189+ echo " 1. Delete this draft release in GitHub"
190+ echo " 2. Create a new release via the GitHub Release UI"
191+ exit 1
192+ fi
193+ fi
194+
195+ echo "Pushing tag $TAG_NAME"
196+ if ! git push origin "$TAG_NAME"; then
197+ echo "::error::Failed to push tag $TAG_NAME."
198+ echo ""
199+ echo "This may be due to concurrent tag creation (tag collision), where someone created a tag with"
200+ echo "the same name while this workflow was running."
201+ echo ""
202+ echo "Recommended resolution:"
203+ echo " 1. Check if the tag $TAG_NAME already exists on the remote"
204+ echo " 2. If the tag exists but points to wrong commit, delete it"
205+ echo " 3. Create a new draft release via the GitHub Release UI"
155206 exit 1
156207 fi
157-
158- # Push the tag created by release:prepare
159- git push origin "$TAG_NAME"
160-
161208 echo "Pushed release commits and tag to $TARGET_BRANCH"
209+
210+ - name : Collect release assets
211+ run : |
212+ set -e
213+ VERSION="${{ steps.validate_tag.outputs.version }}"
214+ mkdir -p artifacts
215+ # Define artifact paths
216+ CLIENT_JAR="xapi-client/target/xapi-client-${VERSION}.jar"
217+ MODEL_JAR="xapi-model/target/xapi-model-${VERSION}.jar"
218+ STARTER_JAR="xapi-model-spring-boot-starter/target/xapi-model-spring-boot-starter-${VERSION}.jar"
219+ # Check existence and copy
220+ for JAR in "$CLIENT_JAR" "$MODEL_JAR" "$STARTER_JAR"; do
221+ if [ ! -f "$JAR" ]; then
222+ echo "::error::Artifact not found: $JAR"
223+ exit 1
224+ fi
225+ cp "$JAR" artifacts/
226+ done
227+
228+ - name : Upload release assets
229+ env :
230+ GITHUB_TOKEN : ${{ steps.generate_token.outputs.token }}
231+ run : |
232+ TAG_NAME="${{ github.event.release.tag_name }}"
233+ for FILE in artifacts/*; do
234+ echo "Uploading $(basename "$FILE")"
235+ gh release upload "$TAG_NAME" "$FILE" --clobber
236+ done
237+
238+ - name : Publish GitHub release
239+ env :
240+ GITHUB_TOKEN : ${{ steps.generate_token.outputs.token }}
241+ run : |
242+ TAG_NAME="${{ github.event.release.tag_name }}"
243+ echo "Publishing draft release for $TAG_NAME"
244+ gh release edit "$TAG_NAME" --draft=false
245+
0 commit comments