Replace AdmZip with node-stream-zip.

This commit is contained in:
Daniel Scalzi
2020-06-02 23:07:59 -04:00
parent 0b063e4bfc
commit d627ce72a3
12 changed files with 283 additions and 215 deletions

3
.vscode/launch.json vendored
View File

@@ -16,7 +16,8 @@
"preLaunchTask": "compile",
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
]
],
"outputCapture": "std"
}
]
}

19
package-lock.json generated
View File

@@ -67,15 +67,6 @@
}
}
},
"@types/adm-zip": {
"version": "0.4.33",
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.33.tgz",
"integrity": "sha512-WM0DCWFLjXtddl0fu0+iN2ZF+qz8RF9RddG5OSy/S90AQz01Fu8lHn/3oTIZDxvG8gVcnBLAHMHOdBLbV6m6Mw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@@ -210,11 +201,6 @@
"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
"dev": true
},
"adm-zip": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz",
"integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g=="
},
"ajv": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
@@ -1126,6 +1112,11 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"node-stream-zip": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.11.2.tgz",
"integrity": "sha512-cowCX+OyzS3tN2i4BMMFxCr/pE6cQlEMTbVCugmos0TNEJQNtcG04tR41CY8lumO1I7F5GFiLaU4WavomJthaA=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",

View File

@@ -26,7 +26,6 @@
},
"homepage": "https://github.com/dscalzi/Nebula#readme",
"devDependencies": {
"@types/adm-zip": "^0.4.33",
"@types/fs-extra": "^9.0.1",
"@types/node": "^12.12.43",
"@types/triple-beam": "^1.3.1",
@@ -38,12 +37,12 @@
"typescript": "^3.9.3"
},
"dependencies": {
"adm-zip": "^0.4.14",
"axios": "^0.19.2",
"dotenv": "^8.2.0",
"fs-extra": "^9.0.0",
"helios-distribution-types": "^1.0.0-pre.1",
"moment": "^2.26.0",
"node-stream-zip": "^1.11.2",
"toml": "^3.0.0",
"triple-beam": "^1.3.0",
"winston": "^3.2.1",

View File

@@ -39,10 +39,10 @@ export class MiscFileStructure extends ModuleStructure {
return acc
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleId(name: string, path: string): Promise<string> {
return name
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleName(name: string, path: string): Promise<string> {
return name
}
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {

View File

@@ -1,5 +1,4 @@
import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra'
import StreamZip from 'node-stream-zip'
import toml from 'toml'
import { capitalize } from '../../../../../util/stringutils'
import { VersionUtil } from '../../../../../util/versionutil'
@@ -33,35 +32,60 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
return ForgeModStructure113.isForVersion(version, libraryVersion)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const fmData = this.getForgeModMetadata(buf, name)
protected async getModuleId(name: string, path: string): Promise<string> {
const fmData = await this.getForgeModMetadata(name, path)
return this.generateMavenIdentifier(fmData.mods[0].modId, fmData.mods[0].version)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize((this.getForgeModMetadata(buf, name)).mods[0].displayName)
protected async getModuleName(name: string, path: string): Promise<string> {
return capitalize((await this.getForgeModMetadata(name, path)).mods[0].displayName)
}
private getForgeModMetadata(buf: Buffer, name: string): ModsToml {
private getForgeModMetadata(name: string, path: string): Promise<ModsToml> {
return new Promise((resolve, reject) => {
if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
const zip = new StreamZip({
file: path,
storeEntries: true
})
zip.on('error', err => reject(err))
zip.on('ready', () => {
try {
const res = this.processZip(zip, name)
zip.close()
resolve(res)
return
} catch(err) {
zip.close()
throw err
}
})
} else {
resolve(this.forgeModMetadata[name] as ModsToml)
return
}
})
}
private processZip(zip: StreamZip, name: string): ModsToml {
// 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) {
let changelogBuf: Buffer
try {
changelogBuf = zip.entryDataSync('changelog.txt')
} catch(err) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = rawChangelog.split('\n')[0].trim()
const info = changelogBuf.toString().split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
@@ -72,20 +96,26 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
return this.forgeModMetadata[name] as ModsToml
}
const raw = zip.readAsText('META-INF/mods.toml')
let raw: Buffer | undefined
try {
raw = zip.entryDataSync('META-INF/mods.toml')
} catch(err) {
// ignored
}
let createDefault = false
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const parsed = toml.parse(raw) as ModsToml
const parsed = toml.parse(raw.toString()) as ModsToml
// tslint:disable-next-line: no-invalid-template-strings
if (parsed.mods[0].version === '${file.jarVersion}') {
let version = '0.0.0'
const manifest = zip.readAsText('META-INF/MANIFEST.MF')
const keys = manifest.split('\n')
try {
const manifest = zip.entryDataSync('META-INF/MANIFEST.MF')
const keys = manifest.toString().split('\n')
ForgeModStructure113.logger.debug(keys)
for (const key of keys) {
const match = ForgeModStructure113.IMPLEMENTATION_VERSION_REGEX.exec(key)
@@ -94,6 +124,9 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
}
}
ForgeModStructure113.logger.debug(`ForgeMod ${name} contains a version wildcard, inferring ${version}`)
} catch {
ForgeModStructure113.logger.debug(`ForgeMod ${name} contains a version wildcard yet no MANIFEST.MF.. Defaulting to ${version}`)
}
parsed.mods[0].version = version
}
@@ -120,10 +153,8 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
}]
})
}
}
return this.forgeModMetadata[name] as ModsToml
}
}

