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); } function replaceUuids () { console.log('build_step_0 开始~!'); 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; } const folderName = getFolderName(path); let oldFireUuid = ''; let oldJsUuid = ''; let oldJsShortUuid = ''; let oldJsId = ''; const fireMetaStr = fs.readFileSync(`assets/${folderName}/scene/${folderName}.fire.meta`); if (fireMetaStr.indexOf('57ea7c61-9b8b-498a-b024-c98ee9124beb') > 0) { // 老Cocos脚手架 oldFireUuid = '57ea7c61-9b8b-498a-b024-c98ee9124beb'; oldJsUuid = 'f4ede462-f8d7-4069-ba80-915611c058ca'; oldJsShortUuid = 'f4edeRi+NdAabqAkVYRwFjK'; oldJsId = 'e687yyoRBIzZAOVRL8Sseh'; } if (fireMetaStr.indexOf('0737ce42-24f0-45c6-8e1a-8bdab4f74ba3') > 0) { // 新Cocos脚手架 oldFireUuid = '0737ce42-24f0-45c6-8e1a-8bdab4f74ba3'; oldJsUuid = '408a67f8-65fa-4cf1-8cf2-83e20e1a0fd5'; oldJsShortUuid = '408a6f4ZfpM8Yzyg+IOGg/V'; oldJsId = 'eaTVUpqahPfZeO9+sUI7RP'; } if (oldFireUuid === '') { return; } 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); } editFolderMeta(path, folderName); const newFireUuid = v4(); fileReplace(`assets/${folderName}/scene/${folderName}.fire.meta`, oldFireUuid, newFireUuid); fileReplace(`assets/${folderName}/scene/${folderName}.fire`, oldFireUuid, newFireUuid); fileReplace('settings/builder.json', oldFireUuid, newFireUuid); const newJsUuid = v4(); const newJsShortUuid = getShortUuid(newJsUuid); const newJsId = v4().replace(/-/g, '').substring(0, oldJsId.length); fileReplace(`assets/${folderName}/scene/${folderName}.ts.meta`, oldJsUuid, newJsUuid); fileReplace(`assets/${folderName}/scene/${folderName}.js.meta`, oldJsUuid, newJsUuid); fileReplace(`assets/${folderName}/scene/${folderName}.fire`, oldFireUuid, newFireUuid); fileReplace(`assets/${folderName}/scene/${folderName}.fire`, oldJsShortUuid, newJsShortUuid); fileReplace(`assets/${folderName}/scene/${folderName}.fire`, oldJsId, newJsId); 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 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 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 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 成功!'); } };