一、前言

通常使用uniapp开发app时,大多数会使用项目的云服务打包,否则的话再借助原生会变得极其复杂,还要去安装对应大内存的环境。如果恰好此时,你有一个需求是,可以随意的更换logo和封面、标题切换成另外一个app,那么很明显你的HBuilderX维护起来是非常复杂的,如果我们想借助命令行打包命令动态打包该怎么做呢?

二、cli命令介绍

cli是HBuilderX给开发者提供可以通过cli命令行指示HBuilderX进行启动、打包、登录等操作。前提是你还是要有HBuilderX的应用,才能进行使用哦,官方链接

三、设计

  • 本地代码打包
  • 打包后的资源copy至本地项目
  • 本地资源进行替换
  • 本地代码进行打包
  • 取出云端的链接

四、核心实现过程

1.本地代码打包生成app资源代码

这里借助了child_processnpm插件方便我们执行cmd命令,cli是你的hbuilderx的cli命令的根位置,它可能是这样的 '/Applications/HBuilderX.app/Contents/MacOS/cli'

var {spawn} = require('child_process');
const {cli} = require('./../config/setting')
module.exports =  function(){
  return new Promise(async (resolve, reject) => {
    let resk = await spawn(cli, ['publish','--platform','APP','--type','appResource','--project','qxyuqing-app'])
    resk.on('close', async (code) => {
      console.log('[qxyuqing-app] app资源本地打包完成')
      resolve('success')
    })
  })
}
2.打包后的资源copy至本地项目
var {spawn,exec} = require('child_process');
const {cli,codeSource,uniId,codeTarget} =require('./../config/setting')
// codeSource 代码的地址
// uniId 项目分配Dcloud_AppId
// codeTarget 目标位置
module.exports = async function(callback){
  const pathUrl = new Promise((resolve, reject) => {
    spawn('pwd').stdout.on('data', (data) => {
      resolve(data.toString())
    })
  }) 
  let resUrl = await pathUrl
  resUrl = resUrl.replace(/\n/g, "");// 当前所在目录
  // 删除源码
  const args = ['-r',`${resUrl}/src/${codeTarget}`]
  let resk1 = await spawn('rm', args)
  resk1.on('close', async (code) => {
    console.log('delete old code success!')
    // copy源码到指令目录
    let resk2 = await spawn('cp', ['-r',`${codeSource}/${uniId}/www`,`${resUrl}/src`])
    resk2.on('close', async (code) => {
      console.log('copy success!')
      let resk3 = await spawn(cli, ['project','open','--path',`${resUrl}/src/${codeTarget}`])
      resk3.on('close', async (code) => {
        console.log('导入 hubildx success!')
        callback('success')
      })
    });
  });
}
3.本地资源进行替换

这里就是将app的logo,隐私协议、manifest.json、启动页进行更换

const { copyFiles, mergeJSONFiles,mergePngToZip,copyFileReplace } = require('./file')
const {codeTarget} = require('./../config/setting')

module.exports = function(node_env){
  return new Promise(async (resolve, reject) => {
    const folderA = `./config/${node_env}`
    const folderB = `./src/${codeTarget}`
    await copyFiles(`${folderA}/unpackage/res/icons`, `${folderB}/unpackage/res/icons`)
    console.log('logo替换完成!');
    // 替换logo oem默认配置
    // await copyFiles(`${folderA}/img/res/icons`, `${folderB}/unpackage/res/icons`)
    await copyFileReplace(`${folderA}/img/logo.png`,`${folderB}/static/logo.png`,'logo.png')
    await copyFileReplace(`${folderA}/img/index/logo.png`,`${folderB}/static/img/index/logo.png`,'logo.png')
    await copyFileReplace(`${folderA}/img/index/bj.png`,`${folderB}/static/img/index/bj.png`,'bj.png')
    await copyFileReplace(`${folderA}/img/videoSearch/searchLogo.png`,`${folderB}/static/img/videoSearch/searchLogo.png`,'searchLogo.png')
    console.log('oem资源替换完成!')
    // app名称、介绍配置替换
    await mergeJSONFiles(folderA,'androidPrivacy.json',folderB,'androidPrivacy.json')
    console.log('androidPrivacy隐私协议替换完成!');
    await mergeJSONFiles(folderA,'manifest.json',folderB,'manifest.json')
    console.log('app基础配置替换完成!');
    // ios替换 customStoryBoard 启动页
    await mergePngToZip(`${folderA}/iosCustomStoryBoard`,`${folderB}/static/CustomStoryboard`,`${folderB}/unpackage/CustomStoryboard.zip`)
    console.log('ios启动页替换完成!');
    // andriod 启动页更换
    await copyFileReplace(`${folderA}/iosCustomStoryBoard/dc_launchscreen_portrait_background@3x.png`,`${folderB}/static/yindao.png`,'yindao.png')
    resolve('success')
  })
}
4.本地代码进行打包
var {spawn,exec} = require('child_process');
const {cli,codeTarget} = require('./../config/setting')
module.exports = function(node_env){
  return new Promise(async (resolve, reject) => {
    const command = cli;
    const pathUrl = new Promise((resolve, reject) => {
      spawn('pwd').stdout.on('data', (data) => {
        console.log(`stdout: ${data}`);
        resolve(data.toString())
      })
    }) 
    let resUrl = await pathUrl
    resUrl = resUrl.replace(/\n/g, "");
    console.log(resUrl)
    const args = ['pack','--project',codeTarget, '--config', `${resUrl}/config/${node_env}/pca/configure.json`];
    const child = spawn(command, args);
    child.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
      const regex = /(https?:\/\/[^\s]+)/g;
      const result = data.toString().match(regex);
      if(result){
        const downloadUrl = result.filter(item=>item.indexOf('/build/download')!=-1)
        if(downloadUrl.length>0){
          console.log('++++++++++++++++++++下载地址+++++++++++++++++++++++++++++++++++')
          console.log(downloadUrl[0])
          console.log('++++++++++++++++++++下载地址+++++++++++++++++++++++++++++++++++')
        }
      }
      if(data.toString().indexOf('是否继续提交')!=-1){
        console.log('重新提交')
      }
    });
  
    child.stderr.on('data', (data) => {
      console.error(`stderr: ${data}`);
      reject('err')
    });
  
    child.on('close', (code) => {
      console.log(`子进程退出码:${code}`);
      console.log("已完成");
      resolve('success')
    });
  })

12-27 16:52