从0到1开发一个小程序cli脚手架(二) --版本发布/管理篇
接上文 《从0到1开发一个小程序cli脚手架(一)–创建页面/组件模版篇》
github地址:https://github.com/jinxuanzheng01/xdk-cli 觉得有用的朋友帮忙给项目一个star,谢谢
上文大家应该大致学会了怎么搭建一个cli脚手架,包括实现了一个快速生成启动模版的功能,本质上作为脚手架应该可以做更多的事情,本篇文章会实现一些新的功能,例如:自动发布体验版,版本号控制,环境变量控制
痛点
不知道大家有没有一天发多次版本或者一天给多个小程序发版的经历,按照微信正常的发布流程,需要:
修改版本号/版本描述
修改发布环境
点击微信开发者工具上传体验版
提交审核
确认环境/版本
点击发布
其中所有的1,2步为手工修改config文件,第5步是确认手工修改config文件的正确性,毕竟人总会犯错,作者表示就干过线下环境发布到测试环境的事情,而且这是在做了第5步的情况下,很遗憾没有仔细核对
为了不再次发生同样的事情导致引咎辞职,那么有没有更好的方法呢 ?自然是有的,既然人不可靠,那么直接把它流程化自然就可以了
准备工作
最好阅读了上篇文章《从0到1开发一个小程序cli脚手架(一)–创建页面/组件模版篇》,并搭建了一个简单的demo
需要了解微信小程序提供的一些cli能力, 点击这里
Let‘ go
后续的很多流程上的实操代码为了缩短篇幅会以伪代码的形式来进行描述,强烈推荐先阅读上文,如果需要更详细的实操代码请去github仓库查看
实际效果图:[图片]
梳理流程
识别命令行
询问问题,拿到版本号和版本描述
调用微信提供的cli能力,进行体验版上传
是不是发现非常简单,事实也是如此,整个功能做下来也就60行代码 ~
目录结构
项目结构分为入口文件,配置文件
[代码]- lib
- publish-weapp.js
- index.js
- config.js
- node_modules
- package.json
[代码]
config.js用来记录一些基础常量和默认项的配置,例如项目路径,执行路径等
[代码]module.exports = {
// 根目录
root: __dirname,
// 执行命令目录路径
dir_root: process.cwd(),
// 小程序项目路径
entry: './',
// 项目编译输出文件夹
output: './',
}
[代码]
识别命令行
在入口文件(index.js)处利用第三方库 commander 识别命令行参数,同时作为路由进行任务分发,
[代码]#!/usr/bin/env node
const version = require('./package').version; // 版本号
/* = package import
-------------------------------------------------------------- */
const program = require('commander'); // 命令行解析
/* = task events
-------------------------------------------------------------- */
const publishWeApp = require('./lib/publish-weapp'); // 发布体验版
program
.command('publish')
.description('发布微信小程序体验版')
.action((cmd, options) => publishWeApp();
/* = main entrance
-------------------------------------------------------------- */
program.parse(process.argv)
[代码]
创建交互命令
接下来是一个QA环节,这时候需要根据自己的实际需要安排自己需要的命令,可以回想一下要解决的问题:
修改版本号/版本描述
修改发布环境
根据cli自动上传体验版
大概得出这样一个队列:
[代码]function getQuestion({version, versionDesc} = {}) {
return [
// 确定是否发布正式版
{
type: 'confirm',
name: 'isRelease',
message: '是否为正式发布版本?',
default: true
},
// 设置版本号
{
type: 'list',
name: 'version',
message: `设置上传的版本号 (当前版本号: ${version}):`,
default: 1,
choices: getVersionChoices(version),
filter(opts) {
if (opts === 'no change') {
return version;
}
return opts.split(': ')[1];
},
when(answer) {
return !!answer.isRelease
}
},
// 设置上传描述
{
type: 'input',
name: 'versionDesc',
message: `写一个简单的介绍来描述这个版本的改动过:`,
default: versionDesc
},
]
}
[代码]
最后获得的json对象,是这个样子:
[代码]{isRelease: true, version: '1.0.1', versionDesc: '这是一个体验版'}
[代码]
确定是否发布正式版
主要是因为体验版并非完全是发行版,公司内部测试的时候也是需要发布测试用体验版的,但是又涉及只有正式版本修改本地版本文件,那么就只能多此一问作为区分了
设置版本号 / 设置版本信息
[图片]
设置上述如图的 体验版上的版本号和描述信息,这里有个case是只有第一个问题选择发布正式版才会让你设置版本号,测试版本使用默认版本号0.0.0,这也是区分体验版的一种方式
[代码] when(answer) {
return !!answer.isRelease
}
[代码]
这里只设置了三个问题:是否发布正式版,设置版本号,设置上传描述,当然你也可以设置自己想做的其他事情
上传体验版
翻了下小程序的文档,大概犹如下几个关键点:
找到cli工具
小程序cli并非全局安装,需要自己去索引路径,命令行工具所在位置:macOS: [代码]<安装路径>/Contents/MacOS/cli[代码]
Windows: [代码]<安装路径>/cli.bat[代码]
mac的 安装路径 如果是默认安装的话,是/Applications/wechatwebdevtools.app/, 外加cli的位置是: /Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli
windows 的话作者表示没有这个环境,只能大家自己探索了
拼凑上传命令
官方文档给了非常详细的描述:
[图片]
[代码]# 上传路径 /Users/username/demo 下的项目,指定版本号为 1.0.0,版本备注为 initial release
cli -u 1.0.0@/Users/username/demo --upload-desc 'initial release'
# 上传并将代码包大小等信息存入 /Users/username/info.json
cli -u 1.0.0@/Users/username/demo --upload-desc 'initial release' --upload-info-output /Users/username/info.json
[代码]
编写上传逻辑
基本流程:
获取cli -> 获取当前版本配置 -> 问题队列(获取上传信息)-> 执行上传(cli命令)-> 修改本地版本文件 -> 成功提示
[代码]// ./lib/publish-weapp.js 文件
module.exports = async function(userConf) {
// cli路径
const cli = `/Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli`;
// 版本配置文件路径
const versionConfPath = Config.dir_root + '/xdk.version.json';
// 获取版本配置
const versionConf = require(versionConfPath);
// 开始执行问题队列 anser case: {isRelease: true, version: '1.0.1', versionDesc: '这是一个体验版'}
let answer = await inquirer.prompt(getQuestion(versionConf));
versionConf.version = answer.version || '0.0.0';
versionConf.versionDesc = answer.versionDesc;
//上传体验版
let res = spawn.sync(cli, ['-u', `${versionConf.version}@${Config.output}`, '--upload-desc', versionConf.versionDesc], { stdio: 'inherit' });
if (res.status !== 0) process.exit(1);
// 修改本地版本文件 (当为发行版时)
!!answer.isRelease && await rewriteLocalVersionFile(versionConfPath, versionConf);
// success tips
Log.success(`上传体验版成功, 登录微信公众平台 https://mp.weixin.qq.com 获取体验版二维码`);
}
// 修改本地版本文件
function rewriteLocalVersionFile(filepath, versionConf) {
return new Promise((resolve, reject) => {
fs.writeFile(filepath, jsonFormat(versionConf), err => {
if(err){
Log.error(err);
process.exit(1);
}else {
resolve();
}
})
})
}
[代码]
注意这里需要在项目根目录下创建一个xdk.version.json的版本文件,详细配置说明见仓库
大致是这样的结构:
[代码]// xdk.version.json
{
"version": "0.12.2",
"versionDesc": "12.2版本"
}
[代码]
到这里基本就完成了上传的所有步骤,可以当前项目下键入[代码]xdk-cli publish[代码]查看一下程序是否正常运行,注意上传出错记得检查是否处于登录状态
扩展:关于版本号自增
可以看到在版本号环节使用的是list类型,而非input类型,这是因为手写版本号有写错的风险, 还是让我做选择题吧 emmm…
最终效果:
[图片]
就不在这里展开了,可以下, 搜索[代码]getVersionChoices[代码]函数:
https://github.com/jinxuanzheng01/xdk-cli/blob/master/lib/publish-weapp.js
解决打包工具的问题
你会发现是运行cli命令就直接发布了,那么用gulp,webpack等工具项目因为需要上传dist目录而非src的原因,需要先进行打包再进行发布: gulp -> xdk-cli publish ,将一个动作拆成了两个
遗忘了一个怎么办?纠结了
不知道大家对微信开发者工具的上传钩子还有没有印象,要实现的大概就是这么个东西,
一个上传前预处理的钩子
[图片]
这个实现起来也很简单,首先给用户的配置文件(xdk.config.js)开一个可配置项:
[代码]// xdk.config.js
{
// 发布钩子
publishHook: {
async before(answer) {
this.spawnSync('gulp');
return Promise.resolve();
},
async after(answer) {
this.log('发布后的钩子执行了~');
return Promise.resolve();
}
}
}
[代码]
在publish-weapp.js中去识别钩子即可:
[代码] // 前置钩子函数
await userConf.publishHook.before.call(originPrototype, answer);
//上传体验版
let res = spawn.sync(cli, ['-u', `${versionConf.version}@${Config.output}`, '--upload-desc', versionConf.versionDesc], { stdio: 'inherit' });
// 后置钩子函数
await userConf.publishHook.after.call(originPrototype, answer);
[代码]
当然,你需要判断下钩子是否存在,类型判断,包括需要返回给用户配置里一些基本的方法和属性,例如:日志输出,命令行执行等
我这边以gulp为例,可以看到在publsih前先执行[代码]gulp[代码],后进行发布,最后log出一行提示信息
完全符合预期
解决环境变量切换问题
解决了自动上传的问题,接下来就要解决环境变量切换的问题了,这里还要借用下刚才写的钩子函数:
[代码]{
// 发布钩子
publishHook: {
async before(answer) {
this.spawnSync('gulp', [`--env=${answer.isRelease ? 'online' : 'stage'}`]);
return Promise.resolve();
},
async after(answer) {
this.log.success('发布后的钩子执行了~');
return Promise.resolve();
}
}
}
[代码]
以gulp为例,根据之前的回答的结果answer.isRelease,判断是否为使用正式环境
如果你没有使用编译工具,也可以提出一个env的config文件,使用fs模块直接重写该环境变量
下面是一串伪代码:
[代码]目录结构
- app (小程序项目)
- page
- app.json
...
- env.js
- xdk.config.js
- xdk.version.js
- envConf.js
// envConf.js
module.exports = {
['online']: {
appHost1: 'https://app.xxxxxxxxx.com',
appHost2: 'https://app.xxxxxxxxx.com',
},
['stage']: {
appHost1: 'https://stage.xxxxxxxxx.com',
appHost2: 'https://stage.xxxxxxxxx.com',
}
};
// xdk.config.js
publishHook: {
async before(answer) {
let config = require('envConf.js')[answer.isRelease ? 'online' : 'stage'];
fs.writeFile('./app/env.js', jsonFormat(jsonConf), (err) => {
if(err){
this.log.error('写入失败')
}else {
this.log.success('写入成功);
}
});
return Promise.resolve();
}
[代码]
打包工具的问题还有环境变量的问题,我都没有写在cli里面,是因为不同项目之间对环境变量的写法,格式都不尽相同,具体要怎么做还是要留给开发者自己来确定吧,这样看起来更灵活一些
最后
总之利用脚手架解决了从发布到上线的一连串问题,使得不再担心因为切换环境导致线上bug,也不再担心写版本号写错的问题,确认线上环境这个环境也变成了一个非强需求的事情
整个上线流程也只需要:xdk-cli publish -> 提交审核即可,而且整个代码也并不复杂,publish-weapp.js整个文件算上注释也就60行代码,何乐不为呢?
注:下篇会讲一下如何做自定义指令,帮助小伙伴可以更自由的适配不同的项目~