如何用 GitHub Actions 持续部署微信和支付宝小程序
使用微信官方 miniprogram-ci 和支付宝官方 minidev,把小程序构建、检查与版本上传接入 GitHub Actions。方案不依赖具体框架,原生项目和跨端框架都能使用。
微信官方的 miniprogram-ci 和支付宝官方的 minidev 都可以在命令行里上传小程序代码。把它们接入 GitHub Actions 后,主分支每次更新都能自动构建,并分别上传微信开发版本和支付宝体验版。
下面这套配置不依赖具体框架。原生小程序可以用,Taro、uni-app、MPX 等跨端项目也可以用;只要最后能得到平台认识的构建目录即可。
部署流程
这套流程只关心构建产物,不关心源码使用什么框架:
git push
-> 安装依赖
-> 测试和构建
-> 微信:miniprogram-ci 上传开发版本
-> 支付宝:minidev 上传体验版
原生项目可以直接上传源码目录。跨端项目需要先执行各平台的构建命令,再上传对应的编译目录。测试和构建可以共用一个 job,微信、支付宝上传则适合拆成两个并行 job,互不影响。
常见差异只有两项:
| 项目类型 | 构建命令 | 上传目录 |
|---|---|---|
| 原生小程序 | 可以省略 | 平台工程配置文件所在目录 |
| Taro | 项目中的平台构建命令 | 通常在 dist 下 |
| uni-app | 项目中的平台构建命令 | 以项目配置的输出目录为准 |
| MPX | 项目中的跨端构建命令 | 例如 dist/wx、dist/ali |
部署脚本不需要理解这些框架。微信上传目录里要有 project.config.json,支付宝上传目录里要有 mini.project.json。
安装官方上传工具
在项目中安装 miniprogram-ci 和 minidev:
npm install --save-dev --save-exact miniprogram-ci minidev
建议锁定明确版本,并提交 lockfile。持续集成环境应该安装已经审核过的依赖解析结果,不要在部署时临时升级依赖。
项目还需要相应的构建命令。例如:
{
"scripts": {
"test": "your_test_command_here",
"build:miniprograms": "your_cross_platform_build_command_here"
}
}
只部署一个平台时,构建命令当然也可以只构建一个平台。原生小程序不需要编译时,可以删除构建脚本和 workflow 里的对应步骤。
生成微信代码上传密钥
登录微信公众平台,进入 开发管理 -> 开发设置 -> 小程序代码上传,生成代码上传密钥。
下载得到的文件是 PEM 私钥:
-----BEGIN PRIVATE KEY-----
private_key_content_here
-----END PRIVATE KEY-----
密钥拥有预览和上传代码的权限。不要把它放进仓库,也不要输出到 Actions 日志。
同一页面还可以配置 IP 白名单。普通 GitHub-hosted runner 没有固定出口 IP,本教程使用 ubuntu-latest,因此需要关闭微信代码上传 IP 白名单。需要保留白名单时,请改用具有固定出口 IP 的 runner。
这是安全性与维护成本之间的选择。关闭白名单后,私钥成为主要凭证,更应该限制它的使用范围。
生成支付宝开发工具密钥
支付宝这里最容易卡住的不是密钥本身,而是入口藏得有点深。
登录支付宝开放平台并进入控制台后,不要在某个小程序的开发设置里反复翻。正确路径在页面右上角:
- 点击右上角头像
- 进入 账户中心
- 进入 密钥管理
- 选择 开发工具密钥
- 生成身份密钥并下载
config.json
尤其是 账户中心 这一步,不点头像很难找到。知道路径以后不过几次点击,不知道时却足够在控制台里绕上几圈。
下载得到的是完整的工具身份文件,大致如下:
{
"alipay": {
"authentication": {
"toolId": "...",
"privateKey": "-----BEGIN PRIVATE KEY-----..."
}
}
}
后面配置 ALIPAY_IDENTITY_KEY 时,要保存这个 config.json 的完整内容,不能只复制 privateKey。它也不是支付接口使用的应用私钥,两种密钥不要混在一起。
同一个支付宝账号同时只有一份开发工具密钥有效。重新生成密钥,或者在其他电脑执行 minidev login,都会让旧密钥失效。CI 使用固定密钥文件,正是为了避免每台 runner 都扫码登录。
顺手说一句,支付宝小程序目前本身不收费,对个人项目很友好。流量确实不算多,后台也谈不上好用,还有一套“小程序分”常常打得让人摸不着头脑。不过免费平台肯给上传接口,也就没什么可抱怨的了。
配置 GitHub Environment
打开仓库的 Settings -> Environments,分别创建 wechat-upload 和 alipay-upload 两个 Environment。只部署一个平台时,创建对应的一个即可。
在 wechat-upload 中添加:
- Variable
WECHAT_APPID: 微信小程序 AppID - Secret
WECHAT_PRIVATE_KEY: 完整的 PEM 私钥内容
在 alipay-upload 中添加:
- Variable
ALIPAY_APP_ID: 支付宝小程序 AppID - Secret
ALIPAY_IDENTITY_KEY: 下载得到的config.json完整内容
Environment 不是服务器。它用于管理部署变量、Secrets 和审批规则。上传任务声明对应的 environment 后,才能读取其中的值。
如果仓库不需要环境隔离,也可以使用 Repository Variables 和 Repository Secrets。两种方式都能运行,Environment 的权限边界更清楚。
封装仓库内的上传 Action
把上传逻辑放进仓库内的 Composite Action,可以让 workflow 保持简短,也便于补充参数校验和测试。
微信 Action
创建目录:
.github/actions/wechat-upload/
action.yml
upload.cjs
action.yml 声明 AppID、私钥和构建目录:
name: WeChat Mini Program Upload
description: Upload a built WeChat Mini Program with miniprogram-ci.
inputs:
appid:
description: WeChat Mini Program AppID.
required: true
private-key:
description: Content of the code upload private key.
required: true
project-path:
description: Directory containing project.config.json.
required: true
runs:
using: composite
steps:
- shell: bash
env:
INPUT_APPID: ${{ inputs.appid }}
INPUT_PRIVATE_KEY: ${{ inputs.private-key }}
INPUT_PROJECT_PATH: ${{ inputs.project-path }}
run: node "$GITHUB_ACTION_PATH/upload.cjs"
Action 通过当前 step 的环境变量读取 Secret,不把私钥放进命令参数。
upload.cjs 调用微信官方接口:
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const ci = require('miniprogram-ci')
const root = process.cwd()
const projectPath = path.resolve(process.env.INPUT_PROJECT_PATH)
const { version } = require(path.join(root, 'package.json'))
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-ci-'))
const keyPath = path.join(tempDir, 'private.key')
const privateKey = process.env.INPUT_PRIVATE_KEY.replace(/\\n/g, '\n')
fs.writeFileSync(keyPath, privateKey, { mode: 0o600 })
const project = new ci.Project({
appid: process.env.INPUT_APPID,
type: 'miniProgram',
projectPath,
privateKeyPath: keyPath
})
创建项目对象后执行上传,并在结束时删除临时密钥:
;(async () => {
try {
await ci.upload({
project,
version,
desc: `Actions #${process.env.GITHUB_RUN_NUMBER} ` +
`(${process.env.GITHUB_SHA.slice(0, 7)})`,
robot: 1,
setting: {},
onProgressUpdate: console.log
})
} finally {
fs.rmSync(tempDir, { recursive: true, force: true })
}
})().catch((error) => {
console.error(error)
process.exitCode = 1
})
版本号直接读取 package.json 的 version。GitHub run number 和 commit SHA 放进版本描述,用来区分同一产品版本的不同构建。
实际项目还可以增加这些校验:
- 检查
project.config.json是否存在 - 检查配置文件中的 AppID 是否与 Action 输入一致
- 限制 CI 机器人编号为
1到30 - 把版本号和描述压成单行
上传失败应该直接结束任务。不要捕获错误后继续返回成功状态。
支付宝 Action
支付宝 Action 的结构相同,只是输入换成 AppID、身份密钥和支付宝构建目录:
.github/actions/alipay-upload/
action.yml
upload.cjs
action.yml 把输入转成当前 step 的环境变量:
name: Alipay Mini Program Upload
description: Upload a built Alipay Mini Program with minidev.
inputs:
appid:
required: true
identity-key:
required: true
project-path:
required: true
runs:
using: composite
steps:
- shell: bash
env:
INPUT_APPID: ${{ inputs.appid }}
INPUT_IDENTITY_KEY: ${{ inputs.identity-key }}
INPUT_PROJECT_PATH: ${{ inputs.project-path }}
run: node "$GITHUB_ACTION_PATH/upload.cjs"
upload.cjs 把 ALIPAY_IDENTITY_KEY 的完整内容写进临时文件,再交给官方 minidev:
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const { minidev } = require('minidev')
const root = process.cwd()
const projectPath = path.resolve(process.env.INPUT_PROJECT_PATH)
const { version } = require(path.join(root, 'package.json'))
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'alipay-ci-'))
const identityKeyPath = path.join(tempDir, 'config.json')
fs.writeFileSync(identityKeyPath, process.env.INPUT_IDENTITY_KEY, { mode: 0o600 })
;(async () => {
try {
await minidev.upload({
appId: process.env.INPUT_APPID,
clientType: 'alipay',
project: projectPath,
identityKeyPath,
version,
versionDescription: `Actions #${process.env.GITHUB_RUN_NUMBER}`,
experience: true
})
} finally {
fs.rmSync(tempDir, { recursive: true, force: true })
}
})().catch((error) => {
console.error(error)
process.exitCode = 1
})
experience: true 会在上传成功后把该版本设为支付宝体验版。这个账号需要有对应权限,否则可以先去掉该参数,只上传版本。
编写部署 workflow
创建 .github/workflows/deploy-miniprograms.yml。工作流只响应主分支推送和手动触发,不响应 Pull Request(PR)。先做一次测试和双端构建,再把产物交给两个上传 job:
name: Deploy Mini Programs
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
- run: npm ci
- run: npm test --if-present
- run: npm run build:miniprograms
- uses: actions/upload-artifact@v7
with:
name: wechat-miniprogram
path: dist/wx
- uses: actions/upload-artifact@v7
with:
name: alipay-miniprogram
path: dist/ali
upload-wechat:
needs: validate
runs-on: ubuntu-latest
environment: wechat-upload
concurrency:
group: wechat-upload
cancel-in-progress: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
- run: npm ci
- uses: actions/download-artifact@v8
with:
name: wechat-miniprogram
path: dist/wx
- name: Upload WeChat development version
uses: ./.github/actions/wechat-upload
with:
appid: ${{ vars.WECHAT_APPID }}
private-key: ${{ secrets.WECHAT_PRIVATE_KEY }}
project-path: dist/wx
upload-alipay:
needs: validate
runs-on: ubuntu-latest
environment: alipay-upload
concurrency:
group: alipay-upload
cancel-in-progress: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
- run: npm ci
- uses: actions/download-artifact@v8
with:
name: alipay-miniprogram
path: dist/ali
- name: Upload Alipay experience version
uses: ./.github/actions/alipay-upload
with:
appid: ${{ vars.ALIPAY_APP_ID }}
identity-key: ${{ secrets.ALIPAY_IDENTITY_KEY }}
project-path: dist/ali
把构建命令以及 dist/wx、dist/ali 换成项目自己的配置。使用 pnpm 或 Yarn 时,也只需要替换安装、缓存和脚本命令。
两个上传 job 各自使用独立的 Environment 和并发组。连续推送时,同一平台的上传会排队,微信与支付宝之间则可以并行。
验证部署
提交 workflow、Action 和上传脚本后,推送到 main:
git add .github package.json package-lock.json
git commit -m "feat: deploy Mini Programs with GitHub Actions"
git push origin main
打开仓库的 Actions 页面,依次确认:
- 依赖安装成功
- 测试和双端构建成功
- 微信上传步骤显示
miniprogram-ci编译与上传进度 - 支付宝上传步骤显示
minidev构建与上传结果 - 微信后台出现新的开发版本,支付宝后台出现新的体验版
微信上传提示 IP 不在白名单时,检查微信公众平台的 IP 白名单设置。支付宝提示身份校验失败时,先确认 ALIPAY_IDENTITY_KEY 保存的是完整 config.json,再检查开发工具密钥是否被重新生成过。
部署边界
miniprogram-ci.upload() 对应微信开发者工具里的“上传”,只生成微信后台开发版本。minidev.upload() 会上传支付宝版本;示例中的 experience: true 还会把它设为体验版。两者都没有自动提审或正式发布。
开发版本上传适合自动执行。提审和发布会直接影响用户,还涉及版本说明、隐私能力和审核材料,保留人工确认更稳妥。
一条可靠的流水线不必包办所有事情。它应该把重复劳动拿走,把真正需要判断的地方留给人。