微信小程序CI自动化上传脚本

背景:

多人开发时,通过微信小程序工具上传代码体验版都会不同,需要不断频繁切换体验码/仅限某个人仅限代码上传。

解决方案:

通过微信小程序 miniprogram-ci 脚本 + git 操作进行代码上传

官方文档地址:概述 | 微信开放文档

步骤:

1.密钥及 IP 白名单配置

使用 miniprogram-ci 前应访问"微信公众平台-开发-开发设置"后下载代码上传密钥,并配置 IP 白名单 开发者可选择打开 IP 白名单,打开后只有白名单中的 IP 才能调用相关接口。我们建议所有开发者默认开启这个选项,降低风险 代码上传密钥拥有预览、上传代码的权限,密钥不会明文存储在微信公众平台上,一旦遗失必须重置,请开发者妥善保管

!注意:上传密钥需要保存到指定文件,防止丢失

2.安装miniprogram-ci

vsvscode打开小程序项目,在终端安装ci:

npm install miniprogram-ci

3.创建文件夹及相关文件

安装完 CI 插件后会自动创建 package.json 文件,需要在同级目录创建一个deploy文件夹(命名自定义),并创建 index.js 文件,把第一步下载的上传密钥放在 weapp.key 文件中

上传密钥

上传思路:

上传时怎么获取当前体验版版本号+版本备注信息?

普通的CI上传已不能满足多人多分支的需求,可结合git+本地文件版本号获取线上体验版信息

代码实现:

在 package.json 文件中定义相关项目版本信息,定义 wxconfig 对象,本地读取版本号 + 备注,在上传时进行更改并上传(并同步到远程git)

注:需要额外安装 prompts node交互插件(原生nodejs终端交互已不能满足需求,后面会解释)

安装 prompts 插件 官方文档

npm install --save prompts

注:在 package.json 文件中的 miniprogram-ci 跟 prompts 需要声明在 devDependencies 对象内,而非默认的 dependencies 。

官方文档说明

index.js 文件(内容不做解释,部分复杂的已做注释)

/**
 * 修改版本号并自动上传脚本思路
 * 1.获取传进来的参数 √
 * 2.根据参数进行逻辑处理 √
 * 3.获取package.json中的version参数 √
 * 4.修改version的值写入package.json文件 √
 * 5.git提交package.json文件 √
 * 6.微信自动上传脚本上传到体验版 √
 */

const prompts = require('prompts')
const fs = require('fs').promises // 使用 fs.promises API 来获取异步方法
// 异步子进程
const exec = require('child_process').exec
const ci = require('miniprogram-ci')
const path = require('path')

// 引入package文件内容
const packageJSON = require('../package.json')

// prompts取消时 ctrl + c 不然主动退出会打印错误
const onCancel = (prompt) => {
  process.exit()
}

// exec异步封装成异步函数
function execAsync(command) {
  return new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        reject(error)
      } else {
        resolve(stdout.trim())
      }
    })
  })
}

// 设置ANSI转义码来添加颜色 colorCode: 91 红色  92 绿色
const colorizeText = (text, colorCode) => {
  return `\x1b[${colorCode}m${text}\x1b[0m`
}

let curBranch = ''
// 获取当前本地分支名字
const getCurrentBranch = async () => {
  try {
    curBranch = await execAsync('git rev-parse --abbrev-ref HEAD')

    // if (curBranch !== 'dev') {
    //   console.error(colorizeText(`请切换到 dev 分支进行上传操作!`, 91))
    //   process.exit(1)
    // }
  } catch (error) {
    console.error(colorizeText(`获取分支名字失败!${error}`, 91))
    process.exit(1)
  }
}