View File

@@ -1,5 +1,4 @@
import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra'
import StreamZip from 'node-stream-zip'
import { capitalize } from '../../../../../util/stringutils'
import { VersionUtil } from '../../../../../util/versionutil'
import { McModInfo } from '../../../../forge/mcmodinfo'
@@ -31,35 +30,59 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
return ForgeModStructure17.isForVersion(version, libraryVersion)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const fmData = this.getForgeModMetadata(buf, name)
protected async getModuleId(name: string, path: string): Promise<string> {
const fmData = await this.getForgeModMetadata(name, path)
return this.generateMavenIdentifier(fmData.modid, fmData.version)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize((this.getForgeModMetadata(buf, name)).name)
protected async getModuleName(name: string, path: string): Promise<string> {
return capitalize((await this.getForgeModMetadata(name, path)).name)
}
private getForgeModMetadata(buf: Buffer, name: string): McModInfo {
private getForgeModMetadata(name: string, path: string): Promise<McModInfo> {
return new Promise((resolve, reject) => {
if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
const zip = new StreamZip({
file: path,
storeEntries: true
})
zip.on('error', err => reject(err))
zip.on('ready', () => {
try {
const res = this.processZip(zip, name)
zip.close()
resolve(res)
return
} catch(err) {
zip.close()
throw err
}
})
} else {
resolve(this.forgeModMetadata[name] as McModInfo)
return
}
})
}
private processZip(zip: StreamZip, name: string): McModInfo {
// 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) {
let changelogBuf: Buffer
try {
changelogBuf = zip.entryDataSync('changelog.txt')
} catch(err) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = rawChangelog.split('\n')[0].trim()
const info = changelogBuf.toString().split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
@@ -70,12 +93,11 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
return this.forgeModMetadata[name] as McModInfo
}
let raw
for (const entry of zipEntries) {
if (entry.entryName === 'mcmod.info') {
raw = zip.readAsText(entry)
break
}
let raw: Buffer | undefined
try {
raw = zip.entryDataSync('mcmod.info')
} catch(err) {
// ignored
}
let createDefault = false
@@ -83,7 +105,7 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const resolved = JSON.parse(raw) as (McModInfoList | McModInfo[])
const resolved = JSON.parse(raw.toString()) as (McModInfoList | McModInfo[])
if (Object.prototype.hasOwnProperty.call(resolved, 'modListVersion')) {
this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0]
} else {
@@ -110,7 +132,6 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
version: '0.0.0'
}) as unknown as McModInfo
}
}
return this.forgeModMetadata[name] as McModInfo
}

View File

