Externalize spec typings, use eslint.

Tslint is deprecated, as such we have moved to eslint. Linted the project with more
stringent rules. The configuration will be changed as we figure out which rules we
should keep.
This commit is contained in:
Daniel Scalzi
2020-01-24 19:27:20 -05:00
parent 8911f54039
commit 064a664687
40 changed files with 2121 additions and 493 deletions

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
dist

42
.eslintrc.json Normal file
View File

@@ -0,0 +1,42 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"semi": "off",
"@typescript-eslint/semi": [
"error",
"never"
],
"quotes": "off",
"@typescript-eslint/quotes": [
"error",
"single"
],
"indent": "off",
"@typescript-eslint/indent": [
"error",
4
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "none",
"requireLast": false
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
]
}
}

1954
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
"tsc": "tsc", "tsc": "tsc",
"build": "npm run clean && npm run tsc", "build": "npm run clean && npm run tsc",
"start": "npm run build && node dist/index.js", "start": "npm run build && node dist/index.js",
"lint": "tslint --project ." "lint": "eslint . --ext .js,.ts"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -29,17 +29,20 @@
"@types/adm-zip": "^0.4.32", "@types/adm-zip": "^0.4.32",
"@types/fs-extra": "^8.0.1", "@types/fs-extra": "^8.0.1",
"@types/node": "^12.12.25", "@types/node": "^12.12.25",
"@types/yargs": "^15.0.0", "@types/yargs": "^15.0.1",
"@typescript-eslint/eslint-plugin": "^2.17.0",
"@typescript-eslint/parser": "^2.17.0",
"eslint": "^6.8.0",
"rimraf": "^3.0.0", "rimraf": "^3.0.0",
"tslint": "^5.20.1",
"typescript": "^3.7.5" "typescript": "^3.7.5"
}, },
"dependencies": { "dependencies": {
"adm-zip": "^0.4.13", "adm-zip": "^0.4.13",
"axios": "^0.19.1", "axios": "^0.19.2",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"toml": "^3.0.0", "toml": "^3.0.0",
"yargs": "^15.1.0" "yargs": "^15.1.0",
"helios-distribution-types": "1.0.0-pre.1"
} }
} }

View File