// 填写版本号
const askVersion = async function () {
  console.log(
    `${colorizeText('*', 32)} 当前线上版本号:${colorizeText(
      packageJSON.wxconfig.version,
      36
    )}:\n `
  )
  try {
    // 这里注意不能提前在函数外部定义,
    // initial 不会取到变量,只会取初始值
    let versionQuestions = [
      {
        type: 'select',
        name: 'version',
        message: '请选择当前要修改版本为?',
        hint: '使用 ⇧ / ⇩ 进行选择,回车键确认',
        choices: [
          {
            title: '不做修改',
            description: '版本号不变',
            value: 'old',
          },
          {
            title: '修订补丁',
            description: 'X.Y.Z -> Z+1',
            value: 'z',
          },
          {
            title: '特性更新',
            description: 'X.Y.Z -> Y+1',
            value: 'y',
          },
          {
            title: '版本升级',
            description: 'X.Y.Z -> X+1',
            value: 'x',
          },
          {
            title: '自行填写',
            value: 'new',
          },
        ],
        initial: 0,
      },
      // 选择自行填写
      {
        type: (prev) => (prev === 'new' ? 'text' : null),
        name: 'version',
        message: '请输入当前版本',
        initial: packageJSON.wxconfig.version,
        validate: (value) => {
          const pass = value.trim().match(/^[\dA-Za-z.]+$/)
          if (pass) {
            return true
          } else {
            return `版本号仅限字母、数字和小数点!`
          }
        },
      },
    ]
    const res = await prompts(versionQuestions, { onCancel })
    let newVersion = ''
    let parts = packageJSON.wxconfig.version.split('.')
    // 判断是否符合版本更新特性
    const updateArr = ['x', 'y', 'z']
    if (updateArr.includes(res.version)) {
      switch (res.version) {
        case 'z':
          parts[2] = parseInt(parts[2]) + 1
          newVersion = parts.join('.')
          break

        case 'y':
          parts[1] = parseInt(parts[1]) + 1
          newVersion = parts.join('.')
          break

        case 'x':
          parts[0] = parseInt(parts[0]) + 1
          newVersion = parts.join('.')
          break
      }
    } else if (res.version === 'old') {
      // 不做修改
      newVersion = packageJSON.wxconfig.version
    } else {
      newVersion = res.version
    }
    return newVersion
  } catch (e) {
    process.exit(1)
  }
}

// 填写描述
const askDesc = async function () {
  let descQuestions = [
    {
      type: 'text',
      name: 'desc',
      message: '请输入项目备注:(tab键回显)',
      initial: packageJSON.wxconfig.desc,
    },
  ]
  const res = await prompts(descQuestions, { onCancel })
  return res.desc
}

//
const askQuestion = async function () {
  try {
    const version = await askVersion()
    const desc = await askDesc()

    // 修改package对应的内容
    packageJSON.wxconfig.version = version
    packageJSON.wxconfig.desc = desc

    // 修改更新时间 防止默认回车 git提交时会识别为未更改内容
    packageJSON.wxconfig.updateTime = Date.now()

    console.log('\n修改后版本号信息:', {
      version: version,
      desc: desc,
    })
  } catch (error) {
    console.log(colorizeText(error, 91))
    process.exit(1)
  }
}

// 写入package.json
const writeFile = async () => {
  try {
    // JSON.stringify 方法将 JavaScript 对象转换为 JSON 字符串,第二个参数 null 表示没有替换函数,第三个参数 2 表示缩进空格数为 2,用于格式化输出的 JSON 字符串。
    // 异步写回到文件中
    await fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2))

    console.log(colorizeText('\n修改package.json文件成功!\n', 92))
  } catch (err) {
    console.error(colorizeText(`fs.writeFile err:${err}`, 91))
    process.exit(1)
  }
}

// 提交代码到当前git分支
const gitSave = async () => {
  try {
    await execAsync(`git add .`)
    await execAsync(
      `git commit -m '修改version为:${packageJSON.wxconfig.version};desc:${packageJSON.wxconfig.desc}'`
    )
    // 前面限制了dev分支,这时候就无需一定要推送到dev
    await execAsync(`git push`)

    console.log(colorizeText(`\n成功推送到${curBranch}分支!\n`, 92))
  } catch (error) {
    console.error(colorizeText(`推送git失败!${error.message}`, 91))
    process.exit(1)
  }
}

// 机器人自动化上传
const autoUpload = async () => {
  const project = new ci.Project({
    appid: '填写你的appid',
    type: 'miniProgram',
    // projectPath:项目的路径,即 project.config.json 所在的目录
    projectPath: path.join(__dirname, '../'),
    // privateKeyPath:私钥的路径
    privateKeyPath: path.join(__dirname, './weapp.key'),
    // 指定需要排除的规则
    ignores: ['node_modules/**/*', 'package-lock.json'],
  })
  console.log(colorizeText(`\n正在上传...\n`, 92))

  const uploadResult = await ci.upload({
    project,
    version: packageJSON.wxconfig.version,
    desc: packageJSON.wxconfig.desc,
    setting: {
      es6: true,
      es7: true,
      minifyJS: true,
      minifyWXML: true,
      minifyWXSS: true,
      minify: true,
    },
    onProgressUpdate: console.log,
  })

  console.log(uploadResult)

  console.log(colorizeText('上传完成!', 92)) // 亮绿色代码是 '92'
  console.log(colorizeText('版本号:' + packageJSON.wxconfig.version, 92))
  console.log(colorizeText('描述:' + packageJSON.wxconfig.desc, 92))
}

