Modrinth/plugins/fileUtils.js
Geometrically 6f58e9e7bb
Redo version page (#777)
* Redo version page

* More work on editing page

* Make saving work

* Finish version editing

* Version creation (base)

* Add creation buttons

* Add file uploader to versions page

* Add version file parsing

* Finish PR

* Fix version page responsiveness and use more consistent card design

* Whoops that wasn't supposed to be there

* Fixes + allow whole page dragging

* Re-add lost merge data

* Remove debug line

* Move back to list btm

Co-authored-by: Prospector <prospectordev@gmail.com>
2022-12-20 11:15:31 -07:00

240 lines
7.1 KiB
JavaScript

import JSZip from 'jszip'
import TOML from '@iarna/toml'
import yaml from 'js-yaml'
import { formatBytes } from '~/plugins/shorthands'
/**
* @param {File | Blob} file the file to validate
* @param {{ maxSize: number, alertOnInvalid: boolean }} validationOptions the
* constraints to validate the file against
* @param validationOptions.maxSize the max file size in bytes
* @param validationOptions.alertOnInvalid if an alert should pop up describing
* each validation error
* @returns `true` if the file is valid; `false` otherwise
*/
export const fileIsValid = (file, validationOptions) => {
const { maxSize, alertOnInvalid } = validationOptions
if (maxSize !== null && maxSize !== undefined && file.size > maxSize) {
console.log(`File size: ${file.size}, max size: ${maxSize}`)
if (alertOnInvalid) {
alert(
`File ${file.name} is too big! Must be less than ${formatBytes(
maxSize
)}`
)
}
return false
}
return true
}
export const acceptFileFromProjectType = (projectType) => {
switch (projectType) {
case 'mod':
return '.jar,.zip,.litemod,application/java-archive,application/zip'
case 'plugin':
return '.jar,.zip,application/java-archive,application/zip'
case 'resourcepack':
return '.zip,application/zip'
case 'shader':
return '.zip,application/zip'
case 'modpack':
return '.mrpack,application/x-modrinth-modpack+zip'
default:
return '*'
}
}
export const inferVersionInfo = async function (
rawFile,
project,
gameVersions
) {
function versionType(number) {
if (number.includes('alpha')) {
return 'alpha'
} else if (
number.includes('beta') ||
number.match(/[^A-z](rc)[^A-z]/) || // includes `rc`
number.match(/[^A-z](pre)[^A-z]/) // includes `pre`
) {
return 'beta'
} else {
return 'release'
}
}
// TODO: This func does not handle accurate semver parsing. We should eventually
function gameVersionRange(gameVersionString, gameVersions) {
if (!gameVersionString) {
return []
}
// 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
}
const simplified = gameVersions
.filter((it) => it.version_type === 'release')
.map((it) => it.version)
return simplified.filter((version) => version.startsWith(prefix))
}
const inferFunctions = {
// Forge 1.13+
'META-INF/mods.toml': (file, zip) => {
const metadata = TOML.parse(file)
console.log(JSON.stringify(metadata))
// TODO: Parse minecraft version ranges, handle if version is set to value from manifest
if (metadata.mods && metadata.mods.length > 0) {
return {
name: `${project.title} ${metadata.mods[0].version}`,
version_number: metadata.mods[0].version,
version_type: versionType(metadata.mods[0].version),
loaders: ['forge'],
}
} else {
return {}
}
},
// Old Forge
'mcmod.info': (file) => {
const metadata = JSON.parse(file)
return {
name: metadata.version ? `${project.title} ${metadata.version}` : '',
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),
}
},
// Fabric
'fabric.mod.json': (file) => {
const metadata = JSON.parse(file)
return {
name: `${project.title} ${metadata.version}`,
version_number: metadata.version,
loaders: ['fabric'],
version_type: versionType(metadata.version),
game_versions: metadata.depends
? gameVersionRange(metadata.depends.minecraft, gameVersions)
: [],
}
},
// Quilt
'quilt.mod.json': (file) => {
const metadata = JSON.parse(file)
return {
name: `${project.title} ${metadata.quilt_loader.version}`,
version_number: metadata.quilt_loader.version,
loaders: ['quilt'],
version_type: versionType(metadata.quilt_loader.version),
game_versions: metadata.quilt_loader.depends
? gameVersionRange(
metadata.depends.find((x) => x.id === 'minecraft')
? metadata.depends.find((x) => x.id === 'minecraft').versions
: [],
gameVersions
)
: [],
}
},
// Bukkit + Other Forks
'plugin.yml': (file) => {
const metadata = yaml.load(file)
return {
name: `${project.title} ${metadata.version}`,
version_number: metadata.version,
version_type: versionType(metadata.version),
// We don't know which fork of Bukkit users are using
loaders: [],
game_versions: gameVersions
.filter(
(x) =>
x.version.startsWith(metadata['api-version']) &&
x.version_type === 'release'
)
.map((x) => x.version),
}
},
// Bungeecord + Waterfall
'bungee.yml': (file) => {
const metadata = yaml.load(file)
return {
name: `${project.title} ${metadata.version}`,
version_number: metadata.version,
version_type: versionType(metadata.version),
loaders: ['bungeecord'],
}
},
// Modpacks
'modrinth.index.json': (file) => {
const metadata = JSON.parse(file)
const loaders = []
if ('forge' in metadata.dependencies) loaders.push('forge')
if ('fabric-loader' in metadata.dependencies) loaders.push('fabric')
if ('quilt-loader' in metadata.dependencies) loaders.push('quilt')
return {
name: `${project.title} ${metadata.versionId}`,
version_number: metadata.versionId,
version_type: versionType(metadata.versionId),
loaders,
game_versions: gameVersions
.filter((x) => x.version === metadata.dependencies.minecraft)
.map((x) => x.version),
}
},
}
const zipReader = new JSZip()
const zip = await zipReader.loadAsync(rawFile)
for (const fileName in inferFunctions) {
const file = zip.file(fileName)
if (file !== null) {
const text = await file.async('text')
return inferFunctions[fileName](text, zip)
}
}
}