From a132f0f41dd3b31ea1972797c4870dce7843d632 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 2 Aug 2023 18:32:03 -0230 Subject: [PATCH] Change version infering to use full semver range spec (#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 --- helpers/infer.js | 126 +++++++++++++++++++++++++++++++++-------------- package.json | 1 + pnpm-lock.yaml | 40 +++++++-------- 3 files changed, 109 insertions(+), 58 deletions(-) diff --git a/helpers/infer.js b/helpers/infer.js index 9b011e24c..086f30455 100644 --- a/helpers/infer.js +++ b/helpers/infer.js @@ -1,6 +1,7 @@ import TOML from '@ltd/j-toml' import JSZip from 'jszip' import yaml from 'js-yaml' +import { satisfies } from 'semver' export const inferVersionInfo = async function (rawFile, project, gameVersions) { function versionType(number) { @@ -17,50 +18,87 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions) } } - // TODO: This func does not handle accurate semver parsing. We should eventually - function gameVersionRange(gameVersionString, gameVersions) { - if (!gameVersionString) { + function getGameVersionsMatchingSemverRange(range, gameVersions) { + if (!range) { return [] } + const ranges = Array.isArray(range) ? range : [range] + return gameVersions.filter((version) => { + const semverVersion = version.split('.').length === 2 ? `${version}.0` : version // add patch version if missing (e.g. 1.16 -> 1.16.0) + return ranges.some((v) => satisfies(semverVersion, v)) + }) + } - // Truncate characters after `-` & `+` - const gameString = gameVersionString.replace(/-|\+.*$/g, '') - - let prefix = '' - if (gameString.includes('~')) { - // Include minor versions - // ~1.2.3 -> 1.2 - prefix = gameString.replace('~', '').split('.').slice(0, 2).join('.') - } else if (gameString.includes('>=')) { - // Include minor versions - // >=1.2.3 -> 1.2 - prefix = gameString.replace('>=', '').split('.').slice(0, 2).join('.') - } else if (gameString.includes('^')) { - // Include major versions - // ^1.2.3 -> 1 - prefix = gameString.replace('^', '').split('.')[0] - } else if (gameString.includes('x')) { - // Include versions that match `x.x.x` - // 1.2.x -> 1.2 - prefix = gameString.replace(/\.x$/, '') - } else { - // Include exact version - // 1.2.3 -> 1.2.3 - prefix = gameString + function getGameVersionsMatchingMavenRange(range, gameVersions) { + if (!range) { + return [] + } + const ranges = [] + + while (range.startsWith('[') || range.startsWith('(')) { + let index = range.indexOf(')') + const index2 = range.indexOf(']') + if (index === -1 || (index2 !== -1 && index2 < index)) { + index = index2 + } + if (index === -1) break + ranges.push(range.substring(0, index + 1)) + range = range.substring(index + 1).trim() + if (range.startsWith(',')) { + range = range.substring(1).trim() + } } - const simplified = gameVersions - .filter((it) => it.version_type === 'release') - .map((it) => it.version) - return simplified.filter((version) => version.startsWith(prefix)) + if (range) { + ranges.push(range) + } + + const LESS_THAN_EQUAL = /^\(,(.*)]$/ + const LESS_THAN = /^\(,(.*)\)$/ + const EQUAL = /^\[(.*)]$/ + const GREATER_THAN_EQUAL = /^\[(.*),\)$/ + const GREATER_THAN = /^\((.*),\)$/ + const BETWEEN = /^\((.*),(.*)\)$/ + const BETWEEN_EQUAL = /^\[(.*),(.*)]$/ + const BETWEEN_LESS_THAN_EQUAL = /^\((.*),(.*)]$/ + const BETWEEN_GREATER_THAN_EQUAL = /^\[(.*),(.*)\)$/ + + const semverRanges = [] + + for (const range of ranges) { + let result + if ((result = range.match(LESS_THAN_EQUAL))) { + semverRanges.push(`<=${result[1]}`) + } else if ((result = range.match(LESS_THAN))) { + semverRanges.push(`<${result[1]}`) + } else if ((result = range.match(EQUAL))) { + semverRanges.push(`${result[1]}`) + } else if ((result = range.match(GREATER_THAN_EQUAL))) { + semverRanges.push(`>=${result[1]}`) + } else if ((result = range.match(GREATER_THAN))) { + semverRanges.push(`>${result[1]}`) + } else if ((result = range.match(BETWEEN))) { + semverRanges.push(`>${result[1]} <${result[2]}`) + } else if ((result = range.match(BETWEEN_EQUAL))) { + semverRanges.push(`>=${result[1]} <=${result[2]}`) + } else if ((result = range.match(BETWEEN_LESS_THAN_EQUAL))) { + semverRanges.push(`>${result[1]} <=${result[2]}`) + } else if ((result = range.match(BETWEEN_GREATER_THAN_EQUAL))) { + semverRanges.push(`>=${result[1]} <${result[2]}`) + } + } + return getGameVersionsMatchingSemverRange(semverRanges, gameVersions) } + const simplifiedGameVersions = gameVersions + .filter((it) => it.version_type === 'release') + .map((it) => it.version) + const inferFunctions = { // Forge 1.13+ 'META-INF/mods.toml': async (file, zip) => { const metadata = TOML.parse(file, { joiner: '\n' }) - // TODO: Parse minecraft version ranges if (metadata.mods && metadata.mods.length > 0) { let versionNum = metadata.mods[0].version @@ -80,11 +118,23 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions) } } + let gameVersions = [] + const mcDependencies = Object.values(metadata.dependencies) + .flat() + .filter((dependency) => dependency.modId === 'minecraft') + if (mcDependencies.length > 0) { + gameVersions = getGameVersionsMatchingMavenRange( + mcDependencies[0].versionRange, + simplifiedGameVersions + ) + } + return { name: `${project.title} ${versionNum}`, version_number: versionNum, version_type: versionType(versionNum), loaders: ['forge'], + game_versions: gameVersions, } } else { return {} @@ -99,9 +149,9 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions) version_number: metadata.version, version_type: versionType(metadata.version), loaders: ['forge'], - game_versions: gameVersions - .filter((x) => x.version.startsWith(metadata.mcversion) && x.version_type === 'release') - .map((x) => x.version), + game_versions: simplifiedGameVersions.filter((version) => + version.startsWith(metadata.mcversion) + ), } }, // Fabric @@ -114,7 +164,7 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions) loaders: ['fabric'], version_type: versionType(metadata.version), game_versions: metadata.depends - ? gameVersionRange(metadata.depends.minecraft, gameVersions) + ? getGameVersionsMatchingSemverRange(metadata.depends.minecraft, simplifiedGameVersions) : [], } }, @@ -128,11 +178,11 @@ export const inferVersionInfo = async function (rawFile, project, gameVersions) loaders: ['quilt'], version_type: versionType(metadata.quilt_loader.version), game_versions: metadata.quilt_loader.depends - ? gameVersionRange( + ? getGameVersionsMatchingSemverRange( metadata.quilt_loader.depends.find((x) => x.id === 'minecraft') ? metadata.quilt_loader.depends.find((x) => x.id === 'minecraft').versions : [], - gameVersions + simplifiedGameVersions ) : [], } diff --git a/package.json b/package.json index 1fbfd4bea..6533ca52c 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "markdown-it": "^13.0.1", "omorphia": "^0.4.31", "qrcode.vue": "^3.4.0", + "semver": "^7.5.4", "vue-multiselect": "^3.0.0-alpha.2", "xss": "^1.0.14" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 970a5f6e0..7487a8835 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,6 +37,9 @@ dependencies: qrcode.vue: specifier: ^3.4.0 version: 3.4.0(vue@3.3.4) + semver: + specifier: ^7.5.4 + version: 7.5.4 vue-multiselect: specifier: ^3.0.0-alpha.2 version: 3.0.0-alpha.2 @@ -1102,7 +1105,7 @@ packages: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.5.3 + semver: 7.5.4 tar: 6.1.15 transitivePeerDependencies: - encoding @@ -1158,7 +1161,7 @@ packages: pathe: 1.1.1 pkg-types: 1.0.3 scule: 1.0.0 - semver: 7.5.3 + semver: 7.5.4 unctx: 2.3.1 unimport: 3.0.12(rollup@3.26.0) untyped: 1.3.2 @@ -1184,7 +1187,7 @@ packages: pathe: 1.1.1 pkg-types: 1.0.3 scule: 1.0.0 - semver: 7.5.3 + semver: 7.5.4 unctx: 2.3.1 unimport: 3.0.12(rollup@3.26.0) untyped: 1.3.2 @@ -1619,7 +1622,7 @@ packages: grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.5.3 + semver: 7.5.4 tsutils: 3.21.0(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: @@ -1693,7 +1696,7 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.3 + semver: 7.5.4 tsutils: 3.21.0(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: @@ -1714,7 +1717,7 @@ packages: '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) eslint: 8.41.0 eslint-scope: 5.1.1 - semver: 7.5.3 + semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript @@ -2401,7 +2404,7 @@ packages: /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: - semver: 7.5.3 + semver: 7.5.4 dev: true /bundle-name@3.0.0: @@ -3401,7 +3404,7 @@ packages: is-core-module: 2.12.1 minimatch: 3.1.2 resolve: 1.22.2 - semver: 7.5.3 + semver: 7.5.4 dev: true /eslint-plugin-node@11.1.0(eslint@8.41.0): @@ -3464,7 +3467,7 @@ packages: read-pkg-up: 7.0.1 regexp-tree: 0.1.27 safe-regex: 2.1.1 - semver: 7.5.3 + semver: 7.5.4 strip-indent: 3.0.0 dev: true @@ -3479,7 +3482,7 @@ packages: natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.0.13 - semver: 7.5.3 + semver: 7.5.4 vue-eslint-parser: 9.3.1(eslint@8.41.0) xml-name-validator: 4.0.0 transitivePeerDependencies: @@ -4847,7 +4850,6 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 - dev: true /magic-string-ast@0.1.2: resolution: {integrity: sha512-P53AZrzq7hclCU6HWj88xNZHmP15DKjMmK/vBytO1qnpYP3ul4IEZlyCE0aU3JRnmgWmZPmoTKj4Bls7v0pMyA==} @@ -5132,7 +5134,7 @@ packages: rollup: 3.26.0 rollup-plugin-visualizer: 5.9.2(rollup@3.26.0) scule: 1.0.0 - semver: 7.5.3 + semver: 7.5.4 serve-placeholder: 2.0.1 serve-static: 1.15.0 source-map-support: 0.5.21 @@ -6323,13 +6325,12 @@ packages: hasBin: true dev: true - /semver@7.5.3: - resolution: {integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==} + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} hasBin: true dependencies: lru-cache: 6.0.0 - dev: true /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -7099,7 +7100,7 @@ packages: lodash.debounce: 4.0.8 lodash.pick: 4.4.0 npm-run-path: 4.0.1 - semver: 7.5.3 + semver: 7.5.4 strip-ansi: 6.0.1 tiny-invariant: 1.3.1 typescript: 5.0.4 @@ -7175,7 +7176,7 @@ packages: engines: {vscode: ^1.52.0} dependencies: minimatch: 3.1.2 - semver: 7.5.3 + semver: 7.5.4 vscode-languageserver-protocol: 3.16.0 dev: true @@ -7228,7 +7229,7 @@ packages: espree: 9.6.0 esquery: 1.5.0 lodash: 4.17.21 - semver: 7.5.3 + semver: 7.5.4 transitivePeerDependencies: - supports-color dev: true @@ -7277,7 +7278,7 @@ packages: dependencies: '@volar/vue-language-core': 1.6.5 '@volar/vue-typescript': 1.6.5(typescript@5.0.4) - semver: 7.5.3 + semver: 7.5.4 typescript: 5.0.4 dev: true @@ -7419,7 +7420,6 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true /yaml@2.3.1: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}