;(async () => {
  // 获取当前本地分支名字
  await getCurrentBranch()
  // 交互修改
  await askQuestion()
  // 异步写入package.json文件
  await writeFile()
  // // 保存并提交git
  await gitSave()
  // // 机器人自动化上传
  await autoUpload()
})()

在 package.json 文件配置npm命令

即在终端运行 npm run deploy 就会执行deploy文件下的index.js文件命令

 npm run deploy

即以下部分按顺序执行

(async () => {
  // 获取当前本地分支名字
  await getCurrentBranch()
  // 交互修改
  await askQuestion()
  // 异步写入package.json文件
  await writeFile()
  // // 保存并提交git
  await gitSave()
  // // 机器人自动化上传
  await autoUpload()
})()

问题2:

当前脚本只能做体验版上传,因为体验版本号更新过快,需要根据线上发布版的版本号去做上传。

解决方案:利用 git tag 唯一性模拟发布版本的版本信息

思路跟 index.js 差不多一致

新建 tag.js 文件

/**
 * 修改版本号并自动上传脚本思路(发布版)
 * 1.获取当前分支
 * 2.获取远程tag版本(小程序线上版本)
 * 3.获取tag版本 + 附注信息,进行版本提示
 * 4.选择/输入版本号
 * 5.修改version的值写入package.json文件
 * 6.写入附注tag,并提交到远程
 * 7.删除时间最久的一条tag,防止tag分支过多(可选)
 * 8.微信自动上传脚本上传到体验版(注意机器人号) √
 */

const prompts = require('prompts')
const fs = require('fs').promises // 使用 fs.promises API 来获取异步方法
// 异步子进程
const exec = require('child_process').exec
const ci = require('miniprogram-ci')
const path = require('path')

// 引入package文件内容
const packageJSON = require('../package.json')

// prompts取消时 ctrl + c 不然主动退出会打印错误
const onCancel = (prompt) => {
  process.exit()
}

// exec异步封装成异步函数
function execAsync(command) {
  return new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        reject(error)
      } else {
        resolve(stdout.trim())
      }
    })
  })
}

// 设置ANSI转义码来添加颜色 colorCode: 91 红色  92 绿色
const colorizeText = (text, colorCode) => {
  return `\x1b[${colorCode}m${text}\x1b[0m`
}

let curBranch = ''
// 最新tag名称
let latestTagName = ''
// 最新tag附注
let latestTagDesc = ''

// 获取当前本地分支名字
const getCurrentBranch = async () => {
  try {
    curBranch = await execAsync('git rev-parse --abbrev-ref HEAD')
    console.log(colorizeText(`!请注意当前git分支: ${curBranch}`, 91))
    // if (curBranch !== 'dev') {
    //   console.error(colorizeText(`请切换到 dev 分支进行上传操作!`, 91))
    //   process.exit(1)
    // }
  } catch (error) {
    console.error(colorizeText(`获取分支名字失败!${error}`, 91))
    process.exit(1)
  }
}

// 获取最新tag名称
const getTagName = async () => {
  try {
    /*  
    --sort=-creatordate: 这个选项指定了按照标签的创建日期进行排序。-creatordate 表示按照创建日期的逆序(从最新到最旧)排序。
     |: 这是管道符号,用于将一个命令的输出传递给另一个命令作为输入。
    head -1: 这个命令用于从输入中获取第一行。head 命令默认会显示输入的前 10 行,但是通过 -1 选项,它只会显示第一行。
    */
    latestTagName = await execAsync('git tag --sort=-creatordate | head -1')
  } catch (error) {
    console.error(colorizeText(`获取最新tag名称失败!${error}`, 91))
    process.exit(1)
  }
}

