Complete 26.3 parity acceptance and dual-version release workflow
Build and smoke / changes (push) Waiting to run

This commit is contained in:
dnorberg committed 2026-11-14 12:17:49 -05:00
1 parent 41daff1e61
commit 4a59fb5d02
26 files changed
+1241 -369

No files matched your search

+35 -6
View File
@@ -13,6 +13,8 @@ from pathlib import Path
import secrets
import subprocess
import sys
import tomllib
import zipfile
import urllib.error
import urllib.request
@@ -97,7 +99,8 @@ def request_json(url: str, *, token: str | None = None, token_header: str = "Aut
request = urllib.request.Request(url, data=payload, headers=headers, method=method)
try:
with urllib.request.urlopen(request, timeout=120) as response:
return json.load(response)
body = response.read()
return json.loads(body) if body else None
except urllib.error.HTTPError as error:
if error.code == 404 and not_found_ok:
return None
@@ -150,6 +153,15 @@ def release_metadata(args: argparse.Namespace) -> tuple[dict, dict]:
"releaseType": args.channel,
"isMarkedForManualRelease": False,
}
if args.minecraft == "1.21.1":
modrinth["dependencies"] = [
{"project_id": "ftdbN0KK", "dependency_type": "required"},
{"project_id": "qbUzidyB", "dependency_type": "required"},
]
curseforge["relations"] = {"projects": [
{"slug": "badpackets", "type": "requiredDependency"},
{"slug": "dynamicdimension-ar-edition", "projectID": "1469680", "type": "requiredDependency"},
]}
return modrinth, curseforge
@@ -180,6 +192,27 @@ def publish_curseforge(metadata: dict, jar: Path) -> None:
print(f"CurseForge: https://www.curseforge.com/minecraft/mc-mods/rivencraft/files/{result['id']} (pending approval)")
def verify_artifact(jar: Path, version: str, minecraft: str, java: str, channel: str) -> None:
"""Refuse renamed/wrong-track artifacts before obtaining any upload credential."""
expected_java = {"1.21.1": "21", "26.3": "25"}
if minecraft not in expected_java or java != expected_java[minecraft]:
raise RuntimeError("Unsupported Minecraft/Java target pairing")
_, filename = release_identity(version, minecraft, channel)
if jar.name != filename:
raise RuntimeError("JAR filename does not match Minecraft and release versions")
with zipfile.ZipFile(jar) as archive:
metadata = tomllib.loads(archive.read("META-INF/neoforge.mods.toml").decode())
mod = next((entry for entry in metadata.get("mods", []) if entry.get("modId") == "rivencraft"), None)
if mod is None or mod.get("version") != version:
raise RuntimeError("Embedded mod version does not match the requested release")
dependencies = metadata.get("dependencies", {}).get("rivencraft", [])
game = next((entry for entry in dependencies if entry.get("modId") == "minecraft"), None)
if game is None or game.get("versionRange") != f"[{minecraft}]":
raise RuntimeError("Embedded Minecraft range does not match the requested target")
if any(name.startswith("net/dnorberg/rivencraft/regression/") for name in archive.namelist()):
raise RuntimeError("Refusing an artifact containing disposable multiplayer test fixtures")
def release_identity(version: str, minecraft: str, channel: str) -> tuple[str, str]:
"""New releases prefix the version with Minecraft; retain old beta upload support."""
if version.startswith(minecraft + "."):
@@ -271,11 +304,7 @@ def main() -> None:
raise RuntimeError(f"JAR does not exist: {args.jar}")
if not args.changelog.is_file():
raise RuntimeError(f"Changelog does not exist: {args.changelog}")
if args.minecraft != "26.3":
raise RuntimeError("Only the 26.3 line is configured; 1.21.1 needs required dependency relations")
_, expected_filename = release_identity(args.version, args.minecraft, args.channel)
if args.jar.name != expected_filename:
raise RuntimeError("JAR filename does not match Minecraft and release versions")
verify_artifact(args.jar, args.version, args.minecraft, args.java, args.channel)
modrinth, curseforge = release_metadata(args)
sites = SITES if args.site == "all" else (("modrinth", "curseforge") if args.site == "both" else (args.site,))
print(f"JAR: {args.jar.resolve()} ({args.jar.stat().st_size} bytes)")