1+ # .github/workflows/promote-release.yml
2+
3+ name : Promote Release
4+
5+ on :
6+ workflow_dispatch :
7+ inputs :
8+ source_tag :
9+ description : Existing build release tag to promote, for example v7.0.0-rc.1+build.7
10+ required : true
11+ type : string
12+ target_tag :
13+ description : Clean public release tag, for example v7.0.0-rc.1. Leave empty to strip +build.N automatically.
14+ required : false
15+ type : string
16+ publish_modrinth :
17+ description : Publish promoted release to Modrinth
18+ required : true
19+ default : true
20+ type : boolean
21+ publish_hangar :
22+ description : Publish promoted release to Hangar
23+ required : true
24+ default : true
25+ type : boolean
26+
27+ permissions :
28+ contents : write
29+
30+ concurrency :
31+ group : promote-release-${{ inputs.source_tag }}-${{ inputs.target_tag }}
32+ cancel-in-progress : false
33+
34+ jobs :
35+ promote :
36+ name : Promote and publish release
37+ runs-on : ubuntu-latest
38+ timeout-minutes : 20
39+
40+ steps :
41+ - name : Resolve promotion metadata
42+ id : meta
43+ shell : bash
44+ env :
45+ GH_TOKEN : ${{ github.token }}
46+ run : |
47+ set -euo pipefail
48+
49+ source_tag="${{ inputs.source_tag }}"
50+ target_tag="${{ inputs.target_tag }}"
51+
52+ if [[ "$source_tag" != v* ]]; then
53+ source_tag="v${source_tag}"
54+ fi
55+
56+ if [[ -z "$target_tag" ]]; then
57+ if [[ "$source_tag" != *+build.* ]]; then
58+ echo "target_tag is required when source_tag does not contain +build." >&2
59+ exit 1
60+ fi
61+
62+ target_tag="${source_tag%%+build.*}"
63+ fi
64+
65+ if [[ "$target_tag" != v* ]]; then
66+ target_tag="v${target_tag}"
67+ fi
68+
69+ if [[ "$target_tag" == *+build.* ]]; then
70+ echo "target_tag must be a clean public tag without +build metadata." >&2
71+ exit 1
72+ fi
73+
74+ if [[ "$source_tag" == "$target_tag" ]]; then
75+ echo "source_tag and target_tag must be different." >&2
76+ exit 1
77+ fi
78+
79+ if ! gh release view "$source_tag" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
80+ echo "Source release does not exist: ${source_tag}" >&2
81+ exit 1
82+ fi
83+
84+ if gh release view "$target_tag" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
85+ echo "Target release already exists: ${target_tag}" >&2
86+ exit 1
87+ fi
88+
89+ source_commit="$(
90+ gh release view "$source_tag" \
91+ --repo "${GITHUB_REPOSITORY}" \
92+ --json targetCommitish \
93+ --jq '.targetCommitish'
94+ )"
95+
96+ if [[ -z "$source_commit" || "$source_commit" == "null" ]]; then
97+ echo "Could not resolve target commit from source release: ${source_tag}" >&2
98+ exit 1
99+ fi
100+
101+ source_url="$(
102+ gh release view "$source_tag" \
103+ --repo "${GITHUB_REPOSITORY}" \
104+ --json url \
105+ --jq '.url'
106+ )"
107+
108+ target_version="${target_tag#v}"
109+
110+ github_prerelease="false"
111+ modrinth_channel="release"
112+ hangar_channel="Release"
113+
114+ if [[ "$target_version" == *alpha* ]]; then
115+ github_prerelease="true"
116+ modrinth_channel="alpha"
117+ hangar_channel="Alpha"
118+ elif [[ "$target_version" == *-* ]]; then
119+ github_prerelease="true"
120+ modrinth_channel="beta"
121+ hangar_channel="Beta"
122+ fi
123+
124+ echo "source_tag=${source_tag}" >> "$GITHUB_OUTPUT"
125+ echo "target_tag=${target_tag}" >> "$GITHUB_OUTPUT"
126+ echo "target_version=${target_version}" >> "$GITHUB_OUTPUT"
127+ echo "source_commit=${source_commit}" >> "$GITHUB_OUTPUT"
128+ echo "source_url=${source_url}" >> "$GITHUB_OUTPUT"
129+ echo "github_prerelease=${github_prerelease}" >> "$GITHUB_OUTPUT"
130+ echo "modrinth_channel=${modrinth_channel}" >> "$GITHUB_OUTPUT"
131+ echo "hangar_channel=${hangar_channel}" >> "$GITHUB_OUTPUT"
132+
133+ - name : Download source release jar
134+ id : asset
135+ shell : bash
136+ env :
137+ GH_TOKEN : ${{ github.token }}
138+ run : |
139+ set -euo pipefail
140+
141+ mkdir -p dist/source dist/promoted
142+
143+ gh release download "${{ steps.meta.outputs.source_tag }}" \
144+ --repo "${GITHUB_REPOSITORY}" \
145+ --pattern "HeadDB-*.jar" \
146+ --dir dist/source
147+
148+ mapfile -t jars < <(find dist/source -maxdepth 1 -type f -name 'HeadDB-*.jar' | sort)
149+
150+ if [[ "${#jars[@]}" -ne 1 ]]; then
151+ echo "Expected exactly one HeadDB jar in source release, found ${#jars[@]}." >&2
152+ printf '%s\n' "${jars[@]}" >&2
153+ exit 1
154+ fi
155+
156+ promoted_jar="dist/promoted/HeadDB-${{ steps.meta.outputs.target_version }}.jar"
157+ cp "${jars[0]}" "$promoted_jar"
158+
159+ echo "jar=${promoted_jar}" >> "$GITHUB_OUTPUT"
160+
161+ - name : Create release notes
162+ shell : bash
163+ run : |
164+ set -euo pipefail
165+
166+ cat > release-notes.md <<'EOF'
167+ HeadDB ${{ steps.meta.outputs.target_version }}
168+
169+ This release promotes an already-built GitHub release artifact.
170+
171+ Source build: ${{ steps.meta.outputs.source_tag }}
172+ Source release: ${{ steps.meta.outputs.source_url }}
173+ Source commit: ${{ steps.meta.outputs.source_commit }}
174+
175+ The attached jar is the promoted jar from the source build release.
176+ EOF
177+
178+ - name : Create promoted GitHub release
179+ shell : bash
180+ env :
181+ GH_TOKEN : ${{ github.token }}
182+ run : |
183+ set -euo pipefail
184+
185+ args=(
186+ "${{ steps.meta.outputs.target_tag }}"
187+ "${{ steps.asset.outputs.jar }}"
188+ --repo "${GITHUB_REPOSITORY}"
189+ --target "${{ steps.meta.outputs.source_commit }}"
190+ --title "HeadDB ${{ steps.meta.outputs.target_version }}"
191+ --notes-file release-notes.md
192+ )
193+
194+ if [[ "${{ steps.meta.outputs.github_prerelease }}" == "true" ]]; then
195+ args+=(--prerelease)
196+ fi
197+
198+ gh release create "${args[@]}"
199+
200+ - name : Publish to Modrinth
201+ if : ${{ inputs.publish_modrinth }}
202+ uses : cloudnode-pro/modrinth-publish@v2
203+ with :
204+ token : ${{ secrets.MODRINTH_TOKEN }}
205+ project : ${{ vars.MODRINTH_PROJECT_ID }}
206+ version : ${{ steps.meta.outputs.target_version }}
207+ name : HeadDB ${{ steps.meta.outputs.target_version }}
208+ channel : ${{ steps.meta.outputs.modrinth_channel }}
209+ changelog : |
210+ HeadDB ${{ steps.meta.outputs.target_version }}
211+
212+ Promoted from ${{ steps.meta.outputs.source_tag }}.
213+
214+ Source release: ${{ steps.meta.outputs.source_url }}
215+ loaders : |-
216+ paper
217+ folia
218+ game-versions : |-
219+ 1.21.x
220+ 26.1.x
221+ 26.2.x
222+ files : ${{ steps.asset.outputs.jar }}
223+
224+ - name : Publish to Hangar
225+ if : ${{ inputs.publish_hangar }}
226+ uses : benwoo1110/hangar-upload-action@v1
227+ with :
228+ api_token : ${{ secrets.HANGAR_API_TOKEN }}
229+ slug : ${{ vars.HANGAR_PROJECT_SLUG }}
230+ version : ${{ steps.meta.outputs.target_version }}
231+ channel : ${{ steps.meta.outputs.hangar_channel }}
232+ description : |
233+ HeadDB ${{ steps.meta.outputs.target_version }}
234+
235+ Promoted from ${{ steps.meta.outputs.source_tag }}.
236+
237+ Source release: ${{ steps.meta.outputs.source_url }}
238+ files : |-
239+ [
240+ {
241+ "path": "${{ steps.asset.outputs.jar }}",
242+ "platforms": ["PAPER"]
243+ }
244+ ]
245+ platform_dependencies : |-
246+ {
247+ "PAPER": ["1.21.x", "26.1.x", "26.2.x"]
248+ }
0 commit comments