185 lines
6.2 KiB
TypeScript
185 lines
6.2 KiB
TypeScript
import fs, { existsSync } from "fs";
|
|
import { ensureDirSync, copyFileSync } from "fs-extra";
|
|
import { createHash } from "crypto";
|
|
import path from "path";
|
|
import { CfgUtils } from "./cfgutils";
|
|
|
|
|
|
|
|
export interface GenPackageConfig {
|
|
pkgName: string;
|
|
files: string[];
|
|
version?: string;
|
|
}
|
|
|
|
class Manifest {
|
|
packageUrl: string = "";
|
|
remoteManifestUrl: string = "";
|
|
remoteVersionUrl: string = "";
|
|
version: string = "";
|
|
assets: object = {};
|
|
searchPaths: string[] = [];
|
|
}
|
|
|
|
class VersionManifest {
|
|
packageUrl: string = "";
|
|
remoteManifestUrl: string = "";
|
|
remoteVersionUrl: string = "";
|
|
version: string = "";
|
|
}
|
|
|
|
function readDir(buildDir: string, destination: string, dir: string, obj: any) {
|
|
try {
|
|
const stat = fs.statSync(dir);
|
|
if (stat.isFile()) {
|
|
const subpath = dir;
|
|
const size = stat.size;
|
|
const md5 = createHash('md5').update(fs.readFileSync(subpath)).digest('hex');
|
|
const compressed = path.extname(subpath).toLowerCase() === '.zip';
|
|
|
|
let relv = path.relative(buildDir, subpath);
|
|
relv = relv.replace(/\\/g, '/');
|
|
relv = encodeURI(relv);
|
|
obj[relv] = {
|
|
"size": size,
|
|
"md5": md5
|
|
};
|
|
if (compressed) {
|
|
obj[relv].compressed = true;
|
|
}
|
|
const index = relv.lastIndexOf("/");
|
|
const subdir = relv.substring(0, index);
|
|
mkdirSync(path.join(destination, subdir));
|
|
copyFileSync(subpath, path.join(destination, relv));
|
|
} else {
|
|
const subpaths = fs.readdirSync(dir);
|
|
for (let i = 0; i < subpaths.length; i++) {
|
|
if (subpaths[i][0] === '.') {
|
|
continue;
|
|
}
|
|
const subpath = path.join(dir, subpaths[i]);
|
|
const stat = fs.statSync(subpath);
|
|
if (stat.isDirectory()) {
|
|
readDir(buildDir, destination, subpath, obj);
|
|
} else if (stat.isFile()) {
|
|
const size = stat.size;
|
|
const md5 = createHash('md5').update(fs.readFileSync(subpath)).digest('hex');
|
|
const compressed = path.extname(subpath).toLowerCase() === '.zip';
|
|
|
|
let relv = path.relative(buildDir, subpath);
|
|
relv = relv.replace(/\\/g, '/');
|
|
relv = encodeURI(relv);
|
|
obj[relv] = {
|
|
"size": size,
|
|
"md5": md5
|
|
};
|
|
if (compressed) {
|
|
obj[relv].compressed = true;
|
|
}
|
|
const index = relv.lastIndexOf("/");
|
|
const subdir = relv.substring(0, index);
|
|
mkdirSync(path.join(destination, subdir));
|
|
copyFileSync(subpath, path.join(destination, relv));
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
function mkdirSync(path: string) {
|
|
try {
|
|
ensureDirSync(path);
|
|
} catch (error: any) {
|
|
if (error.code != 'EEXIST') throw error;
|
|
}
|
|
}
|
|
|
|
function versionCompare(versionA: string, versionB: string) {
|
|
const vA = versionA.split('.');
|
|
const vB = versionB.split('.');
|
|
for (let i = 0; i < vA.length; ++i) {
|
|
const a = parseInt(vA[i]);
|
|
const b = parseInt(vB[i] || '0');
|
|
if (a === b) {
|
|
continue;
|
|
} else {
|
|
return a - b;
|
|
}
|
|
}
|
|
return vB.length > vA.length ? -1 : 0;
|
|
}
|
|
|
|
function clearDir(dir: string) {
|
|
try {
|
|
if (existsSync(dir)) {
|
|
const files = fs.readdirSync(dir);
|
|
for (const file of files) {
|
|
const filePath = path.join(dir, file);
|
|
const stat = fs.statSync(filePath);
|
|
if (stat.isDirectory()) {
|
|
clearDir(filePath);
|
|
fs.rmdirSync(filePath);
|
|
} else {
|
|
fs.unlinkSync(filePath);
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('清理目录失败:', err);
|
|
}
|
|
}
|
|
|
|
export function generator(buildDir: string, output: string, cfg: GenPackageConfig) {
|
|
const destination = path.join(output,cfg.pkgName);
|
|
const manifest = new Manifest();
|
|
const version = new VersionManifest();
|
|
|
|
const cfgData = CfgUtils.getCfgData(cfg.pkgName);
|
|
|
|
if (cfg.version) {
|
|
cfgData.version = cfg.version;
|
|
} else {
|
|
const versionParts = cfgData.version.split('.');
|
|
const patch = parseInt(versionParts[2] || '0') + 1;
|
|
cfgData.version = `${versionParts[0]}.${versionParts[1]}.${patch}`;
|
|
}
|
|
|
|
manifest.version = cfgData.version;
|
|
manifest.packageUrl = `${cfg.pkgName}/${cfgData.version}`;
|
|
manifest.remoteManifestUrl = `${cfg.pkgName}/project.manifest`;
|
|
manifest.remoteVersionUrl = `${cfg.pkgName}/version.manifest`;
|
|
version.version = cfgData.version;
|
|
|
|
// 清理输出目录
|
|
clearDir(destination);
|
|
ensureDirSync(destination);
|
|
|
|
// 处理文件
|
|
for (const file of cfg.files) {
|
|
const filePath = path.join(buildDir, file);
|
|
if (existsSync(filePath)) {
|
|
readDir(buildDir, destination, filePath, manifest.assets);
|
|
}
|
|
}
|
|
|
|
// 生成 manifest 文件到输出目录
|
|
fs.writeFileSync(path.join(destination, 'project.manifest'), JSON.stringify(manifest, null, 2));
|
|
fs.writeFileSync(path.join(destination, 'version.manifest'), JSON.stringify(version, null, 2));
|
|
|
|
// 将 manifest 和 version 文件也存储到 buildDir + "/assets/" + pkgName 目录下
|
|
const buildAssetsDir = path.join(buildDir, "assets", cfg.pkgName);
|
|
ensureDirSync(buildAssetsDir);
|
|
fs.writeFileSync(path.join(buildAssetsDir, 'project.manifest'), JSON.stringify(manifest, null, 2));
|
|
fs.writeFileSync(path.join(buildAssetsDir, 'version.manifest'), JSON.stringify(version, null, 2));
|
|
|
|
console.log(`Manifest文件已生成到构建目录: ${buildAssetsDir}`);
|
|
console.log(`Manifest文件已生成到输出目录: ${destination}`);
|
|
|
|
// 保存配置
|
|
CfgUtils.savaConfig(cfg.pkgName, cfgData);
|
|
|
|
console.log(`热更新包生成完成: ${cfg.pkgName} v${cfgData.version}`);
|
|
}
|