const { spawn } = require("child_process"); const fs = require("fs"); const compressing = require('compressing'); const { v4, parse } = require('uuid'); const { Base64 } = require('js-base64'); const { copyDir, removeDir, fix2 } = require("./utils"); async function buildForm() { const paths = fs.readdirSync('form'); if (!paths.includes('tsconfig.json')) { await copyDir('form', 'dist/form'); return; } if (process.platform == 'win32') { await execCmd('npm.cmd', ['install'], 'form'); } else { await execCmd('npm', ['install'], 'form'); } if (process.platform == 'win32') { await execCmd('npm.cmd', ['run', 'publish'], 'form'); } else { await execCmd('npm', ['run', 'publish'], 'form'); } await compressing.zip.uncompress('form/publish/form.zip', 'dist/form'); } function execCmd(cmd, params, path) { return new Promise((resolve, reject) => { const buffer = spawn( cmd, params, { cwd: path } ); buffer.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); buffer.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); buffer.on('close', (code) => { console.log(`child process exited with code ${code}`); resolve(); }); }); } let creatorBasePath = 'CocosCreator'; if (process.platform !== 'win32') { creatorBasePath = "/Applications/CocosCreator/Creator/2.4.5/CocosCreator.app/Contents/MacOS/CocosCreator"; } const buildCocos = function (args) { return new Promise((resolve, reject) => { const buffer = spawn( creatorBasePath, args, { cwd: '.' } ); buffer.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); buffer.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); buffer.on('close', (code) => { console.log(`child process exited with code ${code}`); resolve(); }); }); }; function getReleaseFileName(projectName) { let date = new Date(); let fileName = `${projectName}_${date.getFullYear()}${fix2(date.getMonth() + 1)}${fix2(date.getDate())} `; fileName += `${fix2(date.getHours())}-${fix2(date.getMinutes())}-${fix2(date.getSeconds())}`; return fileName; } function getFolderName(path) { let folderName = ''; fs.readdirSync(path).find(fileName => { const st = fs.statSync(`${path}/${fileName}`); if (st.isDirectory()) { folderName = fileName; } }); return folderName; } function editFolderMeta(path, folderName, isBundle) { const metaPath = `${path}/${folderName}.meta`; const metaDataStr = fs.readFileSync(metaPath); const metaData = JSON.parse(metaDataStr); metaData.isBundle = isBundle; metaData.isRemoteBundle = { ios: isBundle, android: isBundle }; fs.writeFileSync(metaPath, JSON.stringify(metaData)); } async function buildWebDesktop() { const args = ['--path', './', '--build', 'platform=web-desktop;debug=true', '--force']; await buildCocos(args); } async function buildAndroidBundle() { const args = ['--path', './', '--build', "platform=ios;debug=false;md5Cache=true;buildPath=build_android;appABIs=['armeabi-v7a','x86','x86_64','arm64-v8a'];encryptJs=true;xxteaKey=6bbfce23-28b4-4a;zipCompressJs=true", '--force']; await buildCocos(args); } async function buildIosBundle() { const args = ['--path', './', '--build', "platform=ios;debug=false;md5Cache=true;buildPath=build_ios;encryptJs=true;xxteaKey=6bbfce23-28b4-4a;zipCompressJs=true", '--force']; await buildCocos(args); } async function buildWebBundle() { const args = ['--path', './', '--build', "platform=web-desktop;debug=false;buildPath=build_web_desktop", '--force']; await buildCocos(args); } function createConfigFile(projectName, type) { let iosVersion = ""; let androidVersion = ""; if (!type) { const androidPaths = fs.readdirSync(`dist/android/${projectName}`); const androidConfigFileName = androidPaths.find(path => path.indexOf('config') == 0); androidVersion = androidConfigFileName.split('.')[1]; const iosPaths = fs.readdirSync(`dist/ios/${projectName}`); const iosConfigFileName = iosPaths.find(path => path.indexOf('config') == 0); iosVersion = iosConfigFileName.split('.')[1]; } else { if (type == "android") { const androidPaths = fs.readdirSync(`dist/android/${projectName}`); const androidConfigFileName = androidPaths.find(path => path.indexOf('config') == 0); androidVersion = androidConfigFileName.split('.')[1]; } else { const iosPaths = fs.readdirSync(`dist/ios/${projectName}`); const iosConfigFileName = iosPaths.find(path => path.indexOf('config') == 0); iosVersion = iosConfigFileName.split('.')[1]; } } const config = { "ios": { "sceneName": projectName, "version": iosVersion }, "android": { "sceneName": projectName, "version": androidVersion } } fs.writeFileSync('dist/config.json', JSON.stringify(config)); } function compressAll(projectName) { const tarStream = new compressing.zip.Stream(); tarStream.addEntry('dist/play'); tarStream.addEntry('dist/form'); tarStream.addEntry('dist/ios'); tarStream.addEntry('dist/android'); tarStream.addEntry('dist/web_desktop'); tarStream.addEntry('dist/config.json'); const destStream = fs.createWriteStream(`publish/${getReleaseFileName(projectName)}.zip`); tarStream.pipe(destStream); } function build_check() { const dirNames = process.cwd().split(/\/|\\/); const projectName = dirNames[dirNames.length - 1]; const path = 'assets' let folderName = ''; fs.readdirSync(path).find(fileName => { const st = fs.statSync(`${path}/${fileName}`); if (st.isDirectory()) { folderName = fileName; } }); if (projectName != folderName) { throw (`项目名(${projectName})与bundle文件夹名(${folderName})不相同`); } let same = false; const files = fs.readdirSync(`${path}/${folderName}/scene`); files.forEach(fileName => { fileName.split('.').forEach((str, idx, arr) => { if (str == 'fire') { const sceneName = arr[idx - 1]; if (folderName == sceneName) { same = true; } } }) }); if (!same) { throw (`bundle文件夹名称(${folderName})与scene名称不相同`); } return projectName; } function changeSettingToWebDesktop() { const path = 'assets' const folderName = getFolderName(path); editFolderMeta(path, folderName, false); } function changeSettingsToBundle() { const path = 'assets' const folderName = getFolderName(path); editFolderMeta(path, folderName, true); } async function replaceUuids() { console.log('build_step_0 开始~!'); function editFolderMeta(path, folderName) { const metaPath = `${path}/${folderName}.meta`; const metaDataStr = fs.readFileSync(metaPath); const metaData = JSON.parse(metaDataStr); metaData.isBundle = false; fs.writeFileSync(metaPath, JSON.stringify(metaData)); } function fileReplace(path, replaceStr, newStr) { if (!fs.existsSync(path)) { return; } const fileStr = fs.readFileSync(path); const newFileStr = fileStr.toString().replace(new RegExp(replaceStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), 'g'), newStr); fs.writeFileSync(path, newFileStr); } function getShortUuid(uuid) { const bytes = parse(uuid).subarray(1); return uuid.substring(0, 5) + Base64.fromUint8Array(bytes).substring(2); } const path = 'assets' function getFolderName(path) { let folderName = ''; fs.readdirSync(path).find(fileName => { const st = fs.statSync(`${path}/${fileName}`); if (st.isDirectory()) { folderName = fileName; } }); return folderName; } async function fileForEach(src, func) { //读取目录 const st = fs.statSync(src); if (st.isFile()) { await func(src); return; } const paths = fs.readdirSync(src); for (let i = 0; i < paths.length; i++) { let path = paths[i]; const newSrc = `${src}/${path}`; const st = fs.statSync(newSrc); if (st.isFile()) { await func(newSrc); } else if (st.isDirectory()) { await fileForEach(newSrc, func); } } } function createUuidData(uuid) { const shortUuid = getShortUuid(uuid); const newUuid = v4(); const newShortUuid = getShortUuid(newUuid); return { oldUuid: uuid, oldShortUuid: shortUuid, newUuid: newUuid, newShortUuid: newShortUuid, }; } const uuidList = []; await fileForEach(path, (path) => { const nameList = path.split('.'); if (nameList[nameList.length - 1] == 'meta') { const data = JSON.parse(fs.readFileSync(path)); uuidList.push(createUuidData(data.uuid)); if (data.subMetas) { for (const key in data.subMetas) { const subMet = data.subMetas[key]; if (subMet.uuid) { uuidList.push(createUuidData(subMet.uuid)); } } } } }); const pathList = [path, 'settings', 'project.json']; for (let i = 0; i < pathList.length; i++) { const path = pathList[i]; await fileForEach(path, (path) => { const nameList = path.split('.'); const expectNameList = ['png', 'jpg', 'mp3', 'wav']; if (expectNameList.includes(nameList[nameList.length - 1])) { return; } const data = fs.readFileSync(path); uuidList.forEach(uuiddata => { if (data.includes(uuiddata.oldUuid)) { fileReplace(path, uuiddata.oldUuid, uuiddata.newUuid); } if (data.includes(uuiddata.oldShortUuid)) { fileReplace(path, uuiddata.oldShortUuid, uuiddata.newShortUuid); } }); }); } console.log('build_step_0 完成~!'); } function replaceIndexHtml() { const data = fs.readFileSync('index.html'); fs.writeFileSync('dist/play/index.html', data); } module.exports = { build: async function () { const startTime = new Date().getTime(); // 构建前检查 const projectName = build_check(); // 清理旧文件 // 构建form await removeDir('dist/form'); await buildForm(); // 替换uuid await replaceUuids(); // 改设置为非bundle changeSettingToWebDesktop(); // 构建play await removeDir('dist/play'); await buildWebDesktop(); await copyDir('build/web-desktop', 'dist/play'); replaceIndexHtml(); console.log('构建 web desktop 成功!'); // 改设置为bundle changeSettingsToBundle(); await removeDir('dist/android'); await buildAndroidBundle(); await copyDir('build_android/jsb-link/remote', 'dist/android'); console.log('构建 android bundle 成功!'); await removeDir('dist/ios'); await buildIosBundle(); await copyDir('build_ios/jsb-link/remote', 'dist/ios'); console.log('构建 ios bundle 成功!'); await removeDir('dist/web_desktop'); await buildWebBundle(); await copyDir(`build_web_desktop/web-desktop/assets/${projectName}`, 'dist/web_desktop'); console.log('构建 web bundle 成功!'); // 改设置为非bundle changeSettingToWebDesktop(); createConfigFile(projectName); compressAll(projectName); await removeDir('build'); await removeDir('build_android'); await removeDir('build_ios'); await removeDir('build_web_desktop'); const endTime = new Date().getTime(); const duration = new Date(endTime - startTime); console.log(`打包完成!`); console.log(`用时${duration.getMinutes()}分${duration.getSeconds()}秒。`); }, buildAndroid: async function () { // 构建前检查 const projectName = build_check(); // 替换uuid await replaceUuids(); // 改设置为bundle changeSettingsToBundle(); await removeDir('dist/android'); await buildAndroidBundle(); await copyDir('build_android/jsb-link/remote', 'dist/android'); // 改设置为非bundle changeSettingToWebDesktop(); createConfigFile(projectName, "android"); await removeDir('build_android'); console.log('构建 android bundle 成功!'); }, buildIos: async function () { // 构建前检查 const projectName = build_check(); // 替换uuid await replaceUuids(); // 改设置为bundle changeSettingsToBundle(); await removeDir('dist/ios'); await buildIosBundle(); await copyDir('build_ios/jsb-link/remote', 'dist/ios'); // 改设置为非bundle changeSettingToWebDesktop(); createConfigFile(projectName, "ios"); await removeDir('build_ios'); console.log('构建 ios bundle 成功!'); } };