小程序实现ci - Taro微信小程序自动化部署

2,662 阅读4分钟

背景

在之前的微信小程序版本开发中,受累于小程序版本发布以下几个痛点:

  • 上传代码都要依赖开发者工具,且版本信息和描述都要手动填写
  • 构建-上传-设置体验版-发布二维码通知测试这一套流程过于繁琐
  • 当开发者不一致时,需要去小程序后台频繁设置体验版,容易造成操作失误

刚好看到了前端CI/CD思考 这篇文章,就思考如何基于微信小程序ci的api构建小程序自动化部署流程。

Taro官方推荐的CI插件,也只是对于miniprogram-ci 官方api的封装,并不适用自定义场景。

目标

构建一套自动部署流程/工具,具体流程如下图所示:

就这个流程来说,实现方式有三种:

  1. 自定义cli工具,通过命令部署
  2. gitlab ci,通过runner监听代码提交自动化部署
  3. jenkins,可以拉取git仓库指定分支,可手动部署

本文采用第一、二种方式先完成研发的自动化部署流程。

实现

  1. 微信公众号配置 参考官方文档,在”微信公众平台-开发-开发设置“,下载代码上传密钥并放到项目根目录,设置相应的ip白名单

  1. 配置钉钉群机器人 参考钉钉官方文档,钉钉群-设置-智能群助手-添加机器人-自定义来添加机器人, 注意保存得到的Webhook地址,后续用到

  1. 在微信小程序项目安装几个依赖包
yarn add miniprogram-ci node-fetch ora@5.0.0

并在根目录创建wxci.config.js

module.exports = {
  appid: '', // 微信小程序appid
  privateKeyPath: '', // 第一步的密钥地址
  qrcodeImageUrl:'', // 微信体验版二维码图片网络地址,因为体验版图片地址是固定的,建议转存到cdn上,不然无法在钉钉显示 
  dingTalkUrl:'', // 第二步webhook的url
}

  1. 在根目录建立script文件夹,写入执行逻辑
// index.js
#!/usr/bin/env node
const path = require('path')
const { checkConfigFile, info } = require('./util')

async function main() {
  // 1. 读取配置文件,合并配置
  const filePath = 'wxci.config.js'
  checkConfigFile(filePath)
  const absConfigDir = process.cwd()
  info(`absConfigDir: ${absConfigDir}`)
  const config = require(path.resolve(`${absConfigDir}`, filePath))
  const baseConfig = require(path.resolve(__dirname, './defaultConfig.js'))
  const fullConfig = { ...baseConfig, ...config }

  const {
    appid,
    type,
    projectPath,
    privateKeyPath,
    version,
    desc,
    robot,
    qrcodeImageUrl,
    uploadImage,
    dingTalkUrl,
  } = fullConfig

  const WxCi = require('./wxCi')
  const DingCi = require('./dingCi')
  // 2. 上传/预览小程序
  const wxCi = new WxCi({
    appid,
    type,
    projectPath,
    privateKeyPath,
    outDir,
    uploadImage,
    qrcodeImageUrl,
    version,
    desc,
    robot,
    setting,
    qrcodeFormat,
  })
  const weappQRImgUrl = await wxCi.run()
  // 3. 发送钉钉提醒
  const dingCi = new DingCi({
    absConfigDir,
    weappQRImgUrl,
    dingTalkUrl,
    isExperience,
  })
  await dingCi.run()
}

main()

// wxCi.js
const path = require('path')
const fs = require('fs')
const ci = require('miniprogram-ci')
const { execSync } = require('child_process')
const { checkConfigFile, fail, info, success } = require('./util')

class WxCi {
  constructor(
    options = {
      appid: '',
      privateKeyPath: '',
      projectPath: '',
      qrcodeImageUrl: '',
      version: '',
    }
  ) {
    this.options = options
    this.QRImgUrl = ''
  }

  async run() {
    const {
      projectPath,
      privateKeyPath,
      appid,
      type,
      version,
      desc,
      robot,
      qrcodeImageUrl,
    } = this.options
    // 校验密钥
    if (fs.existsSync()) {
      fail(`${privateKeyPath}密钥文件不存在`)
      process.exit(1)
    }
    info('正在上传...')
    try {
      const project = new ci.Project({ appid, type, projectPath, privateKeyPath })
      info('上传体验版...')
      if(!version){
        const branchName = execSync('git rev-parse --abbrev-ref HEAD', options).toString().trim()
      }
       git rev-parse --abbrev-ref HEAD
      await ci.upload({ project, version, desc, robot })
      // 微信体验版地址不会变,直接写死
      this.QRImgUrl = qrcodeImageUrl
      success('上传成功')
    } catch (error) {
      fail(`上传失败: ${error}`)
      process.exit(1)
    }
    return this.QRImgUrl
  }
}

module.exports = WxCi

// 处理钉钉消息
const { execSync } = require('child_process')
const HOSTNAME = require('os').hostname()
const fetch = require('node-fetch')
const ora = require('ora')
const { info } = require('./util')

class DingCi {
  constructor(
    options = {
      weappQRImgUrl: '',
      dingTalkUrl: '',
    }
  ) {
    this.options = options
    this.uploadType = '体验版'
    this.gitInfo = ''
    this.template = ''
  }

  /**
   * 入口
   */
  async run() {
    // 1.获取git分支及最近的提交记录
    this.getGitInfo()
    // 2.构造消息数据结构
    this.buildTemplate()
    // 3.推送钉钉消息
    await this.sendDingTalk()
  }

  async sendDingTalk() {
    const { isExperience, dingTalkUrl } = this.options
    const postBody = {
      msgtype: 'markdown',
      markdown: {
        title: '小程序构建测试已完成',
        text: this.template,
      },
      at: {
        isAtAll: isExperience,
      },
    }
    const spinner = ora({
      text: `正在推送钉钉消息...\n`,
      spinner: 'moon',
    }).start()
    try {
      await fetch(dingTalkUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(postBody),
      })
      spinner.succeed(`推送钉钉消息成功\n`)
    } catch (error) {
      console.error('推送钉钉消息error', error)
    }
  }

  getGitInfo() {
    try {
      const options = { cwd : this.options.absConfigDir }
      const branchName = execSync('git rev-parse --abbrev-ref HEAD', options).toString().trim()
      this.gitInfo = `\n当前分支: **${branchName}**  \n`
    } catch (error) {
      console.error('获取git日志失败', error)
      this.gitInfo = ''
    }
  }

  buildTemplate() {
    const { weappQRImgUrl, isExperience } = this.options
    const { uploadType, gitInfo } = this
    const wechatPart = weappQRImgUrl && `## 微信${uploadType}:![](${weappQRImgUrl})`
    this.template = `# ${uploadType}小程序构建完成\n---\n  构建机器:${HOSTNAME}  \n  ${gitInfo}  \n---\n${wechatPart || ''}`
  }
}

module.exports = DingCi

在package.json增加脚本

"ci": "yarn && yarn run build && node ./script/index.js",

执行yarn ci测试一下

  1. gitlab 配置 参考使用小程序CI自动上传代码,在一台机器上安装runner并启动注册

在项目根目录创建.gitlab-ci.yml

cache:
  key: modules
  paths:
    - npm_cache
    - node_modules/

stages: 
  - deploy

# 打包项目
deploy_job:
  stage: deploy
  tags: # ci runner标签
    - test-ci
  only: # 指定分支名称
    - test-ci 
  #     variables:
  #       - $CI_COMMIT_MESSAGE =~ /^build/
  cache:
    key: modules
    paths:
      - node_modules/
  before_script:
    - node -v && npm -v
    - yarn global add @tarojs/cli
  script:
    - echo "开始构建🔥🔥🔥"
    # - npm ci
    - npm install --registry=https://registry.npm.taobao.org
    - npm run build
    - echo " 完成构建🔥🔥🔥"
    - echo "开始部署🚀🚀🚀"
    - npm run ci
    - echo " 完成部署🚀🚀🚀"

修改下刚才的脚本

"ci": "node ./script/index.js",

这样提交代码到指定分支就会自动部署,这里指定的是test-ci分支

到此为止,部署流水线的工作基本就完成了🎉🎉🎉

下一步,就要独立出脚本,作为单独的工具包,并结合jenkins更完善部署方案。

参考文章

使用 taro-deploy 自动化构建发布 taro 小程序

无星的前端之旅(四)——小程序持续集成

使用小程序CI自动上传代码

前端运维篇之gitlab-ci 自动构建部署