From 419a4d5e917b3b08714ddb025f47e4dc07d710d9 Mon Sep 17 00:00:00 2001 From: Daniel Scalzi Date: Sun, 12 Jan 2020 01:36:36 -0500 Subject: [PATCH] Completed steps up to downloading forge universal jar for 1.8-1.12. Next step is processing the version.json and transforming it into a deliverable module. --- .vscode/launch.json | 7 +++- package-lock.json | 29 +++++++++++++ package.json | 1 + src/index.ts | 18 ++++++++ src/model/struct/repo/BaseMavenRepo.ts | 35 +++++++++++++++- src/model/struct/repo/forgerepo.struct.ts | 8 +++- src/resolver/ResolverRegistry.ts | 23 ++++++++++ src/resolver/baseresolver.ts | 11 +++++ .../forge/adapter/forge113.resolver.ts | 27 ++++++++++++ .../forge/adapter/forge18.resolver.ts | 42 +++++++++++++++++++ src/resolver/forge/forge.resolver.ts | 21 ++-------- src/util/maven.ts | 32 ++++++++++---- src/util/versionutil.ts | 23 ++++++++++ tsconfig.json | 4 +- 14 files changed, 251 insertions(+), 30 deletions(-) create mode 100644 src/resolver/ResolverRegistry.ts create mode 100644 src/resolver/forge/adapter/forge113.resolver.ts create mode 100644 src/util/versionutil.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index f1f4677d..4d659acb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,12 @@ "type": "node", "request": "launch", "name": "Launch Program", - "program": "${workspaceFolder}\\dist\\index.js", + "program": "${workspaceFolder}\\src\\index.ts", + "args": [ + "test", + "1.12.2", + "14.23.5.2847" + ], "preLaunchTask": "compile", "outFiles": [ "${workspaceFolder}/dist/**/*.js" diff --git a/package-lock.json b/package-lock.json index c77a208f..f644a427 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,14 @@ "sprintf-js": "~1.0.2" } }, + "axios": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.1.tgz", + "integrity": "sha512-Yl+7nfreYKaLRvAvjNPkvfjnQHJM1yLBY3zhqAwcJSwR/6ETkanUgylgtIvkvz0xJ+p/vZuNw8X7Hnb7Whsbpw==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -195,6 +203,14 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -238,6 +254,14 @@ "path-exists": "^4.0.0" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -361,6 +385,11 @@ "minimist": "0.0.8" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index 9a560a93..6ca98005 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "adm-zip": "^0.4.13", + "axios": "^0.19.1", "fs-extra": "^8.1.0", "yargs": "^15.1.0" } diff --git a/src/index.ts b/src/index.ts index 65c48e9a..39d8f9b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ /* tslint:disable:no-shadowed-variable */ import { writeFile } from 'fs-extra' import { resolve as resolvePath } from 'path' +import { URL } from 'url' import yargs from 'yargs' import { DistributionStructure } from './model/struct/model/distribution.struct' +import { ResolverRegistry } from './resolver/ResolverRegistry' function rootOption(yargs: yargs.Argv) { return yargs.option('root', { @@ -166,6 +168,21 @@ const validateCommand: yargs.CommandModule = { } } +const testCommand: yargs.CommandModule = { + command: 'test ', + describe: 'Validate a distribution.json against the spec.', + builder: (yargs) => { + return namePositional(yargs) + }, + handler: async (argv) => { + console.debug(`Invoked test with mcVer ${argv.mcVer} forgeVer ${argv.forgeVer}`) + const resolver = ResolverRegistry.getForgeResolver('1.12.2', '14.23.5.2847', 'D:/TestRoot2', 'D:/TestRoot2') + if (resolver != null) { + await resolver.getModule() + } + } +} + // Registering yargs configuration. // tslint:disable-next-line:no-unused-expression yargs @@ -174,6 +191,7 @@ yargs .command(initCommand) .command(generateCommand) .command(validateCommand) +.command(testCommand) .demandCommand() .help() .argv diff --git a/src/model/struct/repo/BaseMavenRepo.ts b/src/model/struct/repo/BaseMavenRepo.ts index 7bccea08..f1804bd4 100644 --- a/src/model/struct/repo/BaseMavenRepo.ts +++ b/src/model/struct/repo/BaseMavenRepo.ts @@ -1,4 +1,7 @@ -import { resolve } from 'path' +import axios from 'axios' +import { createWriteStream, mkdirs, pathExists } from 'fs-extra' +import { dirname, resolve } from 'path' +import { resolve as resolveURL } from 'url' import { MavenUtil } from '../../../util/maven' import { BaseFileStructure } from '../BaseFileStructure' @@ -19,8 +22,36 @@ export abstract class BaseMavenRepo extends BaseFileStructure { public getArtifactByComponents(group: string, artifact: string, version: string, classifier?: string, extension = 'jar'): string { - throw resolve(this.containerDirectory, + return resolve(this.containerDirectory, MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)) } + public async artifactExists(path: string) { + return pathExists(path) + } + + public async downloadArtifact(url: string, group: string, artifact: string, version: string, + classifier?: string, extension?: string) { + const relative = MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension) + const resolvedURL = resolveURL(url, relative).toString() + console.debug(`Downloading ${resolvedURL}..`) + const response = await axios({ + method: 'get', + url: resolvedURL, + responseType: 'stream' + }) + const localPath = resolve(this.containerDirectory, relative) + await mkdirs(dirname(localPath)) + const writer = createWriteStream(localPath) + response.data.pipe(writer) + // tslint:disable-next-line: no-shadowed-variable + return new Promise((resolve, reject) => { + writer.on('finish', () => { + console.debug(`Completed download of ${resolvedURL}.`) + resolve() + }) + writer.on('error', reject) + }) + } + } diff --git a/src/model/struct/repo/forgerepo.struct.ts b/src/model/struct/repo/forgerepo.struct.ts index 43119b43..22de4c6b 100644 --- a/src/model/struct/repo/forgerepo.struct.ts +++ b/src/model/struct/repo/forgerepo.struct.ts @@ -2,6 +2,9 @@ import { BaseMavenRepo } from './BaseMavenRepo' export class ForgeRepoStructure extends BaseMavenRepo { + public static readonly FORGE_GROUP = 'net.minecraftforge' + public static readonly FORGE_ARTIFACT = 'forge' + constructor( absoluteRoot: string, relativeRoot: string @@ -10,7 +13,10 @@ export class ForgeRepoStructure extends BaseMavenRepo { } public getLocalForge(version: string, classifier?: string) { - this.getArtifactByComponents('net.minecraftforge', 'forge', version, classifier, 'jar') + return this.getArtifactByComponents( + ForgeRepoStructure.FORGE_GROUP, + ForgeRepoStructure.FORGE_ARTIFACT, + version, classifier, 'jar') } } diff --git a/src/resolver/ResolverRegistry.ts b/src/resolver/ResolverRegistry.ts new file mode 100644 index 00000000..53d05aa7 --- /dev/null +++ b/src/resolver/ResolverRegistry.ts @@ -0,0 +1,23 @@ +import { Forge113Adapter } from './forge/adapter/forge113.resolver' +import { Forge18Adapter } from './forge/adapter/forge18.resolver' +import { ForgeResolver } from './forge/forge.resolver' + +export class ResolverRegistry { + + public static readonly FORGE_ADAPTER_IMPL = [ + Forge18Adapter, + Forge113Adapter + ] + + public static getForgeResolver( + minecraftVersion: string, + forgeVersion: string, + absoluteRoot: string, relativeRoot: string): ForgeResolver | undefined { + for (const impl of ResolverRegistry.FORGE_ADAPTER_IMPL) { + if (impl.isForVersion(minecraftVersion)) { + return new impl(absoluteRoot, relativeRoot, minecraftVersion, forgeVersion) + } + } + } + +} diff --git a/src/resolver/baseresolver.ts b/src/resolver/baseresolver.ts index be36a3fc..8f25cd31 100644 --- a/src/resolver/baseresolver.ts +++ b/src/resolver/baseresolver.ts @@ -1,8 +1,17 @@ import { Module } from '../model/spec/module' +import { VersionUtil } from '../util/versionutil' import { Resolver } from './resolver' export abstract class BaseResolver implements Resolver { + protected static isVersionAcceptable(version: string, acceptable: number[]): boolean { + const versionComponents = VersionUtil.getMinecraftVersionComponents(version) + if (versionComponents != null && versionComponents.major === 1) { + return acceptable.find((element) => versionComponents.minor === element) != null + } + return false + } + constructor( protected absoluteRoot: string, protected relativeRoot: string @@ -10,4 +19,6 @@ export abstract class BaseResolver implements Resolver { public abstract getModule(): Promise + public abstract isForVersion(version: string): boolean + } diff --git a/src/resolver/forge/adapter/forge113.resolver.ts b/src/resolver/forge/adapter/forge113.resolver.ts new file mode 100644 index 00000000..a6dc0846 --- /dev/null +++ b/src/resolver/forge/adapter/forge113.resolver.ts @@ -0,0 +1,27 @@ +import { Module } from '../../../model/spec/module' +import { ForgeResolver } from '../forge.resolver' + +export class Forge113Adapter extends ForgeResolver { + + public static isForVersion(version: string) { + return Forge113Adapter.isVersionAcceptable(version, [13, 14, 15]) + } + + constructor( + absoluteRoot: string, + relativeRoot: string, + minecraftVersion: string, + forgeVersion: string + ) { + super(absoluteRoot, relativeRoot, minecraftVersion, forgeVersion) + } + + public async getModule(): Promise { + return null as unknown as Module + } + + public isForVersion(version: string): boolean { + return Forge113Adapter.isForVersion(version) + } + +} diff --git a/src/resolver/forge/adapter/forge18.resolver.ts b/src/resolver/forge/adapter/forge18.resolver.ts index 7f852f77..43792e24 100644 --- a/src/resolver/forge/adapter/forge18.resolver.ts +++ b/src/resolver/forge/adapter/forge18.resolver.ts @@ -1,10 +1,52 @@ import { Module } from '../../../model/spec/module' +import { ForgeRepoStructure } from '../../../model/struct/repo/forgerepo.struct' import { ForgeResolver } from '../forge.resolver' export class Forge18Adapter extends ForgeResolver { + public static isForVersion(version: string) { + return Forge18Adapter.isVersionAcceptable(version, [8, 9, 10, 11, 12]) + } + + constructor( + absoluteRoot: string, + relativeRoot: string, + minecraftVersion: string, + forgeVersion: string + ) { + super(absoluteRoot, relativeRoot, minecraftVersion, forgeVersion) + } + public async getModule(): Promise { + await this.getForgeByVersion() return null as unknown as Module } + public isForVersion(version: string) { + return Forge18Adapter.isForVersion(version) + } + + public async getForgeByVersion() { + const forgeRepo = this.repoStructure.getForgeRepoStruct() + const artifactVersion = `${this.minecraftVersion}-${this.forgeVersion}` + const targetLocalPath = forgeRepo.getLocalForge(artifactVersion, 'universal') + console.debug(`Checking for forge version at ${targetLocalPath}..`) + if (!await forgeRepo.artifactExists(targetLocalPath)) { + console.debug(`Forge not found locally, initializing download..`) + await forgeRepo.downloadArtifact( + this.REMOTE_REPOSITORY, + ForgeRepoStructure.FORGE_GROUP, + ForgeRepoStructure.FORGE_ARTIFACT, + artifactVersion, 'universal', 'jar') + } else { + console.debug('Using locally discovered forge.') + } + console.debug(`Beginning processing of Forge v${this.forgeVersion} (Minecraft ${this.minecraftVersion})`) + } + + // TODO + // extract manifest + // parse manifest + // return module + } diff --git a/src/resolver/forge/forge.resolver.ts b/src/resolver/forge/forge.resolver.ts index 7ae89b08..debfe7e0 100644 --- a/src/resolver/forge/forge.resolver.ts +++ b/src/resolver/forge/forge.resolver.ts @@ -1,33 +1,20 @@ -import { Module } from '../../model/spec/module' import { RepoStructure } from '../../model/struct/repo/repo.struct' import { BaseResolver } from '../baseresolver' -import { Forge18Adapter } from './adapter/forge18.resolver' export abstract class ForgeResolver extends BaseResolver { - public static getResolver(version: string) { - return ForgeResolver.ADAPTER_LIST[version] - } - - // tslint:disable: object-literal-key-quotes - private static readonly ADAPTER_LIST: {[version: string]: any} = { - '1.8': Forge18Adapter, - '1.9': Forge18Adapter, - '1.10': Forge18Adapter, - '1.11': Forge18Adapter, - '1.12': Forge18Adapter - } + protected readonly REMOTE_REPOSITORY = 'https://files.minecraftforge.net/maven/' protected repoStructure: RepoStructure constructor( absoluteRoot: string, - relativeRoot: string + relativeRoot: string, + protected minecraftVersion: string, + protected forgeVersion: string ) { super(absoluteRoot, relativeRoot) this.repoStructure = new RepoStructure(absoluteRoot, relativeRoot) } - public abstract getModule(): Promise - } diff --git a/src/util/maven.ts b/src/util/maven.ts index 5193fe63..4383305b 100644 --- a/src/util/maven.ts +++ b/src/util/maven.ts @@ -3,10 +3,11 @@ import { URL } from 'url' export class MavenUtil { - public static readonly ID_REGEX = /(.+):(.+):([^@-]+)(?:-{1}([^@]+))?(?:@{1}(.+)$)?/ + public static readonly ID_REGEX = /(.+):(.+):([^@]+)()(?:@{1}(.+)$)?/ + public static readonly ID_REGEX_WITH_CLASSIFIER = /(.+):(.+):(?:([^@]+)(?:-([a-zA-Z]+)))(?:@{1}(.+)$)?/ public static isMavenIdentifier(id: string) { - return MavenUtil.ID_REGEX.test(id) + return MavenUtil.ID_REGEX.test(id) || MavenUtil.ID_REGEX_WITH_CLASSIFIER.test(id) } public static mavenIdentifierToString(id: string, extension = 'jar') { @@ -14,12 +15,19 @@ export class MavenUtil { return null } - const result = MavenUtil.ID_REGEX.exec(id) + let result + + if (MavenUtil.ID_REGEX_WITH_CLASSIFIER.test(id)) { + result = MavenUtil.ID_REGEX_WITH_CLASSIFIER.exec(id) + } else { + result = MavenUtil.ID_REGEX.exec(id) + } + if (result != null) { const group = result[1] const artifact = result[2] const version = result[3] - const classifier = result[4] + const classifier = result[4] || undefined const ext = result[5] || extension return MavenUtil.mavenComponentsToString(group, artifact, version, classifier, ext) @@ -27,19 +35,29 @@ export class MavenUtil { return null } - public static mavenComponentsToString(group: string, artifact: string,version: string, + public static mavenComponentsToString(group: string, artifact: string, version: string, classifier?: string, extension = 'jar') { return `${group.replace(/\./g, '/')}/${artifact}/${version}/${artifact}-${version}${classifier != null ? `-${classifier}` : ''}.${extension}` } - public static mavenToUrl(id: string, extension = 'jar') { + public static mavenIdentifierToUrl(id: string, extension = 'jar') { const res = MavenUtil.mavenIdentifierToString(id, extension) return res == null ? null : new URL(res) } - public static mavenToPath(id: string, extension = 'jar') { + public static mavenComponentsToUrl(group: string, artifact: string, version: string, + classifier?: string, extension = 'jar') { + return new URL(MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)) + } + + public static mavenIdentifierToPath(id: string, extension = 'jar') { const res = MavenUtil.mavenIdentifierToString(id, extension) return res == null ? null : normalize(res) } + public static mavenComponentsToPath(group: string, artifact: string, version: string, + classifier?: string, extension = 'jar') { + return normalize(MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension)) + } + } diff --git a/src/util/versionutil.ts b/src/util/versionutil.ts new file mode 100644 index 00000000..efb2c065 --- /dev/null +++ b/src/util/versionutil.ts @@ -0,0 +1,23 @@ +export class VersionUtil { + + public static readonly MINECRAFT_VERSION_REGEX = /(\d+).(\d+).(\d+)/ + + public static isMinecraftVersion(version: string) { + return VersionUtil.MINECRAFT_VERSION_REGEX.test(version) + } + + public static getMinecraftVersionComponents(version: string) { + if (VersionUtil.isMinecraftVersion(version)) { + const result = VersionUtil.MINECRAFT_VERSION_REGEX.exec(version) + if (result != null) { + return { + major: Number(result[1]), + minor: Number(result[2]), + revision: Number(result[3]) + } + } + } + return null + } + +} diff --git a/tsconfig.json b/tsconfig.json index e950cb04..fbeb1d79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es5", /* 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'. */ - // "lib": [], /* 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. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */