想一想,输入一个命令就可以直接上传小程序的代码,还能做一些前置操作,让整个上传流程自动化,岂不是很爽!
需求背景
原生的小体型的微信小程序项目通常不需要思考这方面的解决方案,因为将代码上传到微信服务器不需要太多的前置操作。
如果是大体型的,或是使用第三方框架开发的微信小程序项目,在上传前就需要一定的前置操作。
如切换分支,切换环境,编译代码,维护日志等。
场景也不仅限一个微信小程序项目,一套代码是有肯能发布多个微信小程序项目的,切换微信开发者工具去一个个上传代码效率非常低下。
同时人工的做这些操作是有可能出现问题的,如依赖的子项目忘记更新就上传了代码。
这些需要人工成本的固定步骤,希望有一套工具可以解放劳动力,同时可以避免因为人工失误带来的问题。
方案
微信开发者工具提供了命令行工具来做基础操作。
可以通过 cli upload
来上传代码。
cd 命令行工具所在目录
./cli upload --project 项目地址 -v 项目版本 -d '版本描述'
基于这个命令行工具,向上扩展内容即可。
需求
希望通过简单的命令就可以达到上传的目的。
假设工具名是 mpup
。
mpup --config=配置文件
分析配置文件
按照最基础版本来分析
首先是必要的配置项:
-
项目路径
也就是要上传的代码 -
小程序开发者工具提供的命令行工具路径
用于定位工具,上传代码 -
版本号
每次上传代码时候的标志,小程序官方要求的必填项
其他配置项:
根据需求,代码上传前要前置的做一些事情,这些事情其实都是一些命令。
如切换分支,就是执行 git
命令,编译代码,可能会使用 babel
相关工具。
所以需要一个命令集合。
另外应该在各个阶段暴露一些钩子,提供中断操作等。
-
命令集合
上传前要执行的所有命令 -
各个阶段的钩子
提供一些额外能力,如记录日志,中断上传
所以一个配置文件大概会是这样的:
module.exports = {
// 项目路径
projectPath: '',
// 小程序工具路径
mpToolPath: '',
// 代码版本
ver: '',
// 版本描述,这个配置是传递给命令行工具的,微信上传代码前填写的表单中的一项,但不是必填项
desc: '',
// 命令集合
commends: [
{
// 要执行的命令
cmd: '',
// 执行命令前做的事
async before() {},
// 执行命令后做的事
async after() {},
// 执行命令失败的时候做的事
async error() {},
},
],
// 所有钩子执行前
async beforeExecAllCommends() {},
// 所有钩子执行后
async afterExecAllCommends() {},
// 上传前
async beforeUpload() {},
// 上传后
async afterUpload() {},
};
实现
获取配置文件
配置文件是通过命令后传入的,所以使用 yargs
来处理命令后传入的参数。
const yargs = require('yargs');
const argv = yargs
.usage('mpup [options]')
.option('config', {
describe: '上传配置',
type: 'string',
})
.help('help')
.argv;
使用 mpup --config=./mpup.config.js
,就可以从 argv
里取出 config
参数。
获取到参数后就是取到配置文件。
const path = require('path');
const { config } = argv;
const configPath = path.resolve(process.cwd(), config);
const mpupConfig = require(configPath);
通过 process.cwd
获取到执行命令的路径,然后和 config
参数一起处理,得到配置文件路径,再 require
到就得到了配置。
中断方法
只要退出了当前进程,就是中断了上传,所以中断方法很简单。
const abort = () => {
process.exit();
};
执行所有前置命令
执行命令需要用到 child_process
模块的 exec
或者 execSync
方法,这两个方法作用是相同的,用于在某个工作目录下执行某个命令。
这里使用 exec
并简单封装一下。
const { exec } = require('child_process');
const execCmd = (cmd, path) => {
return new Promise((resolve, reject) => {
try {
exec(cmd, { cwd: path }, (err, stdout, stderr) => {
if (stdout) {
resolve(stdout);
return;
}
reject(err || stderr);
});
} catch (e) {
reject(e);
}
});
};
随后就是从 mpupConfig
中获取到所有命令,并执行。
const {
commends,
beforeExecAllCommends,
afterExecAllCommends,
} = mpupConfig;
// 开始执行命令前的钩子
await beforeExecAllCommends({ abort });
for (let i = 0, len = commends.length; i < len; i++) {
const commend = commends[i];
const {
cmd,
before = () => {},
after = () => {},
error = () => {},
} = commend;
await before({ abort, commend });
let stdout = '';
try {
stdout = await execCmd(cmd);
} catch (e) {
await error({
e,
abort, // 中断函数
})
}
await after({ abort, commend, stdout });
}
// 结束所有命令后的钩子
await afterExecAllCommends({ abort });
上传代码
这一步就是调用微信团队提供的工具了。
// 判断平台,因为在windows和mac下执行的命令不同
const isMacOS = () => {
return !(/^win/.test(process.platform));
};
// 获取到 项目路径 、 工具路径 、 版本 、 版本描述
const {
projectPath,
mpToolPath,
desc,
ver,
beforeUpload,
afterUpload,
} = mpupConfig;
// 根据环境和配置拼出命令
const uploadCmd = `${isMacOS() ? './' : ''}cli${isMacOS() ? '' : '.bat'} upload --project=${projectPath} --version=${ver} --desc=${desc}`;
// 上传前的钩子
await beforeUpload({ abort });
// 到命令行工具的地址执行上传命令
const stdout = await execCmd(uploadCmd, mpToolPath);
// 上传后的钩子
await afterUpload({ abort, stdout });
创建软连接
因为最终是要作为命令后工具使用,所以需要创建一个软连接。
在 package.json
中加入 bin
属性。
{
"bin": {
"mpup-test": "./mpup.js"
}
}
上述代码都放在 ./mpup.js
中,为了防止本地已经安装好的 mpup
冲突,这里将名字作为了 mpup-test
。
随后在 package.json
所在的目录执行 npm link
,就会建立软连接,碰到权限问题记得加 sudo
前缀。
mpup.js
文件第一行需要加入 #! /usr/bin/env node
,表示使用 node
来执行文件。
成果
先汇总一份代码:
#! /usr/bin/env node
const { exec } = require('child_process');
const yargs = require('yargs');
const path = require('path');
// 用于判断平台,因为在windows和mac下执行的命令不同
const isMacOS = () => {
return !(/^win/.test(process.platform));
};
const execCmd = (cmd, path) => {
return new Promise((resolve, reject) => {
try {
exec(cmd, { cwd: path }, (err, stdout, stderr) => {
if (stdout) {
resolve(stdout);
return;
}
reject(err || stderr);
});
} catch (e) {
reject(e);
}
});
};
const abort = () => {
process.exit();
};
const argv = yargs
.usage('mpup [options]')
.option('config', {
describe: '上传配置',
type: 'string',
})
.help('help')
.argv;
(async () => {
const { config } = argv;
const configPath = path.resolve(process.cwd(), config);
const mpupConfig = require(configPath);
const {
commends,
beforeExecAllCommends,
afterExecAllCommends,
} = mpupConfig;
// 开始执行命令前的钩子
await beforeExecAllCommends({ abort });
for (let i = 0, len = commends.length; i < len; i++) {
const commend = commends[i];
const {
cmd,
before = () => {},
after = () => {},
error = () => {},
} = commend;
await before({ abort, commend });
let stdout = '';
try {
stdout = await execCmd(cmd);
} catch (e) {
await error({
e,
abort, // 中断函数
})
}
await after({ abort, commend, stdout });
}
// 结束所有命令后的钩子
await afterExecAllCommends({ abort });
// 获取到 项目路径 、 工具路径 、 版本 、 版本描述
const {
projectPath,
mpToolPath,
desc,
ver,
beforeUpload,
afterUpload,
} = mpupConfig;
// 根据环境和配置拼出命令
const uploadCmd = `${isMacOS() ? './' : ''}cli${isMacOS() ? '' : '.bat'} upload --project=${projectPath} --version=${ver} --desc=${desc}`;
// 上传前的钩子
await beforeUpload({ abort });
// 到命令行工具的地址执行上传命令
const uploadStdout = await execCmd(uploadCmd, mpToolPath);
// 上传后的钩子
await afterUpload({ abort, stdout: uploadStdout });
})();
现在可以在工程目录下先安装一下依赖 yargs
。
npm i yargs -S
随后提供一份配置文件。
// mpup.config.js
module.exports = {
// 项目路径
// 记得配置这个哦 不然会失败的
projectPath: '',
// 小程序工具路径
mpToolPath: '/Applications/wechatwebdevtools.app/Contents/MacOS',
// 代码版本
ver: '1.3.1',
// 版本描述,这个配置是传递给命令行工具的,微信上传代码前填写的表单中的一项,但不是必填项
desc: 'beta版本',
// 命令集合
commends: [
{
// 要执行的命令
cmd: 'ls -a',
// 执行命令前做的事
async before() {
console.log('before ls -a');
},
// 执行命令后做的事
async after({ stdout }) {
console.log('after ls -a');
console.log(stdout);
},
},
],
// 所有钩子执行前
async beforeExecAllCommends() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('开始前先停两秒!');
resolve();
}, 2000);
});
},
// 所有钩子执行后
async afterExecAllCommends() {
console.log('执行完所有钩子啦');
},
// 上传前
async beforeUpload() {
console.log('开始上传');
},
// 上传后
async afterUpload({ stdout }) {
console.log(stdout);
console.log('结束!');
},
};
配置文件名为 mpup.config.js
。
接下来就是运行了。
执行
执行前有一些前置工作:
- 打开小程序的服务端口,(小程序开发者工具 => 设置 => 安全设置 => 开启服务端口)
- 使用较新稳定版的开发者工具,测试中发现老的稳定版本、 RC 版本和 Nightly Build 版本的命令行工具可能无法运行
- 登入开发者工具,且登入的账户拥有项目的上传代码权限
执行一遍 🚀!
mpup-test --config=./mpup.config.js
上面这份配置的执行结果是这样的:
扩展
到目前为止实现的都是基础功能,代码也仅仅是为了可以让流程跑通而写的,有许多可以改进的地方。
这也是整个工具最核心的部分。
目前代码已经开源,项目名也是 mpup
,还在持续维护迭代中,因为测试场景不足,可能会有一些bug。
项目:https://github.com/hiNISAL/mpup
其配套的服务端工具也在开发中,因为本地上传代码有局限性,所以理想的情况应该是服务端上传。
谢谢各位观众老爷。