Added metadata readers for litemods and forgemods.
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -24,6 +24,15 @@
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/adm-zip": {
|
||||
"version": "0.4.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.32.tgz",
|
||||
"integrity": "sha512-hv1O7ySn+XvP5OeDQcJFWwVb2v+GFGO1A9aMTQ5B/bzxb7WW21O8iRhVdsKKr8QwuiagzGmPP+gsUAYZ6bRddQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.0.tgz",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/dscalzi/Nebula#readme",
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.4.32",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/node": "^12.7.2",
|
||||
"@types/yargs": "^13.0.2",
|
||||
|
||||
22
src/model/forge/mcmodinfo.ts
Normal file
22
src/model/forge/mcmodinfo.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// https://mcforge.readthedocs.io/en/latest/gettingstarted/structuring/#the-mcmodinfo-file
|
||||
export interface McModInfo {
|
||||
|
||||
modid: string
|
||||
name: string
|
||||
description: string
|
||||
version: string
|
||||
mcversion: string
|
||||
url: string
|
||||
updateUrl?: string
|
||||
updateJSON: string
|
||||
authorList: string[]
|
||||
credits: string
|
||||
logoFile: string
|
||||
screenshots: string[]
|
||||
parent: string
|
||||
useDependencyInformation: boolean
|
||||
requiredMods: string[]
|
||||
dependencies: string[]
|
||||
dependants: string[] // Spelled as dependants on forge's wiki.
|
||||
|
||||
}
|
||||
7
src/model/forge/mcmodinfolist.ts
Normal file
7
src/model/forge/mcmodinfolist.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { McModInfo } from './mcmodinfo'
|
||||
|
||||
export interface McModInfoList {
|
||||
|
||||
modListVersion: number
|
||||
modList: McModInfo[]
|
||||
}
|
||||
10
src/model/liteloader/litemod.ts
Normal file
10
src/model/liteloader/litemod.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface LiteMod {
|
||||
|
||||
name: string
|
||||
version: string
|
||||
mcversion: string
|
||||
revision: string
|
||||
description: string
|
||||
author?: string
|
||||
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Stats } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { resolve } from 'url'
|
||||
import { Type } from '../../spec/type'
|
||||
import { ModuleStructure } from './module.struct'
|
||||
|
||||
@@ -18,11 +20,11 @@ export class FileStructure extends ModuleStructure {
|
||||
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return name
|
||||
}
|
||||
protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return 'TODO'
|
||||
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
|
||||
return resolve(this.baseUrl, join(this.relativeRoot, name))
|
||||
}
|
||||
protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return 'TODO'
|
||||
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
|
||||
return name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import AdmZip from 'adm-zip'
|
||||
import { Stats } from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import { resolve } from 'url'
|
||||
import { capitalize } from '../../../util/stringutils'
|
||||
import { McModInfo } from '../../forge/mcmodinfo'
|
||||
import { McModInfoList } from '../../forge/mcmodinfolist'
|
||||
import { Type } from '../../spec/type'
|
||||
import { ModuleStructure } from './module.struct'
|
||||
|
||||
export class ForgeModStructure extends ModuleStructure {
|
||||
|
||||
private forgeModMetadata: {[property: string]: McModInfo | undefined} = {}
|
||||
|
||||
constructor(
|
||||
absoluteRoot: string,
|
||||
relativeRoot: string,
|
||||
@@ -13,16 +21,77 @@ export class ForgeModStructure extends ModuleStructure {
|
||||
}
|
||||
|
||||
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return name
|
||||
const fmData = this.getForgeModMetadata(buf, name)
|
||||
return this.generateMavenIdentifier(fmData.modid, fmData.version)
|
||||
}
|
||||
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return name
|
||||
return capitalize((this.getForgeModMetadata(buf, name)).name)
|
||||
}
|
||||
protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return 'TODO'
|
||||
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
|
||||
return resolve(this.baseUrl, join(this.relativeRoot, name))
|
||||
}
|
||||
protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return 'TODO'
|
||||
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
|
||||
return null
|
||||
}
|
||||
|
||||
private getForgeModMetadata(buf: Buffer, name: string): McModInfo {
|
||||
if (!this.forgeModMetadata.hasOwnProperty(name)) {
|
||||
const zip = new AdmZip(buf)
|
||||
const zipEntries = zip.getEntries()
|
||||
|
||||
// Optifine is a tweak that can be loaded as a forge mod. It does not
|
||||
// appear to contain a mcmod.info class. This a special case we will
|
||||
// account for.
|
||||
if (name.toLowerCase().indexOf('optifine') > -1) {
|
||||
// Read zip for changelog.txt
|
||||
let rawChangelog
|
||||
for (const entry of zipEntries) {
|
||||
if (entry.entryName === 'changelog.txt') {
|
||||
rawChangelog = zip.readAsText(entry)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!rawChangelog) {
|
||||
throw new Error('Failed to read OptiFine changelog.')
|
||||
}
|
||||
const info = rawChangelog.split('\n')[0].trim()
|
||||
const version = info.split(' ')[1]
|
||||
this.forgeModMetadata[name] = ({
|
||||
modid: 'optifine',
|
||||
name: info,
|
||||
version,
|
||||
mcversion: version.substring(0, version.indexOf('_'))
|
||||
}) as unknown as McModInfo
|
||||
return this.forgeModMetadata[name] as McModInfo
|
||||
}
|
||||
|
||||
let raw
|
||||
for (const entry of zipEntries) {
|
||||
if (entry.entryName === 'mcmod.info') {
|
||||
raw = zip.readAsText(entry)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
// Assuming the main mod will be the first entry in this file.
|
||||
const resolved = JSON.parse(raw) as object
|
||||
if (resolved.hasOwnProperty('modListVersion')) {
|
||||
this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0]
|
||||
} else {
|
||||
this.forgeModMetadata[name] = (resolved as McModInfo[])[0]
|
||||
}
|
||||
} else {
|
||||
console.error(`ForgeMod ${name} does not contain mcmod.info file.`)
|
||||
this.forgeModMetadata[name] = ({
|
||||
modid: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
|
||||
name,
|
||||
version: '0.0.0'
|
||||
}) as unknown as McModInfo
|
||||
}
|
||||
}
|
||||
|
||||
return this.forgeModMetadata[name] as McModInfo
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import AdmZip from 'adm-zip'
|
||||
import { Stats } from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import { resolve } from 'url'
|
||||
import { capitalize } from '../../../util/stringutils'
|
||||
import { LiteMod } from '../../liteloader/litemod'
|
||||
import { Type } from '../../spec/type'
|
||||
import { ModuleStructure } from './module.struct'
|
||||
|
||||
export class LiteModStructure extends ModuleStructure {
|
||||
|
||||
private liteModMetadata: {[property: string]: LiteMod | undefined} = {}
|
||||
|
||||
constructor(
|
||||
absoluteRoot: string,
|
||||
relativeRoot: string,
|
||||
@@ -14,16 +20,40 @@ export class LiteModStructure extends ModuleStructure {
|
||||
}
|
||||
|
||||
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return name
|
||||
const liteModData = this.getLiteModMetadata(buf, name)
|
||||
return this.generateMavenIdentifier(liteModData.name, `${liteModData.version}-${liteModData.mcversion}`)
|
||||
}
|
||||
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return name
|
||||
return capitalize(this.getLiteModMetadata(buf, name).name)
|
||||
}
|
||||
protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return 'TODO'
|
||||
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
|
||||
return resolve(this.baseUrl, join(this.relativeRoot, name))
|
||||
}
|
||||
protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||
return 'TODO'
|
||||
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
|
||||
return null
|
||||
}
|
||||
|
||||
private getLiteModMetadata(buf: Buffer, name: string): LiteMod {
|
||||
if (!this.liteModMetadata.hasOwnProperty(name)) {
|
||||
const zip = new AdmZip(buf)
|
||||
const zipEntries = zip.getEntries()
|
||||
|
||||
let raw
|
||||
for (const entry of zipEntries) {
|
||||
if (entry.entryName === 'litemod.json') {
|
||||
raw = zip.readAsText(entry)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
this.liteModMetadata[name] = JSON.parse(raw) as LiteMod
|
||||
} else {
|
||||
throw new Error(`Litemod ${name} does not contain litemod.json file.`)
|
||||
}
|
||||
}
|
||||
|
||||
return this.liteModMetadata[name] as LiteMod
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createHash } from 'crypto'
|
||||
import { lstat, pathExists, readdir, readFile, Stats } from 'fs-extra'
|
||||
import { resolve } from 'path'
|
||||
import { Module } from '../../spec/module'
|
||||
import { Type } from '../../spec/type'
|
||||
import { Type, TypeMetadata } from '../../spec/type'
|
||||
import { BaseModelStructure } from '../basemodel.struct'
|
||||
|
||||
export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
||||
@@ -25,10 +25,14 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
||||
return this.resolvedModels
|
||||
}
|
||||
|
||||
protected generateMavenIdentifier(name: string, version: string) {
|
||||
return `generated.${this.type.toLowerCase()}:${name}-${version}@${TypeMetadata[this.type].defaultExtension}`
|
||||
}
|
||||
|
||||
protected async abstract getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
||||
protected async abstract getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
||||
protected async abstract getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
||||
protected async abstract getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
||||
protected async abstract getModuleUrl(name: string, path: string, stats: Stats): Promise<string>
|
||||
protected async abstract getModulePath(name: string, path: string, stats: Stats): Promise<string | null>
|
||||
|
||||
private async _doModuleRetrieval(): Promise<Module[]> {
|
||||
|
||||
@@ -41,7 +45,7 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
||||
const stats = await lstat(filePath)
|
||||
const buf = await readFile(filePath)
|
||||
if (stats.isFile()) {
|
||||
accumulator.push({
|
||||
const mdl: Module = {
|
||||
id: await this.getModuleId(file, filePath, stats, buf),
|
||||
name: await this.getModuleName(file, filePath, stats, buf),
|
||||
type: this.type,
|
||||
@@ -52,10 +56,14 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
||||
artifact: {
|
||||
size: stats.size,
|
||||
MD5: createHash('md5').update(buf).digest('hex'),
|
||||
url: await this.getModuleUrl(file, filePath, stats, buf),
|
||||
path: await this.getModulePath(file, filePath, stats, buf)
|
||||
url: await this.getModuleUrl(file, filePath, stats)
|
||||
}
|
||||
})
|
||||
}
|
||||
const pth = await this.getModulePath(file, filePath, stats)
|
||||
if (pth) {
|
||||
mdl.artifact.path = pth
|
||||
}
|
||||
accumulator.push(mdl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
src/util/stringutils.ts
Normal file
6
src/util/stringutils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function capitalize(str: string) {
|
||||
if (!str) {
|
||||
return str
|
||||
}
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
Reference in New Issue
Block a user