// 获取最新tag附注
const getTagDesc = async () => {
  try {
    /* 
    git tag -n --format='%(contents)': 这个命令用于列出所有标签的附注消息
    并且使用 --format='%(contents)' 选项指定只显示标签的附注消息而不包含标签名。
    */
    latestTagDesc = await execAsync(
      `git tag ${latestTagName} -n --format="%(contents)" | head -1`
    )
  } catch (error) {
    console.error(colorizeText(`获取最新tag附注失败!${error}`, 91))
    process.exit(1)
  }
}

// 获取tag信息
const getTagInfo = async () => {
  try {
    // git fetch --tags 从远程仓库中获取所有的标签,并将它们拉取到你的本地仓库。
    await execAsync('git fetch --tags')

    // 获取最新tag名称
    await getTagName()
    await getTagDesc()
  } catch (error) {
    console.error(colorizeText(`获取tag信息失败!${error}`, 91))
    process.exit(1)
  }
}

// 填写版本号
const askVersion = async function () {
  console.log(
    `${colorizeText('*', 32)} 当前正式环境线上版本号: ${colorizeText(
      latestTagName,
      36
    )}:\n `
  )
  try {
    // 这里注意不能提前在函数外部定义,
    // initial: latestTagName不会取到变量,只会取初始值
    let versionQuestions = [
      {
        type: 'select',
        name: 'version',
        message: '请选择当前要修改版本为?',
        hint: '使用 ⇧ / ⇩ 进行选择,回车键确认',
        choices: [
          {
            title: '不做修改',
            description: '版本号不变',
            value: 'old',
          },
          {
            title: '修订补丁',
            description: 'X.Y.Z -> Z+1',
            value: 'z',
          },
          {
            title: '特性更新',
            description: 'X.Y.Z -> Y+1',
            value: 'y',
          },
          {
            title: '版本升级',
            description: 'X.Y.Z -> X+1',
            value: 'x',
          },
          {
            title: '自行填写',
            value: 'new',
          },
        ],
        initial: 0,
      },
      // 如果是自行填写
      {
        type: (prev) => (prev === 'new' ? 'text' : null),
        name: 'version',
        message: '请输入当前版本',
        initial: latestTagName,
        validate: (value) => {
          const pass = value.trim().match(/^[\dA-Za-z.]+$/)
          if (pass) {
            return true
          } else {
            return `版本号仅限字母、数字和小数点!`
          }
        },
      },
    ]
    const res = await prompts(versionQuestions, { onCancel })
    let newVersion = ''
    let parts = latestTagName.split('.')
    // 判断是否符合版本更新特性
    const updateArr = ['x', 'y', 'z']
    if (updateArr.includes(res.version)) {
      switch (res.version) {
        case 'z':
          parts[2] = parseInt(parts[2]) + 1
          newVersion = parts.join('.')
          break

        case 'y':
          parts[1] = parseInt(parts[1]) + 1
          newVersion = parts.join('.')
          break

        case 'x':
          parts[0] = parseInt(parts[0]) + 1
          newVersion = parts.join('.')
          break
      }
    } else if (res.version === 'old') {
      // 不做修改
      newVersion = latestTagName
    } else {
      newVersion = res.version
    }
    return newVersion
  } catch (e) {
    process.exit(1)
  }
}

// 填写描述
const askDesc = async function () {
  let descQuestions = [
    {
      type: 'text',
      name: 'desc',
      message: '请输入项目备注:(tab键回显)',
      initial: latestTagDesc,
    },
  ]
  const res = await prompts(descQuestions, { onCancel })
  return res.desc
}

//
const askQuestion = async function () {
  try {
    const version = await askVersion()
    const desc = await askDesc()

    // 修改package对应的内容  !(本地备份)
    packageJSON.wxconfig.tagName = version
    packageJSON.wxconfig.tagDesc = desc

    // 修改更新时间 防止默认回车 git提交时会识别为未更改内容
    packageJSON.wxconfig.updateTime = Date.now()

    console.log('\n修改后版本号信息:', {
      version: version,
      desc: desc,
    })
  } catch (error) {
    console.log(colorizeText(error, 91))
    process.exit(1)
  }
}

// 写入package.json
const writeFile = async () => {
  try {
    // JSON.stringify 方法将 JavaScript 对象转换为 JSON 字符串,第二个参数 null 表示没有替换函数,第三个参数 2 表示缩进空格数为 2,用于格式化输出的 JSON 字符串。
    // 异步写回到文件中
    await fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2))

    console.log(colorizeText('\n修改package.json文件成功!\n', 92))
  } catch (err) {
    console.error(colorizeText(`fs.writeFile err:${err}`, 91))
    process.exit(1)
  }
}

