From 809eb1a3a4d20d9c6f2334cf54c4c52c8ba07413 Mon Sep 17 00:00:00 2001 From: Space Walker Date: Mon, 12 Jan 2026 19:47:51 +0100 Subject: [PATCH 1/6] update semver --- public/develop.js | 4 +- public/minecraft_semver.js | 721 ++++++++++++++++++++++++++----------- 2 files changed, 511 insertions(+), 214 deletions(-) diff --git a/public/develop.js b/public/develop.js index d3eb729..2bdadd8 100644 --- a/public/develop.js +++ b/public/develop.js @@ -9,7 +9,7 @@ import { getLatestNestsBuild } from "./meta_maven_utils.js"; -import { getMinecraftSemverVersion } from "./minecraft_semver.js"; +import { normalizeMinecraftVersion } from "./minecraft_semver.js"; (async () => { const genSelectorRadios = { @@ -118,7 +118,7 @@ import { getMinecraftSemverVersion } from "./minecraft_semver.js"; } async function getFmjDependenciesNote(minecraftVersion) { - let version = await getMinecraftSemverVersion(minecraftVersion); + let version = await normalizeMinecraftVersion(minecraftVersion); let text = ["### fabric.mod.json "]; text.push(`"minecraft": "${version}"`); diff --git a/public/minecraft_semver.js b/public/minecraft_semver.js index 7fc7501..008a9c9 100644 --- a/public/minecraft_semver.js +++ b/public/minecraft_semver.js @@ -1,95 +1,130 @@ // Converting Minecraft versions to SemVer: -// https://github.com/FabricMC/fabric-loader/blob/0462eb84f3cb5d5dd9f69e151b2b916b6952d471/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java - -const VERSION_PATTERN = new RegExp("0\\.\\d+(\\.\\d+)?a?(_\\d+)?|" // match classic versions first: 0.1.2a_34 - + "\\d+\\.\\d+(\\.\\d+)?(-pre\\d+| Pre-[Rr]elease \\d+)?|" // modern non-snapshot: 1.2, 1.2.3, optional -preN or " Pre-Release N" suffix - + "\\d+\\.\\d+(\\.\\d+)?(-rc\\d+| [Rr]elease Candidate \\d+)?|" // 1.16+ Release Candidate - + "\\d+w\\d+[a-z]|" // modern snapshot: 12w34a - + "[a-c]\\d\\.\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?|" // alpha/beta a1.2.3_45 - + "(Alpha|Beta) v?\\d+\\.\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?|" // long alpha/beta names: Alpha v1.2.3_45 - + "Inf?dev (0\\.31 )?\\d+(-\\d+)?|" // long indev/infdev names: Infdev 12345678-9 - + "(rd|inf?)-\\d+|" // early rd-123, in-20100223, inf-123 - + "1\\.RV-Pre1|3D Shareware v1\\.34|23w13a_or_b|24w14potato|25w14craftmine|" // odd exceptions - + "(.*[Ee]xperimental [Ss]napshot )(\\d+)" // Experimental versions.) -); -const RELEASE_PATTERN = new RegExp("\\d+\\.\\d+(\\.\\d+)?"); -const PRE_RELEASE_PATTERN = new RegExp(".+(?:-pre| Pre-[Rr]elease )(\\d+)"); -const RELEASE_CANDIDATE_PATTERN = new RegExp(".+(?:-rc| [Rr]elease Candidate )(\\d+)"); -const SNAPSHOT_PATTERN = new RegExp("(?:Snapshot )?(\\d+)w0?(0|[1-9]\\d*)([a-z])"); -const EXPERIMENTAL_PATTERN = new RegExp("(?:.*[Ee]xperimental [Ss]napshot )(\\d+)"); -const BETA_PATTERN = new RegExp("(?:b|Beta v?)1\\.(\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?)"); -const ALPHA_PATTERN = new RegExp("(?:a|Alpha v?)[01]\\.(\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?)"); -const INDEV_PATTERN = new RegExp("(?:inf?-|Inf?dev )(?:0\\.31 )?(\\d+(-\\d+)?)"); - -export async function getMinecraftSemverVersion(version) { - let release = getRelease(version); - return normalizeVersion(version, release); +// https://github.com/FabricMC/fabric-loader/blob/088e1fe3487f64ae3b36eb0b3499def3167b944b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java + +const RELEASE_PATTERN = new RegExp("(1\\.(\\d+)(?:\\.(\\d+))?)(?:-(\\d+))?"); // 1.6, 1.16.5, 1,16+231620, 1.21.11_unobfuscated +const TEST_BUILD_PATTERN = new RegExp(".+(?:-tb| Test Build )(\\d+)?(?:-(\\d+))?"); // ... Test Build 1, ...-tb2, ...-tb3-1234 +const PRE_RELEASE_PATTERN = new RegExp(".+(?:-pre| Pre-?[Rr]elease ?)(?:(\\d+)(?: ;\\))?)?(?:-(\\d+))?"); // ... Prerelease, ... Pre-release 1, ... Pre-Release 2, ...-pre3, ...-pre4-1234, ...-pre1_unobfuscated +const RELEASE_CANDIDATE_PATTERN = new RegExp(".+(?:-rc| RC| [Rr]elease Candidate )(\\d+)(?:-(\\d+))?"); // ... RC1, ... Release Candidate 2, ...-rc3, ...-rc4-1234, ...-rc1_unobfuscated +const SNAPSHOT_PATTERN = new RegExp("(?:Snapshot )?(\\d+)w0?(0|[1-9]\\d*)([a-z])(?:-(\\d+)|[ _]([Uu]nobfuscated))?"); // Snapshot 16w02a, 20w13b, 22w18c-1234, 25w45a Unobfuscated +const EXPERIMENTAL_PATTERN = new RegExp(".+(?:-exp|(?:_deep_dark)?_experimental[_-]snapshot-|(?: Deep Dark)? [Ee]xperimental [Ss]napshot )(\\d+)"); // 1.18 Experimental Snapshot 1, 1.18_experimental-snapshot-2, 1.18-exp3, 1.19 Deep Dark Experimental Snapshot 1 +const BETA_PATTERN = new RegExp("(?:b|Beta v?)1\\.((\\d+)(?:\\.(\\d+))?(_0\\d)?)([a-z])?(?:-(\\d+))?(?:-(launcher))?"); // Beta 1.2, b1.2_02-launcher, b1.3b, b1.3-1731, Beta v1.5_02, b1.8.1 +const ALPHA_PATTERN = new RegExp("(?:(?:server-)?a|Alpha v?)[01]\\.(\\d+\\.\\d+(?:_0\\d)?)([a-z])?(?:-(\\d+))?(?:-(launcher))?"); // Alpha v1.0.1, Alpha 1.0.1_01, a1.0.4-launcher, a1.1.0-131933, a1.2.2a, a1.2.3_05, Alpha 0.1.0, server-a0.2.8 +const INDEV_PATTERN = new RegExp("(?:inf?-|Inf?dev )(?:0\\.31 )?(\\d+)(?:-(\\d+))?"); // Indev 0.31 200100110, in-20100124-2310, Infdev 0.31 20100227-1433, inf-20100611 +const CLASSIC_SERVER_PATTERN = new RegExp("(?:(?:server-)?c)1\\.(\\d\\d?(?:\\.\\d)?)(?:-(\\d+))?"); // c1.0, server-c1.3, server-c1.5-1301, c1.8.1, c1.10.1 +const LATE_CLASSIC_PATTERN = new RegExp("(?:c?0\\.)(\\d\\d?)(?:_0(\\d))?(?:_st)?(?:_0(\\d))?([a-z])?(?:-([cs]))?(?:-(\\d+))?(?:-(renew))?"); // c0.24_st, 0.24_st_03, 0.25_st-1658, c0.25_05_st, 0.29, c0.30-s, 0.30-c-renew, c0.30_01c +const EARLY_CLASSIC_PATTERN = new RegExp("(?:c?0\\.0\\.)(\\d\\d?)a(?:_0(\\d))?(?:-(\\d+))?(?:-(launcher))?"); // c0.0.11a, c0.0.13a_03-launcher, c0.0.17a-2014, 0.0.18a_02 +const PRE_CLASSIC_PATTERN = new RegExp("(?:rd|pc)-(\\d+)(?:-(launcher))?"); // rd-132211, pc-132011-launcher +const TIMESTAMP_PATTERN = new RegExp("(.+)(?:-(\\d+))"); +const VERSION_PATTERN = new RegExp( + PRE_CLASSIC_PATTERN.source + + "|" + EARLY_CLASSIC_PATTERN.source + + "|" + LATE_CLASSIC_PATTERN.source + + "|" + CLASSIC_SERVER_PATTERN.source + + "|" + INDEV_PATTERN.source + + "|" + ALPHA_PATTERN.source + + "|" + BETA_PATTERN.source + + "(" + TEST_BUILD_PATTERN.source.substring(2) + ")?" + + "(" + PRE_RELEASE_PATTERN.source.substring(2) + ")?" + + "(" + RELEASE_CANDIDATE_PATTERN.source.substring(2) + ")?" + + "|" + RELEASE_PATTERN.source + + "(" + TEST_BUILD_PATTERN.source.substring(2) + ")?" + + "(" + PRE_RELEASE_PATTERN.source.substring(2) + ")?" + + "(" + RELEASE_CANDIDATE_PATTERN.source.substring(2) + ")?" + + "(" + EXPERIMENTAL_PATTERN.source.substring(2) + ")?" + + "|" + SNAPSHOT_PATTERN.source + + "|" + "[Cc]ombat(?: Test )?\\d[a-z]?" // combat snapshots + + "|" + "Minecraft RC\\d" // special case for 1.0.0 release candidates + + "|" + "2.0|1\\.RV-Pre1|3D Shareware v1\\.34" // odd exceptions + + "(" + TIMESTAMP_PATTERN.source + ")?"); + +function matchExact(s, expr) { + const matches = s.match(expr); + if (matches == null || s != matches[0]) { + return null; + } + return matches; } function getRelease(version) { - if (RELEASE_PATTERN.test(version)) { - return version; - } + // 1.6, 1.16.5, 1,16+231620 + let matches = matchExact(version, RELEASE_PATTERN); - if (!isProbableVersion(version)) { - throw new Error("Not a valid version: " + version); + if (matches != null) { + // return name without timestamp + return matches[1]; } - let pos = version.indexOf("-pre"); + // version ids as found in versions manifest + // ... as in 1.19_deep_dark_experimental_snapshot-1 + let pos = version.indexOf("_deep_dark_experimental_snapshot-"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in 1.18_experimental-snapshot-1 + pos = version.indexOf("_experimental-snapshot-"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in 1.18-exp1 + pos = version.indexOf("-exp"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in b1.6-tb3 + pos = version.indexOf("-tb"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in 1.21.6-pre1 + pos = version.indexOf("-pre"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in 1.21.6-rc1 + pos = version.indexOf("-rc"); + if (pos >= 0) return version.substring(0, pos); + + // version names as extracted from the jar + // ... as in 1.19 Deep Dark Experimental Snapshot 1 + pos = version.indexOf(" Deep Dark Experimental Snapshot"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in 1.18 Experimental Snapshot 1 + pos = version.indexOf(" Experimental Snapshot"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in 1.18 experimental snapshot 2 + pos = version.indexOf(" experimental snapshot "); + if (pos >= 0) return version.substring(0, pos); + + // ... as in Beta 1.6 Test Build 3 + pos = version.indexOf(" Test Build"); if (pos >= 0) return version.substring(0, pos); - pos = version.indexOf(" Pre-Release "); + // ... as in 1.21.6 Pre-Release 1 + pos = version.indexOf(" Pre-Release"); if (pos >= 0) return version.substring(0, pos); - pos = version.indexOf(" Pre-release "); + // ... as in Beta 1.8 Pre-release 1 + pos = version.indexOf(" Pre-release"); if (pos >= 0) return version.substring(0, pos); - pos = version.indexOf(" Release Candidate "); + // ... as in Beta 1.9 Prerelease 2 + pos = version.indexOf(" Prerelease"); if (pos >= 0) return version.substring(0, pos); - let matches = version.match(SNAPSHOT_PATTERN); + // ... as in Minecraft RC1 + pos = version.indexOf(" RC"); + if (pos >= 0) return version.substring(0, pos); + + // ... as in 1.21.6 Release Candidate 1 + pos = version.indexOf(" Release Candidate"); + if (pos >= 0) return version.substring(0, pos); + + matches = matchExact(version, SNAPSHOT_PATTERN); // Snapshot 16w02a, 20w13b, 22w18c-1234, 25w45a Unobfuscated if (matches != null) { let year = matches[1]; let week = matches[2]; - if (year == 25 && week >= 15 || year > 25) { - return "1.21.6"; - } else if (year == 25 && week >= 2 && week <= 10) { - return "1.21.5"; - } else if (year == 24 && week >= 44) { - return "1.21.4"; - } else if (year == 24 && week >= 33 && week <= 40) { - return "1.21.2"; - } else if (year == 24 && week >= 18 && week <= 21) { - return "1.21"; - } else if (year == 23 && week >= 51 || year == 24 && week <= 14) { - return "1.20.5"; - } else if (year == 23 && week >= 40 && week <= 46) { - return "1.20.3"; - } else if (year == 23 && week >= 31 && week <= 35) { - return "1.20.2"; - } else if (year == 23 && week >= 12 && week <= 18) { - return "1.20"; - } else if (year == 23 && week <= 7) { - return "1.19.4"; - } else if (year == 22 && week >= 42) { - return "1.19.3"; - } else if (year == 22 && week == 24) { - return "1.19.1"; - } else if (year == 22 && week >= 11 && week <= 19) { - return "1.19"; - } else if (year == 22 && week >= 3 && week <= 7) { - return "1.18.2"; - } else if (year == 21 && week >= 37 && week <= 44) { - return "1.18"; - } else if (year == 20 && week >= 45 || year == 21 && week <= 20) { - return "1.17"; - } else if (year == 20 && week >= 27 && week <= 30) { - return "1.16.2"; - } else if (year == 20 && week >= 6 && week <= 22) { - return "1.16"; + if (year > 19) { + throw new Error("hey now, Ornithe does not support Minecraft versions that new!"); } else if (year == 19 && week >= 34) { return "1.15"; } else if (year == 18 && week >= 43 || year == 19 && week <= 14) { @@ -140,97 +175,307 @@ function getRelease(version) { return null; } +export async function normalizeMinecraftVersion(name) { + let release = getRelease(name); + return normalizeVersion(name, release); +} + function normalizeVersion(name, release) { if (release == null || name == release) { let ret = normalizeSpecialVersion(name); if (ret != null) { return ret; } - return normalizeOldVersion(name); + return normalizeReleaseOrOldVersion(name); } - + + let normalizedRelease = normalizeReleaseOrOldVersion(release); + // timestamps distinguish between re-uploads of the same version + let timestamp = null; + + let ret = []; + ret.push(normalizedRelease); + ret.push('-'); + let matches; - - if ((matches = name.match(EXPERIMENTAL_PATTERN)) != null) { - return release + "-Experimental." + matches[1]; + + if ((matches = matchExact(name, RELEASE_PATTERN)) != null) { // 1.6, 1.16.5, 1.16+131620 + timestamp = matches[4]; + + // remove - separator + ret.pop(); + } else if ((matches = matchExact(name, EXPERIMENTAL_PATTERN)) != null) { // 1.18 Experimental Snapshot 1, 1.18 experimental snapshot 2, 1.18-exp3 + ret.push("Experimental."); + ret.push(matches[1]); // exp build nr } else if (name.startsWith(release)) { - matches = name.match(RELEASE_CANDIDATE_PATTERN); - - if (matches != null) { + if ((matches = matchExact(name, RELEASE_CANDIDATE_PATTERN)) != null) { // ... RC1, ... Release Candidate 2, ...-rc3, ...-rc4-1234 let rcBuild = matches[1]; + timestamp = matches[2]; - // This is a hack to fake 1.16's new release candidates to follow on from the 8 pre releases. - if (release == "1.16") { - let build = rcBuild; - rcBuild = 8 + build; + // 1.0.0 release candidates are simply known as eg. 'Minecraft RC1' in the jar + if (release == "Minecraft") { + ret[0] = ret[0].replace(0, "Minecraft".length, "1.0.0"); } - name = "rc." + rcBuild; - } else { - matches = name.match(PRE_RELEASE_PATTERN); + ret.push("rc."); + ret.push(rcBuild); + } else if ((matches = matchExact(name, PRE_RELEASE_PATTERN)) != null) { // ... Prerelease, ... Pre-release 1, ... Pre-Release 2, ...-pre3, ...-pre4-1234 + // Pre-releases in Beta need special treatment + let releaseMatches = matchExact(release, BETA_PATTERN); // Beta 1.2, b1.3-1731, Beta v1.5_02, b1.8.1 + + if (releaseMatches != null) { + // Beta versions with pre-releases end with .r after normalization + // for pre-releases, the pre-release nr is put in that place instead + if (normalizedRelease.charAt(normalizedRelease.length - 1) != 'r') { + throw new Error("improperly normalized release " + release + " to " + normalizedRelease + " for pre-release " + name); + } - if (matches != null) { - let legacyVersion = release.match("\d+\.(\d+).*")[1] <= 16; - // Mark pre-releases as 'beta' versions, except for version 1.16 and before, where they are 'rc' - if (legacyVersion) { - name = "rc." + matches[1]; - } else { - name = "beta." + matches[1]; + let prBuild = matches[1]; + timestamp = matches[2]; + + // pre-release 1 is sometimes just called 'Prerelease' + if (prBuild == null) { + prBuild = "1"; } + + // remove the - separator and replace the final r + // of the normalized release version + ret.pop(); + ret[0] = ret[0].substring(0, ret[0].length - 1); + ret.push(prBuild); } else { - let ret = normalizeSpecialVersion(name); - if (ret != null) { - return ret; + let prBuild = matches[1]; + timestamp = matches[2]; + + if (prBuild == null) { + // between 1.2 and 1.7, regular release ids were used for + // pre-releases, yet omniarchive marks these versions with + // a 'pre' identifier + // we won't do that here because it would be inconsistent + // with the snapshot release targets + + releaseMatches = matchExact(release, RELEASE_PATTERN); // 1.6, 1.16.5, 1.16+131620 + + if (releaseMatches == null) { + throw new Error("version " + name + " is a pre-release targeting neither a Beta version, nor a release version?!"); + } + + let minor = parseInt(releaseMatches[2]); + let patch = (releaseMatches[3] == null) + ? 0 // use 0 if no patch version is given (1.7 -> 1.7.0) + : parseInt(releaseMatches[3]); + + let showAsRelease = (minor == 2 && patch == 0) // 1.2 + || (minor == 3 && patch == 0) // 1.3 + || (minor == 4 && (patch == 0 || patch == 1 || patch == 3)) // 1.4, 1.4.1, 1.4.3 + || (minor == 6 && (patch == 0 || patch == 3)) // 1.6, 1.6.3 + || (minor == 7 && (patch == 0 || patch == 1 || patch == 3)); // 1.7, 1.7.1, 1.7.3 + + if (showAsRelease) { + // remove the - separator + ret.pop(); + } else { + // then there are also actual pre-releases that use regular + // release ids that were later re-used for the actual release + // e.g. 1.6.3-pre and 1.7.4-pre + + // use 'rc' to be consistent with other pre-releases + // for versions older than 1.16 + ret.push("rc"); + } + } else { + // Mark pre-releases as 'beta' versions, except for version 1.16 and before, where they are 'rc' + ret.push("rc."); + ret.push(prBuild); } - return normalizeOldVersion(name); } + } else if ((matches = matchExact(name, TEST_BUILD_PATTERN)) != null) { // ... Test Build 1, ...-tb2, ...-tb3-1234 + // Test builds in Beta need special treatment + let releaseMatches = matchExact(release, BETA_PATTERN); // Beta 1.2, b1.3-1731, Beta v1.5_02, b1.8.1 + + if (releaseMatches != null) { + // Beta versions with test builds end with .r after normalization + // for test builds, the build nr is put in that place instead + if (normalizedRelease.charAt(normalizedRelease.length - 1) != 'r') { + throw new Error("improperly normalized release " + release + " to " + normalizedRelease + " for test build " + name); + } + + let tbBuild = matches[1]; + timestamp = matches[2]; + + // remove the - separator and replace the final r + // of the normalized release version + ret.pop(); + ret[0] = ret[0].substring(0, ret[0].length - 1); + ret.push(tbBuild); + } else { + let tbBuild = matches[1]; + timestamp = matches[2]; + + ret.push("test."); + ret.push(tbBuild); + } + } else { + let normalized = normalizeSpecialVersion(name); + if (normalized != null) return normalized; } - } else if ((matches = name.match(SNAPSHOT_PATTERN)) != null) { - name = "alpha." + matches[1] + "." + matches[2] + "." + matches[3]; + } else if ((matches = matchExact(name, SNAPSHOT_PATTERN)) != null) { // Snapshot 16w02a, 20w13b, 22w18c-1234 + timestamp = matches[4]; + + ret.push("alpha."); + ret.push(matches[1]); // year + ret.push('.'); + ret.push(matches[2]); // week + ret.push('.'); + ret.push(matches[3]); // patch } else { // Try short-circuiting special versions which are complete on their own - let ret = normalizeSpecialVersion(name); - if (ret != null) { - return ret; - } - return normalizeOldVersion(name); + let normalized = normalizeSpecialVersion(name); + if (normalized != null) return normalized; + + ret.push(normalizeVersion(name)); + } + + // add timestamp as extra build information + if (timestamp != null) { + ret.push('+'); + ret.push(timestamp); } - return release + "-" + name; + return ret.join(""); } -function normalizeOldVersion(version) { +function normalizeReleaseOrOldVersion(version) { + // timestamps distinguish between re-uploads of the same version + let timestamp = null; + // omniarchive marks some versions with a -launcher suffix + // and there is one classic version marked -renew + let suffix = null; + + let prepl = []; + // old version normalization scheme // do this before the main part of normalization as we can get crazy strings like "Indev 0.31 12345678-9" let matches; - if ((matches = version.match(BETA_PATTERN)) != null) { // beta 1.2.3: 1.0.0-beta.2 - version = "1.0.0-beta." + matches[1]; - } else if ((matches = version.match(ALPHA_PATTERN)) != null) { // alpha 1.2.3: 1.0.0-alpha.2.3 - version = "1.0.0-alpha." + matches[1]; - } else if ((matches = version.match(INDEV_PATTERN)) != null) { // indev/infdev 12345678: 0.31.12345678 - version = "0.31." + matches[1]; - } else if (version.startsWith("c0.")) { // classic: unchanged, except remove prefix - version = version.substring(1); - } else if (version.startsWith("rd-")) { // pre-classic - version = version.substring("rd-".length); - if ("20090515" === version) version = "150000"; // account for a weird exception to the pre-classic versioning scheme - version = "0.0.0-rd." + version; + if ((matches = matchExact(version, BETA_PATTERN)) != null) { // Beta 1.2, b1.3-1731, Beta v1.5_02, b1.8.1 + let trail = matches[5]; + timestamp = matches[6]; + suffix = matches[7]; + + prepl.push("1.0.0-beta."); + prepl.push(matches[1]); + + // there are pre-releases in Beta too, and they + // are annoying to normalize + // the solution we use is to use the pre-release + // numbers as patch numbers, then for the 'release' + // version, use some text - the letter 'r' - instead + // to ensure it is sorted after the pre-releases + // for this to work, a minor number must be present + // but it is only necessary for b1.6, b1.8 and b1.9 + // the minor version is also set to 0 to ensure + // following minor versions are sorted after + if (matches[3] == null && matches[4] == null) { + let maj = parseInt(matches[2]); + + if (maj == 6 || maj == 8 || maj == 9) { + prepl.push(".0.r"); // 'r' for 'release' + } + } + + // in the launcher manifest, some Beta versions have + // trailing alphabetic chars + if (trail != null) { + // if no minor version is given, set it to 0 to + // ensure this version is sorted before subsequent + // minor updates + if (matches[3] == null && matches[4] == null) { + prepl.push(".0"); + } + + prepl.push('.').push(trail); + } + } else if ((matches = matchExact(version, ALPHA_PATTERN)) != null) { // Alpha v1.0.1, Alpha 1.0.1_01, a1.1.0-131933, a1.2.3_05, Alpha 0.1.0, a0.2.8 + let trail = matches[2]; + timestamp = matches[3]; + suffix = matches[4]; + + prepl.push("1.0.0-alpha."); + prepl.push(matches[1]); + + // in the launcher manifest, some Alpha versions have + // trailing alphabetic chars + if (trail != null) { + prepl.push('.').push(trail); + } + } else if ((matches = matchExact(version, INDEV_PATTERN)) != null) { // Indev 0.31 200100110, in-20100124-2310, Infdev 0.31 20100227-1433, inf-20100611 + let date = matches[1]; + // multiple releases could occur on the same day! + let time = matches[2]; + + prepl.push("0.31."); + prepl.push(date); + if (time != null) prepl.push('-').push(time); + } else if ((matches = matchExact(version, EARLY_CLASSIC_PATTERN)) != null // c0.0.11a, c0.0.17a-2014, 0.0.18a_02 + || (matches = matchExact(version, LATE_CLASSIC_PATTERN)) != null) { // c0.24_st, 0.24_st_03, 0.25_st-1658, c0.25_05_st, 0.29, c0.30-s, 0.30-c-renew + let late = LATE_CLASSIC_PATTERN.matcher(version).matches(); + + let minor = matches[1]; + let patch = matches[2]; + let trail = late ? matches[4] : null; + let type = late ? matches[5] : null; + timestamp = matches.group(late ? 6 : 3); + suffix = matches.group(late ? 7 : 4); + + // in late classic, sometimes the patch number appears before + // the survival test identifier (_st), and sometimes after it + if (late && patch == null) { + patch = matches[3]; + } + + prepl.push("0."); + prepl.push(minor); + if (patch != null) prepl.push('.').push(patch); + // in the launcher manifest, some Classic versions have trailing alphabetic chars + if (trail != null) prepl.push('-').push(trail); + // in the Omniarchive manifest, some classic versions releases for creative and survival + if (type != null) prepl.push('-').push(type); + } else if ((matches = matchExact(version, CLASSIC_SERVER_PATTERN)) != null) { + let release = matches[1]; + timestamp = matches[2]; + + prepl.push("0."); + prepl.push(release); + } else if ((matches = matchExact(version, PRE_CLASSIC_PATTERN)) != null) { // rd-132211 + let build = matches[1]; + suffix = matches[2]; + + // account for a weird exception to the pre-classic versioning scheme + if ("20090515".equals(build)) { + build = "150000"; + } + + prepl.push("0.0.0-rd."); + prepl.push(build); + } else { + prepl.push(version); } - let ret = ""; + let prep = prepl.join(""); + let retl = []; let lastIsDigit = false; let lastIsLeadingZero = false; let lastIsSeparator = false; - for (let i = 0, max = version.length; i < max; i++) { - let c = version.charAt(i); + for (let i = 0, max = prep.length; i < max; i++) { + let c = prep.charAt(i); if (c >= '0' && c <= '9') { if (i > 0 && !lastIsDigit && !lastIsSeparator) { // no separator between non-number and number, add one - ret += "."; + retl.push('.'); } else if (lastIsDigit && lastIsLeadingZero) { // leading zero in output -> strip - ret = ret.substring(0, ret.length - 1); + retl.pop(); } lastIsLeadingZero = c == '0' && (!lastIsDigit || lastIsLeadingZero); // leading or continued leading zero(es) @@ -248,120 +493,172 @@ function normalizeOldVersion(version) { lastIsSeparator = true; lastIsDigit = false; } else { // keep other characters (alpha) - if (lastIsDigit) ret += "."; // no separator between number and non-number, add one + if (lastIsDigit) retl.push('.'); // no separator between number and non-number, add one lastIsSeparator = false; lastIsDigit = false; } - ret += c; + retl.push(c); } + let ret = retl.join(""); + + // strip leading and trailing . + let start = 0; while (start < ret.length && ret.charAt(start) == '.') start++; let end = ret.length; while (end > start && ret.charAt(end - 1) == '.') end--; - return ret.substring(start, end); -} - -function normalizeSpecialVersion(version) { - switch (version) { - case "13w12~": - // A pair of debug snapshots immediately before 1.5.1-pre - return "1.5.1-alpha.13.12.a"; - - case "15w14a": - // The Love and Hugs Update, forked from 1.8.3 - return "1.8.4-alpha.15.14.a+loveandhugs"; - - case "1.RV-Pre1": - // The Trendy Update, probably forked from 1.9.2 (although the protocol/data versions immediately follow 1.9.1-pre3) - return "1.9.2-rv+trendy"; - - case "3D Shareware v1.34": - // Minecraft 3D, forked from 19w13b - return "1.14-alpha.19.13.shareware"; - - case "20w14~": - // The Ultimate Content update, forked from 20w13b - return "1.16-alpha.20.13.inf"; // Not to be confused with the actual 20w14a - - case "1.14.3 - Combat Test": - // The first Combat Test, forked from 1.14.3 Pre-Release 4 - return "1.14.3-rc.4.combat.1"; - - case "Combat Test 2": - // The second Combat Test, forked from 1.14.4 - return "1.14.5-combat.2"; - - case "Combat Test 3": - // The third Combat Test, forked from 1.14.4 - return "1.14.5-combat.3"; - - case "Combat Test 4": - // The fourth Combat Test, forked from 1.15 Pre-release 3 - return "1.15-rc.3.combat.4"; - - case "Combat Test 5": - // The fifth Combat Test, forked from 1.15.2 Pre-release 2 - return "1.15.2-rc.2.combat.5"; - - case "Combat Test 6": - // The sixth Combat Test, forked from 1.16.2 Pre-release 3 - return "1.16.2-beta.3.combat.6"; - - case "Combat Test 7": - // Private testing Combat Test 7, forked from 1.16.2 - return "1.16.3-combat.7"; + ret = ret.substring(start, end); + retl = [] - case "1.16_combat-2": - // Private testing Combat Test 7b, forked from 1.16.2 - return "1.16.3-combat.7.b"; + // add timestamp and suffix as extra build information + if (timestamp != null || suffix != null) { + retl.push('+'); - case "1.16_combat-3": - // The seventh Combat Test 7c, forked from 1.16.2 - return "1.16.3-combat.7.c"; + if (timestamp != null) { + retl.push(timestamp); + if (suffix != null) retl.push('.'); + } - case "1.16_combat-4": - // Private testing Combat Test 8(a?), forked from 1.16.2 - return "1.16.3-combat.8"; + if (suffix != null) { + retl.push(suffix); + } + } - case "1.16_combat-5": - // The eighth Combat Test 8b, forked from 1.16.2 - return "1.16.3-combat.8.b"; + return ret + retl.join(""); +} - case "1.16_combat-6": - // The ninth Combat Test 8c, forked from 1.16.2 - return "1.16.3-combat.8.c"; +function normalizeSpecialVersion(version) { + // first attempt to normalize the version as-is + let normalized = normalizeSpecialVersionBase(version); - case "2point0_red": - // 2.0 update version red, forked from 1.5.1 - return "1.5.2-red"; + // only if that yields no result, check if it's a re-upload from Omniarchive + if (normalized == null) { + // timestamps distinguish between re-uploads of the same version + let timestamp = null; - case "2point0_purple": - // 2.0 update version purple, forked from 1.5.1 - return "1.5.2-purple"; + let matches = matchExact(version, TIMESTAMP_PATTERN); - case "2point0_blue": - // 2.0 update version blue, forked from 1.5.1 - return "1.5.2-blue"; + if (matches != null) { + version = matches[1]; + timestamp = matches[2]; + } - case "23w13a_or_b": - // Minecraft 23w13a_or_b, forked from 23w13a - return "1.20-alpha.23.13.ab"; + normalized = normalizeSpecialVersionBase(version); - case "24w14potato": - // Minecraft 24w14potato, forked from 24w12a - return "1.20.5-alpha.24.12.potato"; + // add timestamp as extra build information + if (normalized != null && timestamp != null) { + normalized += "+" + timestamp; + } + } - case "25w14craftmine": - // Minecraft 25w14craftmine, forked from 1.21.5 - return "1.21.6-alpha.25.14.craftmine"; + return normalized; +} - default: - return null; //Don't recognise the version +function normalizeSpecialVersionBase(version) { + switch (version) { + case "b1.2_02-dev": + // a dev version of b1.2 + return "1.0.0-beta.2.dev"; + case "b1.3-demo": + // a demo version of b1.3 given to PC Gamer magazine + return "1.0.0-beta.3.demo"; + case "b1.6-trailer": + case "b1.6-pre-trailer": + // pre-release version used for the Beta 1.6 trailer + return "1.0.0-beta.6.0.0"; // sort it before the test builds + + case "13w02a-whitetexturefix": + // a fork from 13w02a to attempt to fix a white texture glitch + return "1.5-alpha.13.2.a.whitetexturefix"; + case "13w04a-whitelinefix": + // a fork from 13w04a to attempt to fix a white line glitch + return "1.5-alpha.13.4.a.whitelinefix"; + case "1.5-whitelinefix": + case "1.5-pre-whitelinefix": + // a pre-release for 1.5 to attempt to fix a white line glitch + return "1.5-rc.whitelinefix"; + case "13w12~": + // A pair of debug snapshots immediately before 1.5.1-pre + return "1.5.1-alpha.13.12.a"; + + case "2.0": + // 2.0 update version as known in the jar, forked from 1.5.1 + return "1.5.2-2.0"; + + case "2.0-preview": + // a preview for the 2.0 april fools version, similar to blue + return "1.5.2-2.0+preview" + + case "2.0-red": + case "2point0_red": + case "af-2013-red": + // 2.0 update version red, forked from 1.5.1 + return "1.5.2-2.0+red"; + + case "2.0-purple": + case "2point0_purple": + case "af-2013-purple": + // 2.0 update version purple, forked from 1.5.1 + return "1.5.2-2.0+purple"; + + case "2.0-blue": + case "2point0_blue": + case "af-2013-blue": + // 2.0 update version blue, forked from 1.5.1 + return "1.5.2-2.0+blue"; + + case "15w14a": + case "af-2015": + // The Love and Hugs Update, forked from 1.8.3 + return "1.8.4-alpha.15.14.a+loveandhugs"; + + case "1.RV-Pre1": + case "af-2016": + // The Trendy Update, probably forked from 1.9.2 (although the protocol/data versions immediately follow 1.9.1-pre3) + return "1.9.2-rv+trendy"; + + case "3D Shareware v1.34": + case "af-2019": + // Minecraft 3D, forked from 19w13b + return "1.14-alpha.19.13.shareware"; + + case "1.14_combat-212796": + case "1.14.3 - Combat Test": + case "combat1": + // The first Combat Test, forked from 1.14.3 Pre-Release 4 + return "1.14.3-rc.4.combat.1"; + + case "1.14_combat-0": + case "Combat Test 2": + case "combat2": + // The second Combat Test, forked from 1.14.4 + return "1.14.5-combat.2"; + + case "1.14_combat-3": + case "Combat Test 3": + case "combat3": + // The third Combat Test, forked from 1.14.4 + return "1.14.5-combat.3"; + + case "1.15_combat-1": + case "Combat Test 4": + case "combat4": + // The fourth Combat Test, forked from 1.15 Pre-release 3 + return "1.15-rc.3.combat.4"; + + case "1.15_combat-6": + case "Combat Test 5": + case "combat5": + // The fifth Combat Test, forked from 1.15.2 Pre-release 2 + return "1.15.2-rc.2.combat.5"; + + default: + return null; //Don't recognise the version } } From d8c088716cb9e282c42f0904c234fbc2dee32f3d Mon Sep 17 00:00:00 2001 From: Space Walker Date: Fri, 6 Feb 2026 19:01:15 +0100 Subject: [PATCH 2/6] semver fixes --- public/minecraft_semver.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/public/minecraft_semver.js b/public/minecraft_semver.js index 008a9c9..7f2858d 100644 --- a/public/minecraft_semver.js +++ b/public/minecraft_semver.js @@ -394,7 +394,8 @@ function normalizeReleaseOrOldVersion(version) { prepl.push(".0"); } - prepl.push('.').push(trail); + prepl.push('.'); + prepl.push(trail); } } else if ((matches = matchExact(version, ALPHA_PATTERN)) != null) { // Alpha v1.0.1, Alpha 1.0.1_01, a1.1.0-131933, a1.2.3_05, Alpha 0.1.0, a0.2.8 let trail = matches[2]; @@ -407,7 +408,8 @@ function normalizeReleaseOrOldVersion(version) { // in the launcher manifest, some Alpha versions have // trailing alphabetic chars if (trail != null) { - prepl.push('.').push(trail); + prepl.push('.'); + prepl.push(trail); } } else if ((matches = matchExact(version, INDEV_PATTERN)) != null) { // Indev 0.31 200100110, in-20100124-2310, Infdev 0.31 20100227-1433, inf-20100611 let date = matches[1]; @@ -416,17 +418,20 @@ function normalizeReleaseOrOldVersion(version) { prepl.push("0.31."); prepl.push(date); - if (time != null) prepl.push('-').push(time); + if (time != null) { + prepl.push('-'); + prepl.push(time); + } } else if ((matches = matchExact(version, EARLY_CLASSIC_PATTERN)) != null // c0.0.11a, c0.0.17a-2014, 0.0.18a_02 || (matches = matchExact(version, LATE_CLASSIC_PATTERN)) != null) { // c0.24_st, 0.24_st_03, 0.25_st-1658, c0.25_05_st, 0.29, c0.30-s, 0.30-c-renew - let late = LATE_CLASSIC_PATTERN.matcher(version).matches(); + let late = matchExact(version, LATE_CLASSIC_PATTERN) != null; let minor = matches[1]; let patch = matches[2]; let trail = late ? matches[4] : null; let type = late ? matches[5] : null; - timestamp = matches.group(late ? 6 : 3); - suffix = matches.group(late ? 7 : 4); + timestamp = matches[late ? 6 : 3]; + suffix = matches[late ? 7 : 4]; // in late classic, sometimes the patch number appears before // the survival test identifier (_st), and sometimes after it @@ -436,11 +441,20 @@ function normalizeReleaseOrOldVersion(version) { prepl.push("0."); prepl.push(minor); - if (patch != null) prepl.push('.').push(patch); + if (patch != null) { + prepl.push('.'); + prepl.push(patch); + } // in the launcher manifest, some Classic versions have trailing alphabetic chars - if (trail != null) prepl.push('-').push(trail); + if (trail != null) { + prepl.push('-'); + prepl.push(trail); + } // in the Omniarchive manifest, some classic versions releases for creative and survival - if (type != null) prepl.push('-').push(type); + if (type != null) { + prepl.push('-'); + prepl.push(type); + } } else if ((matches = matchExact(version, CLASSIC_SERVER_PATTERN)) != null) { let release = matches[1]; timestamp = matches[2]; @@ -452,7 +466,7 @@ function normalizeReleaseOrOldVersion(version) { suffix = matches[2]; // account for a weird exception to the pre-classic versioning scheme - if ("20090515".equals(build)) { + if (build == "20090515") { build = "150000"; } From 0aff87dcba273c0c93163ea643cfba18ee81475f Mon Sep 17 00:00:00 2001 From: Space Walker Date: Fri, 6 Feb 2026 19:08:44 +0100 Subject: [PATCH 3/6] redesign develop page --- public/develop.js | 739 ++++++++++++++++++++++++++++--------- public/meta_maven_utils.js | 117 ++++-- src/pages/develop.astro | 173 ++++++--- src/pages/index.astro | 30 +- src/styles/global.css | 22 +- tailwind.config.mjs | 2 + 6 files changed, 812 insertions(+), 271 deletions(-) diff --git a/public/develop.js b/public/develop.js index 2bdadd8..53217df 100644 --- a/public/develop.js +++ b/public/develop.js @@ -1,242 +1,647 @@ import { - getMinecraftStableVersions, getMinecraftVersions, - getLatestLoader, - getLatestOsl, - getLatestFeatherBuild, - getLatestRavenBuild, - getLatestSparrowBuild, - getLatestNestsBuild + getLatestLoaderVersion, + getLatestOslVersion, + getLatestFeatherBuilds, + getLatestRavenBuilds, + getLatestSparrowBuilds, + getLatestNestsBuilds, + getVersionDetails, + isSharedVersioning } from "./meta_maven_utils.js"; import { normalizeMinecraftVersion } from "./minecraft_semver.js"; (async () => { - const genSelectorRadios = { + const FABRIC_LOOM = "net.fabricmc.fabric-loom-remap" + const QUILT_LOOM = "org.quiltmc.loom.remap" + const LOOM_VERSION = "1.15-SNAPSHOT" + const PLOCEUS = "ploceus" + + const FABRIC_LOADER = "net.fabricmc:fabric-loader" + const QUILT_LOADER = "org.quiltmc:quilt-loader" + + const minecraftVersionSelector = document.getElementById("mc-version"); + const minecraftVersionList = document.getElementById("version-list"); + const calamusGenSelector = document.getElementById("calamus-gen-selectors"); + const calamusGenSelectorRadios = { gen1: document.getElementById("generation-gen1"), gen2: document.getElementById("generation-gen2") } - - const gen = Object.entries(genSelectorRadios).find(([_, button]) => button.checked)[0]; - let minecraftStableVersions = await getMinecraftStableVersions(gen); - let minecraftAllVersions = await getMinecraftVersions(gen); - - let possibleVersions; - - const loaderSelectorRadios = { + const modLoaderSelector = document.getElementById("mod-loader-selectors"); + const modLoaderSelectorRadios = { fabric: document.getElementById("mod-loader-fabric"), quilt: document.getElementById("mod-loader-quilt") } - const versionSelectorInput = document.getElementById("mc-version"); - const versionListElement = document.getElementById("version-list"); - const allowSnapshotsCheck = document.getElementById("allow-snapshots"); - const featherGenSelector = document.getElementById("calamus-gen-selectors"); + + let minecraftVersions = []; - function setExtraMsg(message) { - document.getElementById("dependencies-extra-message").innerText = message; + function selectedMinecraftVersion() { + return minecraftVersionSelector.value; } - function getExtraMsg() { - return document.getElementById("dependencies-extra-message").innerText; + function selectedCalamusGeneration() { + return Object.entries(calamusGenSelectorRadios).find(([_, button]) => button.checked)[0]; } - function addExtraMsg(message) { - setExtraMsg(getExtraMsg() + (getExtraMsg() !== "" ? "\n" : "") + message) + function selectedModLoader() { + return Object.entries(modLoaderSelectorRadios).find(([_, button]) => button.checked)[0]; } - async function updateOrnitheDependencies() { - if (possibleVersions.some(version => versionSelectorInput.value === version)) { - const minecraftVersion = versionSelectorInput.value; - setExtraMsg(""); + async function updateDisplayedElements() { + const minecraftVersion = selectedMinecraftVersion(); + const intermediaryGen = selectedCalamusGeneration(); + const modLoader = selectedModLoader(); + + if (minecraftVersions.includes(minecraftVersion)) { + const versionDetails = await getVersionDetails(intermediaryGen, minecraftVersion); + const loaderVersion = await getLatestLoaderVersion(modLoader); + const featherBuilds = await getLatestFeatherBuilds(intermediaryGen, versionDetails); + const ravenBuilds = await getLatestRavenBuilds(versionDetails); + const sparrowBuilds = await getLatestSparrowBuilds(versionDetails); + const nestsBuilds = await getLatestNestsBuilds(versionDetails); + const oslVersion = await getLatestOslVersion(intermediaryGen); + + const sharedVersioning = isSharedVersioning(versionDetails); + + // wait for requests to finish to avoid flicker + hideElements(); + wipeElementContents(); + + showElement("build.gradle"); + showElement("gradle.properties"); + + const singleProject = (intermediaryGen != "gen1" || !sharedVersioning || versionDetails.sharedMappings || !versionDetails.client || !versionDetails.server); + + if (singleProject) { + await displayBuildScript(versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + await displayProjectProperties(versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + } else { + await displayBuildScriptForGen1Split("root", versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + await displayProjectPropertiesForGen1Split("root", versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + + if (versionDetails.client) { + showElement("client.build.gradle"); + showElement("client.gradle.properties"); + + await displayBuildScriptForGen1Split("client", versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + await displayProjectPropertiesForGen1Split("client", versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + } + if (versionDetails.server) { + showElement("server.build.gradle"); + showElement("server.gradle.properties"); + + await displayBuildScriptForGen1Split("server", versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + await displayProjectPropertiesForGen1Split("server", versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + } + } - setDependencyBlockLines("ornithe-dependencies", await constructOrnitheDependenciesMessage(minecraftVersion)); + switch (modLoader) { + case "fabric": + showElement("fabric.mod.json"); + await displayFabricModMetadata(versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + break; + case "quilt": + showElement("quilt.mod.json"); + await displayQuiltModMetadata(versionDetails, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion); + break; + default: + throw new Error("unknown mod loader " + modLoader); + } + } else { + hideElements(); + } + } - const gen = Object.entries(genSelectorRadios).find(([_, button]) => button.checked)[0]; + async function displayBuildScript(minecraftVersion, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion) { + const sharedVersioning = isSharedVersioning(minecraftVersion); + const elementId = "build.gradle.content"; - const mergedFeatherBuild = await getLatestFeatherBuild(gen, minecraftVersion); + const plugins = [ + "\tid 'java'" + ]; + + switch (modLoader) { + case "fabric": + plugins.push(`\tid '${FABRIC_LOOM}' version '${LOOM_VERSION}'`); + break; + case "quilt": + plugins.push(`\tid '${QUILT_LOOM}' version '${LOOM_VERSION}'`); + break; + default: + throw new Error("unknown mod loader " + modLoader); + } + + plugins.push(`\tid '${PLOCEUS}' version '${LOOM_VERSION}'`); - if (mergedFeatherBuild != null) { - hideDependencyBlock("ornithe-dependencies-client"); - hideDependencyBlock("ornithe-dependencies-server"); + if (plugins) { + plugins.unshift("plugins {"); + plugins.push("}"); + + displayCodeBlockLines(elementId, plugins); + } + + const loomCommands = []; + const ploceusCommands = [ + `\tsetIntermediaryGeneration(${intermediaryGen.substring(3)})` + ] + + if (!minecraftVersion.client || !minecraftVersion.server) { + if (minecraftVersion.client) { + loomCommands.push("\tclientOnlyMinecraftJar()"); + if (intermediaryGen == "gen1") { + ploceusCommands.push("\tclientOnlyMappings()"); + } + } + if (minecraftVersion.server) { + loomCommands.push("\tserverOnlyMinecraftJar()"); + if (intermediaryGen == "gen1") { + ploceusCommands.push("\tserverOnlyMappings()"); + } } + } + + if (loomCommands) { + loomCommands.unshift("loom {"); + loomCommands.unshift(""); // empty line between blocks + loomCommands.push("}"); + + displayCodeBlockLines(elementId, loomCommands); + } + if (ploceusCommands) { + ploceusCommands.unshift("ploceus {"); + ploceusCommands.unshift(""); // empty line between blocks + ploceusCommands.push("}"); + + displayCodeBlockLines(elementId, ploceusCommands); + } + + const dependencies = [ + `\tminecraft "com.mojang:minecraft:\${project.minecraft_version}"`, + ]; + switch (modLoader) { + case "fabric": + dependencies.push(`\tmodImplementation "${FABRIC_LOADER}:\${project.loader_version}"`); + break; + case "quilt": + dependencies.push(`\tmodImplementation "${QUILT_LOADER}:\${project.loader_version}"`); + break; + default: + throw new Error("unknown mod loader " + modLoader); + } - if (mergedFeatherBuild != null) { - // for gen1, Feather builds for Minecraft 1.3+ are for all sides - // for gen2, Feather builds for all Minecraft versions are for all sides - appendDependencyBlockLines("ornithe-dependencies", await getOrnitheDependenciesForMerged(gen, minecraftVersion, mergedFeatherBuild)); + dependencies.push(""); + dependencies.push(`\tmappings ploceus.featherMappings(project.feather_build)`); + + if (ravenBuilds.merged != null) { + if (sharedVersioning || intermediaryGen != "gen1") { + dependencies.push(`\texceptions ploceus.raven(project.raven_build)`); + } else { + dependencies.push(`\texceptions ploceus.raven(project.raven_build, '${minecraftVersion.client ? "client" : "server"}')`); + } + } else { + if (ravenBuilds.client != null) { + dependencies.push(`\tclientExceptions ploceus.raven(project.client_raven_build, 'client')`); + } + if (ravenBuilds.server != null) { + dependencies.push(`\tserverExceptions ploceus.raven(project.server_raven_build, 'server')`); + } + } - // Raven, Sparrow, Nests builds for Minecraft versions between b1.0 and 1.3 - // are for one side only, regardless of the intermediary gen - appendDependencyBlockLines("ornithe-dependencies", await getOrnitheDependenciesForSplit(gen, minecraftVersion, "client", null)); - appendDependencyBlockLines("ornithe-dependencies", await getOrnitheDependenciesForSplit(gen, minecraftVersion, "server", null)); + if (sparrowBuilds.merged != null) { + if (sharedVersioning || intermediaryGen != "gen1") { + dependencies.push(`\tsignatures ploceus.sparrow(project.sparrow_build)`); } else { - // this block is only reached for gen1 for Minecraft versions older than 1.3 - // where Feather builds are for one side only - const clientFeatherBuild = await getLatestFeatherBuild(gen, `${minecraftVersion}-client`); - const serverFeatherBuild = await getLatestFeatherBuild(gen, `${minecraftVersion}-server`); - setDependencyBlockLines("ornithe-dependencies-client", await getOrnitheDependenciesForSplit(gen, minecraftVersion, "client", clientFeatherBuild)); - setDependencyBlockLines("ornithe-dependencies-server", await getOrnitheDependenciesForSplit(gen, minecraftVersion, "server", serverFeatherBuild)); - - // Raven, Sparrow, Nests builds for Minecraft versions older than b1.0 - // or newer than 1.3 are for all sides, regardless of the intermediary gen - appendDependencyBlockLines("ornithe-dependencies", await getOrnitheDependenciesForMerged(gen, minecraftVersion, null)); - } - - if (gen === "gen2") { - addExtraMsg("Make sure to use the \"gen2\" mod template!"); - } else if (mergedFeatherBuild != null) { - addExtraMsg("Make sure to use the \"merged\" mod template for this Minecraft version!"); + dependencies.push(`\tsignatures ploceus.sparrow(project.sparrow_build, '${minecraftVersion.client ? "client" : "server"}')`); + } + } else { + if (sparrowBuilds.client != null) { + dependencies.push(`\tclientSignatures ploceus.sparrow(project.client_sparrow_build, 'client')`); + } + if (sparrowBuilds.server != null) { + dependencies.push(`\tserverSignatures ploceus.sparrow(project.server_sparrow_build, 'server')`); + } + } + + if (nestsBuilds.merged != null) { + if (sharedVersioning || intermediaryGen != "gen1") { + dependencies.push(`\tnests ploceus.nests(project.nests_build)`); } else { - addExtraMsg("Make sure to use the \"split\" mod template for this Minecraft version!"); + dependencies.push(`\tnests ploceus.nests(project.nests_build, '${minecraftVersion.client ? "client" : "server"}')`); } - setDependencyBlockLines("ornithe-dependencies-fmj", await getFmjDependenciesNote(minecraftVersion)); } else { - document.getElementById("ornithe-dependencies").innerText = "Please select a valid Minecraft version!"; - hideDependencyBlock("ornithe-dependencies-client"); - hideDependencyBlock("ornithe-dependencies-server"); - hideDependencyBlock("ornithe-dependencies-fmj"); - setExtraMsg(""); + if (nestsBuilds.client != null) { + dependencies.push(`\tclientNests ploceus.nests(project.client_nests_build, 'client')`); + } + if (nestsBuilds.server != null) { + dependencies.push(`\tserverNests ploceus.nests(project.server_nests_build, 'server')`); + } } - } - function appendDependencyBlockLines(blockId, lines) { - let elem = document.getElementById(blockId); - elem.innerText = (elem.innerText.split("\n").concat(lines)).join("\n"); - } + if (oslVersion != null) { + dependencies.push(""); + dependencies.push(`\tploceus.dependOsl(project.osl_version)`); + } - function setDependencyBlockLines(blockId, lines) { - let elem = document.getElementById(blockId); - elem.style = ""; - elem.innerText = lines.join("\n"); - } + if (dependencies) { + dependencies.unshift("dependencies {"); + dependencies.unshift(""); // empty line between blocks + dependencies.push("}"); - function hideDependencyBlock(blockId) { - let elem = document.getElementById(blockId); - elem.style = "display: none;" - elem.innerText = ""; + displayCodeBlockLines(elementId, dependencies); + } } - async function getFmjDependenciesNote(minecraftVersion) { - let version = await normalizeMinecraftVersion(minecraftVersion); + async function displayBuildScriptForGen1Split(project, minecraftVersion, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion) { + if (project == "root") { + const elementId = "build.gradle.content"; + + let plugins = [ + "\tid 'java'" + ]; + + switch (modLoader) { + case "fabric": + plugins.push(`\tid '${FABRIC_LOOM}' version '${LOOM_VERSION}' apply false`); + break; + case "quilt": + plugins.push(`\tid '${QUILT_LOOM}' version '${LOOM_VERSION}' apply false`); + break; + default: + throw new Error("unknown mod loader " + modLoader); + } - let text = ["### fabric.mod.json "]; - text.push(`"minecraft": "${version}"`); - return text; - } + plugins.push(`\tid '${PLOCEUS}' version '${LOOM_VERSION}' apply false`); - async function constructOrnitheDependenciesMessage(minecraftVersion) { - let lines = [ - "### /gradle.properties", - "# Dependencies" - ]; + if (plugins) { + plugins.unshift("plugins {"); + plugins.push("}"); + + displayCodeBlockLines(elementId, plugins); + } + + displayCodeBlockLines(elementId, [ + "", + "def configure(project) {" + ]); + + plugins = [ + "\t\tproject.apply plugin: 'java'" + ]; + + switch (modLoader) { + case "fabric": + plugins.push(`\t\tproject.apply plugin: '${FABRIC_LOOM}'`); + break; + case "quilt": + plugins.push(`\t\tproject.apply plugin: '${QUILT_LOOM}'`); + break; + default: + throw new Error("unknown mod loader " + modLoader); + } + + plugins.push(`\t\tproject.apply plugin: '${PLOCEUS}'`); + + if (plugins) { + plugins.unshift("\tproject.plugins {"); + plugins.push("\t}"); + + displayCodeBlockLines(elementId, plugins); + } + + const loomCommands = []; + const ploceusCommands = [ + `\t\tsetIntermediaryGeneration(${intermediaryGen.substring(3)})` + ] + + loomCommands.push("\t\tif (project.environment == 'client') {"); + loomCommands.push("\t\t\tclientOnlyMinecraftJar()"); + loomCommands.push("\t\t}"); + loomCommands.push("\t\tif (project.environment == 'server') {"); + loomCommands.push("\t\t\tserverOnlyMinecraftJar()"); + loomCommands.push("\t\t}"); + + ploceusCommands.push(""); + ploceusCommands.push("\t\tif (project.environment == 'client') {"); + ploceusCommands.push("\t\t\tclientOnlyMappings()"); + ploceusCommands.push("\t\t}"); + ploceusCommands.push("\t\tif (project.environment == 'server') {"); + ploceusCommands.push("\t\t\tserverOnlyMappings()"); + ploceusCommands.push("\t\t}"); + + if (loomCommands) { + loomCommands.unshift("\tproject.loom {"); + loomCommands.unshift(""); // empty line between blocks + loomCommands.push("\t}"); + + displayCodeBlockLines(elementId, loomCommands); + } + if (ploceusCommands) { + ploceusCommands.unshift("\tproject.ploceus {"); + ploceusCommands.unshift(""); // empty line between blocks + ploceusCommands.push("\t}"); + + displayCodeBlockLines(elementId, ploceusCommands); + } + + const dependencies = [ + `\t\tminecraft "com.mojang:minecraft:\${project.minecraft_version}"`, + ]; + + switch (modLoader) { + case "fabric": + dependencies.push(`\t\tmodImplementation "${FABRIC_LOADER}:\${project.loader_version}"`); + break; + case "quilt": + dependencies.push(`\t\tmodImplementation "${QUILT_LOADER}:\${project.loader_version}"`); + break; + default: + throw new Error("unknown mod loader " + modLoader); + } + + dependencies.push(""); + dependencies.push(`\t\tmappings project.ploceus.featherMappings(project.feather_build)`); + + if (ravenBuilds.client != null || ravenBuilds.server != null) { + dependencies.push(`\t\texceptions project.ploceus.raven(project.raven_build)`); + } - const loader = Object.entries(loaderSelectorRadios).find(([_, button]) => button.checked)[0]; + if (sparrowBuilds.client != null || sparrowBuilds.server != null) { + dependencies.push(`\t\tsignatures project.ploceus.sparrow(project.sparrow_build)`); + } - const loaderVersion = await getLatestLoader(loader); - const oslVersion = await getLatestOsl(); + if (nestsBuilds.client != null || nestsBuilds.server != null) { + dependencies.push(`\t\tnests project.ploceus.nests(project.nests_build)`); + } - lines.push( - `minecraft_version = ${minecraftVersion}`, - `loader_version = ${loaderVersion}`, - `osl_version = ${oslVersion}` - ); + if (oslVersion != null) { + dependencies.push(""); + dependencies.push(`\t\tproject.ploceus.dependOsl(project.osl_version)`); + } - return lines; + if (dependencies) { + dependencies.unshift("\tproject.dependencies {"); + dependencies.unshift(""); // empty line between blocks + dependencies.push("\t}"); + + displayCodeBlockLines(elementId, dependencies); + } + + displayCodeBlockLines(elementId, ["}"]); + } else { + const elementId = `${project}.build.gradle.content`; + + displayCodeBlockLines(elementId, ["configure(project)"]); + } } - async function getOrnitheDependenciesForMerged(gen, minecraftVersion, featherBuild) { + async function displayProjectProperties(minecraftVersion, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion) { + const sharedVersioning = isSharedVersioning(minecraftVersion); + const elementId = "gradle.properties.content"; + const lines = []; - const ravenBuild = await getLatestRavenBuild(minecraftVersion); - const sparrowBuild = await getLatestSparrowBuild(minecraftVersion); - const nestsBuild = await getLatestNestsBuild(minecraftVersion); - - if (featherBuild !== null) { - lines.push(`feather_build = ${featherBuild}`); + lines.push(`minecraft_version = ${minecraftVersion.id}`); + lines.push(`loader_version = ${loaderVersion}`); + lines.push(""); + if (sharedVersioning) { + lines.push(`feather_build = ${featherBuilds.merged}`); + } else { + lines.push(`feather_build = ${minecraftVersion.client ? featherBuilds.client : featherBuilds.server}`); } - if (ravenBuild !== null) { - lines.push(`raven_build = ${ravenBuild}`); + if (ravenBuilds.merged != null) { + lines.push(`raven_build = ${ravenBuilds.merged}`); + } else { + if (ravenBuilds.client != null) { + lines.push(`client_raven_build = ${ravenBuilds.client}`); + } + if (ravenBuilds.server != null) { + lines.push(`server_raven_build = ${ravenBuilds.server}`); + } } - if (sparrowBuild !== null) { - lines.push(`sparrow_build = ${sparrowBuild}`); + if (sparrowBuilds.merged != null) { + lines.push(`sparrow_build = ${sparrowBuilds.merged}`); + } else { + if (sparrowBuilds.client != null) { + lines.push(`client_sparrow_build = ${sparrowBuilds.client}`); + } + if (sparrowBuilds.server != null) { + lines.push(`server_sparrow_build = ${sparrowBuilds.server}`); + } } - if (nestsBuild !== null) { - lines.push(`nests_build = ${nestsBuild}`); + if (nestsBuilds.merged != null) { + lines.push(`nests_build = ${nestsBuilds.merged}`); + } else { + if (nestsBuilds.client != null) { + lines.push(`client_nests_build = ${nestsBuilds.client}`); + } + if (nestsBuilds.server != null) { + lines.push(`server_nests_build = ${nestsBuilds.server}`); + } + } + if (oslVersion != null) { + lines.push(""); + lines.push(`osl_version = ${oslVersion}`); } - return lines - } - async function getOrnitheDependenciesForSplit(gen, minecraftVersion, environment, featherBuild) { - const ravenBuild = await getLatestRavenBuild(`${minecraftVersion}-${environment}`); - const sparrowBuild = await getLatestSparrowBuild(`${minecraftVersion}-${environment}`); - const nestsBuild = await getLatestNestsBuild(`${minecraftVersion}-${environment}`); + if (lines) { + displayCodeBlockLines(elementId, lines); + } + } - if (featherBuild !== null || ravenBuild !== null || sparrowBuild !== null || nestsBuild !== null) { - let lines = []; - if (gen === "gen1") { - lines.push(`### /${environment}/gradle.properties`) - lines.push(`environment = ${environment}`) - } else { - lines.push("") + async function displayProjectPropertiesForGen1Split(project, minecraftVersion, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion) { + let elementId; + const lines = []; + + if (project == "root") { + elementId = "gradle.properties.content"; + + lines.push(`minecraft_version = ${minecraftVersion.id}`); + lines.push(`loader_version = ${loaderVersion}`); + if (oslVersion != null) { + lines.push(""); + lines.push(`osl_version = ${oslVersion}`); } - // the gen1 mod template uses a subproject structure - // similar properties can have the same name in different subprojects - const prefix = gen === "gen1" ? "" : `${environment}_`; - - if (featherBuild != null) { - lines.push(`feather_build = ${featherBuild}`); - } - if (ravenBuild !== null) { - lines.push(`${prefix}raven_build = ${ravenBuild}`); - } - if (sparrowBuild !== null) { - lines.push(`${prefix}sparrow_build = ${sparrowBuild}`); + } else { + elementId = `${project}.gradle.properties.content`; + + if (project == "client") { + lines.push(`feather_build = ${featherBuilds.client}`); + if (ravenBuilds.client != null) { + lines.push(`raven_build = ${ravenBuilds.client}`); + } + if (sparrowBuilds.client != null) { + lines.push(`sparrow_build = ${sparrowBuilds.client}`); + } + if (nestsBuilds.client != null) { + lines.push(`nests_build = ${nestsBuilds.client}`); + } } - if (nestsBuild !== null) { - lines.push(`${prefix}nests_build = ${nestsBuild}`); + if (project == "server") { + lines.push(`feather_build = ${featherBuilds.server}`); + if (ravenBuilds.client != null) { + lines.push(`raven_build = ${ravenBuilds.server}`); + } + if (sparrowBuilds.client != null) { + lines.push(`sparrow_build = ${sparrowBuilds.server}`); + } + if (nestsBuilds.client != null) { + lines.push(`nests_build = ${nestsBuilds.server}`); + } } - return lines; } - return []; + + if (lines) { + displayCodeBlockLines(elementId, lines); + } + } + + async function displayFabricModMetadata(minecraftVersion, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion) { + const elementId = "fabric.mod.json.content"; + const lines = []; + + const normalizedVersion = await normalizeMinecraftVersion(minecraftVersion.id); + + lines.push(`{`); + lines.push(`\t"depends": {`); + lines.push(`\t\t"fabricloader": ">=${loaderVersion}"`); + lines.push(`\t\t"minecraft": "${normalizedVersion}"`); + if (oslVersion != null) { + lines.push(`\t\t"osl": ">=${oslVersion}"`); + } + lines.push(`\t}`); + lines.push(`}`); + + + if (lines) { + displayCodeBlockLines(elementId, lines); + } } - Object.entries(loaderSelectorRadios).forEach(([_, button]) => button.addEventListener("change", async _ => await updateOrnitheDependencies())); + async function displayQuiltModMetadata(minecraftVersion, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion) { + const elementId = "quilt.mod.json.content"; + const lines = []; - versionSelectorInput.addEventListener("input", async _ => await updateOrnitheDependencies()) + const normalizedVersion = await normalizeMinecraftVersion(minecraftVersion.id); + + lines.push(`{`); + lines.push(`\t"quilt_loader" {`); + lines.push(`\t\t"depends": [`); + lines.push(`\t\t\t{`); + lines.push(`\t\t\t\t"id": "quilt_loader"`); + lines.push(`\t\t\t\t"versions": ">=${loaderVersion}"`); + lines.push(`\t\t\t},`); + lines.push(`\t\t\t{`); + lines.push(`\t\t\t\t"id": "minecraft"`); + lines.push(`\t\t\t\t"versions": "${normalizedVersion}"`); + lines.push(`\t\t\t}${oslVersion == null ? "" : ","}`); + if (oslVersion != null) { + lines.push(`\t\t\t{`); + lines.push(`\t\t\t\t"id": "osl"`); + lines.push(`\t\t\t\t"versions": ">=${oslVersion}"`); + lines.push(`\t\t\t}`); + } + lines.push(`\t\t]`); + lines.push(`\t}`); + lines.push(`}`); - allowSnapshotsCheck.addEventListener("change", async _ => { - updateVersionList(); - await updateOrnitheDependencies(); - }) + if (lines) { + displayCodeBlockLines(elementId, lines); + } + } - featherGenSelector.addEventListener("change", async (e) => { - const gen = Object.entries(genSelectorRadios).find(([_, button]) => button === e.target)[0]; - minecraftStableVersions = await getMinecraftStableVersions(gen); - minecraftAllVersions = await getMinecraftVersions(gen); + function hideElements() { + hideElement("build.gradle"); + hideElement("client.build.gradle"); + hideElement("server.build.gradle"); - updateVersionList(); - // Update the dependencies message since it depends on the feather gen - await updateOrnitheDependencies(); - }) + hideElement("gradle.properties"); + hideElement("client.gradle.properties"); + hideElement("server.gradle.properties"); - function updateVersionList() { - if (allowSnapshotsCheck.checked) { - possibleVersions = minecraftAllVersions; - } else { - possibleVersions = minecraftStableVersions; + hideElement("fabric.mod.json"); + hideElement("quilt.mod.json"); + } + + function hideElement(id) { + let elem = document.getElementById(id); + elem.style = "display: none;"; + } + + function showElement(id) { + let elem = document.getElementById(id); + elem.style = ""; + } + + function wipeElement(id) { + let elem = document.getElementById(id); + elem.innerText = ""; + } + + function wipeElementContents() { + wipeElement("build.gradle.content"); + wipeElement("client.build.gradle.content"); + wipeElement("server.build.gradle.content"); + + wipeElement("gradle.properties.content"); + wipeElement("client.gradle.properties.content"); + wipeElement("server.gradle.properties.content"); + + wipeElement("fabric.mod.json.content"); + wipeElement("quilt.mod.json.content"); + } + + function displayCodeBlockLines(id, lines) { + let elem = document.getElementById(id); + elem.innerText += lines.join("\n") + "\n"; + } + + function closeCodeBlock(id) { + let elem = document.getElementById(id); + while (elem.innerText.charAt(elem.innerText.length - 1) == "\n") { + elem.innerText = elem.innerText.substring(0, elem.innerText.length - 1); } + } + + function replaceCodeBlockLines(id, from, to, ...lines) { + let elem = document.getElementById(id); + let f = elem.innerText.indexOf(from) + from.length; + let t = elem.innerText.indexOf(to, f); + let pre = elem.innerText.substring(0, f); + let suf = elem.innerText.substring(t); + elem.innerText = pre + lines.join("\n") + "\n" + suf; + } + + minecraftVersionSelector.addEventListener("blur", async _ => { + await updateDisplayedElements(); + }); + modLoaderSelector.addEventListener("change", async (e) => { + await updateDisplayedElements(); + }); + calamusGenSelector.addEventListener("change", async (e) => { + await fetchVersions(); + await updateVersionList(); + await updateDisplayedElements(); + }); + + async function fetchVersions() { + const intermediaryGen = selectedCalamusGeneration(); + minecraftVersions = await getMinecraftVersions(intermediaryGen); + } - while (versionListElement.firstChild) versionListElement.removeChild(versionListElement.lastChild); - possibleVersions.forEach(e => { + async function updateVersionList() { + while (minecraftVersionList.firstChild) minecraftVersionList.removeChild(minecraftVersionList.lastChild); + minecraftVersions.forEach(e => { const opt = new Option(); opt.value = e; - versionListElement.appendChild(opt); + minecraftVersionList.appendChild(opt); }); } - updateVersionList() - await updateOrnitheDependencies() + await fetchVersions(); + await updateVersionList(); + await updateDisplayedElements(); })() diff --git a/public/meta_maven_utils.js b/public/meta_maven_utils.js index 7f16883..2483227 100644 --- a/public/meta_maven_utils.js +++ b/public/meta_maven_utils.js @@ -21,37 +21,37 @@ async function getVersionsFromMeta(...pathComponents) { return response; } -async function getVersionsWithGenFromMeta(ornitheGen, ...pathComponents) { - let response = await getVersionsFromMeta(ornitheGen, ...pathComponents); +async function getVersionsWithGenFromMeta(intermediaryGen, ...pathComponents) { + let response = await getVersionsFromMeta(intermediaryGen, ...pathComponents); return response; } -async function getMinecraftVersionsMeta(ornitheGen) { - return await getVersionsWithGenFromMeta(ornitheGen, "game"); +async function getMinecraftVersionsMeta(intermediaryGen) { + return await getVersionsWithGenFromMeta(intermediaryGen, "game"); } -export async function getFeatherVersionMeta(ornitheGen, mcVersion) { - return await getVersionsWithGenFromMeta(ornitheGen, "feather", mcVersion); +export async function getFeatherVersionMeta(intermediaryGen, sidedMcVersion) { + return await getVersionsWithGenFromMeta(intermediaryGen, "feather", sidedMcVersion); } -async function getRavenVersionMeta(mcVersion) { - return await getVersionsFromMeta("raven", mcVersion); +async function getRavenVersionMeta(sidedMcVersion) { + return await getVersionsFromMeta("raven", sidedMcVersion); } -async function getSparrowVersionMeta(mcVersion) { - return await getVersionsFromMeta("sparrow", mcVersion); +async function getSparrowVersionMeta(sidedMcVersion) { + return await getVersionsFromMeta("sparrow", sidedMcVersion); } -async function getNestsVersionMeta(mcVersion) { - return await getVersionsFromMeta("nests", mcVersion); +async function getNestsVersionMeta(sidedMcVersion) { + return await getVersionsFromMeta("nests", sidedMcVersion); } async function getLoaderVersionsMeta(loader) { return await getVersionsFromMeta(loader + "-loader"); } -async function getOslVersionsMeta() { - return await getVersionsFromMeta("osl"); +async function getOslVersionsMeta(intermediaryGen) { + return await getVersionsWithGenFromMeta(intermediaryGen, "osl"); } function compareVersion(sv1, sv2) { @@ -71,19 +71,27 @@ function compareVersion(sv1, sv2) { return rec(sv1.split("."), sv2.split(".")); } -export async function getMinecraftVersions(gen) { - return await getMinecraftVersionsMeta(gen) +export async function getMinecraftVersions(intermediaryGen) { + return await getMinecraftVersionsMeta(intermediaryGen) .then(l => l.map(v => v.version)); } -export async function getMinecraftStableVersions(gen) { - return await getMinecraftVersionsMeta(gen) +export async function getMinecraftStableVersions(intermediaryGen) { + return await getMinecraftVersionsMeta(intermediaryGen) .then(l => l.filter(v => v.stable)) .then(l => l.map(v => v.version)); } -export async function getLatestFeatherBuild(gen, mcVersion) { - return await getFeatherVersionMeta(gen, mcVersion) +export async function getLatestFeatherBuilds(intermediaryGen, minecraftVersion) { + return { + merged: (intermediaryGen == "gen1" && !minecraftVersion.sharedMappings) ? null : await getLatestFeatherBuild(intermediaryGen, minecraftVersion.id), + client: (intermediaryGen != "gen1" || minecraftVersion.sharedMappings || !minecraftVersion.client) ? null : await getLatestFeatherBuild(intermediaryGen, `${minecraftVersion.id}-client`), + server: (intermediaryGen != "gen1" || minecraftVersion.sharedMappings || !minecraftVersion.server) ? null : await getLatestFeatherBuild(intermediaryGen, `${minecraftVersion.id}-server`) + }; +} + +export async function getLatestFeatherBuild(intermediaryGen, sidedMcVersion) { + return await getFeatherVersionMeta(intermediaryGen, sidedMcVersion) .then(l => l.sort((e1, e2) => e2.build - e1.build)) .then(s => { console.log(s); @@ -93,36 +101,66 @@ export async function getLatestFeatherBuild(gen, mcVersion) { .then(e => e !== undefined ? e.build : null); } -export async function getLatestRavenBuild(mcVersion) { - return await getRavenVersionMeta(mcVersion) +export async function getLatestRavenBuilds(minecraftVersion) { + const sharedVersioning = isSharedVersioning(minecraftVersion); + + return { + merged: (sharedVersioning && !minecraftVersion.sharedMappings) ? null : await getLatestRavenBuild(minecraftVersion.id), + client: (!sharedVersioning || minecraftVersion.sharedMappings || !minecraftVersion.client) ? null : await getLatestRavenBuild(`${minecraftVersion.id}-client`), + server: (!sharedVersioning || minecraftVersion.sharedMappings || !minecraftVersion.server) ? null : await getLatestRavenBuild(`${minecraftVersion.id}-server`) + }; +} + +export async function getLatestRavenBuild(sidedMcVersion) { + return await getRavenVersionMeta(sidedMcVersion) .then(l => l.sort((e1, e2) => e2.build - e1.build)) .then(([head, ..._]) => head) .then(e => e !== undefined ? e.build : null); } -export async function getLatestSparrowBuild(mcVersion) { - return await getSparrowVersionMeta(mcVersion) +export async function getLatestSparrowBuilds(minecraftVersion) { + const sharedVersioning = isSharedVersioning(minecraftVersion); + + return { + merged: (sharedVersioning && !minecraftVersion.sharedMappings) ? null : await getLatestSparrowBuild(minecraftVersion.id), + client: (!sharedVersioning || minecraftVersion.sharedMappings || !minecraftVersion.client) ? null : await getLatestSparrowBuild(`${minecraftVersion.id}-client`), + server: (!sharedVersioning || minecraftVersion.sharedMappings || !minecraftVersion.server) ? null : await getLatestSparrowBuild(`${minecraftVersion.id}-server`) + }; +} + +export async function getLatestSparrowBuild(sidedMcVersion) { + return await getSparrowVersionMeta(sidedMcVersion) .then(l => l.sort((e1, e2) => e2.build - e1.build)) .then(([head, ..._]) => head) .then(e => e !== undefined ? e.build : null); } -export async function getLatestNestsBuild(mcVersion) { - return await getNestsVersionMeta(mcVersion) +export async function getLatestNestsBuilds(minecraftVersion) { + const sharedVersioning = isSharedVersioning(minecraftVersion); + + return { + merged: (sharedVersioning && !minecraftVersion.sharedMappings) ? null : await getLatestNestsBuild(minecraftVersion.id), + client: (!sharedVersioning || minecraftVersion.sharedMappings || !minecraftVersion.client) ? null : await getLatestNestsBuild(`${minecraftVersion.id}-client`), + server: (!sharedVersioning || minecraftVersion.sharedMappings || !minecraftVersion.server) ? null : await getLatestNestsBuild(`${minecraftVersion.id}-server`) + }; +} + +export async function getLatestNestsBuild(sidedMcVersion) { + return await getNestsVersionMeta(sidedMcVersion) .then(l => l.sort((e1, e2) => e2.build - e1.build)) .then(([head, ..._]) => head) .then(e => e !== undefined ? e.build : null); } -export async function getLatestLoader(loader) { +export async function getLatestLoaderVersion(loader) { return await getLoaderVersionsMeta(loader) .then(l => l.filter(e => e.stable)) .then(([head, ..._]) => head) .then(e => e.version); } -export async function getLatestOsl() { - return await getOslVersionsMeta() +export async function getLatestOslVersion(intermediaryGen) { + return await getOslVersionsMeta(intermediaryGen) .then(l => l.sort((e1, e2) => compareVersion(e1.version, e2.version))) .then(([head, ..._]) => head) .then(e => e.version); @@ -143,9 +181,9 @@ function makeOrnitheMavenUrl(...pathComponents) { return makeMavenUrl("net/ornithemc", ...pathComponents); } -export async function getFeatherBuildMaven(ornitheGen, version) { +export async function getFeatherBuildMaven(intermediaryGen, sidedMcVersion) { let feather; - switch (ornitheGen) { + switch (intermediaryGen) { case "gen1": { feather = "feather"; break; @@ -155,9 +193,22 @@ export async function getFeatherBuildMaven(ornitheGen, version) { break; } default: { - throw new Error("Invalid generation: " + ornitheGen); + throw new Error("Invalid generation: " + intermediaryGen); } } - const url = makeOrnitheMavenUrl(feather, version, feather + "-" + version + "-tiny.gz"); + const url = makeOrnitheMavenUrl(feather, sidedMcVersion, feather + "-" + sidedMcVersion + "-tiny.gz"); return await fetch(url); } + +const MANIFEST = URL + "/mc-versions" + +export async function getVersionDetails(intermediaryGen, minecraftVersion) { + const url = "https://" + MANIFEST + "/" + intermediaryGen + "/version/" + minecraftVersion + ".json"; + const response = await fetch(url); + return response.json(); +} + +export function isSharedVersioning(minecraftVersion) { + const fc = minecraftVersion.id.charAt(0); + return !(fc == 'r' || fc == 'p' || fc == 'c' || fc == 'i' || fc == 'a' || fc == 's'); +} diff --git a/src/pages/develop.astro b/src/pages/develop.astro index e4889f1..f6d3843 100644 --- a/src/pages/develop.astro +++ b/src/pages/develop.astro @@ -1,5 +1,6 @@ --- import Layout from "../layouts/Layout.astro"; +import Button from "../components/Button.astro"; --- @@ -8,65 +9,133 @@ import Layout from "../layouts/Layout.astro";

Develop

-

Getting started

-

- If you are updating the Loom or Ploceus version in build.gradle file, make sure their version match (example, Ploceus of version 1.10-SNAPSHOT needs Loom to be of version 1.10-SNAPSHOT as well) -

-

Latest versions

-
-
- - -
+
+ This page provides resources to help mod developers create and maintain their projects. +
+ +
+
+

Latest versions

+ + + + + + + + +
+ Select a mod loader + + -
- Select a Calamus (Intermediary) Generation - - + + +
- - -
+
+ Select a Calamus (intermediary) generation + + - - - - - - - + + +
+
+
+
+
+
+
+
+

Project properties

+
+
+ ./gradle.properties +

+          
+
+
+ ./client/gradle.properties +

+          
+
+
+ ./server/gradle.properties +

+          
+
-

Paste these in gradle.properties in your project

+
- # Dependencies - - - -

+
+

Mod metadata

+
+
+ ./src/main/resources/fabric.mod.json +

+          
+
+ ./src/main/resources/quilt.mod.json +

+          
+
+
+
+
+

Build script

+
+
+ ./build.gradle +

+          
+
+
+ ./client/build.gradle +

+          
+
+
+ ./server/build.gradle +

+          
+
+
diff --git a/src/pages/index.astro b/src/pages/index.astro index 0187471..3f7ad00 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -36,16 +36,16 @@ import Button from "../components/Button.astro";

Libraries

-
+

Ornithe Standard Libraries

A light-weight library of APIs that provides hooks and events for modding with Ornithe.
-
+
-
+
@@ -53,20 +53,20 @@ import Button from "../components/Button.astro";

Toolchain

Core Projects

-
+

Feather

A set of mappings that provides a consistent modding experience across a wide range of Minecraft versions.
-
+

Calamus

An intermediate mapping layer that enables cross-version mod compatibility.
-
+

Ploceus

A Gradle plugin that configures Loom for Ornithe mod projects. @@ -75,19 +75,19 @@ import Button from "../components/Button.astro";

Other Projects

-
+

Nests

Patches for modifying inner class attributes in Java class files.
-
+

Sparrow

Patches for modifying generic type signatures in Java class files.
-
+

Raven

Patches for throws clauses in method declarations in Java class files. @@ -95,19 +95,19 @@ import Button from "../components/Button.astro";
-
+

Nester

A tool that patches inner class attributes in Java class files.
-
+

Signature Changer

A tool that patches generic type signatures in Java class files.
-
+

Exceptor

A tool that patches throws clauses in method declarations in Java class files. @@ -115,19 +115,19 @@ import Button from "../components/Button.astro";
-
+

Condor

A local variable table generator for Java class files.
-
+

Preen

Miscellaneous de-obfuscation patches for Java class files.
-
+
diff --git a/src/styles/global.css b/src/styles/global.css index 51c7b8b..7d1ed90 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -16,8 +16,10 @@ :root { --bg-ornithe: #ffffff; --bg-o-transparent: #00000020; - --bg-input: #e5e5e5; + --bg-input: #e0efff; --bg-button: #93c5fd; + --bg-code-inline: #e5e5e5; + --bg-code-block: #e0efff; --text-ornithe: #000000; --tile-bg-lightness: 98%; --tile-bd-lightness: 70%; @@ -25,8 +27,10 @@ :root[class~="dark"] { --bg-ornithe: #000000; --bg-o-transparent: #ffffff20; - --bg-input: #171717; + --bg-input: #171c2c; --bg-button: #172554; + --bg-code-inline: #171717; + --bg-code-block: #171c2c; --text-ornithe: #ffffff; --tile-bg-lightness: 5%; --tile-bd-lightness: 85%; @@ -53,10 +57,10 @@ a { } code { - @apply bg-ornithe-input-bg p-1; + @apply bg-ornithe-code-inline-bg p-1; } code[class~="dark"] { - @apply bg-ornithe-input-bg p-1; + @apply bg-ornithe-code-inline-bg p-1; } fieldset { @@ -96,6 +100,11 @@ div { } .column { + flex: 1 1 auto; + flex-direction: column; +} + +.tile { flex: 1 1 0px; margin: 4px 10px; padding: 20px; @@ -166,6 +175,11 @@ div { border-color: hsl(240, 100%, var(--tile-bd-lightness)) } +.code-block { + background-color: var(--bg-code-block); + padding: 20px; +} + /*If someone could get a tailwind.css file working I'd be grateful. -worldwidepixel*/ @tailwind utilities; diff --git a/tailwind.config.mjs b/tailwind.config.mjs index f4776cd..80ba875 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -17,6 +17,8 @@ export default { "ornithe-transparent-bg": "var(--bg-o-transparent)", "ornithe-input-bg": "var(--bg-input)", "ornithe-button-bg": "var(--bg-button)", + "ornithe-code-inline-bg": "var(--bg-code-inline)", + "ornithe-code-block-bg": "var(--bg-code-block)", "ornithe-text": "var(--text-ornithe)", }, }, From 00b6c705657345d31bf89538f68138aeb0fa9780 Mon Sep 17 00:00:00 2001 From: Space Walker Date: Fri, 6 Feb 2026 19:36:06 +0100 Subject: [PATCH 4/6] add environment to gen1 split project properties --- public/develop.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/develop.js b/public/develop.js index 53217df..3f69800 100644 --- a/public/develop.js +++ b/public/develop.js @@ -467,6 +467,9 @@ import { normalizeMinecraftVersion } from "./minecraft_semver.js"; } else { elementId = `${project}.gradle.properties.content`; + lines.push(`environment = ${project}`); + lines.push(""); + if (project == "client") { lines.push(`feather_build = ${featherBuilds.client}`); if (ravenBuilds.client != null) { From 1ca0bf1ff3c11380e22f7b582f5c99837835ca32 Mon Sep 17 00:00:00 2001 From: Space Walker Date: Fri, 6 Feb 2026 19:45:55 +0100 Subject: [PATCH 5/6] remove loader and osl from mod metadata --- public/develop.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/public/develop.js b/public/develop.js index 3f69800..4923a31 100644 --- a/public/develop.js +++ b/public/develop.js @@ -509,11 +509,7 @@ import { normalizeMinecraftVersion } from "./minecraft_semver.js"; lines.push(`{`); lines.push(`\t"depends": {`); - lines.push(`\t\t"fabricloader": ">=${loaderVersion}"`); lines.push(`\t\t"minecraft": "${normalizedVersion}"`); - if (oslVersion != null) { - lines.push(`\t\t"osl": ">=${oslVersion}"`); - } lines.push(`\t}`); lines.push(`}`); @@ -533,19 +529,9 @@ import { normalizeMinecraftVersion } from "./minecraft_semver.js"; lines.push(`\t"quilt_loader" {`); lines.push(`\t\t"depends": [`); lines.push(`\t\t\t{`); - lines.push(`\t\t\t\t"id": "quilt_loader"`); - lines.push(`\t\t\t\t"versions": ">=${loaderVersion}"`); - lines.push(`\t\t\t},`); - lines.push(`\t\t\t{`); lines.push(`\t\t\t\t"id": "minecraft"`); lines.push(`\t\t\t\t"versions": "${normalizedVersion}"`); - lines.push(`\t\t\t}${oslVersion == null ? "" : ","}`); - if (oslVersion != null) { - lines.push(`\t\t\t{`); - lines.push(`\t\t\t\t"id": "osl"`); - lines.push(`\t\t\t\t"versions": ">=${oslVersion}"`); - lines.push(`\t\t\t}`); - } + lines.push(`\t\t\t}`); lines.push(`\t\t]`); lines.push(`\t}`); lines.push(`}`); From bb3cd697658593f3f24d2b112196ae1742b2edd0 Mon Sep 17 00:00:00 2001 From: Space Walker Date: Fri, 6 Feb 2026 19:54:39 +0100 Subject: [PATCH 6/6] fix feather build in project properties for gen1 beta versions --- public/develop.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/develop.js b/public/develop.js index 4923a31..8b5ef7a 100644 --- a/public/develop.js +++ b/public/develop.js @@ -397,15 +397,13 @@ import { normalizeMinecraftVersion } from "./minecraft_semver.js"; } async function displayProjectProperties(minecraftVersion, intermediaryGen, modLoader, loaderVersion, featherBuilds, ravenBuilds, sparrowBuilds, nestsBuilds, oslVersion) { - const sharedVersioning = isSharedVersioning(minecraftVersion); const elementId = "gradle.properties.content"; - const lines = []; lines.push(`minecraft_version = ${minecraftVersion.id}`); lines.push(`loader_version = ${loaderVersion}`); lines.push(""); - if (sharedVersioning) { + if (featherBuilds.merged != null) { lines.push(`feather_build = ${featherBuilds.merged}`); } else { lines.push(`feather_build = ${minecraftVersion.client ? featherBuilds.client : featherBuilds.server}`);