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}`); }