Skip to content

Commit a132f0f

Browse files
Change version infering to use full semver range spec (modrinth#1248)
* Add support for all semver and forge maven range. * Fix that old forge was checking version as an object * Fix new version range checking not being applied to quilt.mod.jsons * run fix
1 parent 7c2ea3d commit a132f0f

File tree

3 files changed

+109
-58
lines changed

3 files changed

+109
-58
lines changed

helpers/infer.js

Lines changed: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import TOML from '@ltd/j-toml'
22
import JSZip from 'jszip'
33
import yaml from 'js-yaml'
4+
import { satisfies } from 'semver'
45

56
export const inferVersionInfo = async function (rawFile, project, gameVersions) {
67
function versionType(number) {
@@ -17,50 +18,87 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions)
1718
}
1819
}
1920

20-
// TODO: This func does not handle accurate semver parsing. We should eventually
21-
function gameVersionRange(gameVersionString, gameVersions) {
22-
if (!gameVersionString) {
21+
function getGameVersionsMatchingSemverRange(range, gameVersions) {
22+
if (!range) {
2323
return []
2424
}
25+
const ranges = Array.isArray(range) ? range : [range]
26+
return gameVersions.filter((version) => {
27+
const semverVersion = version.split('.').length === 2 ? `${version}.0` : version // add patch version if missing (e.g. 1.16 -> 1.16.0)
28+
return ranges.some((v) => satisfies(semverVersion, v))
29+
})
30+
}
2531

26-
// Truncate characters after `-` & `+`
27-
const gameString = gameVersionString.replace(/-|\+.*$/g, '')
28-
29-
let prefix = ''
30-
if (gameString.includes('~')) {
31-
// Include minor versions
32-
// ~1.2.3 -> 1.2
33-
prefix = gameString.replace('~', '').split('.').slice(0, 2).join('.')
34-
} else if (gameString.includes('>=')) {
35-
// Include minor versions
36-
// >=1.2.3 -> 1.2
37-
prefix = gameString.replace('>=', '').split('.').slice(0, 2).join('.')
38-
} else if (gameString.includes('^')) {
39-
// Include major versions
40-
// ^1.2.3 -> 1
41-
prefix = gameString.replace('^', '').split('.')[0]
42-
} else if (gameString.includes('x')) {
43-
// Include versions that match `x.x.x`
44-
// 1.2.x -> 1.2
45-
prefix = gameString.replace(/\.x$/, '')
46-
} else {
47-
// Include exact version
48-
// 1.2.3 -> 1.2.3
49-
prefix = gameString
32+
function getGameVersionsMatchingMavenRange(range, gameVersions) {
33+
if (!range) {
34+
return []
35+
}
36+
const ranges = []
37+
38+
while (range.startsWith('[') || range.startsWith('(')) {
39+
let index = range.indexOf(')')
40+
const index2 = range.indexOf(']')
41+
if (index === -1 || (index2 !== -1 && index2 < index)) {
42+
index = index2
43+
}
44+
if (index === -1) break
45+
ranges.push(range.substring(0, index + 1))
46+
range = range.substring(index + 1).trim()
47+
if (range.startsWith(',')) {
48+
range = range.substring(1).trim()
49+
}
5050
}
5151

52-
const simplified = gameVersions
53-
.filter((it) => it.version_type === 'release')
54-
.map((it) => it.version)
55-
return simplified.filter((version) => version.startsWith(prefix))
52+
if (range) {
53+
ranges.push(range)
54+
}
55+
56+
const LESS_THAN_EQUAL = /^\(,(.*)]$/
57+
const LESS_THAN = /^\(,(.*)\)$/
58+
const EQUAL = /^\[(.*)]$/
59+
const GREATER_THAN_EQUAL = /^\[(.*),\)$/
60+
const GREATER_THAN = /^\((.*),\)$/
61+
const BETWEEN = /^\((.*),(.*)\)$/
62+
const BETWEEN_EQUAL = /^\[(.*),(.*)]$/
63+
const BETWEEN_LESS_THAN_EQUAL = /^\((.*),(.*)]$/
64+
const BETWEEN_GREATER_THAN_EQUAL = /^\[(.*),(.*)\)$/
65+
66+
const semverRanges = []
67+
68+
for (const range of ranges) {
69+
let result
70+
if ((result = range.match(LESS_THAN_EQUAL))) {
71+
semverRanges.push(`<=${result[1]}`)
72+
} else if ((result = range.match(LESS_THAN))) {
73+
semverRanges.push(`<${result[1]}`)
74+
} else if ((result = range.match(EQUAL))) {
75+
semverRanges.push(`${result[1]}`)
76+
} else if ((result = range.match(GREATER_THAN_EQUAL))) {
77+
semverRanges.push(`>=${result[1]}`)
78+
} else if ((result = range.match(GREATER_THAN))) {
79+
semverRanges.push(`>${result[1]}`)
80+
} else if ((result = range.match(BETWEEN))) {
81+
semverRanges.push(`>${result[1]} <${result[2]}`)
82+
} else if ((result = range.match(BETWEEN_EQUAL))) {
83+
semverRanges.push(`>=${result[1]} <=${result[2]}`)
84+
} else if ((result = range.match(BETWEEN_LESS_THAN_EQUAL))) {
85+
semverRanges.push(`>${result[1]} <=${result[2]}`)
86+
} else if ((result = range.match(BETWEEN_GREATER_THAN_EQUAL))) {
87+
semverRanges.push(`>=${result[1]} <${result[2]}`)
88+
}
89+
}
90+
return getGameVersionsMatchingSemverRange(semverRanges, gameVersions)
5691
}
5792

93+
const simplifiedGameVersions = gameVersions
94+
.filter((it) => it.version_type === 'release')
95+
.map((it) => it.version)
96+
5897
const inferFunctions = {
5998
// Forge 1.13+
6099
'META-INF/mods.toml': async (file, zip) => {
61100
const metadata = TOML.parse(file, { joiner: '\n' })
62101

63-
// TODO: Parse minecraft version ranges
64102
if (metadata.mods && metadata.mods.length > 0) {
65103
let versionNum = metadata.mods[0].version
66104

@@ -80,11 +118,23 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions)
80118
}
81119
}
82120

121+
let gameVersions = []
122+
const mcDependencies = Object.values(metadata.dependencies)
123+
.flat()
124+
.filter((dependency) => dependency.modId === 'minecraft')
125+
if (mcDependencies.length > 0) {
126+
gameVersions = getGameVersionsMatchingMavenRange(
127+
mcDependencies[0].versionRange,
128+
simplifiedGameVersions
129+
)
130+
}
131+
83132
return {
84133
name: `${project.title} ${versionNum}`,
85134
version_number: versionNum,
86135
version_type: versionType(versionNum),
87136
loaders: ['forge'],
137+
game_versions: gameVersions,
88138
}
89139
} else {
90140
return {}
@@ -99,9 +149,9 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions)
99149
version_number: metadata.version,
100150
version_type: versionType(metadata.version),
101151
loaders: ['forge'],
102-
game_versions: gameVersions
103-
.filter((x) => x.version.startsWith(metadata.mcversion) && x.version_type === 'release')
104-
.map((x) => x.version),
152+
game_versions: simplifiedGameVersions.filter((version) =>
153+
version.startsWith(metadata.mcversion)
154+
),
105155
}
106156
},
107157
// Fabric
@@ -114,7 +164,7 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions)
114164
loaders: ['fabric'],
115165
version_type: versionType(metadata.version),
116166
game_versions: metadata.depends
117-
? gameVersionRange(metadata.depends.minecraft, gameVersions)
167+
? getGameVersionsMatchingSemverRange(metadata.depends.minecraft, simplifiedGameVersions)
118168
: [],
119169
}
120170
},
@@ -128,11 +178,11 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions)
128178
loaders: ['quilt'],
129179
version_type: versionType(metadata.quilt_loader.version),
130180
game_versions: metadata.quilt_loader.depends
131-
? gameVersionRange(
181+
? getGameVersionsMatchingSemverRange(
132182
metadata.quilt_loader.depends.find((x) => x.id === 'minecraft')
133183
? metadata.quilt_loader.depends.find((x) => x.id === 'minecraft').versions
134184
: [],
135-
gameVersions
185+
simplifiedGameVersions
136186
)
137187
: [],
138188
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"markdown-it": "^13.0.1",
4747
"omorphia": "^0.4.31",
4848
"qrcode.vue": "^3.4.0",
49+
"semver": "^7.5.4",
4950
"vue-multiselect": "^3.0.0-alpha.2",
5051
"xss": "^1.0.14"
5152
},

pnpm-lock.yaml

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)