@@ -12,11 +12,11 @@ import { VersionUtil } from './util/versionutil'
dotenv.config() dotenv.config()
function getRoot() { function getRoot(): string {
return resolvePath(process.env.ROOT as string) return resolvePath(process.env.ROOT as string)
} }
function getBaseURL() { function getBaseURL(): string {
let baseUrl = process.env.BASE_URL as string let baseUrl = process.env.BASE_URL as string
// Users must provide protocol in all other instances. // Users must provide protocol in all other instances.
if (baseUrl.indexOf('//') === -1) { if (baseUrl.indexOf('//') === -1) {
@@ -63,6 +63,7 @@ function getBaseURL() {
// }) // })
// } // }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function namePositional(yargs: yargs.Argv) { function namePositional(yargs: yargs.Argv) {
return yargs.option('name', { return yargs.option('name', {
describe: 'Distribution index file name.', describe: 'Distribution index file name.',
@@ -101,7 +102,7 @@ const initCommand: yargs.CommandModule = {
describe: 'Base init command.', describe: 'Base init command.',
builder: (yargs) => { builder: (yargs) => {
return yargs return yargs
.command(initRootCommand) .command(initRootCommand)
}, },
handler: (argv) => { handler: (argv) => {
argv._handled = true argv._handled = true
@@ -117,32 +118,32 @@ const generateServerCommand: yargs.CommandModule = {
builder: (yargs) => { builder: (yargs) => {
// yargs = rootOption(yargs) // yargs = rootOption(yargs)
return yargs return yargs
.positional('id', { .positional('id', {
describe: 'Server id.', describe: 'Server id.',
type: 'string' type: 'string'
}) })
.positional('version', { .positional('version', {
describe: 'Minecraft version.', describe: 'Minecraft version.',
type: 'string' type: 'string'
}) })
.option('forge', { .option('forge', {
describe: 'Forge version.', describe: 'Forge version.',
type: 'string', type: 'string',
default: null default: null
}) })
.option('liteloader', { .option('liteloader', {
describe: 'LiteLoader version.', describe: 'LiteLoader version.',
type: 'string', type: 'string',
default: null default: null
}) })
}, },
handler: async (argv) => { handler: async (argv) => {
argv.root = getRoot() argv.root = getRoot()
console.debug(`Root set to ${argv.root}`) console.debug(`Root set to ${argv.root}`)
console.debug(`Generating server ${argv.id} for Minecraft ${argv.version}.`, console.debug(`Generating server ${argv.id} for Minecraft ${argv.version}.`,
`\n\t├ Forge version: ${argv.forge}`, `\n\t├ Forge version: ${argv.forge}`,
`\n\t└ LiteLoader version: ${argv.liteloader}`) `\n\t└ LiteLoader version: ${argv.liteloader}`)
if (VersionUtil.isPromotionVersion(argv.forge as string)) { if (VersionUtil.isPromotionVersion(argv.forge as string)) {
console.debug(`Resolving ${argv.forge} Forge Version..`) console.debug(`Resolving ${argv.forge} Forge Version..`)
@@ -197,8 +198,8 @@ const generateCommand: yargs.CommandModule = {
describe: 'Base generate command.', describe: 'Base generate command.',
builder: (yargs) => { builder: (yargs) => {
return yargs return yargs
.command(generateServerCommand) .command(generateServerCommand)
.command(generateDistroCommand) .command(generateDistroCommand)
}, },
handler: (argv) => { handler: (argv) => {
argv._handled = true argv._handled = true
@@ -273,14 +274,14 @@ const testCommand: yargs.CommandModule = {
// Registering yargs configuration. // Registering yargs configuration.
// tslint:disable-next-line:no-unused-expression // tslint:disable-next-line:no-unused-expression
yargs yargs
.version(false) .version(false)
.scriptName('') .scriptName('')
.command(initCommand) .command(initCommand)
.command(generateCommand) .command(generateCommand)
.command(validateCommand) .command(validateCommand)
.command(latestForgeCommand) .command(latestForgeCommand)
.command(recommendedForgeCommand) .command(recommendedForgeCommand)
.command(testCommand) .command(testCommand)
.demandCommand() .demandCommand()
.help() .help()
.argv .argv

View File

@@ -2,7 +2,7 @@
export interface ModsToml { export interface ModsToml {
modLoader: string, modLoader: string
loaderVersion: string loaderVersion: string
issueTrackerURL?: string issueTrackerURL?: string
@@ -19,9 +19,9 @@ export interface ModsToml {
}> }>
dependencies?: {[modId: string]: { dependencies?: {[modId: string]: {
modId: string, modId: string
mandatory: boolean, mandatory: boolean
versionRange: string, versionRange: string
ordering?: 'NONE' | 'BEFORE' | 'AFTER' ordering?: 'NONE' | 'BEFORE' | 'AFTER'
side: 'BOTH' | 'CLIENT' | 'SERVER' side: 'BOTH' | 'CLIENT' | 'SERVER'
}} }}

View File

@@ -6,17 +6,17 @@ export interface VersionManifest113 {
type: string type: string
mainClass: string mainClass: string
inheritsFrom: string inheritsFrom: string
logging: any logging: {}
arguments: { arguments: {
game: string[] game: string[]
} }
libraries: Array<{ libraries: Array<{
name: string, name: string
downloads: { downloads: {
artifact: { artifact: {
path: string, path: string
url: string, url: string
sha1: string, sha1: string
size: number size: number
} }
} }

View File

@@ -8,13 +8,13 @@ export interface VersionManifest17 {
mainClass: string mainClass: string
inheritsFrom: string inheritsFrom: string
jar: string jar: string
logging: any logging: {}
libraries: Array<{ libraries: Array<{
name: string, name: string
url?: string, url?: string
checksums?: string[], checksums?: string[]
serverreq?: boolean, serverreq?: boolean
clientreq?: boolean, clientreq?: boolean
comment?: string comment?: string
}> }>

View File

@@ -1,25 +0,0 @@
export interface Artifact {
/**
* The size of the artifact.
*/
size: number
/**
* The MD5 hash of the artifact. This will be used to validate local artifacts.
*/
MD5: string
/**
* The artifact's download url.
*/
url: string
/**
* A relative path to where the file will be saved. This is appended to the base
* path for the module's declared type.
* If this is not specified, the path will be resolved based on the module's ID.
*/
path?: string
}

View File

@@ -1,35 +0,0 @@
import { Server } from './server'
export interface Distribution {
version: string
/**
* Global settings for Discord Rich Presence.
*/
discord?: {
/**
* Client ID for the Application registered with Discord.
*/
clientId: string,
/**
* Tootltip for the smallImageKey.
*/
smallImageText: string,
/**
* Name of the uploaded image for the small profile artwork.
*/
smallImageKey: string
}
/**
* A URL to a RSS feed. Used for loading news.
*/
rss: string
/**
* Array of server objects.
*/
servers: Server[]
}

View File

@@ -1,51 +0,0 @@
import { Artifact } from './artifact'
import { Required } from './required'
import { Type } from './type'
export interface Module {
/**
* The ID of the module. All modules that are not of type File MUST use a maven identifier.
* Version information and other metadata is pulled from the identifier. Modules which are
* stored maven style use the identifier to resolve the destination path. If the extension
* is not provided, it defaults to jar.
*
* Template
*
* my.group:arifact:version@extension
*
* my/group/artifact/version/artifact-version.extension
*
* If the module's artifact does not declare the path property, its path will be resolved from the ID.
*/
id: string
/**
* The name of the module. Used on the UI.
*/
name: string
/**
* The type of the module.
*/
type: Type
/**
* Defines whether or not the module is required. If omitted, then the module will be required.
*/
required?: Required
/**
* The download artifact for the module.
*/
artifact: Artifact
/**
* An array of sub modules declared by this module. Typically, files which require other files
* are declared as submodules. A quick example would be a mod, and the configuration file for
* that mod. Submodules can also declare submodules of their own. The file is parsed recursively,
* so there is no limit.
*/
subModules?: Module[]
}

View File

@@ -1,14 +0,0 @@
export interface Required {
/**
* If the module is required. Defaults to true if this property is omited.
*/
value?: boolean
/**
* If the module is enabled by default. Has no effect unless Required.value
* is false. Defaults to true if this property is omited.
*/
def?: boolean
}

View File

@@ -1,81 +0,0 @@
import { Module } from './module'
export interface Server {
/**
* The ID of the server. The launcher saves mod configurations and selected servers
* by ID. If the ID changes, all data related to the old ID will be wiped.
*/
id: string
/**
* The name of the server. This is what users see on the UI.
*/
name: string
/**
* A brief description of the server. Displayed on the UI to provide users more information.
*/
description: string
/**
* A URL to the server's icon. Will be displayed on the UI.
*/
icon: string
/**
* The version of the server configuration.
*/
version: string
/**
* The server's IP address.
*/
address: string
/**
* The version of minecraft that the server is running.
*/
minecraftVersion: string
/**
* Server specific settings used for Discord Rich Presence.
*/
discord?: {
/**
* Short ID for the server. Displayed on the second status line as Server: shortId.
*/
shortId: string,
/**
* Ttooltip for the largeImageKey.
*/
largeImageText: string
/**
* Name of the uploaded image for the large profile artwork.
*/
largeImageKey: string
}
/**
* Only one server in the array should have the mainServer property enabled. This
* will tell the launcher that this is the default server to select if either the
* previously selected server is invalid, or there is no previously selected server.
* If this field is not defined by any server (avoid this), the first server will
* be selected as the default. If multiple servers have mainServer enabled, the first
* one the launcher finds will be the effective value. Servers which are not the default
* may omit this property rather than explicitly setting it to false.
*/
mainServer?: boolean
/**
* Whether or not the server can be autoconnected to. If false, the server will
* not be autoconnected to even when the user has the autoconnect setting enabled.
*/
autoconnect: boolean
/**
* An array of module objects.
*/
modules: Module[]
}

View File

@@ -1,58 +0,0 @@
export enum Type {
Library = 'Library',
ForgeHosted = 'ForgeHosted',
Forge = 'Forge',
LiteLoader = 'LiteLoader',
ForgeMod = 'ForgeMod',
LiteMod = 'LiteMod',
File = 'File',
VersionManifest = 'VersionManifest'
}
export interface TypeMetadata {
id: string
defaultExtension?: string
}
export const TypeMetadata: {[property: string]: TypeMetadata} = {
Library: {
id: Type.Library,
defaultExtension: 'jar'
},
/**
* @deprecated Will be replaced by Types.Forge.
*/
ForgeHosted: {
id: Type.ForgeHosted,
defaultExtension: 'jar'
},
Forge: {
id: Type.Forge,
defaultExtension: 'jar'
},
LiteLoader: {
id: Type.LiteLoader,
defaultExtension: 'jar'
},
ForgeMod: {
id: Type.ForgeMod,
defaultExtension: 'jar'
},
LiteMod: {
id: Type.LiteMod,
defaultExtension: 'litemod'
},
File: {
id: Type.File
},
VersionManifest: {
id: Type.VersionManifest,
defaultExtension: 'json'
}
}

View File

@@ -15,7 +15,7 @@ export abstract class BaseFileStructure implements FileStructure {
this.containerDirectory = resolve(absoluteRoot, structRoot) this.containerDirectory = resolve(absoluteRoot, structRoot)
} }
public async init() { public async init(): Promise<void> {
mkdirs(this.containerDirectory) mkdirs(this.containerDirectory)
} }

View File

@@ -1,5 +1,5 @@
import { mkdirs } from 'fs-extra' import { mkdirs } from 'fs-extra'
import { Distribution } from '../../spec/distribution' import { Distribution } from 'helios-distribution-types'
import { ModelStructure } from './ModelStructure' import { ModelStructure } from './ModelStructure'
import { ServerStructure } from './server.struct' import { ServerStructure } from './server.struct'
@@ -14,7 +14,7 @@ export class DistributionStructure implements ModelStructure<Distribution> {
this.serverStruct = new ServerStructure(this.absoluteRoot, this.baseUrl) this.serverStruct = new ServerStructure(this.absoluteRoot, this.baseUrl)
} }
public async init() { public async init(): Promise<void> {
await mkdirs(this.absoluteRoot) await mkdirs(this.absoluteRoot)
await this.serverStruct.init() await this.serverStruct.init()
} }

View File

@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Stats } from 'fs' import { Stats } from 'fs'
import { Type } from 'helios-distribution-types'
import { join } from 'path' import { join } from 'path'
import { resolve } from 'url' import { resolve } from 'url'
import { Type } from '../../../spec/type'
import { ModuleStructure } from './module.struct' import { ModuleStructure } from './module.struct'
export class MiscFileStructure extends ModuleStructure { export class MiscFileStructure extends ModuleStructure {

View File

@@ -1,8 +1,8 @@
import { Stats } from 'fs-extra' import { Stats } from 'fs-extra'
import { Type } from 'helios-distribution-types'
import { join } from 'path' import { join } from 'path'
import { resolve } from 'url' import { resolve } from 'url'
import { VersionSegmented } from '../../../../util/VersionSegmented' import { VersionSegmented } from '../../../../util/VersionSegmented'
import { Type } from '../../../spec/type'
import { ModuleStructure } from './module.struct' import { ModuleStructure } from './module.struct'
export abstract class BaseForgeModStructure extends ModuleStructure implements VersionSegmented { export abstract class BaseForgeModStructure extends ModuleStructure implements VersionSegmented {
@@ -17,9 +17,11 @@ export abstract class BaseForgeModStructure extends ModuleStructure implements V
public abstract isForVersion(version: string): boolean public abstract isForVersion(version: string): boolean
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> { protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
return resolve(this.baseUrl, join(this.relativeRoot, name)) return resolve(this.baseUrl, join(this.relativeRoot, name))
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> { protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
return null return null
} }

View File

@@ -10,7 +10,7 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
public static readonly IMPLEMENTATION_VERSION_REGEX = /^Implementation-Version: (.+)[\r\n]/ public static readonly IMPLEMENTATION_VERSION_REGEX = /^Implementation-Version: (.+)[\r\n]/
public static isForVersion(version: string) { public static isForVersion(version: string): boolean {
return VersionUtil.isVersionAcceptable(version, [13, 14, 15]) return VersionUtil.isVersionAcceptable(version, [13, 14, 15])
} }
@@ -37,8 +37,7 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
} }
private getForgeModMetadata(buf: Buffer, name: string): ModsToml { private getForgeModMetadata(buf: Buffer, name: string): ModsToml {
if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
if (!this.forgeModMetadata.hasOwnProperty(name)) {
const zip = new AdmZip(buf) const zip = new AdmZip(buf)
const zipEntries = zip.getEntries() const zipEntries = zip.getEntries()

View File

@@ -8,7 +8,7 @@ import { BaseForgeModStructure } from '../forgemod.struct'
export class ForgeModStructure17 extends BaseForgeModStructure { export class ForgeModStructure17 extends BaseForgeModStructure {
public static isForVersion(version: string) { public static isForVersion(version: string): boolean {
return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12]) return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12])
} }
@@ -35,7 +35,7 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
} }
private getForgeModMetadata(buf: Buffer, name: string): McModInfo { private getForgeModMetadata(buf: Buffer, name: string): McModInfo {
if (!this.forgeModMetadata.hasOwnProperty(name)) { if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
const zip = new AdmZip(buf) const zip = new AdmZip(buf)
const zipEntries = zip.getEntries() const zipEntries = zip.getEntries()
@@ -79,7 +79,7 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
// Assuming the main mod will be the first entry in this file. // Assuming the main mod will be the first entry in this file.
try { try {
const resolved = JSON.parse(raw) as object const resolved = JSON.parse(raw) as object
if (resolved.hasOwnProperty('modListVersion')) { if (Object.prototype.hasOwnProperty.call(resolved, 'modListVersion')) {
this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0] this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0]
} else { } else {
this.forgeModMetadata[name] = (resolved as McModInfo[])[0] this.forgeModMetadata[name] = (resolved as McModInfo[])[0]

View File

@@ -1,10 +1,10 @@
import AdmZip from 'adm-zip' import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra' import { Stats } from 'fs-extra'
import { Type } from 'helios-distribution-types'
import { join } from 'path' import { join } from 'path'
import { resolve } from 'url' import { resolve } from 'url'
import { capitalize } from '../../../../util/stringutils' import { capitalize } from '../../../../util/stringutils'
import { LiteMod } from '../../../liteloader/litemod' import { LiteMod } from '../../../liteloader/litemod'
import { Type } from '../../../spec/type'
import { ModuleStructure } from './module.struct' import { ModuleStructure } from './module.struct'
export class LiteModStructure extends ModuleStructure { export class LiteModStructure extends ModuleStructure {
@@ -26,15 +26,17 @@ export class LiteModStructure extends ModuleStructure {
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> { protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize(this.getLiteModMetadata(buf, name).name) return capitalize(this.getLiteModMetadata(buf, name).name)
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> { protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
return resolve(this.baseUrl, join(this.relativeRoot, name)) return resolve(this.baseUrl, join(this.relativeRoot, name))
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> { protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
return null return null
} }
private getLiteModMetadata(buf: Buffer, name: string): LiteMod { private getLiteModMetadata(buf: Buffer, name: string): LiteMod {
if (!this.liteModMetadata.hasOwnProperty(name)) { if (!Object.prototype.hasOwnProperty.call(this.liteModMetadata, name)) {
const zip = new AdmZip(buf) const zip = new AdmZip(buf)
const zipEntries = zip.getEntries() const zipEntries = zip.getEntries()

View File

@@ -1,8 +1,7 @@
import { createHash } from 'crypto' import { createHash } from 'crypto'
import { lstat, pathExists, readdir, readFile, Stats } from 'fs-extra' import { lstat, pathExists, readdir, readFile, Stats } from 'fs-extra'
import { Module, Type, TypeMetadata } from 'helios-distribution-types'
import { resolve } from 'path' import { resolve } from 'path'
import { Module } from '../../../spec/module'
import { Type, TypeMetadata } from '../../../spec/type'
import { BaseModelStructure } from '../basemodel.struct' import { BaseModelStructure } from '../basemodel.struct'
export abstract class ModuleStructure extends BaseModelStructure<Module> { export abstract class ModuleStructure extends BaseModelStructure<Module> {
@@ -25,7 +24,7 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
return this.resolvedModels return this.resolvedModels
} }
protected generateMavenIdentifier(name: string, version: string) { protected generateMavenIdentifier(name: string, version: string): string {
return `generated.${this.type.toLowerCase()}:${name}:${version}@${TypeMetadata[this.type].defaultExtension}` return `generated.${this.type.toLowerCase()}:${name}:${version}@${TypeMetadata[this.type].defaultExtension}`
} }

View File

@@ -1,9 +1,9 @@
import { lstat, mkdirs, pathExists, readdir, readFile, writeFile } from 'fs-extra' import { lstat, mkdirs, pathExists, readdir, readFile, writeFile } from 'fs-extra'
import { Server } from 'helios-distribution-types'
import { dirname, join, resolve as resolvePath } from 'path' import { dirname, join, resolve as resolvePath } from 'path'
import { resolve as resolveUrl } from 'url' import { resolve as resolveUrl } from 'url'
import { VersionSegmentedRegistry } from '../../../util/VersionSegmentedRegistry' import { VersionSegmentedRegistry } from '../../../util/VersionSegmentedRegistry'
import { ServerMeta } from '../../nebula/servermeta' import { ServerMeta } from '../../nebula/servermeta'
import { Server } from '../../spec/server'
import { BaseModelStructure } from './basemodel.struct' import { BaseModelStructure } from './basemodel.struct'
import { MiscFileStructure } from './module/file.struct' import { MiscFileStructure } from './module/file.struct'
import { LiteModStructure } from './module/litemod.struct' import { LiteModStructure } from './module/litemod.struct'
@@ -19,7 +19,7 @@ export class ServerStructure extends BaseModelStructure<Server> {
super(absoluteRoot, '', 'servers', baseUrl) super(absoluteRoot, '', 'servers', baseUrl)
} }
public async getSpecModel() { public async getSpecModel(): Promise<Server[]> {
if (this.resolvedModels == null) { if (this.resolvedModels == null) {
this.resolvedModels = await this._doSeverRetrieval() this.resolvedModels = await this._doSeverRetrieval()
} }
@@ -33,7 +33,7 @@ export class ServerStructure extends BaseModelStructure<Server> {
forgeVersion?: string forgeVersion?: string
liteloaderVersion?: string liteloaderVersion?: string
} }
) { ): Promise<void> {
const effectiveId = `${id}-${minecraftVersion}` const effectiveId = `${id}-${minecraftVersion}`
const absoluteServerRoot = resolvePath(this.containerDirectory, effectiveId) const absoluteServerRoot = resolvePath(this.containerDirectory, effectiveId)
const relativeServerRoot = join(this.relativeRoot, effectiveId) const relativeServerRoot = join(this.relativeRoot, effectiveId)
@@ -81,7 +81,7 @@ export class ServerStructure extends BaseModelStructure<Server> {
const match = this.ID_REGEX.exec(file) const match = this.ID_REGEX.exec(file)
if (match == null) { if (match == null) {
console.warn(`Server directory ${file} does not match the defined standard.`) console.warn(`Server directory ${file} does not match the defined standard.`)
console.warn(`All server ids must end with -<minecraft version> (ex. -1.12.2)`) console.warn('All server ids must end with -<minecraft version> (ex. -1.12.2)')
continue continue
} }

View File

@@ -15,38 +15,40 @@ export abstract class BaseMavenRepo extends BaseFileStructure {
super(absoluteRoot, relativeRoot, structRoot) super(absoluteRoot, relativeRoot, structRoot)
} }
public getArtifactById(mavenIdentifier: string, extension?: string): string | null { public getArtifactById(mavenIdentifier: string, extension?: string): string {
const resolved = MavenUtil.mavenIdentifierToString(mavenIdentifier, extension) return resolve(this.containerDirectory, MavenUtil.mavenIdentifierToString(mavenIdentifier, extension))
return resolved == null ? null : resolve(this.containerDirectory, resolved)
} }
public getArtifactByComponents(group: string, artifact: string, version: string, public getArtifactByComponents(
classifier?: string, extension = 'jar'): string { group: string, artifact: string, version: string, classifier?: string, extension = 'jar'
): string {
return resolve(this.containerDirectory, return resolve(this.containerDirectory,
MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)) MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension))
} }
public getArtifactUrlByComponents(baseURL: string, group: string, artifact: string, version: string, public getArtifactUrlByComponents(
classifier?: string, extension = 'jar'): string { baseURL: string, group: string, artifact: string, version: string, classifier?: string, extension = 'jar'
): string {
return resolveURL(baseURL, join(this.relativeRoot, return resolveURL(baseURL, join(this.relativeRoot,
MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension))) MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)))
} }
public async artifactExists(path: string) { public async artifactExists(path: string): Promise<boolean> {
return pathExists(path) return pathExists(path)
} }
public async downloadArtifactById(url: string, mavenIdentifier: string, extension?: string) { public async downloadArtifactById(url: string, mavenIdentifier: string, extension?: string): Promise<void> {
return this.downloadArtifactBase(url, MavenUtil.mavenIdentifierToString(mavenIdentifier, extension) as string) return this.downloadArtifactBase(url, MavenUtil.mavenIdentifierToString(mavenIdentifier, extension) as string)
} }
public async downloadArtifactByComponents(url: string, group: string, artifact: string, version: string, public async downloadArtifactByComponents(
classifier?: string, extension?: string) { url: string, group: string, artifact: string, version: string, classifier?: string, extension?: string
): Promise<void> {
return this.downloadArtifactBase(url, return this.downloadArtifactBase(url,
MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)) MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension))
} }
private async downloadArtifactBase(url: string, relative: string) { private async downloadArtifactBase(url: string, relative: string): Promise<void> {
const resolvedURL = resolveURL(url, relative).toString() const resolvedURL = resolveURL(url, relative).toString()
console.debug(`Downloading ${resolvedURL}..`) console.debug(`Downloading ${resolvedURL}..`)
const response = await axios({ const response = await axios({

View File

@@ -18,14 +18,14 @@ export class LibRepoStructure extends BaseMavenRepo {
super(absoluteRoot, relativeRoot, 'lib') super(absoluteRoot, relativeRoot, 'lib')
} }
public getLocalForge(version: string, classifier?: string) { public getLocalForge(version: string, classifier?: string): string {
return this.getArtifactByComponents( return this.getArtifactByComponents(
LibRepoStructure.FORGE_GROUP, LibRepoStructure.FORGE_GROUP,
LibRepoStructure.FORGE_ARTIFACT, LibRepoStructure.FORGE_ARTIFACT,
version, classifier, 'jar') version, classifier, 'jar')
} }
public getLocalLiteLoader(version: string, classifier?: string) { public getLocalLiteLoader(version: string, classifier?: string): string {
return this.getArtifactByComponents( return this.getArtifactByComponents(
LibRepoStructure.LITELOADER_GROUP, LibRepoStructure.LITELOADER_GROUP,
LibRepoStructure.LITELOADER_ARTIFACT, LibRepoStructure.LITELOADER_ARTIFACT,

View File

@@ -17,25 +17,25 @@ export class RepoStructure extends BaseFileStructure {
this.versionRepoStruct = new VersionRepoStructure(this.containerDirectory, this.relativeRoot) this.versionRepoStruct = new VersionRepoStructure(this.containerDirectory, this.relativeRoot)
} }
public async init() { public async init(): Promise<void> {
super.init() super.init()
await this.libRepoStruct.init() await this.libRepoStruct.init()
await this.versionRepoStruct.init() await this.versionRepoStruct.init()
} }
public getLibRepoStruct() { public getLibRepoStruct(): LibRepoStructure {
return this.libRepoStruct return this.libRepoStruct
} }
public getVersionRepoStruct() { public getVersionRepoStruct(): VersionRepoStructure {
return this.versionRepoStruct return this.versionRepoStruct
} }
public getTempDirectory() { public getTempDirectory(): string {
return join(this.absoluteRoot, 'temp') return join(this.absoluteRoot, 'temp')
} }
public getWorkDirectory() { public getWorkDirectory(): string {
return join(this.absoluteRoot, 'work') return join(this.absoluteRoot, 'work')
} }

View File

@@ -11,16 +11,16 @@ export class VersionRepoStructure extends BaseFileStructure {
super(absoluteRoot, relativeRoot, 'versions') super(absoluteRoot, relativeRoot, 'versions')
} }
public getFileName(minecraftVersion: string, forgeVersion: string) { public getFileName(minecraftVersion: string, forgeVersion: string): string {
return `${minecraftVersion}-forge-${forgeVersion}` return `${minecraftVersion}-forge-${forgeVersion}`
} }
public getVersionManifest(minecraftVersion: string, forgeVersion: string) { public getVersionManifest(minecraftVersion: string, forgeVersion: string): string {
const fileName = this.getFileName(minecraftVersion, forgeVersion) const fileName = this.getFileName(minecraftVersion, forgeVersion)
return join(this.containerDirectory, fileName, `${fileName}.json`) return join(this.containerDirectory, fileName, `${fileName}.json`)
} }
public getVersionManifestURL(url: string, minecraftVersion: string, forgeVersion: string) { public getVersionManifestURL(url: string, minecraftVersion: string, forgeVersion: string): string {
const fileName = this.getFileName(minecraftVersion, forgeVersion) const fileName = this.getFileName(minecraftVersion, forgeVersion)
return resolveURL(url, join(this.relativeRoot, fileName, `${fileName}.json`)) return resolveURL(url, join(this.relativeRoot, fileName, `${fileName}.json`))
} }

View File

@@ -1,4 +1,4 @@
import { Module } from '../model/spec/module' import { Module } from 'helios-distribution-types'
import { VersionSegmented } from '../util/VersionSegmented' import { VersionSegmented } from '../util/VersionSegmented'
import { Resolver } from './resolver' import { Resolver } from './resolver'

View File

@@ -1,9 +1,8 @@
import { spawn } from 'child_process' import { spawn } from 'child_process'
import { copy, lstat, mkdirs, move, pathExists, readFile, remove, writeFile } from 'fs-extra' import { copy, lstat, mkdirs, move, pathExists, readFile, remove, writeFile } from 'fs-extra'
import { Module, Type } from 'helios-distribution-types'
import { basename, dirname, join } from 'path' import { basename, dirname, join } from 'path'
import { VersionManifest113 } from '../../../model/forge/versionmanifest113' import { VersionManifest113 } from '../../../model/forge/versionmanifest113'
import { Module } from '../../../model/spec/module'
import { Type } from '../../../model/spec/type'
import { LibRepoStructure } from '../../../model/struct/repo/librepo.struct' import { LibRepoStructure } from '../../../model/struct/repo/librepo.struct'
import { JavaUtil } from '../../../util/javautil' import { JavaUtil } from '../../../util/javautil'
import { MavenUtil } from '../../../util/maven' import { MavenUtil } from '../../../util/maven'
@@ -12,7 +11,7 @@ import { ForgeResolver } from '../forge.resolver'
export class Forge113Adapter extends ForgeResolver { export class Forge113Adapter extends ForgeResolver {
public static isForVersion(version: string) { public static isForVersion(version: string): boolean {
return VersionUtil.isVersionAcceptable(version, [13, 14, 15]) return VersionUtil.isVersionAcceptable(version, [13, 14, 15])
} }
@@ -34,12 +33,12 @@ export class Forge113Adapter extends ForgeResolver {
return Forge113Adapter.isForVersion(version) return Forge113Adapter.isForVersion(version)
} }
private async process() { private async process(): Promise<Module> {
const libRepo = this.repoStructure.getLibRepoStruct() const libRepo = this.repoStructure.getLibRepoStruct()
const installerPath = libRepo.getLocalForge(this.artifactVersion, 'installer') const installerPath = libRepo.getLocalForge(this.artifactVersion, 'installer')
console.debug(`Checking for forge installer at ${installerPath}..`) console.debug(`Checking for forge installer at ${installerPath}..`)
if (!await libRepo.artifactExists(installerPath)) { if (!await libRepo.artifactExists(installerPath)) {
console.debug(`Forge installer not found locally, initializing download..`) console.debug('Forge installer not found locally, initializing download..')
await libRepo.downloadArtifactByComponents( await libRepo.downloadArtifactByComponents(
this.REMOTE_REPOSITORY, this.REMOTE_REPOSITORY,
LibRepoStructure.FORGE_GROUP, LibRepoStructure.FORGE_GROUP,
@@ -65,7 +64,7 @@ export class Forge113Adapter extends ForgeResolver {
// Required for the installer to function. // Required for the installer to function.
await writeFile(join(workDir, 'launcher_profiles.json'), JSON.stringify({})) await writeFile(join(workDir, 'launcher_profiles.json'), JSON.stringify({}))
console.debug(`Spawning forge installer`) console.debug('Spawning forge installer')
console.log('============== [ IMPORTANT ] ==============') console.log('============== [ IMPORTANT ] ==============')
console.log('When the installer opens please set the client installation directory to:') console.log('When the installer opens please set the client installation directory to:')
@@ -96,7 +95,7 @@ export class Forge113Adapter extends ForgeResolver {
return forgeModule return forgeModule
} }
private async processLibraries(manifest: VersionManifest113) { private async processLibraries(manifest: VersionManifest113): Promise<Module[]> {
const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries') const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries')
const libRepo = this.repoStructure.getLibRepoStruct() const libRepo = this.repoStructure.getLibRepoStruct()
@@ -150,7 +149,7 @@ export class Forge113Adapter extends ForgeResolver {
} }
private async processForgeModule(versionManifest: VersionManifest113) { private async processForgeModule(versionManifest: VersionManifest113): Promise<Module> {
const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries') const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries')
const mcpVersion = this.getMCPVersion(versionManifest.arguments.game) const mcpVersion = this.getMCPVersion(versionManifest.arguments.game)
@@ -267,7 +266,7 @@ export class Forge113Adapter extends ForgeResolver {
return forgeModule return forgeModule
} }
private async processVersionManifest() { private async processVersionManifest(): Promise<[VersionManifest113, Module]> {
const workDir = this.repoStructure.getWorkDirectory() const workDir = this.repoStructure.getWorkDirectory()
const versionRepo = this.repoStructure.getVersionRepoStruct() const versionRepo = this.repoStructure.getVersionRepoStruct()
const versionName = versionRepo.getFileName(this.minecraftVersion, this.forgeVersion) const versionName = versionRepo.getFileName(this.minecraftVersion, this.forgeVersion)
@@ -297,8 +296,8 @@ export class Forge113Adapter extends ForgeResolver {
return [versionManifest, versionManifestModule] return [versionManifest, versionManifestModule]
} }
private executeInstaller(installerExec: string) { private executeInstaller(installerExec: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise(resolve => {
const child = spawn(JavaUtil.getJavaExecutable(), [ const child = spawn(JavaUtil.getJavaExecutable(), [
'-jar', '-jar',
installerExec installerExec
@@ -307,14 +306,14 @@ export class Forge113Adapter extends ForgeResolver {
}) })
child.stdout.on('data', (data) => console.log('[Forge Installer]', data.toString('utf8').trim())) child.stdout.on('data', (data) => console.log('[Forge Installer]', data.toString('utf8').trim()))
child.stderr.on('data', (data) => console.error('[Forge Installer]', data.toString('utf8').trim())) child.stderr.on('data', (data) => console.error('[Forge Installer]', data.toString('utf8').trim()))
child.on('close', (code, signal) => { child.on('close', code => {
console.log('[Forge Installer]', 'Exited with code', code) console.log('[Forge Installer]', 'Exited with code', code)
resolve() resolve()
}) })
}) })
} }
private getMCPVersion(args: string[]) { private getMCPVersion(args: string[]): string | null {
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
if (args[i] === '--fml.mcpVersion') { if (args[i] === '--fml.mcpVersion') {
return args[i + 1] return args[i + 1]

View File

@@ -1,10 +1,9 @@
import AdmZip from 'adm-zip' import AdmZip from 'adm-zip'
import { createHash } from 'crypto' import { createHash } from 'crypto'
import { copy, lstat, mkdirs, pathExists, readFile, remove } from 'fs-extra' import { copy, lstat, mkdirs, pathExists, readFile, remove } from 'fs-extra'
import { Module, Type } from 'helios-distribution-types'
import { basename, join } from 'path' import { basename, join } from 'path'
import { VersionManifest17 } from '../../../model/forge/versionmanifest17' import { VersionManifest17 } from '../../../model/forge/versionmanifest17'
import { Module } from '../../../model/spec/module'
import { Type } from '../../../model/spec/type'
import { LibRepoStructure } from '../../../model/struct/repo/librepo.struct' import { LibRepoStructure } from '../../../model/struct/repo/librepo.struct'
import { MavenUtil } from '../../../util/maven' import { MavenUtil } from '../../../util/maven'
import { PackXZExtractWrapper } from '../../../util/PackXZExtractWrapper' import { PackXZExtractWrapper } from '../../../util/PackXZExtractWrapper'
@@ -13,7 +12,7 @@ import { ForgeResolver } from '../forge.resolver'
export class Forge17Adapter extends ForgeResolver { export class Forge17Adapter extends ForgeResolver {
public static isForVersion(version: string) { public static isForVersion(version: string): boolean {
return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12]) return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12])
} }
@@ -31,16 +30,16 @@ export class Forge17Adapter extends ForgeResolver {
return this.getForgeByVersion() return this.getForgeByVersion()
} }
public isForVersion(version: string) { public isForVersion(version: string): boolean {
return Forge17Adapter.isForVersion(version) return Forge17Adapter.isForVersion(version)
} }
public async getForgeByVersion() { public async getForgeByVersion(): Promise<Module> {
const libRepo = this.repoStructure.getLibRepoStruct() const libRepo = this.repoStructure.getLibRepoStruct()
const targetLocalPath = libRepo.getLocalForge(this.artifactVersion, 'universal') const targetLocalPath = libRepo.getLocalForge(this.artifactVersion, 'universal')
console.debug(`Checking for forge version at ${targetLocalPath}..`) console.debug(`Checking for forge version at ${targetLocalPath}..`)
if (!await libRepo.artifactExists(targetLocalPath)) { if (!await libRepo.artifactExists(targetLocalPath)) {
console.debug(`Forge not found locally, initializing download..`) console.debug('Forge not found locally, initializing download..')
await libRepo.downloadArtifactByComponents( await libRepo.downloadArtifactByComponents(
this.REMOTE_REPOSITORY, this.REMOTE_REPOSITORY,
LibRepoStructure.FORGE_GROUP, LibRepoStructure.FORGE_GROUP,
@@ -121,7 +120,7 @@ export class Forge17Adapter extends ForgeResolver {
} }
} }
} else { } else {
console.debug(`Not found locally, downloading..`) console.debug('Not found locally, downloading..')
queueDownload = true queueDownload = true
} }
@@ -176,12 +175,13 @@ export class Forge17Adapter extends ForgeResolver {
return forgeModule return forgeModule
} }
private determineExtension(checksums: string[] | undefined) { private determineExtension(checksums: string[] | undefined): string {
return checksums != null && checksums.length > 1 ? 'jar.pack.xz' : 'jar' return checksums != null && checksums.length > 1 ? 'jar.pack.xz' : 'jar'
} }
private async processPackXZFiles( private async processPackXZFiles(
processingQueue: Array<{id: string, localPath: string}>): Promise<Array<{id: string, MD5: string}>> { processingQueue: Array<{id: string, localPath: string}>
): Promise<Array<{id: string, MD5: string}>> {
const accumulator = [] const accumulator = []

View File

@@ -1,6 +1,6 @@
import { createHash } from 'crypto' import { createHash } from 'crypto'
import { Stats } from 'fs-extra' import { Stats } from 'fs-extra'
import { Artifact } from '../../model/spec/artifact' import { Artifact } from 'helios-distribution-types'
import { RepoStructure } from '../../model/struct/repo/repo.struct' import { RepoStructure } from '../../model/struct/repo/repo.struct'
import { BaseResolver } from '../baseresolver' import { BaseResolver } from '../baseresolver'
@@ -25,7 +25,7 @@ export abstract class ForgeResolver extends BaseResolver {
// Coverage is not 100% but that doesnt matter. // Coverage is not 100% but that doesnt matter.
// It's enough and you should always use the latest version anyway. // It's enough and you should always use the latest version anyway.
public inferArtifactVersion() { public inferArtifactVersion(): string {
const version = `${this.minecraftVersion}-${this.forgeVersion}` const version = `${this.minecraftVersion}-${this.forgeVersion}`
const ver = this.forgeVersion.split('.') const ver = this.forgeVersion.split('.')

View File

@@ -1,4 +1,4 @@
import { Module } from '../model/spec/module' import { Module } from 'helios-distribution-types'
export interface Resolver { export interface Resolver {

View File

@@ -4,24 +4,24 @@ import { JavaUtil } from './javautil'
export class PackXZExtractWrapper { export class PackXZExtractWrapper {
public static getPackXZExtract() { public static getPackXZExtract(): string {
return join(process.cwd(), 'libraries', 'java', 'PackXZExtract.jar') return join(process.cwd(), 'libraries', 'java', 'PackXZExtract.jar')
} }
public static extractUnpack(paths: string[]) { public static extractUnpack(paths: string[]): Promise<void> {
return PackXZExtractWrapper.execute('-packxz', paths) return PackXZExtractWrapper.execute('-packxz', paths)
} }
public static extract(paths: string[]) { public static extract(paths: string[]): Promise<void> {
return PackXZExtractWrapper.execute('-xz', paths) return PackXZExtractWrapper.execute('-xz', paths)
} }
public static unpack(paths: string[]) { public static unpack(paths: string[]): Promise<void> {
return PackXZExtractWrapper.execute('-pack', paths) return PackXZExtractWrapper.execute('-pack', paths)
} }
private static execute(command: string, paths: string[]) { private static execute(command: string, paths: string[]): Promise<void> {
return new Promise((resolve, reject) => { return new Promise(resolve => {
const child = spawn(JavaUtil.getJavaExecutable(), [ const child = spawn(JavaUtil.getJavaExecutable(), [
'-jar', '-jar',
PackXZExtractWrapper.getPackXZExtract(), PackXZExtractWrapper.getPackXZExtract(),
@@ -30,7 +30,7 @@ export class PackXZExtractWrapper {
]) ])
child.stdout.on('data', (data) => console.log('[PackXZExtract]', data.toString('utf8').trim())) child.stdout.on('data', (data) => console.log('[PackXZExtract]', data.toString('utf8').trim()))
child.stderr.on('data', (data) => console.error('[PackXZExtract]', data.toString('utf8').trim())) child.stderr.on('data', (data) => console.error('[PackXZExtract]', data.toString('utf8').trim()))
child.on('close', (code, signal) => { child.on('close', code => {
console.log('[PackXZExtract]', 'Exited with code', code) console.log('[PackXZExtract]', 'Exited with code', code)
resolve() resolve()
}) })

View File

@@ -2,6 +2,8 @@ import { ForgeModStructure113 } from '../model/struct/model/module/forgemod/forg
import { ForgeModStructure17 } from '../model/struct/model/module/forgemod/forgemod17.struct' import { ForgeModStructure17 } from '../model/struct/model/module/forgemod/forgemod17.struct'
import { Forge113Adapter } from '../resolver/forge/adapter/forge113.resolver' import { Forge113Adapter } from '../resolver/forge/adapter/forge113.resolver'
import { Forge17Adapter } from '../resolver/forge/adapter/forge17.resolver' import { Forge17Adapter } from '../resolver/forge/adapter/forge17.resolver'
import { ForgeResolver } from '../resolver/forge/forge.resolver'
import { BaseForgeModStructure } from '../model/struct/model/module/forgemod.struct'
export class VersionSegmentedRegistry { export class VersionSegmentedRegistry {
@@ -20,7 +22,8 @@ export class VersionSegmentedRegistry {
forgeVersion: string, forgeVersion: string,
absoluteRoot: string, absoluteRoot: string,
relativeRoot: string, relativeRoot: string,
baseURL: string) { baseURL: string
): ForgeResolver {
for (const impl of VersionSegmentedRegistry.FORGE_ADAPTER_IMPL) { for (const impl of VersionSegmentedRegistry.FORGE_ADAPTER_IMPL) {
if (impl.isForVersion(minecraftVersion)) { if (impl.isForVersion(minecraftVersion)) {
return new impl(absoluteRoot, relativeRoot, baseURL, minecraftVersion, forgeVersion) return new impl(absoluteRoot, relativeRoot, baseURL, minecraftVersion, forgeVersion)
@@ -34,7 +37,7 @@ export class VersionSegmentedRegistry {
absoluteRoot: string, absoluteRoot: string,
relativeRoot: string, relativeRoot: string,
baseUrl: string baseUrl: string
) { ): BaseForgeModStructure {
for (const impl of VersionSegmentedRegistry.FORGEMOD_STRUCT_IML) { for (const impl of VersionSegmentedRegistry.FORGEMOD_STRUCT_IML) {
if (impl.isForVersion(minecraftVersion)) { if (impl.isForVersion(minecraftVersion)) {
return new impl(absoluteRoot, relativeRoot, baseUrl) return new impl(absoluteRoot, relativeRoot, baseUrl)

View File

@@ -1,6 +1,6 @@
export class JavaUtil { export class JavaUtil {
public static getJavaExecutable() { public static getJavaExecutable(): string {
return process.env.JAVA_EXECUTABLE as string return process.env.JAVA_EXECUTABLE as string
} }

View File

@@ -6,8 +6,13 @@ export class MavenUtil {
public static readonly ID_REGEX = /(.+):(.+):([^@]+)()(?:@{1}(.+)$)?/ public static readonly ID_REGEX = /(.+):(.+):([^@]+)()(?:@{1}(.+)$)?/
public static readonly ID_REGEX_WITH_CLASSIFIER = /(.+):(.+):(?:([^@]+)(?:-([a-zA-Z]+)))(?:@{1}(.+)$)?/ public static readonly ID_REGEX_WITH_CLASSIFIER = /(.+):(.+):(?:([^@]+)(?:-([a-zA-Z]+)))(?:@{1}(.+)$)?/
public static mavenComponentsToIdentifier(group: string, artifact: string, version: string, public static mavenComponentsToIdentifier(
classifier?: string, extension?: string) { group: string,
artifact: string,
version: string,
classifier?: string,
extension?: string
): string {
return `${group}:${artifact}:${version}${classifier != null ? `:${classifier}` : ''}${extension != null ? `@${extension}` : ''}` return `${group}:${artifact}:${version}${classifier != null ? `:${classifier}` : ''}${extension != null ? `@${extension}` : ''}`
} }
@@ -15,7 +20,13 @@ export class MavenUtil {
return MavenUtil.ID_REGEX.test(id) || MavenUtil.ID_REGEX_WITH_CLASSIFIER.test(id) return MavenUtil.ID_REGEX.test(id) || MavenUtil.ID_REGEX_WITH_CLASSIFIER.test(id)
} }
public static getMavenComponents(id: string, extension = 'jar') { public static getMavenComponents(id: string, extension = 'jar'): {
group: string
artifact: string
version: string
classifier?: string
extension: string
} {
if (!MavenUtil.isMavenIdentifier(id)) { if (!MavenUtil.isMavenIdentifier(id)) {
throw new Error('Id is not a maven identifier.') throw new Error('Id is not a maven identifier.')
} }
@@ -41,39 +52,37 @@ export class MavenUtil {
throw new Error('Failed to process maven data.') throw new Error('Failed to process maven data.')
} }
public static mavenIdentifierToString(id: string, extension = 'jar') { public static mavenIdentifierToString(id: string, extension = 'jar'): string {
const tmp = MavenUtil.getMavenComponents(id, extension) const tmp = MavenUtil.getMavenComponents(id, extension)
if (tmp != null) { return MavenUtil.mavenComponentsToString(
return MavenUtil.mavenComponentsToString(tmp.group, tmp.artifact, tmp.version, tmp.group, tmp.artifact, tmp.version, tmp.classifier, tmp.extension
tmp.classifier, tmp.extension) )
} else {
return null
}
} }
public static mavenComponentsToString(group: string, artifact: string, version: string, public static mavenComponentsToString(
classifier?: string, extension = 'jar') { group: string, artifact: string, version: string, classifier?: string, extension = 'jar'
): string {
return `${group.replace(/\./g, '/')}/${artifact}/${version}/${artifact}-${version}${classifier != null ? `-${classifier}` : ''}.${extension}` return `${group.replace(/\./g, '/')}/${artifact}/${version}/${artifact}-${version}${classifier != null ? `-${classifier}` : ''}.${extension}`
} }
public static mavenIdentifierToUrl(id: string, extension = 'jar') { public static mavenIdentifierToUrl(id: string, extension = 'jar'): URL {
const res = MavenUtil.mavenIdentifierToString(id, extension) return new URL(MavenUtil.mavenIdentifierToString(id, extension))
return res == null ? null : new URL(res)
} }
public static mavenComponentsToUrl(group: string, artifact: string, version: string, public static mavenComponentsToUrl(
classifier?: string, extension = 'jar') { group: string, artifact: string, version: string, classifier?: string, extension = 'jar'
): URL {
return new URL(MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)) return new URL(MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension))
} }
public static mavenIdentifierToPath(id: string, extension = 'jar') { public static mavenIdentifierToPath(id: string, extension = 'jar'): string {
const res = MavenUtil.mavenIdentifierToString(id, extension) return normalize(MavenUtil.mavenIdentifierToString(id, extension))
return res == null ? null : normalize(res)
} }
public static mavenComponentsToPath(group: string, artifact: string, version: string, public static mavenComponentsToPath(
classifier?: string, extension = 'jar') { group: string, artifact: string, version: string, classifier?: string, extension = 'jar'
): string {
return normalize(MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)) return normalize(MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension))
} }

View File

@@ -1,4 +1,4 @@
export function capitalize(str: string) { export function capitalize(str: string): string {
if (!str) { if (!str) {
return str return str
} }

View File

@@ -10,11 +10,11 @@ export class VersionUtil {
public static readonly MINECRAFT_VERSION_REGEX = /(\d+).(\d+).(\d+)/ public static readonly MINECRAFT_VERSION_REGEX = /(\d+).(\d+).(\d+)/
public static isMinecraftVersion(version: string) { public static isMinecraftVersion(version: string): boolean {
return VersionUtil.MINECRAFT_VERSION_REGEX.test(version) return VersionUtil.MINECRAFT_VERSION_REGEX.test(version)
} }
public static getMinecraftVersionComponents(version: string) { public static getMinecraftVersionComponents(version: string): { major: number, minor: number, revision: number } {
if (VersionUtil.isMinecraftVersion(version)) { if (VersionUtil.isMinecraftVersion(version)) {
const result = VersionUtil.MINECRAFT_VERSION_REGEX.exec(version) const result = VersionUtil.MINECRAFT_VERSION_REGEX.exec(version)
if (result != null) { if (result != null) {
@@ -36,11 +36,11 @@ export class VersionUtil {
return false return false
} }
public static isPromotionVersion(version: string) { public static isPromotionVersion(version: string): boolean {
return VersionUtil.PROMOTION_TYPE.indexOf(version.toLowerCase()) > -1 return VersionUtil.PROMOTION_TYPE.indexOf(version.toLowerCase()) > -1
} }
public static async getPromotionIndex() { public static async getPromotionIndex(): Promise<PromotionsSlim> {
const response = await Axios({ const response = await Axios({
method: 'get', method: 'get',
url: 'https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json', url: 'https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json',
@@ -49,18 +49,18 @@ export class VersionUtil {
return response.data as PromotionsSlim return response.data as PromotionsSlim
} }
public static getPromotedVersionStrict(index: PromotionsSlim, minecraftVersion: string, promotion: string) { public static getPromotedVersionStrict(index: PromotionsSlim, minecraftVersion: string, promotion: string): string {
const workingPromotion = promotion.toLowerCase() const workingPromotion = promotion.toLowerCase()
return index.promos[`${minecraftVersion}-${workingPromotion}`] return index.promos[`${minecraftVersion}-${workingPromotion}`]
} }
public static async getPromotedForgeVersion(minecraftVersion: string, promotion: string) { public static async getPromotedForgeVersion(minecraftVersion: string, promotion: string): Promise<string> {
const workingPromotion = promotion.toLowerCase() const workingPromotion = promotion.toLowerCase()
const res = await VersionUtil.getPromotionIndex() const res = await VersionUtil.getPromotionIndex()
let version = res.promos[`${minecraftVersion}-${workingPromotion}`] let version = res.promos[`${minecraftVersion}-${workingPromotion}`]
if (version == null) { if (version == null) {
console.warn(`No ${workingPromotion} version found for Forge ${minecraftVersion}.`) console.warn(`No ${workingPromotion} version found for Forge ${minecraftVersion}.`)
console.warn(`Attempting to pull latest version instead.`) console.warn('Attempting to pull latest version instead.')
version = res.promos[`${minecraftVersion}-latest`] version = res.promos[`${minecraftVersion}-latest`]
if (version == null) { if (version == null) {
throw new Error(`No latest version found for Forge ${minecraftVersion}.`) throw new Error(`No latest version found for Forge ${minecraftVersion}.`)

View File

@@ -2,9 +2,9 @@
"compilerOptions": { "compilerOptions": {
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "incremental": true, /* Enable incremental compilation */
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["ES2019"], /* Specify library files to be included in the compilation. */ "lib": ["ES2019"], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */ // "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */