前言
本文的应用场景主要以小程序为例,不过其他项目也可以参考前面ci/cd的配置自己定制。
一、为什么要做小程序的自动化?
1)主要目的有两个
- 去除开发和测试时的
重复提交代码
这个操作,提升开发体验 - 避免在小程序
提审和上线
时,由于人为操作的失误带来线上事故
2)在接入CI/CD前有遭遇哪些问题
- 开发体验差。每次修复完bug,都需要把小程序重新提到体验版,会经历以下步骤:
- 把自己的代码合并到test分支,拉取别人代码合并
- 我们使用的是uni-app,需要执行run build的命令,再等待几十秒
- 打开另一个开发工具,找到编译好的目录,打开,再等待十几秒,填写小程序上传时的相关备注,上传完成
- 打开小程序后台管理的网页,将小程序体验版切换成自己的,再通知到测试
- 人为操作失误导致的事故。比如忘记拉取其他人代码就直接上传了体验版,或者弄错开发/生产的环境变量了,又或者弄混了直销/分销的代码
- 每个人电脑上node_modules的依赖可能不一致。目前使用的是npm,且存在公司提供一些工具包
- 每当有新人入职后,还需要把这一套开发/上线的重复流程给新人培训,避免不熟悉流程的情况下容易造成线上事故
- 我们产品分为
直销/分销和第三方
共8个小程序,这一套组合拳下来,直接占用我半天的时间
3)接入了之后改善了哪些问题
- 开发体验。直接提交完代码就完事,自动编译直分销和第三方多个版本。平均每个人每次节省了5分钟的时间,一天下来就是半个钟了
- 上线不背锅。无需再繁琐地检查上线流程,一键提审,再也不用担心把测试环境的代码提交到线上啦
二、创建一个简单的CI/CD流程
1)gitlab CI/CD的基本工作流程
- 注册一台runner机子,填入项目地址和令牌,就可以关联到对应的仓库
- 当你推送代码的时候,会检查项目下有没有
.gitlab-ci.yml
文件 - 当存在
.gitlab-ci.yml
文件时,会触发hooks在你当前runner机所处的位置,执行yml文件中描述的任务
2)注册一个runner机子
这里分开windows和linux两种版本,实际业务中都是放在linux服务器,windows版可以自己用来熟悉一下yml的一些命令和ci的代码测试
1. windows版(docs.gitlab.com/runner/inst…
- 从刚刚设置的界面点到windows的安装
- 有几步需要注意的,我简单说下(其实文档里面都有描述就不再赘述了)
- 下载完之后,把那个.exe文件重命名为,
gitlab-runner.exe
方便后面跟着步骤操作 - 完成下载之后要注册才能开始使用
- 下载完之后,把那个.exe文件重命名为,
- 开始注册
- 这里就是gitlab项目中cicd的一些配置,主要是令牌和url,后面注册的时候需要复制
- 注册流程(docs.gitlab.com/runner/regi…
- 执行命令 ./gitlab-runner.exe register
- 填入复制的url和令牌
- 填入描述(备注一下机器的用途就行)
- 填入runner的tags,后续执行ci操作的时候会根据这个匹配
- 选择执行脚本的语言,这里选shell,后续有些shell命令相关操作
- 完成注册。这时候目录下会多一个config.toml文件。刷新gitlab后台会看到一台新的注册机子
- 启动runner
.\gitlab-runner.exe run
,执行完后,刷新gitlab后台可以看到机器的小点变绿色了,代表机器在运行。- 这时候只要配置了正确的yml文件,后续推送代码的时候,就会触发ci
2. linux版(docs.gitlab.com/runner/regi…
- 如果是Ubuntu系统
dpkg -i gitlab-runner_<arch>.deb
,如果是CentOS执行rpm -i gitlab-runner_<arch>.rpm
- 开始注册,
sudo gitlab-runner register
- 后面的填信息的步骤和windows的是一致的
3)创建一个.gitlab-ci.yml文件
1. windows版
一些步骤直接写在代码的注释中。如果想运行简单的示例,直接把涉及到的业务分支名称和脚本删除即可
# 小程序ci配置,后续上线流程有改动的话,注意重新检查这里的流程
image: node:latest
# 这里是步骤流程,可以自定义顺序
stages:
- build
- test
cache:
paths:
- node_modules/
# 执行安装依赖的任务
install_job:
# 这里是第一个流程build,目前只有一个并行任务
stage: build
script:
- npm --registry https://registry.npm.taobao.org install
# 这个对应的是刚刚注册的runner的名字,这个非常重要,决定了你是否能启用某个runner机子
tags:
- test_ci
# 这里是触发的限制
only:
# 这个是限制的分支,这里表示只有在这三个分支推送时,才会触发cicd
refs:
- master
- pre-production
- production
# 这个是触发的变量,gitlab的默认变量可以去gitlab-cicd的文档中去找
variables:
# 这里代表commit的备注中,存在cicd这几个关键词时,才会触发
- $CI_COMMIT_TITLE =~ /cicd/
# master分支
# 执行编译和上传的任务
master_no_oem_job:
# 这里是第二个流程build,相同的流程可以并行执行。可并行的任务数量需要设置
stage: test
# 项目中小程序编译和上传代码的相关命令,这些就是之前重复的步骤,现在全部在脚本中自动实现
script:
# 编译直销/分销环境
- npm run copy_diffModule_d
# 编译打包小程序代码
- npm run build:directsale
# 将打包好的代码上传到对应的直销/分销小程序,这里可以接小程序官方文档提供的api
- npm run upload:devd
# 将同样的代码改变环境变量,再上传一份到对应的第三方小程序
- cross-env NODE_ENV=development isOem=false isThird=true node ./script/ci/uploadCode.js
tags:
- test_ci
only:
refs:
- master
variables:
- $CI_COMMIT_TITLE =~ /cicd/
# 执行编译和上传的任务,和上一个任务基本一致,只是小程序不一样
master_oem_job:
stage: test
script:
- npm run copy_diffModule_nd
- npm run build:no-directsale
- npm run upload:devnd
- cross-env NODE_ENV=development isOem=true isThird=true node ./script/ci/uploadCode.js
tags:
- test_ci
only:
refs:
- master
variables:
- $CI_COMMIT_TITLE =~ /cicd/
2. linux版
和windows版基本一致,有些点需注意
- 如果项目中是通过软链的方式连接到其他地方的,依赖的安装可能要更换下时机
- 系统的变量可能在软链的地方拿不到,需要提前输出在目录中
# 小程序ci配置,后续上线流程有改动的话,注意重新检查这里的流程
# 目前仅针对master,pre,pro三个分支进行处理
image: node:latest
stages:
- build
- test
before_script:
# 这里因为业务的原因需要链接到另一台服务器,不需要可以去掉
- ssh -p 10086 faier@**.**.cc << ssh
- node -v
- pwd
- ls
- echo $CI_COMMIT_REF_NAME
- echo $CI_COMMIT_BRANCH
after_script:
# 这里因为业务的原因需要链接到另一台服务器,不需要可以去掉
- ssh
# 定义一些通用变量,方便后面引用
variables:
LIB_DIR: "~/gitlab-cicd/ts-app/libs/yx-miniapp/node_modules/"
MATER_NO_OEM_DIR: "~/gitlab-cicd/ts-app/master/no_oem/yx-miniapp"
MATER_OEM_DIR: "~/gitlab-cicd/ts-app/master/oem/yx-miniapp"
PRE_NO_OEM_DIR: "~/gitlab-cicd/ts-app/pre-production/no_oem/yx-miniapp"
PRE_OEM_DIR: "~/gitlab-cicd/ts-app/pre-production/oem/yx-miniapp"
PRO_NO_OEM_DIR: "~/gitlab-cicd/ts-app/production/no_oem/yx-miniapp"
PRO_OEM_DIR: "~/gitlab-cicd/ts-app/production/oem/yx-miniapp"
# 有包需要更新的时候才执行,相当于自己做一步缓存
# 这里如果不是软链的方式可以按照正常的npm install的流程
install_job:
stage: build
script:
- rm -rf $MATER_NO_OEM_DIR/node_modules/ $MATER_OEM_DIR/node_modules/ $PRE_NO_OEM_DIR/node_modules/ $PRE_OEM_DIR/node_modules/ $PRO_NO_OEM_DIR/node_modules/ $PRO_OEM_DIR/node_modules/
- cp -r $LIB_DIR $MATER_NO_OEM_DIR
tags:
- test_ci
only:
refs:
- master
- pre-production
- production
- waldon_ci_feat
# git的提交备注中含有reInstall关键词才触发重新安装依赖
variables:
- $CI_COMMIT_TITLE =~ /reInstall/
# pro分支,包括第三方直分销
pro_no_oem_job:
stage: test
script:
- cd ~/gitlab-cicd/ts-app/production/no_oem/yx-miniapp
# 因为链接到了另一台服务器,拿不到系统默认变量,需要手动输出然后自己再引用
- echo "CI_COMMIT_TITLE"=$CI_COMMIT_TITLE >> ".env.ci"
- pwd
# 正常情况是会默认自动更新代码的,这里也是软链的原因
- git pull origin production
- npm run copy_diffModule_d
- npm run build:directsale-pro
- npm run upload:prod
- cross-env NODE_ENV=production isOem=false isThird=true node ./script/ci/uploadCode.js
- cp -r $PRO_NO_OEM_DIR/dist/build/mp-weixin/ ~/gitlab-cicd/ts-app_build/production/no_oem/
tags:
- test_ci
only:
refs:
- production
variables:
- $CI_COMMIT_TITLE =~ /cicd/
4)触发了ci后的效果
- 管道这里会跑对应的任务
- 机子所在位置的目录下会生成对应的文件
- 如果执行失败了,可以点进对应的任务里面看报错提示。
三、在小程序中实现CI自动上传/预览代码
1)密钥及 IP 白名单配置
使用 miniprogram-ci前应访问"微信公众平台-开发-开发设置"后下载代码上传密钥,并配置 IP 白名单 开发者可选择打开 IP 白名单,打开后只有白名单中的 IP 才能调用相关接口
2)安装小程序ci的依赖包,npm install miniprogram-ci --save
3)将下载好的秘钥放到安全的目录
4)编写小程序ci的脚本
这里就直接贴出我们项目里面的实现,仅供参考。
如果涉及到第三方小程序,需注意把extEnable
这个字段设为false,这个官方文档中无提及,这个坑我踩了一波
const ci = require("miniprogram-ci");
const fs = require("fs-extra");
const path = require("path");
const dotenv = require("dotenv");
const isOem = process.env.isOem === "true";
const isThird = process.env.isThird === "true";
const isPreview = process.env.isPreview === "true";
const mode = process.env.NODE_ENV;
const filePath = `../../.env.${mode}`;
const filePath_ci = `../../.env.ci`; // 处理上传时候的参数
const devInfoPath = path.resolve(__dirname, "../../src/devInfo.json");
let ciPreviewNumb = 18; // 预览机器人序号
const readFileInfo = filePath => {
let fileName = path.resolve(__dirname, filePath);
let data = fs.readFileSync(fileName, { encoding: "utf8" });
const obj = dotenv.parse(data);
return obj;
};
let desc = "";
const fileInfo = readFileInfo(filePath);
if (isPreview) {
try {
const devInfo = fs.readJsonSync(devInfoPath);
const { ci_number, ci_desc } = devInfo;
desc = ci_desc;
ciPreviewNumb = ci_number;
} catch (error) {
console.info(`读取用户信息文件失败`, error);
}
} else {
const fileInfo_ci = readFileInfo(filePath_ci);
desc = `${fileInfo.VUE_APP_ENV}${isOem ? "分销" : "直销"}${
!!isThird ? "第三方" : ""
},${fileInfo_ci.CI_COMMIT_TITLE},${new Date().toLocaleString()}`;
}
const projectPath = path.resolve(__dirname, "../../dist/build/mp-weixin");
let appid = "";
if (!!isThird) {
// 第三方暂时只提交pro分支
appid = isOem
? fileInfo.VUE_APP_NO_DIRECTSALE_THIRD_APPID
: fileInfo.VUE_APP_DIRECTSALE_THIRD_APPID;
const extPath = path.resolve(
__dirname,
"../../dist/build/mp-weixin/ext.json",
);
let extData = fs.readJSONSync(extPath);
// 文档和社区都没有关于extEnable这个字段会影响ci的说明,官方人员自己都不知道,绝了
extData.extEnable = false;
extData.extAppid = appid;
extData.ext.extAppid = appid;
fs.writeJSONSync(extPath, extData);
} else {
appid = isOem
? fileInfo.VUE_APP_NO_DIRECTSALE_APPID
: fileInfo.VUE_APP_DIRECTSALE_APPID;
}
const privateKey = `./privateKey/private.${appid}.key`; // 在不同的环境拿对应的秘钥
const privateKeyPath = path.resolve(__dirname, privateKey);
(async () => {
const project = new ci.Project({
appid,
type: "miniProgram",
projectPath,
privateKeyPath,
ignores: ["node_modules/**/*"],
});
if (isPreview) {
const previewResult = await ci.preview({
project,
desc, // 此备注将显示在“小程序助手”开发版列表中
setting: {
autoPrefixWXSS: true, // 样式补全
},
robot: ciPreviewNumb,
pagePath: "pages/loading/main", // 预览页面
onProgressUpdate: () => {},
});
console.info(`previewResult`, previewResult);
} else {
const uploadResult = await ci.upload({
project,
version: "2.2.12", // 版本上线或重新提交,改这里的版本即可-
desc,
setting: {
autoPrefixWXSS: true, // 样式补全
},
onProgressUpdate: () => {},
});
console.info(`uploadResult:`, uploadResult);
}
})();
5)在package.json中定义脚本入口
这里就是对应之前在gitlab-cicd.yml中定义的script
"upload:devd": "cross-env NODE_ENV=development isOem=false node ./script/ci/uploadCode.js",
"upload:devnd": "cross-env NODE_ENV=development isOem=true node ./script/ci/uploadCode.js",
参考
- docs.gitlab.com/ee/ gitlab的官方文档
- developers.weixin.qq.com/miniprogram… 小程序ci的官方文档