@honor-minigame/cli
Version:
honor minigame pack cli
166 lines (155 loc) • 5.49 kB
JavaScript
import path from 'path'
import JSZip from 'jszip'
import crypto from 'crypto'
import fs from 'fs-extra'
import { info } from '../../utils/logger.js'
import { signZip } from './bundle.js'
import { LOG_TITLE } from './constanst.js'
import * as fflate from 'fflate'
// 文件摘要包(zip 文件)
const DIGEST_ZIP_PATH = 'META-INF/CERT'
// 压缩参数,设置输出 buffer,以便对 buffer 进行操作
const COMPRESS_OPTS = {
type: 'nodebuffer',
compression: 'DEFLATE',
compressionOptions: {
level: 9
}
}
// 获取buf的hash
function getBufferDigest(buf) {
const signer = crypto.createHash('SHA256')
signer.update(buf)
return signer.digest()
}
/**
* 返回摘要文件签名buff d
* @param {} fileHashObj - 文件资源列表和对应hash
* @param {Buffer} privatekey - 私钥文件 buffer
* @param {Buffer} certificate - 证书文件 buffer
* @returns {Object} signedDigestBuf - 摘要文件签名buff
*/
async function signZipResourcesMeta(fileHashObj, privatekey, certificate, pathTo) {
// ZIP元文件
const metaZipFile = new JSZip()
metaZipFile.file(
'hash.json',
JSON.stringify({
algorithm: 'SHA-256',
digests: fileHashObj
})
)
const content = await metaZipFile.generateAsync(COMPRESS_OPTS)
const digestHash = {
name: 'hash.json',
hash: getBufferDigest(content)
}
return signZip(content, [digestHash], privatekey, certificate)
}
/**
* 对文件进行重新 排序
* 第一位 入口文件 第二位 js文件 第三位 manifest(读取到这个文件引擎会执行js)
* 第四位 icon和防沉迷页面图片 第五位 游戏的资源文件
* @param {Array(String)} res 文件路径数组
* @param {Object} manifest 小游戏配置文件
*/
function sortAgain(res, manifest) {
const firstFileArr = ['main.js', 'game.js']
const firstFile = []
const secondFile = []
const imageFileArr = [manifest && manifest.icon.substr(1), manifest && manifest.homePage && manifest.homePage.substr(1)]
const imageFile = []
const manifestFileArr = ['manifest.json']
const manifestFile = []
const lastFile = []
if (!res) { return [] }
// 将输入数组深拷贝,避免源数据被修改,执行后续操作时出问题
const newRes = JSON.parse(JSON.stringify(res))
for (let index = newRes.length - 1; index >= 0; index--) {
const element = newRes[index]
const extname = path.extname(element)
if (firstFileArr.indexOf(element) > -1) {
// 入口文件
firstFile.push(element)
newRes.splice(index, 1)
// 其次是icon和首页图片
} else if (extname === '.js') {
// js文件
secondFile.push(element)
newRes.splice(index, 1)
} else if (manifestFileArr.indexOf(element) > -1) {
// 配置文件 manifest.json
manifestFile.push(element)
newRes.splice(index, 1)
} else if (imageFileArr.indexOf(element) > -1) {
// icon和防沉迷页面图片
imageFile.push(element)
newRes.splice(index, 1)
} else {
lastFile.push(element)
}
}
return firstFile.concat(secondFile, manifestFile, imageFile, lastFile)
}
/**
* 打rpk包
* @param {String} name rpk包的文件名
* @param {Array(String)} buildReses rpk对应的资源路径
* @param {String} pathFrom 打包rpk到何处
* @param {String} pathTo 打包rpk到何处
* @param {Object} signFile 签名文件信息
* @param {Object} manifest 小游戏配置文件
*/
export default async function rpk(name, buildReses, pathFrom, pathTo, signFile, manifest) {
await fflateRpk(name, buildReses, pathFrom, pathTo, signFile, manifest)
}
async function fflateRpk(name, buildReses, pathFrom, pathTo, signFile, manifest) {
// 保证打包路径存在,不存在就创建
const rpkStartTime = (new Date()).valueOf()
fs.ensureDirSync(pathTo)
info(`${LOG_TITLE}开始fflate压缩:${name} from:${pathFrom}`)
const fileHashObj = {} // 文件hash列表 缓存
const zipFileHashs = [] // 文件hash列表打包使用
const fileContents = {} // fflate压缩的文件列表
const privatekey = fs.readFileSync(signFile.privatekey)
const certificate = fs.readFileSync(signFile.certificate)
// 对打包的文件 进行排序
const newBuildRess = sortAgain(buildReses, manifest)
newBuildRess.forEach(name => {
const file = path.join(pathFrom, name)
const buf = fs.readFileSync(file)
const fileHash = getBufferDigest(buf)
fileHashObj[name] = fileHash.toString('hex')
zipFileHashs.push({
name,
hash: fileHash
})
// 添加文件到压缩文件列表
fileContents[name] = new Uint8Array(buf)
})
const fflateZip = async (
files,
compressOptions
) => {
try {
const options = compressOptions || {}
const zippedContent = await new Promise((resolve, reject) => {
fflate.zip(files, options, (err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
return Buffer.from(zippedContent)
} catch (err) {
return Promise.reject(new Error(`compress failed: ${err}`))
}
}
// fflate 打包zip包
const content = await fflateZip(fileContents, { level: 9 })
const size = (Number(content.length / 1024)).toFixed(2)
// 对压缩包进行签名
signZip(content, zipFileHashs, privatekey, certificate, path.join(pathTo, `${name}`))
info(`${LOG_TITLE}压缩fflate完成:${name} 耗时:${((new Date()).valueOf() - rpkStartTime) / 1000}s 大小:${size}Kb`)
}