@@ -2,23 +2,37 @@ name: Release
22
33on :
44 workflow_dispatch :
5+ inputs :
6+ dry_run :
7+ description : " Do not push/tag or create a GitHub Release"
8+ type : boolean
9+ default : true
10+ base_ref :
11+ description : " Branch to release from (for testing)"
12+ type : string
13+ default : " main"
14+
15+ permissions :
16+ contents : write
517
618concurrency :
7- group : release-${{ github.ref }}
19+ group : release-${{ github.event.inputs.base_ref || github. ref }}
820 cancel-in-progress : true
921
1022jobs :
11- tag :
23+ prepare-release :
1224 runs-on : ubuntu-latest
1325 outputs :
1426 tag_name : ${{ steps.set_tag.outputs.tag_name }}
1527 dsl_version : ${{ steps.get_dsl_version.outputs.dsl_version }}
1628 artifact_id : ${{ steps.get_artifact_id.outputs.artifact_id }}
1729 steps :
18- - name : Checkout code
30+ - name : Checkout base branch
1931 uses : actions/checkout@v5
2032 with :
2133 fetch-depth : 0
34+ ref : ${{ github.event.inputs.base_ref || 'main' }}
35+ persist-credentials : true
2236
2337 - name : Set up Java
2438 uses : actions/setup-java@v5
@@ -29,55 +43,106 @@ jobs:
2943
3044 - name : Get DSL version from pom.xml
3145 id : get_dsl_version
46+ shell : bash
3247 run : |
33- DSL_VERSION=$(mvn help:evaluate -Dexpression=rosetta.dsl.version -q -DforceStdout)
34- echo "dsl_version=$DSL_VERSION" >> $GITHUB_OUTPUT
48+ set -euo pipefail
49+ DSL_VERSION=$(mvn -q help:evaluate -Dexpression=rosetta.dsl.version -DforceStdout)
50+ if [ -z "${DSL_VERSION:-}" ]; then
51+ echo "Failed to resolve rosetta.dsl.version from pom.xml"
52+ exit 1
53+ fi
54+ echo "dsl_version=$DSL_VERSION" >> "$GITHUB_OUTPUT"
3555
3656 - name : Get artifactId from pom.xml
3757 id : get_artifact_id
58+ shell : bash
3859 run : |
39- ARTIFACT_ID=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)
40- echo "artifact_id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
60+ set -euo pipefail
61+ ARTIFACT_ID=$(mvn -q help:evaluate -Dexpression=project.artifactId -DforceStdout)
62+ if [ -z "${ARTIFACT_ID:-}" ]; then
63+ echo "Failed to resolve project.artifactId from pom.xml"
64+ exit 1
65+ fi
66+ echo "artifact_id=$ARTIFACT_ID" >> "$GITHUB_OUTPUT"
4167
42- - name : Fetch all tags
43- run : git fetch --tags
68+ - name : Ensure up-to-date and fetch tags
69+ shell : bash
70+ run : |
71+ set -euo pipefail
72+ git config --global pull.ff only
73+ git fetch --prune --tags origin
74+ git pull --ff-only origin "${{ github.event.inputs.base_ref || 'main' }}"
4475
4576 - name : Determine next tag
4677 id : set_tag
78+ shell : bash
4779 run : |
80+ set -euo pipefail
4881 DSL_VERSION="${{ steps.get_dsl_version.outputs.dsl_version }}"
49- TAGS=$(git tag --list "${DSL_VERSION}.*" | sort -V)
50- if [ -z "$TAGS" ]; then
82+ DSL_ESCAPED=$(printf '%s\n' "$DSL_VERSION" | sed -e 's/[]\/$*.^|[]/\\&/g')
83+
84+ EXISTING=$(git tag -l "${DSL_VERSION}.*" | grep -E "^${DSL_ESCAPED}\.[0-9]+$" || true)
85+ if [ -z "$EXISTING" ]; then
5186 NEXT_TAG="${DSL_VERSION}.0"
5287 else
53- MAX_N=$(echo "$TAGS" | sed "s/^${DSL_VERSION}\.//" | sort -n | tail -1)
54- NEXT_N=$((MAX_N + 1))
55- NEXT_TAG="${DSL_VERSION}.${NEXT_N}"
88+ MAX_N=$(echo "$EXISTING" | sed -E "s/^${DSL_ESCAPED}\.//" | sort -n | tail -1)
89+ NEXT_TAG="${DSL_VERSION}.$((MAX_N + 1))"
5690 fi
57- echo "tag_name=$NEXT_TAG" >> $GITHUB_OUTPUT
91+ echo "Next tag computed: $NEXT_TAG"
92+ echo "tag_name=$NEXT_TAG" >> "$GITHUB_OUTPUT"
5893
59- - name : Create and push tag
94+ - name : Guard: only allow real release from main
95+ if : ${{ github.event.inputs.dry_run != 'true' }}
96+ shell : bash
97+ run : |
98+ set -euo pipefail
99+ if [ "${{ github.event.inputs.base_ref || 'main' }}" != "main" ]; then
100+ echo "Refusing to perform a real release from '${{ github.event.inputs.base_ref }}'. Use main or set dry_run=true."
101+ exit 1
102+ fi
103+
104+ - name : Bump version, tag, and push (skipped in dry run)
60105 env :
61- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
106+ TAG_NAME : ${{ steps.set_tag.outputs.tag_name }}
107+ DRY_RUN : ${{ github.event.inputs.dry_run }}
108+ BASE_REF : ${{ github.event.inputs.base_ref || 'main' }}
109+ shell : bash
62110 run : |
63- TAG_NAME="${{ steps.set_tag.outputs.tag_name }}"
64- if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
65- echo "Tag $TAG_NAME already exists. Exiting."
111+ set -euo pipefail
112+ if [ "${DRY_RUN}" = "true" ]; then
113+ echo "[DRY RUN] Would set project version to ${TAG_NAME}, commit, tag, and push atomically to ${BASE_REF}."
114+ exit 0
115+ fi
116+
117+ mvn -q -B versions:set -DnewVersion="${TAG_NAME}" -DgenerateBackupPoms=false
118+ if ! git diff --quiet; then
119+ git config user.name "github-actions[bot]"
120+ git config user.email "github-actions[bot]@users.noreply.github.com"
121+ git add -A
122+ git commit -m "Release ${TAG_NAME}: set project version to ${TAG_NAME}"
123+ else
124+ echo "No changes to commit (version already ${TAG_NAME})."
125+ fi
126+
127+ git fetch --prune --tags origin
128+ if git rev-parse "${TAG_NAME}" >/dev/null 2>&1; then
129+ echo "Tag ${TAG_NAME} already exists. Exiting."
66130 exit 1
67131 fi
68- git config user.name "github-actions[bot]"
69- git config user.email "github-actions[bot]@users.noreply.github.com"
70- git tag "$TAG_NAME"
71- git push origin "$TAG_NAME"
132+
133+ git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}"
134+ git push --atomic origin HEAD:"${BASE_REF}" "${TAG_NAME}"
72135
73136 build-and-release :
74- needs : tag
137+ needs : prepare-release
138+ if : ${{ github.event.inputs.dry_run != 'true' }}
75139 runs-on : ubuntu-latest
76140 steps :
77- - name : Checkout code
141+ - name : Checkout the tag (build exactly what was released)
78142 uses : actions/checkout@v5
79143 with :
80144 fetch-depth : 0
145+ ref : ${{ needs.prepare-release.outputs.tag_name }}
81146
82147 - name : Set up Java
83148 uses : actions/setup-java@v5
@@ -86,39 +151,33 @@ jobs:
86151 java-version : 21
87152 cache : maven
88153
89- - name : Save original pom.xml
90- run : cp pom.xml pom.xml.bak
91-
92- - name : Update pom.xml version to match tag
93- run : |
94- mvn -B versions:set -DnewVersion=${{ needs.tag.outputs.tag_name }}
95- mvn -B versions:commit
96-
97154 - name : Build JARs
155+ shell : bash
98156 run : mvn -B clean package
99157
100- - name : Revert pom.xml to original
101- run : mv pom.xml.bak pom.xml
102-
103- - name : Archive JARs
158+ - name : Collect artifact paths
104159 id : archive
160+ shell : bash
105161 run : |
106- ARTIFACT_ID="${{ needs.tag.outputs.artifact_id }}"
107- TAG_NAME="${{ needs.tag.outputs.tag_name }}"
162+ set -euo pipefail
163+ ARTIFACT_ID="${{ needs.prepare-release.outputs.artifact_id }}"
164+ TAG_NAME="${{ needs.prepare-release.outputs.tag_name }}"
108165 JAR="target/${ARTIFACT_ID}-${TAG_NAME}.jar"
109- JAVADOC_JAR="target/${ARTIFACT_ID}-${TAG_NAME}-javadoc.jar"
110- echo "jar_path=$JAR" >> $GITHUB_OUTPUT
111- echo "javadoc_jar_path=$JAVADOC_JAR" >> $GITHUB_OUTPUT
166+ JDOC="target/${ARTIFACT_ID}-${TAG_NAME}-javadoc.jar"
167+ if [ ! -f "$JAR" ]; then echo "Missing $JAR"; exit 1; fi
168+ if [ ! -f "$JDOC" ]; then echo "Missing $JDOC"; exit 1; fi
169+ echo "jar_path=$JAR" >> "$GITHUB_OUTPUT"
170+ echo "javadoc_jar_path=$JDOC" >> "$GITHUB_OUTPUT"
112171
113172 - name : Create GitHub Release
114173 uses : softprops/action-gh-release@v2
115174 with :
116- tag_name : ${{ needs.tag .outputs.tag_name }}
117- name : ${{ needs.tag .outputs.tag_name }}
175+ tag_name : ${{ needs.prepare-release .outputs.tag_name }}
176+ name : ${{ needs.prepare-release .outputs.tag_name }}
118177 body : |
119- Automated release for DSL version ${{ needs.tag .outputs.dsl_version }}
178+ Automated release for DSL version ${{ needs.prepare-release .outputs.dsl_version }}
120179 files : |
121180 ${{ steps.archive.outputs.jar_path }}
122181 ${{ steps.archive.outputs.javadoc_jar_path }}
123182 env :
124- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
183+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
0 commit comments