ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

告别手动点上传:miniprogram-ci 把小程序提审打包成一条命令

告别手动点上传:miniprogram-ci 把小程序提审打包成一条命令 告别手动点上传miniprogram-ci 把小程序提审打包成一条命令适用读者负责微信小程序日常迭代与发版的前端工程师、维护小程序 CI/CD 流水线的 DevOps 同学。假设你已经能独立跑通微信开发者工具的预览与上传对 Node.js 脚本和 Git 打 tag 有基本操作经验。上周三晚上十点半测试在群里说「这个 bug 修复版帮我传一下」。值班同事打开微信开发者工具点了上传版本号随手填了个 1.2.3——和两个小时前另一个人传的版本号撞了。微信后台「版本管理」里两条记录同名谁也不知道哪份代码对应哪个提交最后只能靠后台显示的上传时间和打包 md5 反推。这种场景你一定不陌生版本号靠记忆、描述靠手打、谁传的靠翻聊天记录三个人协作一个小程序后台堆了一排同名版本出问题根本没法回溯。那天晚上我下定决心把发版脚本化前后花了两个晚上现在整个流程收在一条命令里npm run release——版本号强制取自 git tag描述自动取 commit message人只需要打一个 tag剩下的交给流水线。TL;DR 速览一条命令用 miniprogram-ci 把上传打包成npm run release。版本可回溯版本号强制取自 git tag描述取 commit message。密钥当生产凭据按生产凭据保管IP 白名单按场景取舍。提审发布靠人提审与发布仍需人工在 mp 后台操作。手动上传到底有多疼先量化一下问题。我们团队 4 个人共用一个小程序项目迭代周期一周两版。我对过去一个月的发版记录做了次盘点把手动上传和接入 miniprogram-ci 之后的表现放在一起对比对比项手动开发者工具上传miniprogram-ci 脚本上传单次操作耗时90~150 秒含开工具、编译、手填版本25~40 秒纯上传打包版本号来源人工记忆随手填强制取自 git tag不填 tag 直接失败版本描述「修复了一些问题」自动取 commit message精确到提交多人覆盖常见后台同名版本堆积robot 号隔离互相不干扰谁都能传是任何装了工具的人否密钥只有 CI 环境持有出问题回溯翻聊天记录找谁传的git log CI 构建记录一一对应最要命的不是慢是版本号和代码之间没有约束关系。版本描述栏里写「fix bug」三个月后没人知道修的是哪个 bug。脚本化之后这些问题全部消掉因为信息只能从 Git 仓库里来人没有填错的机会。miniprogram-ci 能做什么miniprogram-ci 是微信官方提供的 Node.js 模块npm i miniprogram-ci把开发者工具里「预览」「上传」「代码依赖分析」这几个动作做成了可编程接口。常用的三个能力ci.upload把本地项目编译打包后上传到微信后台「开发版本」等价于工具里的上传按钮参数里可以指定版本号、描述、编译设置和 robot 编号。ci.preview生成预览版产物是一张预览码图可存成文件扫码即可在真机上打开临时版本不占用正式版本位。ci.analyse跑代码依赖分析和体积分析输出主包/分包大小、依赖关系适合放在流水线里做体积门禁。它不需要安装微信开发者工具纯命令行运行这是它和开发者工具 CLIcli命令行调用本机工具最本质的区别——CI 容器里通常装不了 GUI 工具miniprogram-ci 就是为此设计的。上传密钥和 IP 白名单机制先搞清楚再动手这是整个方案里最容易踩坑的一块值得单独拆开讲。miniprogram-ci 的鉴权不走个人微信账号而是走「小程序代码上传密钥」——在 mp.weixin.qq.com 后台「开发管理 → 开发设置」里生成下载得到一个.key结尾的私钥文件。上传时用这个私钥对请求签名微信服务端验证后才放行。这里有个高频混淆点代码上传密钥IP 白名单和「开发者工具的安全域名」是两套独立体系。开发者工具里上传代码走的是登录账号的票据不校验出口 IP而代码上传密钥受「IP 白名单」开关保护——你可以在后台开启「仅允许白名单内 IP 调用」此时只有指定出口 IP 的机器能用这把密钥上传。我们第一次接 CI 就栽在这里本地脚本跑得好好的丢到 GitHub Actions 上直接报错40125 invalid ip因为 Actions 的 runner 出口 IP 不固定不在白名单里。解法有两条路后台不开启 IP 白名单强校验默认就是关的只靠密钥文件本身保密用带固定出口 IP 的自建 Runner 或云主机跑 Jenkins / self-hosted runner把出口 IP 配进白名单。我们的选择是折中GitHub Actions 上不启用白名单但密钥只放在 Actions Secrets 里、日志里绝不打印公司内网 Jenkins 的机器走白名单强校验。密钥文件的权限等级等同于小程序的发布权限谁拿到谁就能传代码保管规格按生产凭据对待。另外注意 robot 编号后台允许配置多个机器人1~30不同 robot 上传的版本在后台是分区展示的。我们给 CI 固定用 robot 3本地应急手传用 robot 1这样后台一眼就能分清哪条是流水线产物。封装 upload 脚本version 从 tag 取desc 取 commit核心思路是让脚本自己从 Git 里挖元数据人不参与填写。下面是我们仓库里scripts/upload.js的完整实现Node 14 以上可跑依赖只有 miniprogram-ci 本体// scripts/upload.js —— 小程序上传脚本发版入口// 依赖npm i miniprogram-cilatestNode 14// 环境变量MP_APPID小程序 AppID、MP_KEY_PATH私钥文件路径// 使用方式npm run upload且当前分支必须已打 vX.Y.Z 的 tagconstcirequire(miniprogram-ci)const{execSync}require(child_process)// 取当前分支最近的 tag 作为版本号// 没有 tag 时直接抛错杜绝「随手填版本号」的可能functiongetVersion(){// git describe 会找到当前分支可到达的最近一个 tag// --abbrev0 表示只要 tag 本身不带 commit 后缀consttagexecSync(git describe --tags --abbrev0).toString().trim()// 校验语义化版本格式防止打成 v1 或 build-2024 这类自由 tagif(!/^v\d\.\d\.\d$/.test(tag)){thrownewError(tag${tag}不符合 vX.Y.Z 格式请先打规范版本 tag)}// 去掉前缀 v微信后台只要数字部分returntag.slice(1)}// 版本描述取最近一条 commit message// 后台「版本描述」栏会原文展示回溯时直接定位到提交functiongetDesc(){// %s 只取标题行不带正文长度可控constmsgexecSync(git log -1 --pretty%s).toString().trim()// 微信对描述长度有限制截断到 60 字符保险// 超长截断比报错友好描述不参与完整性校验returnmsg.length60?msg.slice(0,60):msg}asyncfunctionmain(){// 元数据全部来自 Git脚本不提供任何手动传参入口constversiongetVersion()constdescgetDesc()console.log(开始上传version${version}desc${desc})// Project 实例封装了项目信息与私钥后续 upload/preview 复用constprojectnewci.Project({appid:process.env.MP_APPID,// AppID 从环境变量读不硬编码type:miniProgram,projectPath:process.cwd(),// 小程序项目根目录含 project.config.jsonprivateKeyPath:process.env.MP_KEY_PATH,// 上传密钥私钥文件路径ignores:[node_modules/**/*],// 打包时排除依赖目录})constt0Date.now()constresultawaitci.upload({project,version,desc,setting:{es6:true,// 开启 ES6 转 ES5和工具里设置保持一致minify:true,// 压缩代码主包体积能小 15% 左右autoPrefixWXSS:true,// 样式自动补前缀},robot:3,// CI 专用机器人编号和本地手传区分})// 上传完成打印耗时和后台包信息便于留档console.log(上传完成耗时${Math.round((Date.now()-t0)/1000)}s)console.log(分包信息${JSON.stringify(result.subPackageInfo||[])})}// 统一入口任何异常都转成非零退出码main().catch((e){console.error(上传失败,e.message)process.exit(1)// 非零退出码让 CI 正确判定失败})配套的package.json里加两条 script{scripts:{upload:node scripts/upload.js,preview:node scripts/preview.js}}实际跑起来我们一个主包 1.6MB 两个分包合计 2.8MB 的项目ci.upload全程 28 秒左右公司 200M 带宽内网比开发者工具里的「上传」按钮快不少因为省掉了 GUI 编译面板的初始化。tag 校验那行曾救过我们一次——有人打成v2.1就想发布脚本直接拦下来了。接进 CI密钥保管与流水线编排脚本能跑只是第一步关键是让它在流水线里安全地跑。以 GitHub Actions 为例两个密钥MP_APPID放 repo 的 VariablesMP_PRIVATE_KEY把私钥文件内容不是路径存进 Actions Secrets工作流里现场落盘成临时文件再传给脚本# .github/workflows/release.yml —— 打 tag 触发小程序上传# 触发条件刻意收紧在 tag防止日常 push 误触发上传name:miniapp-releaseon:push:tags:[v*]# 只有 vX.Y.Z 的 tag 才触发jobs:upload:runs-on:ubuntu-latest# 单 job 串行执行上传失败后续步骤不跑steps:# 拉全量历史git describe 才能找到 tag-uses:actions/checkoutv4with:fetch-depth:0# Node 版本与本地开发保持一致避免编译行为漂移-uses:actions/setup-nodev4with:node-version:18-name:还原上传密钥run:|# 私钥内容从 Secrets 写入临时文件用完即弃 echo ${{ secrets.MP_PRIVATE_KEY }} /tmp/private.wx.key-run:npm ci# 体积门禁主包超限直接让流水线失败# analyse 脚本内部也用非零退出码上报失败-name:依赖分析体积门禁run:node scripts/analyse.js-name:上传到微信后台env:# AppID 放 Variables私钥放 Secrets权限分级管理# 私钥只存内容不存路径路径在运行时生成MP_APPID:${{vars.MP_APPID}}MP_KEY_PATH:/tmp/private.wx.keyrun:npm run upload# 无论成败都删掉私钥文件不留痕在 runner 磁盘# if: always() 保证失败分支也执行清理-name:清理密钥文件if:always()run:rm-f /tmp/private.wx.key流水线编排成这样一条链主包超限通过打 tag v1.3.0Actions 触发npm ci 安装依赖analyse 体积门禁流水线失败阻塞发布ci.upload 上传ci.preview 生成预览预览码推测试群测试验收通过后台手动提审Jenkins 侧的差别主要在密钥私钥文件用 Credentials 管理成 Secret file 类型流水线里通过withCredentials挂载机器出口 IP 配进后台白名单。原理相通就不贴第二份配置了。把 upload 的鉴权与上传时序画出来方便理解私钥到底在哪一步起作用preview 推群验收流水线的最后一环上传成功不等于可以提审中间还差一道测试验收。我们把ci.preview接在 upload 之后生成的预览码图直接存到构建产物目录// scripts/preview.js —— 生成预览版本供真机验收// 验收流程构建产物下载预览码图 → 真机打开 → 群里回验收结论constcirequire(miniprogram-ci)// Project 初始化逻辑与 upload.js 相同此处省略// 依赖环境变量与 upload.js 完全一致复用同一把私钥asyncfunctionmain(){constprojectawaitrequire(./makeProject)()// 复用初始化// preview 与 upload 参数结构几乎一致只是产物不同constresultawaitci.preview({project,// 描述里带上版本号群里对版本时不用翻构建日志desc:preview${require(./upload).getVersion()},setting:{es6:true,minify:true},// qrcodeFormat 支持 base64 / image / terminal 三种qrcodeFormat:image,// 输出为图片文件qrcodeOutputDest:./dist/preview.jpg,// 存到构建产物目录robot:3,// 与 upload 同一 robot版本可对应onProgressUpdate:console.log,// 打印编译进度便于排查})// preview 结果里带真机调试相关配置可按需存档console.log(预览版已生成dist/preview.jpg)}// 失败同样以非零退出码上抛给 CImain().catch((e){console.error(e);process.exit(1)})Actions 里再加一步用现成的上传构建产物的 action 把dist/preview.jpg存成 artifact通知机器人把下载链接甩进测试群。测试同学扫码进预览版验完在群里回「1.3.0 OK」负责发布的同学再去后台点提审。预览码本身只指向临时体验版本不经过群文件流转也不产生安全问题但截图里若带了项目名信息对外群还是要留意。提审的边界ci 到此为止这一步还在人手里提审后的验证与发布提审通过后发布这一步同样在 mp 后台由人完成但发布前的验证和发布后的回滚值得单独梳理成一套固定动作避免「审核过了就以为万事大吉」。发布前的核对清单审核通过后后台「版本管理」里会出现「审核通过」状态的版本。点「发布」之前先过一遍这份清单版本号确认后台显示的版本号与 git tag 一致如1.3.0别把审核中的旧版本当成最新版发布。版本描述核对描述是否对应本次迭代的 commit message避免「修复了一些问题」这类无法回溯的描述上线。分包大小确认主包/分包体积在微信限制内主包 2MB、总包 20MB超限版本即使审核通过也可能在线上被降级或拦截。线上功能冒烟测试发布后立即在真机上跑一遍核心链路——登录、首页加载、关键页面跳转、支付如有确认没有白屏或接口报错。发布与验证点「发布」后微信后台会有一个短暂的发布生效过程通常几十秒到几分钟。我们的做法是发布后立刻在测试群同步一条消息附上版本号和冒烟结论让测试同学在真机上再确认一遍。发布不等于上线完成线上版本以「版本管理」里最新一条「已发布」状态为准。回滚如有微信后台支持把线上版本回退到历史「已发布」版本。回滚的触发条件一般是线上出现严重 bug、接口大面积报错、或数据异常。操作路径是「版本管理 → 选择历史已发布版本 → 设为线上版本」。回滚有两个注意点回滚只切版本不切代码线上回退到旧版本后代码仓库里仍是新版本需要尽快修复并重新走一遍「打 tag → 上传 → 提审 → 发布」流程否则下次发布又会把问题版本带上去。回滚有延迟微信后台切版本不是瞬时的切完后要等生效再冒烟验证别切完就以为立刻恢复了。小结提审通过只是发版流程的中点发布、验证、回滚这三步仍然需要人盯着。把「核对清单 冒烟测试 回滚预案」固化成团队约定发版这件事才算真正闭环。必须说清楚一个能力边界miniprogram-ci 不包含提审和发布的 API。上传upload生成的是「开发版本」把开发版本提交审核、审核通过后发布这两步官方只开放给了第三方平台代开发的场景submitAudit属于开放平台第三方接口普通自研小程序用不了。所以自研小程序的流水线终点是「开发版本就绪 预览验收通过」提审按钮仍然在 mp 后台由人按下。这个边界设计其实合理提审涉及审核规则判断——类目资质是否齐、有没有违规内容机器不好兜底。我们的实践是让流水线把「该准备的都准备好」版本号规范、描述可回溯、体积达标、预览验收留痕人只做最后一次判断。提审高峰期比如赶大版本后台审核排队 2~6 小时不等提审后到通过前开发版本不能被覆盖上传这也是为什么 robot 分区很重要——CI 继续用 robot 3 传下一个日常版本不会动 robot 区里正在审核的那个。原理侧ci.upload 在本地到底做了什么先看一张鉴权时序图私钥在整条链路里只出现一次但每一步校验都不能少微信上传网关miniprogram-ciNode 脚本微信上传网关miniprogram-ciNode 脚本本地完成编译压缩计算整包 md5验签 IP 白名单校验传入 appid 与私钥路径签名(appid版本md5)40001/40125 或放行返回上传结果与分包信息把ci.upload当黑盒用没问题但排查构建差异时得知道它和开发者工具的差异在哪。ci.upload在本地完成了完整的前端编译链Babel 转译es6 选项、代码压缩minify、WXSS 前缀补全、WXML 编译然后按project.config.json里的packOptions规则收集文件计算整包 md5最后用私钥对「appid 版本 包 md5」做签名连同代码包一起 POST 到微信上传网关。服务端验签通过才落库。这意味着两个结论编译行为由脚本参数和 project.config.json 共同决定两边 setting 不一致就会出现「我本地工具里好好的CI 传上去就不对」——我们把工具里setting的每一项都对齐到脚本参数后才稳定下来。第二签名机制决定了私钥文件损坏或格式不对比如从 Secrets 还原时多了换行符会报code 40001这类签名错误排查时优先检查密钥文件内容是否被流水线污染我们踩过的这几个坑集中列一下坑现象报错解法密钥文件带 BOM/多余换行40001 invalid signatureSecrets 写入后sed -i s/\r$//清理或 base64 转存还原Actions runner IP 不在白名单40125 invalid ip not in whitelist关闭强校验或换固定出口 IP 的 self-hosted runnercheckout 没拉全量历史git describe报fatal: No tags foundfetch-depth: 0拉全量projectPath 指错层Error: 项目未找到 app.json指向含 project.config.json 的根目录robot 用了别人的编号后台版本区混乱、覆盖团队约定编号表写进 README常见报错速查表上面表格里列的是我们踩过的坑这里再补一张更通用的速查表覆盖 miniprogram-ci 上传时的高频报错方便你遇到报错时按错误码快速定位错误码典型场景排查步骤解决方案40001 invalid signature密钥文件被流水线污染BOM、多余换行、base64 还原出错1. 检查私钥文件内容是否与后台下载的原始文件一致2. 用xxd或cat -A查看文件头尾是否有异常字符Secrets 写入后执行sed -i s/\r$// /tmp/private.wx.key清理换行或改用 base64 编码存储、运行时base64 -d还原40125 invalid ip not in whitelistGitHub Actions / 云函数等出口 IP 不固定的环境且后台开启了 IP 白名单强校验1. 确认后台「IP 白名单」开关状态2. 查看 runner 出口 IPcurl ifconfig.me是否在白名单内关闭白名单强校验仅靠密钥保密或改用固定出口 IP 的 self-hosted runner / 内网 Jenkins把出口 IP 配进白名单600001或系统繁忙上传频率过高、并发上传同一 robot、或微信服务端临时抖动1. 检查是否同一 robot 短时间内多次上传2. 查看 CI 日志确认是否并发触发多个 upload job给流水线加concurrency限制同一 robot 串行上传重试一次微信服务端偶发抖动重试通常能过Error: 项目未找到 app.jsonprojectPath指向了错误目录或项目根目录缺少project.config.json1. 确认projectPath指向含project.config.json的根目录2. 检查project.config.json里miniprogramRoot是否指向了小程序代码子目录把projectPath改为项目根目录若代码在子目录在project.config.json里正确配置miniprogramRootfatal: No tags foundCI 里git describe找不到 tag通常是 checkout 没拉全量历史1. 确认当前分支是否已打 tag2. 检查 CI 的 checkout 步骤是否只拉了浅克隆在 Actions 的actions/checkoutv4里加fetch-depth: 0拉全量历史本地确认git tag已推送version 格式不正确版本号不符合微信后台要求如v1.3、1.3带前缀、或含非法字符1. 检查脚本里getVersion()的返回值2. 确认 tag 是否符合vX.Y.Z格式统一 tag 规范为vX.Y.Z脚本里用正则/^v\d\.\d\.\d$/校验不合法直接抛错拦截排查时建议在upload.js的catch里把e.message完整打印出来错误码通常就在消息开头再结合上面的表格按「错误码 → 场景 → 排查 → 解决」的顺序走大部分问题都能在十分钟内定位。误区澄清两点常见误解值得摆正。一是「有了 ci 就能全自动发版」——不对提审和发布环节官方没开放给自研小程序全自动只到上传为止刻意绕过人工提审的思路找非官方接口有账号风控风险不要碰。二是「不开 IP 白名单就不安全」——白名单只是纵深防御的一层密钥文件本身的保管Secrets 加密、日志脱敏、离职回收才是核心白名单解决的是密钥泄露后被异地滥用的场景两者不互斥。小程序工程化这条路微信官方还在持续补能力代码依赖分析、体积告警这些点值得盯着 ci 的版本更新日志跟进工具链每前进一步人就少点一次按钮。有问题欢迎评论区交流尤其是 Actions 上传微信小程序踩过的别的坑。参考与延伸miniprogram-ci 官方文档 — upload/preview/analyse 全部参数说明微信小程序开发框架文档 — project.config.json 配置项与编译选项开发者工具 CLI 说明 — 与 miniprogram-ci 的能力边界对照GitHub Actions 文档 — Secrets 管理与工作流语法微信小程序 · miniprogram-ci · CI/CD · 自动化构建 · 代码上传密钥 · 小程序上传 · 持续集成
返回列表