import { BuildHook, IBuildResult, IBuildTaskOption, } from "@cocos/creator-types/editor/packages/builder/@types/public"; import path from "path"; import { existsSync, readFile, writeFile } from "fs"; import { ensureDirSync } from "fs-extra"; import { generator, GenPackageConfig } from "./version_generator"; import { createMinIOUploader, MinIOConfig } from "./minio-uploader"; const injectScript = `(function () { const native = globalThis?.jsb || window.jsb || native; if (typeof native === 'object') { var hotUpdateSearchPaths = localStorage.getItem('HotUpdateSearchPaths'); if (hotUpdateSearchPaths) { var paths = JSON.parse(hotUpdateSearchPaths); native.fileUtils.setSearchPaths(paths); var fileList = []; var storagePath = paths[0] || ''; var tempPath = storagePath + '_temp/'; var baseOffset = tempPath.length; if (native.fileUtils.isDirectoryExist(tempPath) && !native.fileUtils.isFileExist(tempPath + 'project.manifest.temp')) { native.fileUtils.listFilesRecursively(tempPath, fileList); fileList.forEach(srcPath => { var relativePath = srcPath.substr(baseOffset); var dstPath = storagePath + relativePath; if (srcPath[srcPath.length - 1] === '/') { native.fileUtils.createDirectory(dstPath) } else { if (native.fileUtils.isFileExist(dstPath)) { native.fileUtils.removeFile(dstPath) } native.fileUtils.renameFile(srcPath, dstPath); } }) native.fileUtils.removeDirectory(tempPath); } } } })();`; export const onBeforeBuild: BuildHook.onBeforeBuild = async function ( options: IBuildTaskOption, result: IBuildResult ) {}; export const onAfterBuild: BuildHook.onAfterBuild = async function ( options: IBuildTaskOption, result: IBuildResult ) { console.log("onAfterBuild platform", options.platform); if (!["android", "mac", "ios", "windows"].includes(options.platform)) { return; } let url = path.join(result.dest, "data", "main.js"); console.log("onAfterBuild url", url); if (!existsSync(url)) { url = path.join(result.dest, "assets", "main.js"); } console.log("onAfterBuild url", url); const hotupdateConfig: { hotupdatePluginCheck: boolean; configInput: string; outInput: string; // MinIO 配置 minioAutoUpload: boolean; minioEndpoint: string; minioAccessKeyId: string; minioSecretAccessKey: string; minioBucketName: string; minioUseSSL: boolean; minioPathPrefix: string; } = options.packages?.["max-framework"]; console.log( "onAfterBuild hotupdateConfig", url, hotupdateConfig.hotupdatePluginCheck, hotupdateConfig.configInput, hotupdateConfig.outInput, JSON.stringify(hotupdateConfig) ); if (!!hotupdateConfig.hotupdatePluginCheck) { // 验证配置参数 if (!hotupdateConfig.configInput || !hotupdateConfig.outInput) { console.error("热更新配置不完整:configInput 和 outInput 不能为空"); return; } try { // configInput 是带 project:// 前缀的,需要转换为实际文件路径 let configPath = hotupdateConfig.configInput; if (configPath.startsWith("project://")) { configPath = path.resolve( Editor.Project.path, configPath.replace("project://", "") ); } else if (!path.isAbsolute(configPath)) { configPath = path.resolve(Editor.Project.path, configPath); } let outInput = hotupdateConfig.outInput; if (outInput.startsWith("project://")) { outInput = path.resolve( Editor.Project.path, outInput.replace("project://", "") ); } else if (!path.isAbsolute(outInput)) { outInput = path.resolve(Editor.Project.path, outInput); } console.log("onAfterBuild configPath", configPath, outInput); const obj = require(configPath) as GenPackageConfig[]; for (const item of obj) { // 验证包名不能为空 if (!item.pkgName || item.pkgName.trim() === "") { console.error("热更新包名不能为空:", item); continue; } const source = path.join(result.dest, "data"); console.log("开始生成Package: ", item.pkgName, source); ensureDirSync( path.join(outInput, item.pkgName) ); generator(source, outInput, item); } // MinIO上传功能 if (hotupdateConfig.minioAutoUpload) { console.log("开始MinIO自动上传流程..."); await uploadToMinIO(hotupdateConfig, outInput, obj); } } catch (error) { console.error("热更新配置文件加载失败:", error); return; } } return new Promise((resolve, reject) => { readFile(url, "utf8", (err, data) => { if (err) { reject(err); return; } const newStr = injectScript + data; writeFile(url, newStr, (err) => { if (err) { reject(err); return; } resolve(); }); }); }); }; /** * 上传到MinIO * @param config 热更新配置 * @param outputPath 输出路径 * @param packages 包配置列表 */ async function uploadToMinIO( config: { minioEndpoint: string; minioAccessKeyId: string; minioSecretAccessKey: string; minioBucketName: string; minioUseSSL: boolean; minioPathPrefix: string; }, outputPath: string, packages: GenPackageConfig[] ) { try { // 验证MinIO配置 if (!config.minioEndpoint || !config.minioAccessKeyId || !config.minioSecretAccessKey || !config.minioBucketName) { console.error("MinIO配置不完整,跳过上传"); return; } // 创建MinIO上传器 const minioConfig: MinIOConfig = { endpoint: config.minioEndpoint, accessKeyId: config.minioAccessKeyId, secretAccessKey: config.minioSecretAccessKey, bucketName: config.minioBucketName, useSSL: config.minioUseSSL, pathPrefix: config.minioPathPrefix }; const uploader = createMinIOUploader(minioConfig); // 上传每个包 for (const pkg of packages) { if (!pkg.pkgName || pkg.pkgName.trim() === "") { continue; } const packageDir = path.join(outputPath, pkg.pkgName); if (!existsSync(packageDir)) { console.error(`包目录不存在: ${packageDir}`); continue; } console.log(`开始上传包到MinIO: ${pkg.pkgName}`); const result = await uploader.uploadDirectory(packageDir, pkg.pkgName); if (result.success) { console.log(`包 ${pkg.pkgName} 上传成功,共 ${result.uploadedFiles.length} 个文件`); console.log(`上传的文件: ${result.uploadedFiles.join(', ')}`); } else { console.error(`包 ${pkg.pkgName} 上传失败: ${result.error}`); } } console.log("所有包MinIO上传完成"); } catch (error) { console.error("MinIO上传过程中发生错误:", error); } }