Skip to content

Commit db3a662

Browse files
committed
Overhaul CI/CD pipeline
- Overhauled GitHub action workflows to automatically determine supported Minecraft versions from pack.mcmeta using the Spyglass API, build development versions on push, set mod version upper bounds and generate job summaries - Adjusted mod version metadata - Annotated the required Java version for mod versions
1 parent 00d0f15 commit db3a662

7 files changed

Lines changed: 582 additions & 167 deletions

File tree

.github/workflows/cd.yml

Lines changed: 248 additions & 41 deletions
Large diffs are not rendered by default.

.github/workflows/check_commands.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

.github/workflows/ci.yaml

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
name: Validate & Build Dev Version
2+
3+
on:
4+
push:
5+
workflow_dispatch:
6+
inputs:
7+
tag:
8+
description: 'Version tag to build (if omitted, latest --describe tag from git is used)'
9+
required: false
10+
mc_upper:
11+
description: 'Upper bound of Minecraft version range (exact version), e.g. "1.21.5"'
12+
required: false
13+
mc_lower:
14+
description: 'Lower bound of Minecraft version range (exact version), e.g. "1.17"'
15+
required: false
16+
skip_check:
17+
description: 'Set to true to skip command validation'
18+
required: false
19+
type: boolean
20+
default: false
21+
22+
env:
23+
# Fallback Lower: when Spyglass API is unreachable
24+
FALLBACK_MC_LOWER: "1.17"
25+
# Fallback Upper: when Spyglass API and Mojang API are unreachable
26+
FALLBACK_MC_UPPER: "1.21.5"
27+
28+
jobs:
29+
deploy:
30+
runs-on: ubuntu-latest
31+
name: Validate and build development version
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v4
35+
with:
36+
show-progress: false
37+
fetch-depth: 25 # Fetch 25 commits to ensure we have enough history for git describe
38+
fetch-tags: true
39+
40+
# Determine tag version to use
41+
- name: Determine tag version
42+
id: get_tag
43+
shell: bash
44+
run: |
45+
if [ -n "${{ github.event.inputs.tag }}" ]; then
46+
TAG="${{ github.event.inputs.tag }}"
47+
else
48+
TAG_RAW=$(git describe --tags --always)
49+
TAG="${TAG_RAW#v}"
50+
fi
51+
echo "Using tag: $TAG"
52+
echo "TAG=$TAG" >> $GITHUB_ENV
53+
54+
# Determine Minecraft versions from pack.mcmeta using Spyglass API
55+
- name: Determine Minecraft version bounds from pack.mcmeta using Spyglass API
56+
id: determine_versions
57+
shell: bash
58+
run: |
59+
set -euo pipefail
60+
61+
if [ -n "${{ github.event.inputs.mc_upper }}" ]; then
62+
MC_UPPER="${{ github.event.inputs.mc_upper }}"
63+
MC_LOWER="${{ github.event.inputs.mc_lower }}"
64+
if [ -z "$MC_LOWER" ]; then
65+
MC_LOWER="$FALLBACK_MC_LOWER"
66+
fi
67+
echo "Using provided Minecraft version bounds: MC_LOWER=$MC_LOWER, MC_UPPER=$MC_UPPER"
68+
# Build ranges
69+
MACHINE_RANGE_FABRIC=">=${MC_LOWER} <=${MC_UPPER}"
70+
MACHINE_RANGE_FORGE="[${MC_LOWER},${MC_UPPER}]"
71+
MACHINE_RANGE_NEOFORGE="[1.20.5,${MC_UPPER}]"
72+
CMD_VALIDATOR_MC=$(echo "$MC_UPPER" | awk -F. '{print $1"."$2}')
73+
MC_HUMAN_VERSION_RANGE="${MC_LOWER}-${MC_UPPER}"
74+
else
75+
76+
echo "::group::Determining Minecraft version bounds..."
77+
echo "Reading supported_formats from pack.mcmeta..."
78+
if [ -f pack.mcmeta ]; then
79+
LOWER_DP=$(jq -r '.pack.supported_formats[0]' pack.mcmeta)
80+
UPPER_DP=$(jq -r '.pack.supported_formats[1]' pack.mcmeta)
81+
else
82+
echo "::error:: pack.mcmeta file not found!"
83+
exit 1
84+
fi
85+
echo "Lower data pack format: $LOWER_DP, Upper: $UPPER_DP"
86+
87+
echo "Fetching versions from Spyglass API..."
88+
API_JSON=$(curl -s https://api.spyglassmc.com/mcje/versions || true)
89+
if [ -z "$API_JSON" ] || ! echo "$API_JSON" | jq empty > /dev/null 2>&1; then
90+
echo "::warning:: Spyglass API unreachable, using fallback values. The annotated Minecraft versions may not be accurate."
91+
echo " Response: $API_JSON"
92+
ENDPOINT_DOWN=true
93+
else
94+
ENDPOINT_DOWN=false
95+
fi
96+
97+
if [ "$ENDPOINT_DOWN" = true ]; then
98+
MC_LOWER=${FALLBACK_MC_LOWER}
99+
# Use latest version from Mojang API as fallback upper
100+
MC_UPPER=$(curl -s https://piston-meta.mojang.com/mc/game/version_manifest_v2.json || true | jq -r '.latest.release' || echo "${FALLBACK_MC_UPPER}")
101+
# Build ranges
102+
MACHINE_RANGE_FABRIC=">=${MC_LOWER}"
103+
MACHINE_RANGE_FORGE="[${MC_LOWER},)"
104+
MACHINE_RANGE_NEOFORGE="[1.20.5,)"
105+
MC_HUMAN_VERSION_RANGE="${MC_LOWER}-${MC_UPPER}"
106+
# Trim patch version for mecha validation
107+
CMD_VALIDATOR_MC=$(echo "$MC_UPPER" | awk -F. '{print $1"."$2}')
108+
109+
echo "Used fallback values: MC_LOWER=$MC_LOWER, MC_UPPER=$MC_UPPER"
110+
else
111+
# Determine MC_LOWER: oldest version matching LOWER_DP
112+
# LIMITATION: Mod loaders don't accept snapshot versions, so we assume lower pack format is a release (and not a snapshot)
113+
MC_LOWER=$(echo "$API_JSON" | jq -r --argjson lower_dp "$LOWER_DP" '[.[] | select(.data_pack_version == $lower_dp and .type == "release")] | sort_by(.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601) | .[0].id')
114+
if [ -z "$MC_LOWER" ] || [ "$MC_LOWER" = "null" ]; then
115+
echo "::error:: Expected a release version for pack format ($LOWER_DP), but none was found!"
116+
exit 1
117+
fi
118+
119+
# Determine MC_UPPER: newest version matching UPPER_DP
120+
CHOSEN_UPPER_OBJ=$(echo "$API_JSON" | jq -c --argjson upper_dp "$UPPER_DP" '[.[] | select(.data_pack_version == $upper_dp)] | sort_by(.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601) | reverse | .[0]')
121+
if [ -z "$CHOSEN_UPPER_OBJ" ] || [ "$CHOSEN_UPPER_OBJ" = "null" ]; then
122+
# If no version is found, use the latest version from the API
123+
CHOSEN_UPPER_OBJ=$(echo "$API_JSON" | jq -c 'sort_by(.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601) | reverse | .[0]')
124+
fi
125+
MC_UPPER=$(echo "$CHOSEN_UPPER_OBJ" | jq -r '.id')
126+
127+
echo "Found MC_LOWER=$MC_LOWER, MC_UPPER=$MC_UPPER"
128+
129+
# Check if upper version is a release
130+
CHOSEN_UPPER_TYPE=$(echo "$CHOSEN_UPPER_OBJ" | jq -r '.type')
131+
if [ "$CHOSEN_UPPER_TYPE" != "release" ]; then
132+
echo "Exact upper version is not a release. Attempting to find the next release version for mod version ranges..."
133+
134+
# Exact upper is not a release, try to find the next higher release (i.e. one with a release_time greater than the chosen upper one)
135+
CHOSEN_TIME_NUM=$(echo "$CHOSEN_UPPER_OBJ" | jq -r '.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601')
136+
NEXT_RELEASE_OBJ=$(echo "$API_JSON" | jq -c --argjson chosen_time_num "$CHOSEN_TIME_NUM" 'map(select(.type == "release" and (.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601 > $chosen_time_num))) | sort_by(.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601) | .[0]')
137+
NEXT_RELEASE=$(echo "$NEXT_RELEASE_OBJ" | jq -r '.id')
138+
139+
if [ -n "$NEXT_RELEASE" ] && [ "$NEXT_RELEASE" != "null" ]; then
140+
# Next release version is available, use it as the upper bound
141+
MACHINE_RANGE_FABRIC=">=${MC_LOWER} <${NEXT_RELEASE}"
142+
MACHINE_RANGE_FORGE="[${MC_LOWER},${NEXT_RELEASE})"
143+
MACHINE_RANGE_NEOFORGE="[1.20.5,${NEXT_RELEASE})"
144+
CMD_VALIDATOR_MC=$(echo "$NEXT_RELEASE" | awk -F. '{print $1"."$2}')
145+
146+
echo "Next release version found: $NEXT_RELEASE"
147+
else
148+
# Next release version is not available, omit the upper bound for Fabric and (Neo)Forge
149+
MACHINE_RANGE_FABRIC=">=${MC_LOWER}"
150+
MACHINE_RANGE_FORGE="[${MC_LOWER},)"
151+
MACHINE_RANGE_NEOFORGE="[1.20.5,)"
152+
153+
# Pick the most recent release that is less than or equal to the chosen upper's release_time for mecha validation
154+
CMD_VALIDATOR_OBJ=$(echo "$API_JSON" | jq -c --argjson chosen_time_num "$CHOSEN_TIME_NUM" 'map(select(.type == "release" and (.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601 <= $chosen_time_num))) | sort_by(.release_time | sub("\\+00:00$"; "Z") | fromdateiso8601) | reverse | .[0]')
155+
CMD_VALIDATOR_MC=$(echo "$CMD_VALIDATOR_OBJ" | jq -r '.id' | awk -F. '{print $1"."$2}')
156+
# Use fallback upper if no release version is found
157+
if [ "$CMD_VALIDATOR_MC" = "null" ]; then
158+
CMD_VALIDATOR_MC=$(echo "${FALLBACK_MC_UPPER}" | awk -F. '{print $1"."$2}')
159+
fi
160+
161+
echo "No next release version found, using lower bound $MC_LOWER for mod version ranges and $CMD_VALIDATOR_MC for command validation"
162+
fi
163+
164+
else
165+
# Exact upper is a release, use it as the upper bound
166+
MACHINE_RANGE_FABRIC=">=${MC_LOWER} <=${MC_UPPER}"
167+
MACHINE_RANGE_FORGE="[${MC_LOWER},${MC_UPPER}]"
168+
MACHINE_RANGE_NEOFORGE="[1.20.5,${MC_UPPER}]"
169+
CMD_VALIDATOR_MC=$(echo "$MC_UPPER" | awk -F. '{print $1"."$2}')
170+
fi
171+
172+
# Use exact values for human-readable version range
173+
MC_HUMAN_VERSION_RANGE="${MC_LOWER}-${MC_UPPER}"
174+
fi
175+
echo "::endgroup::"
176+
fi
177+
178+
echo "MC_LOWER=$MC_LOWER" >> $GITHUB_ENV
179+
echo "MC_UPPER=$MC_UPPER" >> $GITHUB_ENV
180+
echo "MC_HUMAN_VERSION_RANGE=$MC_HUMAN_VERSION_RANGE" >> $GITHUB_ENV
181+
echo "MACHINE_RANGE_FABRIC=$MACHINE_RANGE_FABRIC" >> $GITHUB_ENV
182+
echo "MACHINE_RANGE_FORGE=$MACHINE_RANGE_FORGE" >> $GITHUB_ENV
183+
echo "MACHINE_RANGE_NEOFORGE=$MACHINE_RANGE_NEOFORGE" >> $GITHUB_ENV
184+
echo "CMD_VALIDATOR_MC=$CMD_VALIDATOR_MC" >> $GITHUB_ENV
185+
echo "Determined versions:"
186+
echo " Human range: $MC_HUMAN_VERSION_RANGE"
187+
echo " Fabric range: $MACHINE_RANGE_FABRIC"
188+
echo " Forge range: $MACHINE_RANGE_FORGE"
189+
echo " NeoForge range: $MACHINE_RANGE_NEOFORGE"
190+
echo " Validator version: $CMD_VALIDATOR_MC"
191+
192+
# Optionally validate commands
193+
- name: Validate commands
194+
if: ${{ github.event.inputs.skip_check != 'true' }}
195+
uses: mcbeet/check-commands@v1
196+
with:
197+
source: .
198+
stats: true
199+
minecraft: ${{ env.CMD_VALIDATOR_MC }}
200+
201+
# Update version info in files via find/replace actions
202+
- name: Replace uninstall file name
203+
uses: jacobtomlinson/gha-find-replace@v3
204+
with:
205+
find: "${file_name}"
206+
replace: "${{ github.repository }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-datapack.zip"
207+
regex: false
208+
include: "**uninstall.mcfunction"
209+
- name: Set data pack version
210+
uses: jacobtomlinson/gha-find-replace@v3
211+
with:
212+
find: "${version}"
213+
replace: "${{ env.TAG }}"
214+
regex: false
215+
- name: Set supported Minecraft version range (Human-readable)
216+
uses: jacobtomlinson/gha-find-replace@v3
217+
with:
218+
find: "${mc_human_version_range}"
219+
replace: "${{ env.MC_HUMAN_VERSION_RANGE }}"
220+
regex: false
221+
- name: Set supported Minecraft version range (Fabric)
222+
uses: jacobtomlinson/gha-find-replace@v3
223+
with:
224+
find: "${mc_version_range_fabric}"
225+
replace: "${{ env.MACHINE_RANGE_FABRIC }}"
226+
regex: false
227+
- name: Set supported Minecraft version range (Forge)
228+
uses: jacobtomlinson/gha-find-replace@v3
229+
with:
230+
find: "${mc_version_range_forge}"
231+
replace: "${{ env.MACHINE_RANGE_FORGE }}"
232+
regex: false
233+
- name: Set supported Minecraft version range (NeoForge)
234+
uses: jacobtomlinson/gha-find-replace@v3
235+
with:
236+
find: "${mc_version_range_neoforge}"
237+
replace: "${{ env.MACHINE_RANGE_NEOFORGE }}"
238+
regex: false
239+
240+
# Check for existence of directories
241+
- name: Check for data pack folder
242+
id: check_datapack
243+
uses: andstor/file-existence-action@v3
244+
with:
245+
files: "data"
246+
- name: Check for mod folder
247+
id: check_mod
248+
uses: andstor/file-existence-action@v3
249+
with:
250+
files: "META-INF, net, fabric.mod.json, assets"
251+
- name: Check for resource pack folder
252+
id: check_assets
253+
uses: andstor/file-existence-action@v3
254+
with:
255+
files: "assets/minecraft"
256+
257+
# Package files (zip/jar) conditionally if folder exists
258+
- name: Create data pack zip file
259+
if: steps.check_datapack.outputs.files_exists == 'true'
260+
shell: bash
261+
run: |
262+
zip "${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-datapack.zip" -r . -x "assets/**" "net/**" "META-INF/**" fabric.mod.json "unused/**" "src/**" "wiki/**" CHANGES.md ".*"
263+
- name: Create mod jar file
264+
if: steps.check_mod.outputs.files_exists == 'true'
265+
shell: bash
266+
# Use jar -cvf "${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-mod.jar" -C . META-INF net fabric.mod.json assets data?
267+
run: |
268+
zip "${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-mod.jar" -r . -x "unused/**" "src/**" "wiki/**" CHANGES.md ".*" "${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-datapack.zip"
269+
- name: Create resource pack zip file
270+
if: steps.check_assets.outputs.files_exists == 'true'
271+
shell: bash
272+
run: |
273+
zip "${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-resourcepack.zip" -r . -x "data/**" "net/**" "META-INF/**" fabric.mod.json "unused/**" "src/**" "wiki/**" CHANGES.md ".*" "${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-datapack.zip" "${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-mod.jar"
274+
275+
# Capture and upload artifacts
276+
- name: Upload artifacts
277+
id: upload
278+
uses: actions/upload-artifact@v4
279+
with:
280+
name: "${{ github.event.repository.name }} v${{ env.TAG }} Development Builds (Unzip Me)"
281+
path: |
282+
./${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-datapack.zip
283+
./${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-mod.jar
284+
./${{ github.event.repository.name }}-v${{ env.TAG }}-mc${{ env.MC_HUMAN_VERSION_RANGE }}-resourcepack.zip
285+
286+
# Build summary
287+
- name: Set job summary
288+
if: always()
289+
shell: bash
290+
run: |
291+
echo "Building summary..."
292+
if [ "${{ job.status }}" == "success" ]; then
293+
cat << EOF >> $GITHUB_STEP_SUMMARY
294+
<picture>
295+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/light-theme/success.svg">
296+
<img alt="✅ Success" src="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/dark-theme/success.svg">
297+
</picture><br>
298+
299+
**${{ github.event.repository.name }} v${{ env.TAG }}** for **Minecraft ${{ env.MC_HUMAN_VERSION_RANGE }}** built successfully! [See changes since last release](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGES.md).
300+
301+
[Download via nightly.link](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }})
302+
303+
Make sure to always back up your world before using development builds!
304+
EOF
305+
306+
else
307+
cat << EOF >> $GITHUB_STEP_SUMMARY
308+
<picture>
309+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/light-theme/error.svg">
310+
<img alt="❌ Error" src="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/dark-theme/error.svg">
311+
</picture><br>
312+
313+
The build v${{ env.TAG }} for mc${{ env.MC_HUMAN_VERSION_RANGE }} failed! Please check the action logs for details.
314+
EOF
315+
fi

0 commit comments

Comments
 (0)