一、前言
通常使用uniapp开发app时,大多数会使用项目的云服务打包,否则的话再借助原生会变得极其复杂,还要去安装对应大内存的环境。如果恰好此时,你有一个需求是,可以随意的更换logo和封面、标题切换成另外一个app,那么很明显你的HBuilderX维护起来是非常复杂的,如果我们想借助命令行打包命令动态打包该怎么做呢?
二、cli命令介绍
cli是HBuilderX给开发者提供可以通过cli命令行指示HBuilderX进行启动、打包、登录等操作。前提是你还是要有HBuilderX的应用,才能进行使用哦,官方链接
三、设计
- 本地代码打包
- 打包后的资源copy至本地项目
- 本地资源进行替换
- 本地代码进行打包
- 取出云端的链接
四、核心实现过程
1.本地代码打包生成app资源代码
这里借助了child_process
npm插件方便我们执行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')
});
})