From b574b59b96a32f280927db7c46a60504cb385fde Mon Sep 17 00:00:00 2001 From: Asket <117373630+asketmc@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:28:16 +0200 Subject: [PATCH] Correct Modrinth game version coverage --- .github/modrinth.json | 25 +++++++++++++++--- .github/workflows/release.yml | 7 +++-- CHANGELOG.md | 5 ++++ PUBLISHING.md | 9 ++++--- scripts/check-release-security.py | 25 ++++++++++++++++-- scripts/prepare-modrinth-release.py | 41 +++++++++++++++-------------- scripts/test-modrinth-release.py | 41 ++++++++++++++++++++++------- 7 files changed, 112 insertions(+), 41 deletions(-) diff --git a/.github/modrinth.json b/.github/modrinth.json index a7f60c7..7a47c7e 100644 --- a/.github/modrinth.json +++ b/.github/modrinth.json @@ -6,8 +6,27 @@ "paper", "purpur" ], - "game_version_min": "1.20.6", - "game_version_max_exclusive": "1.22", - "compatibility_label": "`1.20.6` and `1.21.x`", + "game_versions": [ + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5", + "1.21.6", + "1.21.7", + "1.21.8", + "1.21.9", + "1.21.10", + "1.21.11" + ], + "compatibility_label": "`1.20.x` and `1.21.x` through `1.21.11`", "release_notes_directory": "docs/releases" } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae1928b..d5bcd4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -453,8 +453,11 @@ jobs: and .status == "listed" and (.version_type == "release" or .version_type == "beta" or .version_type == "alpha") and (.featured == (.version_type == "release")) - and (.game_versions | index("1.20.6") != null) - and (.game_versions | all(.[]; test("^(1\\.20\\.6|1\\.21(\\.[0-9]+)?)$")))' \ + and .game_versions == [ + "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6", + "1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", + "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11" + ]' \ "${metadata}/payload.json" >/dev/null cmp -s "${metadata}/changelog.md" \ <(jq -r '.changelog' "${metadata}/payload.json") diff --git a/CHANGELOG.md b/CHANGELOG.md index 70e1acd..847d60a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ Keep a Changelog format. selects current supported Minecraft tags, renders polished Markdown release notes, and verifies the Modrinth API readback without exposing the publication token to build or repository code. +### Fixed + +- Advertise every stable Minecraft 1.20.x version and Minecraft 1.21.x through 1.21.11 on Modrinth, + while rejecting later game-version tags until compatibility is reviewed explicitly. + ## [1.0.9] - 2026-07-16 ### Fixed diff --git a/PUBLISHING.md b/PUBLISHING.md index dc590a8..8020c30 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -73,10 +73,11 @@ Enter the token at the secure prompt. Never put it in a command argument, commit workflow log. If the credential is missing or expired, GitHub Release publication remains complete and the Modrinth job fails explicitly; replace the Environment secret and rerun the failed jobs. -Public channel metadata is versioned in `.github/modrinth.json`. Stable Minecraft releases in the -configured `1.20.6 <= version < 1.22` range are selected automatically, while snapshots and unsupported -loaders are rejected. Modrinth has no Pufferfish loader tag, so releases declare Paper and Purpur; Folia -is intentionally excluded because it is not supported. +Public channel metadata is versioned in `.github/modrinth.json`. The exact stable Modrinth tags for every +`1.20.x` release and every `1.21.x` release through `1.21.11` are validated against the live API before +publication. Snapshots, `1.21.12+`, `1.22+`, and unsupported loaders are rejected until the reviewed +configuration is intentionally changed. Modrinth has no Pufferfish loader tag, so releases declare Paper +and Purpur; Folia is intentionally excluded because it is not supported. Release notes are rendered from the exact `CHANGELOG.md` version section with a consistent compatibility, verification, and upgrade panel. For a richer player-facing release, add `docs/releases/x.y.z.md` before diff --git a/scripts/check-release-security.py b/scripts/check-release-security.py index fdcc955..f2e61b1 100644 --- a/scripts/check-release-security.py +++ b/scripts/check-release-security.py @@ -162,6 +162,8 @@ def main() -> int: "include_changelog=false", "Authorization: ${MODRINTH_TOKEN}", 'and .loaders == ["paper", "purpur"]', + "and .game_versions == [", + '"1.21.10", "1.21.11"', "already-present", "modrinth-readback.json", ): @@ -199,8 +201,27 @@ def main() -> int: "project_id": "ri1Ibgnf", "project_slug": "amctimber", "loaders": ["paper", "purpur"], - "game_version_min": "1.20.6", - "game_version_max_exclusive": "1.22", + "game_versions": [ + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5", + "1.21.6", + "1.21.7", + "1.21.8", + "1.21.9", + "1.21.10", + "1.21.11", + ], } for field, expected in expected_modrinth_config.items(): if modrinth_config.get(field) != expected: diff --git a/scripts/prepare-modrinth-release.py b/scripts/prepare-modrinth-release.py index ba0f20a..cd6d919 100644 --- a/scripts/prepare-modrinth-release.py +++ b/scripts/prepare-modrinth-release.py @@ -56,8 +56,6 @@ def validate_config(config: Any) -> dict[str, Any]: "project_id", "project_slug", "display_name", - "game_version_min", - "game_version_max_exclusive", "compatibility_label", "release_notes_directory", ) @@ -74,21 +72,24 @@ def validate_config(config: Any) -> dict[str, Any]: or len(loaders) != len(set(loaders)) ): raise PreparationError("loaders must be a non-empty list of unique names") - minimum = parse_game_version(config["game_version_min"]) - maximum = parse_game_version(config["game_version_max_exclusive"]) - if minimum >= maximum: - raise PreparationError("game version range must be non-empty") + game_versions = config.get("game_versions") + if ( + not isinstance(game_versions, list) + or not game_versions + or any(not isinstance(version, str) or not version for version in game_versions) + or len(game_versions) != len(set(game_versions)) + ): + raise PreparationError("game_versions must be a non-empty list of unique names") + parsed_versions = [parse_game_version(version) for version in game_versions] + if parsed_versions != sorted(parsed_versions): + raise PreparationError("game_versions must be in ascending Minecraft version order") return config -def select_game_versions( - entries: Any, minimum_text: str, maximum_text: str -) -> list[str]: +def select_game_versions(entries: Any, configured: list[str]) -> list[str]: if not isinstance(entries, list): raise PreparationError("Modrinth game-version response must be a JSON array") - minimum = parse_game_version(minimum_text) - maximum = parse_game_version(maximum_text) - selected: dict[tuple[int, int, int], str] = {} + available: set[str] = set() for entry in entries: if not isinstance(entry, dict) or entry.get("version_type") != "release": continue @@ -96,16 +97,17 @@ def select_game_versions( if not isinstance(value, str): continue try: - numeric = parse_game_version(value) + parse_game_version(value) except PreparationError: continue - if minimum <= numeric < maximum: - selected[numeric] = value - if minimum not in selected: + available.add(value) + missing = [version for version in configured if version not in available] + if missing: raise PreparationError( - f"Modrinth does not currently advertise required baseline {minimum_text}" + "Modrinth does not currently advertise configured stable versions: " + + ", ".join(missing) ) - return [selected[key] for key in sorted(selected)] + return configured def validate_loaders(entries: Any, configured: list[str]) -> list[str]: @@ -231,8 +233,7 @@ def prepare_candidate( config = validate_config(read_json(config_path)) game_versions = select_game_versions( read_json(game_versions_path), - config["game_version_min"], - config["game_version_max_exclusive"], + config["game_versions"], ) loaders = validate_loaders(read_json(loaders_path), config["loaders"]) version_type = classify_version_type(version, marked_prerelease) diff --git a/scripts/test-modrinth-release.py b/scripts/test-modrinth-release.py index 6afe7f1..41e803f 100644 --- a/scripts/test-modrinth-release.py +++ b/scripts/test-modrinth-release.py @@ -20,12 +20,31 @@ SPEC.loader.exec_module(MODULE) +SUPPORTED_VERSIONS = [ + "1.20", + "1.20.1", + "1.20.2", + "1.20.3", + "1.20.4", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5", + "1.21.6", + "1.21.7", + "1.21.8", + "1.21.9", + "1.21.10", + "1.21.11", +] GAME_VERSIONS = [ - {"version": "1.20.5", "version_type": "release"}, - {"version": "1.20.6", "version_type": "release"}, - {"version": "1.21", "version_type": "release"}, - {"version": "1.21.1", "version_type": "release"}, + *({"version": version, "version_type": "release"} for version in SUPPORTED_VERSIONS), {"version": "1.21.12-pre1", "version_type": "snapshot"}, + {"version": "1.21.12", "version_type": "release"}, {"version": "1.22", "version_type": "release"}, ] LOADERS = [ @@ -39,13 +58,14 @@ class ModrinthReleaseTests(unittest.TestCase): def test_selects_only_supported_stable_range(self) -> None: self.assertEqual( - ["1.20.6", "1.21", "1.21.1"], - MODULE.select_game_versions(GAME_VERSIONS, "1.20.6", "1.22"), + SUPPORTED_VERSIONS, + MODULE.select_game_versions(GAME_VERSIONS, SUPPORTED_VERSIONS), ) - def test_rejects_missing_compatibility_baseline(self) -> None: - with self.assertRaisesRegex(MODULE.PreparationError, "required baseline"): - MODULE.select_game_versions(GAME_VERSIONS[2:], "1.20.6", "1.22") + def test_rejects_missing_configured_stable_version(self) -> None: + without_120 = [entry for entry in GAME_VERSIONS if entry["version"] != "1.20"] + with self.assertRaisesRegex(MODULE.PreparationError, "configured stable versions"): + MODULE.select_game_versions(without_120, SUPPORTED_VERSIONS) def test_validates_plugin_loader_tags(self) -> None: self.assertEqual( @@ -103,7 +123,8 @@ def test_prepares_polished_current_release_payload(self) -> None: payload = json.loads((output / "payload.json").read_text(encoding="utf-8")) self.assertEqual("ri1Ibgnf", payload["project_id"]) self.assertEqual(["paper", "purpur"], payload["loaders"]) - self.assertEqual(["1.20.6", "1.21", "1.21.1"], payload["game_versions"]) + self.assertEqual(SUPPORTED_VERSIONS, payload["game_versions"]) + self.assertNotIn("1.21.12", payload["game_versions"]) self.assertEqual("server_only", payload["environment"]) self.assertTrue(payload["featured"]) self.assertIn("# 🌲 AMCTimber 1.0.9", payload["changelog"])