// 提交代码到当前git分支
const gitSave = async () => {
  try {
    await execAsync(`git add .`)
    await execAsync(
      `git commit -m '正式环境修改version为:${packageJSON.wxconfig.tagName};desc:${packageJSON.wxconfig.tagDesc}'`
    )
    // 前面限制了dev分支,这时候就无需一定要推送到dev
    await execAsync(`git push`)

    console.log(colorizeText(`\n成功推送到 ${curBranch} 分支!\n`, 92))
  } catch (error) {
    console.error(colorizeText(`推送git失败!${error.message}`, 91))
    process.exit(1)
  }
}

// 进行打tag
const gitTag = async () => {
  try {
    // 本地进行打一个 附注tag ,注意,需要强制更新tag,防止本地已有相同tag会报错
    await execAsync(
      `git tag -a ${packageJSON.wxconfig.tagName} -m "${packageJSON.wxconfig.tagDesc}" --force`
    )
    // 并上传到远程仓库
    await execAsync(`git push origin ${packageJSON.wxconfig.tagName}  --force`)

    console.log(
      colorizeText(
        `\n成功推送 ${packageJSON.wxconfig.tagName} tag标签到远程\n`,
        92
      )
    )
  } catch (error) {
    console.error(colorizeText(`git tag失败!${error.message}`, 91))
    process.exit(1)
  }
}

//删除指定tag
const deleteTag = async () => {
  try {
    // 删除时间最久的一条tag,防止tag分支过多(可选)

    // 根据时间正序获取最久远的一条tag名称
    let lastTagName = await execAsync(`git tag --sort=creatordate | head -1`)
    // 删除本地一条tag
    await execAsync(`git tag -d ${lastTagName}`)
    // 删除远程一条tag
    await execAsync(`git push origin --delete ${lastTagName}`)

    console.log(colorizeText(`\n删除 ${lastTagName} tag成功\n`, 92))
  } catch (error) {
    console.error(colorizeText(`删除tag失败!${error.message}`, 91))
    process.exit(1)
  }
}

// 机器人自动化上传
const autoUpload = async () => {
  const project = new ci.Project({
    appid: '填写你的appid',
    type: 'miniProgram',
    // projectPath:项目的路径,即 project.config.json 所在的目录
    projectPath: path.join(__dirname, '../'),
    // privateKeyPath:私钥的路径
    privateKeyPath: path.join(__dirname, './weapp.key'),
    // 指定需要排除的规则
    ignores: ['node_modules/**/*', 'package-lock.json'],
  })
  console.log(colorizeText(`\n正在上传...\n`, 92))

  const uploadResult = await ci.upload({
    project,
    // 自定义版本号
    version: packageJSON.wxconfig.tagName,
    desc: packageJSON.wxconfig.tagDesc,
    setting: {
      es6: true,
      es7: true,
      minifyJS: true,
      minifyWXML: true,
      minifyWXSS: true,
      minify: true,
      // autoPrefixWXSS 对应小程序开发者工具的 "样式自动补全"
      // autoPrefixWXSS: true,
    },
    onProgressUpdate: console.log,
    // 指定使用哪一个 ci 机器人,可选值:1 ~ 30
    robot: 8,
    // 指定本地编译过程中开启的线程数
    // threads: 10,
  })

  console.log(uploadResult)

  console.log(colorizeText('tag版本上传完成!', 92)) // 亮绿色代码是 '92'
  console.log(colorizeText('版本号:' + packageJSON.wxconfig.tagName, 92))
  console.log(colorizeText('描述:' + packageJSON.wxconfig.tagDesc, 92))
}

;(async () => {
  // 获取当前本地分支名字
  await getCurrentBranch()

  // 获取最新tag信息
  await getTagInfo()

  // 交互修改
  await askQuestion()

  // 异步写入package.json文件
  await writeFile()

  // 保存并提交git
  await gitSave()

  // git tag操作
  await gitTag()

  // 删除指定tag,可选
  // await deleteTag()

  // 机器人自动化上传
  await autoUpload()
})()

在 package.json 文件配置npm命令,并运行 npm run tag 即可上传符合发布版的版本,再到小程序管理进行发布升级

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值