buildCocos.js 11.5 KB
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 = 'C:\\CocosDashboard_1.0.6\\resources\\.editors\\Creator\\2.4.5\\CocosCreator.exe';
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 开始~!');

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

  const path = 'assets'
  const folderName = getFolderName(path);
  editFolderMeta(path, folderName);

  const oldFireUuid = '57ea7c61-9b8b-498a-b024-c98ee9124beb';
  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);


  function getShortUuid(uuid) {
    const bytes = parse(uuid).subarray(1);
    return uuid.substring(0, 5) + Base64.fromUint8Array(bytes).substring(2);
  }

  const oldJsUuid = 'f4ede462-f8d7-4069-ba80-915611c058ca';
  const oldJsShortUuid = 'f4edeRi+NdAabqAkVYRwFjK';
  const oldJsId = 'e687yyoRBIzZAOVRL8Sseh';
  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}.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();
    // 改设置为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();
    // 改设置为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 成功!');
  }

};