213 lines
5.7 KiB
TypeScript
213 lines
5.7 KiB
TypeScript
export interface VersionRange {
|
|
type: 'exact' | 'gte' | 'lte' | 'range' | 'wildcard'
|
|
major: number
|
|
minor: number
|
|
patch?: number
|
|
upperMajor?: number
|
|
upperMinor?: number
|
|
upperPatch?: number
|
|
upperInclusive?: boolean
|
|
}
|
|
|
|
export function parseVersion(versionStr: string): {
|
|
major: number
|
|
minor: number
|
|
patch: number
|
|
} | null {
|
|
const match = versionStr.match(/^(\d+)\.(\d+)(?:\.(\d+))?$/)
|
|
if (!match) return null
|
|
return {
|
|
major: parseInt(match[1], 10),
|
|
minor: parseInt(match[2], 10),
|
|
patch: match[3] ? parseInt(match[3], 10) : 0,
|
|
}
|
|
}
|
|
|
|
export function parseVersionRange(rangeStr: string): VersionRange | null {
|
|
const trimmed = rangeStr.trim()
|
|
|
|
if (trimmed.startsWith('>=')) {
|
|
const version = parseVersion(trimmed.slice(2))
|
|
if (!version) return null
|
|
return { type: 'gte', ...version }
|
|
}
|
|
|
|
if (trimmed.startsWith('<=')) {
|
|
const version = parseVersion(trimmed.slice(2))
|
|
if (!version) return null
|
|
return { type: 'lte', ...version }
|
|
}
|
|
|
|
if (trimmed.startsWith('>')) {
|
|
const version = parseVersion(trimmed.slice(1))
|
|
if (!version) return null
|
|
return { type: 'gte', ...version, patch: version.patch + 1 }
|
|
}
|
|
|
|
if (trimmed.startsWith('<')) {
|
|
const version = parseVersion(trimmed.slice(1))
|
|
if (!version) return null
|
|
return { type: 'lte', ...version, patch: version.patch - 1 }
|
|
}
|
|
|
|
const bracketMatch = trimmed.match(/^\[(\d+\.\d+(?:\.\d+)?)\s*,\s*(\d+\.\d+(?:\.\d+)?)\)$/)
|
|
if (bracketMatch) {
|
|
const lower = parseVersion(bracketMatch[1])
|
|
const upper = parseVersion(bracketMatch[2])
|
|
if (!lower || !upper) return null
|
|
return {
|
|
type: 'range',
|
|
...lower,
|
|
upperMajor: upper.major,
|
|
upperMinor: upper.minor,
|
|
upperPatch: upper.patch,
|
|
upperInclusive: false,
|
|
}
|
|
}
|
|
|
|
const closedBracketMatch = trimmed.match(/^\[(\d+\.\d+(?:\.\d+)?)\s*,\s*(\d+\.\d+(?:\.\d+)?)\]$/)
|
|
if (closedBracketMatch) {
|
|
const lower = parseVersion(closedBracketMatch[1])
|
|
const upper = parseVersion(closedBracketMatch[2])
|
|
if (!lower || !upper) return null
|
|
return {
|
|
type: 'range',
|
|
...lower,
|
|
upperMajor: upper.major,
|
|
upperMinor: upper.minor,
|
|
upperPatch: upper.patch,
|
|
upperInclusive: true,
|
|
}
|
|
}
|
|
|
|
if (trimmed.includes('x') || trimmed.includes('X')) {
|
|
const match = trimmed.match(/^(\d+)\.(\d+)\.x$/i)
|
|
if (match) {
|
|
return {
|
|
type: 'wildcard',
|
|
major: parseInt(match[1], 10),
|
|
minor: parseInt(match[2], 10),
|
|
}
|
|
}
|
|
const minorWildcard = trimmed.match(/^(\d+)\.x$/i)
|
|
if (minorWildcard) {
|
|
return {
|
|
type: 'wildcard',
|
|
major: parseInt(minorWildcard[1], 10),
|
|
minor: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
if (trimmed.includes('-')) {
|
|
const parts = trimmed.split('-').map((p) => p.trim())
|
|
if (parts.length === 2) {
|
|
const lower = parseVersion(parts[0])
|
|
const upper = parseVersion(parts[1])
|
|
if (!lower || !upper) return null
|
|
return {
|
|
type: 'range',
|
|
...lower,
|
|
upperMajor: upper.major,
|
|
upperMinor: upper.minor,
|
|
upperPatch: upper.patch,
|
|
upperInclusive: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
const exact = parseVersion(trimmed)
|
|
if (exact) {
|
|
return { type: 'exact', ...exact }
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function compareVersions(
|
|
a: { major: number; minor: number; patch: number },
|
|
b: { major: number; minor: number; patch: number },
|
|
): number {
|
|
if (a.major !== b.major) return a.major - b.major
|
|
if (a.minor !== b.minor) return a.minor - b.minor
|
|
return a.patch - b.patch
|
|
}
|
|
|
|
export function compareSemanticVersions(left: string, right: string): number | null {
|
|
const leftVersion = parseVersion(left)
|
|
const rightVersion = parseVersion(right)
|
|
if (!leftVersion || !rightVersion) return null
|
|
return compareVersions(leftVersion, rightVersion)
|
|
}
|
|
|
|
export function isVersionInRange(instanceVersion: string, modRange: string): boolean {
|
|
const instanceParsed = parseVersion(instanceVersion)
|
|
if (!instanceParsed) return false
|
|
|
|
const range = parseVersionRange(modRange)
|
|
if (!range) {
|
|
return instanceVersion.includes(modRange) || modRange.includes(instanceVersion)
|
|
}
|
|
|
|
switch (range.type) {
|
|
case 'exact': {
|
|
const rangeVersion = { major: range.major, minor: range.minor, patch: range.patch ?? 0 }
|
|
return compareVersions(instanceParsed, rangeVersion) === 0
|
|
}
|
|
case 'gte': {
|
|
const rangeVersion = { major: range.major, minor: range.minor, patch: range.patch ?? 0 }
|
|
return compareVersions(instanceParsed, rangeVersion) >= 0
|
|
}
|
|
case 'lte': {
|
|
const rangeVersion = { major: range.major, minor: range.minor, patch: range.patch ?? 0 }
|
|
return compareVersions(instanceParsed, rangeVersion) <= 0
|
|
}
|
|
case 'range': {
|
|
const lowerVersion = { major: range.major, minor: range.minor, patch: range.patch ?? 0 }
|
|
const upperVersion = {
|
|
major: range.upperMajor!,
|
|
minor: range.upperMinor!,
|
|
patch: range.upperPatch ?? 0,
|
|
}
|
|
const lowerCompare = compareVersions(instanceParsed, lowerVersion)
|
|
const upperCompare = compareVersions(instanceParsed, upperVersion)
|
|
|
|
if (lowerCompare < 0) return false
|
|
if (range.upperInclusive) {
|
|
return upperCompare <= 0
|
|
}
|
|
return upperCompare < 0
|
|
}
|
|
case 'wildcard': {
|
|
if (instanceParsed.major !== range.major) return false
|
|
if (range.patch !== undefined) {
|
|
return instanceParsed.minor === range.minor
|
|
}
|
|
return true
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
export const LOADER_COMPATIBILITY: Record<string, string[]> = {
|
|
fabric: ['fabric', 'quilt'],
|
|
quilt: ['fabric', 'quilt'],
|
|
forge: ['forge'],
|
|
neoforge: ['neoforge'],
|
|
}
|
|
|
|
export function areLoadersCompatible(modLoader: string, instanceLoader: string): boolean {
|
|
if (!modLoader || !instanceLoader) return true
|
|
|
|
const modLower = modLoader.toLowerCase()
|
|
const instLower = instanceLoader.toLowerCase()
|
|
|
|
if (modLower === instLower) return true
|
|
|
|
const compatibleWithMod = LOADER_COMPATIBILITY[modLower]
|
|
if (compatibleWithMod && compatibleWithMod.includes(instLower)) return true
|
|
|
|
return false
|
|
}
|