Releasing¶
Versions, branches and releases. How the project is built: Development.
In short¶
- Work on
rc(or on branches merged intorc), noting changes under## [Unreleased]inCHANGELOG.md. - With the first release candidate, on
rc, setgradle.propertiesto the next version:mod_version=0.5.0. - Whenever you want testers to try
rc, publish a release candidate: Actions tab → Release candidate → Run workflow, Use workflow from:rc(orgh workflow run rc.yml --ref rc). It runs CI's checks and puts0.5.0-rc+<commit>on Modrinth as Alpha, replacing the previous candidate. Repeat as often as you like. - When ready, on
rc, inCHANGELOG.md: under the empty## [Unreleased], add## [0.5.0] - 2026-10-15with what changed (square brackets and this exact format: the release notes are taken from it). Optionally publish a last candidate of this commit: then the release pull request doesn't rebuild it. - Optionally run the Game tests workflow on
rc(Actions tab → Game tests → Run workflow, Use workflow from:rc): CI doesn't play them. - Open a pull request from
rcintomain, wait for CI (its release check confirms the new version), merge. - Done. GitHub creates
release/0.5.0, the tagv0.5.0-fabload0.19.5+api0.162.0-mc26.3and the release (a pre-release, Beta on Modrinth: promote it by hand) with the jar, the.mrpack, the Default Content datapack and its resource pack, then uploads the jar to Modrinth (about a minute: Actions tab, then Releases) and deletes the release candidate from Modrinth; then it adds the Pack Editor's portable apps for Windows, macOS and Linux (about ten more minutes). rcis brought up to date withmainautomatically; locally, rungit pullonrc.
The sections below explain each part.
Version numbers¶
MineDnD uses Semantic Versioning: MAJOR.MINOR.PATCH.
| Change | Bump | Example |
|---|---|---|
| Bug fixes only | PATCH | 0.0.1 → 0.0.2 |
| New features, new datapack formats, balance changes | MINOR | 0.0.2 → 0.1.0 |
| Changes that break existing worlds or datapacks (after 1.0.0) | MAJOR | 1.4.2 → 2.0.0 |
While the version starts with 0., anything can still change between minor versions.
The version lives in one place: mod_version in gradle.properties. It flows into the jar name
(MineDnD-<version>.jar), fabric.mod.json (shown in the mods list) and the modpack (MineDnD-<version>.mrpack).
Branches and tags¶
| Name | What it is |
|---|---|
main |
What's released. Every merge into main is a release: pull requests into it come from rc and carry a new version. |
rc |
Release candidate: where changes collect and get tested (installToModrinth, playtests, release candidates on Modrinth) before a release. |
feature/..., fix/..., chore/... |
Short-lived work branches, merged into rc by pull request (or commit straight on rc). |
release/X.Y.Z |
Frozen copy of a released version, to fix it later without taking new features. Created automatically. |
tag vX.Y.Z-fabloadL+apiA-mcM |
The exact commit that was released, e.g. v0.4.2-fabload0.19.5+api0.162.0-mc26.3: mod version, Fabric Loader version (loader_version), Fabric API version (fabric_version without +<minecraft>) and Minecraft version (minecraft_version). Created automatically. Up to 0.4.1 tags were plain vX.Y.Z. |
Making a release¶
Work happens on rc; merging rc into main releases.
- Make changes on
rc(commits, or pull requests fromfeature/...branches intorc). CI doesn't run by itself there: run it by hand before merging (Actions tab → CI → Run workflow, on the branch; the result is in the Actions tab, not in the pull request's checks). Note them under[Unreleased]inCHANGELOG.md. Try them in game withgradlew installToModrinth -Pinstance="<folder>", or publish a release candidate for others to try. - When
rcis ready: onrc, setmod_versioningradle.properties(if a candidate didn't already need it) and move the[Unreleased]notes inCHANGELOG.mdunder a new## [X.Y.Z] - YYYY-MM-DDheading. - Open a pull request from
rcintomain. Its release-check job fails if the version is already released or the changelog has no section for it, so every merge intomainreally is a release. CI builds only what isn't checked yet (see Reviews and branch protection). - Merge it. On every push to
main, the Release workflow (.github/workflows/release.yml) checks whetherX.Y.Zalready has a tag: - No: it creates
release/X.Y.Zand the tag (e.g.v0.4.2-fabload0.19.5+api0.162.0-mc26.3, also the release title), builds, and publishes a GitHub Release withMineDnD-X.Y.Z.jar, the sources jar,MineDnD-X.Y.Z.mrpack,MineDnD-Default-Content-X.Y.Z.zip(the datapack) andMineDnD-Default-Content-Resources-X.Y.Z.zip(its resource pack), using the version'sCHANGELOG.mdsection as notes. Every release is a pre-release on GitHub and Beta on Modrinth: promote it by hand when it's ready (Modrinth: the version's Edit → release channel; GitHub: Edit release). Then it uploads the jar to Modrinth (see below), and builds the Pack Editor's portable apps into the same release. - Yes: nothing is released (the workflow says so in its summary).
rcis brought up to date withmainautomatically: the Release workflow's last step mergesmainintorc(usually a fast-forward). Only after a conflict, which can happen ifrcgot new commits in the meantime, does it warn and ask you to rungit checkout rc && git merge origin/main && git pushyourself. Locally,git pullonrcgets the update.
Watch it in the repository's Actions tab; the release appears on the Releases page and on Modrinth.
Release candidates¶
A release candidate puts what's on rc on Modrinth for testers, before the release. It's published only when you
run it: Actions tab → Release candidate → Run workflow, Use workflow from: rc (from a terminal:
gh workflow run rc.yml --ref rc). The Release candidate workflow (.github/workflows/rc.yml):
- refuses to run on another branch, or while
mod_versionis a version already released: a candidate is always for the next version, so bumpmod_versiononrcwith the first one; - runs the same checks as CI (the mod, modpack and datapack build with the tests, the docs site, the Pack Editor) and stops if one fails;
- uploads the jar to Modrinth as Alpha, not featured, numbered
X.Y.Z-rc-apiA+mcM(e.g.0.5.0-rc-api0.162.0+mc26.3), with the version'sCHANGELOG.mdsection (or[Unreleased]until it's written) as changelog. The jar itself saysX.Y.Z-rc+<commit>(e.g.0.5.0-rc+5a1788f), so the mods list and bug reports show which candidate it is.
Every run replaces the version's previous candidate (it deletes it, then uploads the new one), so Modrinth has at
most one candidate, and the release deletes it. The built jar, .mrpack and datapack zip are also kept with the run
for 7 days. Without the Modrinth secret and variable, only the checks run.
It also (re)makes the candidate's GitHub pre-release rc-vX.Y.Z at its commit
(.github/scripts/rc-prerelease.sh), titled "MineDnD X.Y.Z release candidate (MineDnD-Default-Content-X.Y.Z-rc-<commit>.zip, and its resource pack, MineDnD-Default-Content-Resources-X.Y.Z-rc-<commit>.zip. Like the Modrinth candidate,
it holds only the latest candidate (at another commit it's deleted and made again), and the release deletes it. The tag
starts with rc-, not v, so it doesn't start the Release workflow nor count as a release.
Two checkboxes (both on by default) publish the rest of the candidate, once the checks pass:
- docs: the release candidate's documentation site, https://rc.minednd.pages.dev/;
- pack_editor: the Pack Editor downloads of the candidate, MineDnD Pack Editor RC, added to the same pre-release.
So testers get the mod, its content, its docs and the app of the same commit. Untick the boxes for a quick candidate without docs and app.
If the release pull request's commit is one published as a candidate, CI doesn't build it again (the candidate ran the same checks); only the release check runs.
Documentation site¶
The docs are also a website, https://minednd.pages.dev/ (MkDocs Material, English and Italian, with search), on
Cloudflare Pages: free, and the repository can stay private. The Docs workflow (.github/workflows/docs.yml)
builds and publishes it on every push to main that changes docs/, so the site always describes the released
version; it can also be run by hand from the Actions tab.
There's a second site for the release candidate, https://rc.minednd.pages.dev/: the same workflow run on rc
(from the Release candidate workflow's docs checkbox, or by hand) builds it with
DOCS_CHANNEL=rc and deploys it as the Pages project's rc branch, which Cloudflare keeps out of search engines. Its
bar on top says it describes the next version. On both sites, that bar has a Release / Release candidate switch that
opens the same page, in the same language, on the other site. Until the first candidate is published,
rc.minednd.pages.dev doesn't exist and the switch leads nowhere.
Set up once:
- Account ID: on dash.cloudflare.com, open Workers & Pages (or the account's home): the Account ID is in the
right-hand column (also in the address bar:
dash.cloudflare.com/<account id>/…). Store it as the secretCLOUDFLARE_ACCOUNT_ID:gh secret set CLOUDFLARE_ACCOUNT_ID(paste it when asked). - API token: avatar → Profile → API Tokens → Create Token → Custom token. Permissions: Account →
Cloudflare Pages → Edit; Account resources: your account. Store it as the secret
CLOUDFLARE_API_TOKEN:gh secret set CLOUDFLARE_API_TOKEN. - Run the Docs workflow (Actions tab → Docs → Run workflow, on
main). The first run creates the Pages projectminednd(another name: the repository variableCLOUDFLARE_PAGES_PROJECT, andsite_urlinmkdocs.yml).
Until the secrets are set, the workflow builds the site and only warns. CI builds the site too (the docs job, on release
pull requests or by hand), so a broken link or anchor in either language fails there. How the site is made:
Development.
Modrinth uploads¶
The Release workflow runs .github/scripts/modrinth-upload.sh after the GitHub release. It uploads MineDnD-X.Y.Z.jar
as version X.Y.Z: Fabric, the Minecraft version from gradle.properties, Fabric API as a required dependency, the
release notes as changelog, always Beta (promoted by hand). A version already on Modrinth is skipped, so re-running is safe.
Then it deletes the version's release candidate, if there is one.
Set up once (until then the step only prints "skipped"):
- On modrinth.com: avatar → Settings → Personal access tokens → create one with the Create versions and Delete versions scopes (and Read projects, to find the project while it's a draft or private). Deleting is for release candidates: each one replaces the previous one, and the release removes it.
- Store it in the repository as the secret
MODRINTH_TOKEN:gh secret set MODRINTH_TOKEN(paste it when asked), or Settings → Secrets and variables → Actions → New repository secret. - Store the project's slug (e.g.
minednd) as the variableMODRINTH_PROJECT:gh variable set MODRINTH_PROJECT --body minednd.
CI also runs two checks (on release pull requests, or when run by hand):
- a dry run of the upload (MODRINTH_DRY_RUN=1), which checks the data without contacting Modrinth;
- an access check (MODRINTH_CHECK=1), which reads the project with the token and lists its versions. It shows up in the run as "Modrinth project OK: …", or as an error when the slug or token is wrong.
Pack Editor downloads¶
The MineDnD Pack Editor's portable apps (nothing to install: an .exe, a zipped .app, an AppImage)
are built by Pack Editor downloads (.github/workflows/pack-editor.yml). The Release workflow runs it for every
release, with the new tag; run it by hand from the Actions tab to rebuild them (e.g. if that run failed) or to try a
build.
- Release tag set (e.g.
v0.10.0-fabload0.19.5+api0.162.0-mc26.3): it builds that tag on Windows, macOS (universal) and Linux and addsMineDnD-Pack-Editor-X.Y.Z-windows-portable.exe,…-macos.zipand…-linux-x86_64.AppImageto that release. - Empty, on
rc(or from the Release candidate workflow's pack_editor checkbox): the release candidate's app, MineDnD Pack Editor RC, with its own name and app id, so it runs next to the released one, and a Guide that opens the release candidate's docs site. It addsMineDnD-Pack-Editor-RC-X.Y.Z-windows-portable.exe,…-macos.zipand…-linux-x86_64.AppImageto the candidate's pre-releaserc-vX.Y.Z(see Release candidates; run by hand on a newer commit, it makes that pre-release again, without the datapack). The Release workflow deletes the pre-release when it releases the version. - Empty, elsewhere: it builds the branch you pick and keeps the apps as the run's artifacts for 14 days, to try a build.
They're unsigned (no paid certificates); the Pack Editor page ("Download") tells users how to open them the first time.
Fixing an old release¶
- Branch from
release/X.Y.Z, fix, bump PATCH (X.Y.Z+1), add a changelog entry, open a pull request intorelease/X.Y.Z. - After merging, tag the release branch by hand:
git tag -a "$(bash .github/scripts/release-tag.sh)" -m "MineDnD X.Y.(Z+1)"and push the tag. Pushing a release tag also publishes a release (the tag must be the onegradle.propertiesgives). - Bring the fix to
maintoo (another pull request or a cherry-pick).
Reviews and branch protection¶
.github/CODEOWNERS makes the owner the required reviewer of every file, and CI (.github/workflows/ci.yml) builds the mod
and runs the tests on every release pull request (into main or release/*, where its check checks / build is
required), and on any branch when run by hand. The checks themselves are in .github/workflows/checks.yml, shared with
the Release candidate workflow.
On a pull request, CI skips what's already done, to run as little as possible:
- The commit was published as a release candidate (a successful Release candidate run on it): no build, docs
or Pack Editor job, since the candidate ran them. The skipped
checks / buildcounts as passed. - Only documentation changed (
docs/,mkdocs.yml, Markdown files at the top of the repository): only the docs site is built.
The rules that enforce this (pull request required, code owner approval, CI must pass, no force pushes or deletions
on main and release/*; release tags can't be moved or deleted) are prepared in .github/rulesets/.
GitHub only enforces rulesets on private repositories with a paid plan (GitHub Pro or Team); on a public repository
they're free. To apply them, once that's the case:
gh api -X POST repos/less-hub/MineDnD/rulesets --input .github/rulesets/branches.json
gh api -X POST repos/less-hub/MineDnD/rulesets --input .github/rulesets/tags.json
The repository admin can still merge their own pull requests (GitHub doesn't let authors approve their own pull requests); everyone else's pull requests need the owner's approval.