@@ -34,12 +34,12 @@ export class LibraryStructure extends ModuleStructure {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleId(name: string, path: string): Promise<string> {
const inference = this.attemptCrudeInference(name)
return this.generateMavenIdentifier(inference.name, inference.version)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleName(name: string, path: string): Promise<string> {
const inference = this.attemptCrudeInference(name)
return inference.name
}

View File

@@ -1,4 +1,4 @@
import AdmZip from 'adm-zip'
import StreamZip from 'node-stream-zip'
import { Stats } from 'fs-extra'
import { Type } from 'helios-distribution-types'
import { join } from 'path'
@@ -19,12 +19,12 @@ export class LiteModStructure extends ModuleStructure {
super(absoluteRoot, relativeRoot, 'litemods', baseUrl, Type.LiteMod)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const liteModData = this.getLiteModMetadata(buf, name)
protected async getModuleId(name: string, path: string): Promise<string> {
const liteModData = await this.getLiteModMetadata(name, path)
return this.generateMavenIdentifier(liteModData.name, `${liteModData.version}-${liteModData.mcversion}`)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize(this.getLiteModMetadata(buf, name).name)
protected async getModuleName(name: string, path: string): Promise<string> {
return capitalize((await this.getLiteModMetadata(name, path)).name)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
@@ -35,27 +35,53 @@ export class LiteModStructure extends ModuleStructure {
return null
}
private getLiteModMetadata(buf: Buffer, name: string): LiteMod {
private getLiteModMetadata(name: string, path: string): Promise<LiteMod> {
return new Promise((resolve, reject) => {
if (!Object.prototype.hasOwnProperty.call(this.liteModMetadata, 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
const zip = new StreamZip({
file: path,
storeEntries: true
})
zip.on('error', err => reject(err))
zip.on('ready', () => {
try {
const res = this.processZip(zip, name)
zip.close()
resolve(res)
return
} catch(err) {
zip.close()
throw err
}
})
} else {
resolve(this.liteModMetadata[name] as LiteMod)
return
}
})
}
private processZip(zip: StreamZip, name: string): LiteMod {
let raw: Buffer | undefined
try {
raw = zip.entryDataSync('litemod.json')
} catch(err) {
// ignored
}
if (raw) {
this.liteModMetadata[name] = JSON.parse(raw) as LiteMod
this.liteModMetadata[name] = JSON.parse(raw.toString()) as LiteMod
} else {
throw new Error(`Litemod ${name} does not contain litemod.json file.`)
}
}
return this.liteModMetadata[name] as LiteMod
}
}

View File

@@ -29,16 +29,16 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
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 getModuleId(name: string, path: string): Promise<string>
protected async abstract getModuleName(name: string, path: string): 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>
protected async parseModule(file: string, filePath: string, stats: Stats): Promise<Module> {
const buf = await readFile(filePath)
const mdl: Module = {
id: await this.getModuleId(file, filePath, stats, buf),
name: await this.getModuleName(file, filePath, stats, buf),
id: await this.getModuleId(file, filePath),
name: await this.getModuleName(file, filePath),
type: this.type,
required: {
value: false,

View File

@@ -1,4 +1,3 @@
import AdmZip from 'adm-zip'
import { createHash } from 'crypto'
import { copy, lstat, mkdirs, pathExists, readFile, remove } from 'fs-extra'
import { Module, Type } from 'helios-distribution-types'
@@ -59,24 +58,14 @@ export class ForgeGradle2Adapter extends ForgeResolver {
}
ForgeGradle2Adapter.logger.debug(`Beginning processing of Forge v${this.forgeVersion} (Minecraft ${this.minecraftVersion})`)
const forgeUniversalBuffer = await readFile(targetLocalPath)
const zip = new AdmZip(forgeUniversalBuffer)
const zipEntries = zip.getEntries()
let versionManifest
for (const entry of zipEntries) {
if (entry.entryName === 'version.json') {
versionManifest = zip.readAsText(entry)
break
}
}
if (!versionManifest) {
let versionManifestBuf: Buffer
try {
versionManifestBuf = await this.getVersionManifestFromJar(targetLocalPath)
} catch(err) {
throw new Error('Failed to find version.json in forge universal jar.')
}
versionManifest = JSON.parse(versionManifest) as VersionManifestFG2
const versionManifest = JSON.parse(versionManifestBuf.toString()) as VersionManifestFG2
const forgeModule: Module = {
id: MavenUtil.mavenComponentsToIdentifier(
@@ -87,7 +76,7 @@ export class ForgeGradle2Adapter extends ForgeResolver {
name: 'Minecraft Forge',
type: Type.ForgeHosted,
artifact: this.generateArtifact(
forgeUniversalBuffer,
await readFile(targetLocalPath),
await lstat(targetLocalPath),
libRepo.getArtifactUrlByComponents(
this.baseUrl,

View File

@@ -1,4 +1,3 @@
import AdmZip from 'adm-zip'
import { ForgeResolver } from '../forge.resolver'
import { MinecraftVersion } from '../../../util/MinecraftVersion'
import { LoggerUtil } from '../../../util/LoggerUtil'
@@ -425,24 +424,14 @@ export class ForgeGradle3Adapter extends ForgeResolver {
// Extract version.json from installer.
const forgeInstallerBuffer = await readFile(installerPath)
const zip = new AdmZip(forgeInstallerBuffer)
const zipEntries = zip.getEntries()
let versionManifest
for (const entry of zipEntries) {
if (entry.entryName === 'version.json') {
versionManifest = zip.readAsText(entry)
break
}
}
if (!versionManifest) {
let versionManifestBuf: Buffer
try {
versionManifestBuf = await this.getVersionManifestFromJar(installerPath)
} catch(err) {
throw new Error('Failed to find version.json in forge installer jar.')
}
versionManifest = JSON.parse(versionManifest) as VersionManifestFG3
const versionManifest = JSON.parse(versionManifestBuf.toString()) as VersionManifestFG3
// Save Version Manifest
const versionManifestDest = this.repoStructure.getVersionRepoStruct().getVersionManifest(

View File

@@ -1,3 +1,4 @@
import StreamZip from 'node-stream-zip'
import { createHash } from 'crypto'
import { Stats } from 'fs-extra'
import { Artifact } from 'helios-distribution-types'
@@ -81,4 +82,24 @@ export abstract class ForgeResolver extends BaseResolver {
}
}
protected async getVersionManifestFromJar(jarPath: string): Promise<Buffer>{
return new Promise((resolve, reject) => {
const zip = new StreamZip({
file: jarPath,
storeEntries: true
})
zip.on('ready', () => {
try {
const data = zip.entryDataSync('version.json')
zip.close()
resolve(data)
} catch(err) {
reject(err)
}
})
zip.on('error', err => reject(err))
})
}
}