本文介绍如何使用 miniprogram-ci 实现微信小程序的命令行自动化构建和上传,告别手动打开开发者工具上传代码的繁琐流程。
目录 #
前置条件 #
- 开通代码上传权限:登录 微信公众平台 → 开发 → 开发设置 → 小程序代码上传
- 下载代码上传密钥:在上述页面生成并下载私钥文件
- 配置 IP 白名单:将你的公网 IP 添加到白名单
安装依赖 #
pnpm add -D miniprogram-ci配置私钥 #
将下载的私钥文件放到项目根目录,命名格式:
private.{appid}.key例如:private.wxf97542ac5367bcb2.key
⚠️ 安全提示:如果私钥不提交到 Git,需要在 CI/CD 环境通过环境变量注入。
创建上传脚本 #
创建 scripts/upload-weixin.js:
/**
* 微信小程序 CLI 上传脚本
*
* 使用方法:
* pnpm upload:mp # 版本号读取 package.json,描述使用最新 Git commit
* pnpm upload:mp --version=1.0.1 # 指定版本号(覆盖 package.json)
* pnpm upload:mp --desc="修复bug" # 指定版本描述(覆盖 Git commit)
* pnpm upload:mp --robot=2 # 指定机器人编号(1-30)
* pnpm upload:mp --version=2.0.0 --desc="重大更新" # 组合使用多个参数
*
* 版本号策略: 命令行参数 > package.json version
* 描述策略: 命令行参数 > Git 最新 commit > 默认时间戳
*/
import { execSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import ci from 'miniprogram-ci'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const ROOT_DIR = path.resolve(__dirname, '..')
// 从 package.json 读取版本号
function getPackageVersion() {
try {
const pkgPath = path.resolve(ROOT_DIR, 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
return pkg.version || '1.0.0'
}
catch {
return '1.0.0'
}
}
// 获取最新的 Git commit 信息
function getGitCommitMessage() {
try {
const message = execSync('git log -1 --pretty="%an: %s"', {
cwd: ROOT_DIR,
encoding: 'utf-8',
}).trim()
return message || null
}
catch {
return null
}
}
// 生成默认描述
function getDefaultDesc() {
const gitMessage = getGitCommitMessage()
if (gitMessage) {
return gitMessage
}
return `上传于 ${new Date().toLocaleString('zh-CN')}`
}
// 解析命令行参数
function parseArgs() {
const args = process.argv.slice(2)
const params = {
version: null,
desc: null,
robot: 1,
}
args.forEach((arg) => {
if (arg.startsWith('--version=')) {
params.version = arg.split('=')[1]
}
else if (arg.startsWith('--desc=')) {
params.desc = arg.split('=')[1]
}
else if (arg.startsWith('--robot=')) {
params.robot = Number.parseInt(arg.split('=')[1], 10)
}
})
if (!params.version) {
params.version = getPackageVersion()
}
if (!params.desc) {
params.desc = getDefaultDesc()
}
return params
}
// 读取环境变量
function loadEnvFile(mode = 'production') {
const envPath = path.resolve(ROOT_DIR, 'env', `.env.${mode}`)
const defaultEnvPath = path.resolve(ROOT_DIR, 'env', '.env')
const envContent = {}
// 读取 .env 文件
;[defaultEnvPath, envPath].forEach((filePath) => {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8')
content.split('\n').forEach((line) => {
const trimmed = line.trim()
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=')
if (key) {
envContent[key.trim()] = valueParts.join('=').trim().replace(/^['"]|['"]$/g, '')
}
}
})
}
})
return envContent
}
// 获取私钥路径
function getPrivateKeyPath(appid) {
const keyPatterns = [`private.${appid}.key`, 'private.key']
for (const pattern of keyPatterns) {
const keyPath = path.resolve(ROOT_DIR, pattern)
if (fs.existsSync(keyPath)) {
return keyPath
}
}
throw new Error(`未找到私钥文件,请确保项目根目录存在 private.${appid}.key 文件`)
}
// 主函数
async function main() {
console.log('\n🚀 开始微信小程序上传流程...\n')
const params = parseArgs()
const env = loadEnvFile('production')
const appid = env.VITE_WX_APPID
if (!appid) {
throw new Error('未找到 VITE_WX_APPID 环境变量')
}
console.log(`📱 AppID: ${appid}`)
console.log(`📌 版本号: ${params.version}`)
console.log(`📝 版本描述: ${params.desc}`)
console.log(`🤖 机器人编号: ${params.robot}`)
const privateKeyPath = getPrivateKeyPath(appid)
console.log(`🔑 私钥路径: ${privateKeyPath}`)
// 构建小程序
console.log('\n📦 正在构建小程序...\n')
execSync('pnpm build:mp:prod', {
cwd: ROOT_DIR,
stdio: 'inherit',
env: {
...process.env,
SKIP_OPEN_DEVTOOLS: 'true', // 跳过打开开发者工具
},
})
// 上传
const projectPath = path.resolve(ROOT_DIR, 'dist', 'build', 'mp-weixin')
console.log(`📂 项目路径: ${projectPath}`)
console.log('\n⬆️ 正在上传到微信服务器...\n')
const project = new ci.Project({
appid,
type: 'miniProgram',
projectPath,
privateKeyPath,
ignores: ['node_modules/**/*'],
})
await ci.upload({
project,
version: params.version,
desc: params.desc,
robot: params.robot,
setting: {
es6: true,
es7: true,
minify: true,
autoPrefixWXSS: true,
minifyWXML: true,
minifyWXSS: true,
minifyJS: true,
},
})
console.log('\n✅ 上传成功!')
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log(` 📌 版本号: ${params.version}`)
console.log(` 📝 描述: ${params.desc}`)
console.log(` 🤖 机器人: ${params.robot}`)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('\n📋 下一步操作:')
console.log(' 1. 登录微信公众平台: https://mp.weixin.qq.com')
console.log(' 2. 进入 "管理 -> 版本管理"')
console.log(' 3. 在 "开发版本" 中找到刚上传的版本')
console.log(' 4. 点击 "选为体验版" 按钮\n')
}
main().catch((error) => {
console.error('❌ 执行出错:', error)
process.exit(1)
})配置 npm scripts #
在 package.json 中添加:
{
"scripts": {
"upload:mp": "node ./scripts/upload-weixin.js"
}
}使用方法 #
基本用法 #
# 自动读取 package.json 版本号,使用最新 Git commit 作为描述
pnpm upload:mp指定参数 #
# 指定版本号
pnpm upload:mp --version=1.0.1
# 指定描述
pnpm upload:mp --desc="修复登录问题"
# 指定机器人编号(1-30,用于区分不同开发者)
pnpm upload:mp --robot=2
# 组合使用
pnpm upload:mp --version=2.0.0 --desc="重大更新" --robot=2版本管理建议 #
# 修复 bug
npm version patch # 1.0.0 → 1.0.1
pnpm upload:mp
# 新增功能
npm version minor # 1.0.1 → 1.1.0
pnpm upload:mp
# 大版本更新
npm version major # 1.1.0 → 2.0.0
pnpm upload:mpVite 插件优化 #
在 vite.config.ts 中,可以通过环境变量控制是否打开开发者工具:
const { UNI_PLATFORM, SKIP_OPEN_DEVTOOLS } = process.env
export default defineConfig({
plugins: [
// 上传时跳过打开开发者工具
SKIP_OPEN_DEVTOOLS !== 'true' && openDevTools({ mode }),
// ... 其他插件
],
})Netlify 自动部署 #
netlify.toml 配置 #
[build]
command = "pnpm netlify:build"
publish = "dist/build/h5"
[build.environment]
NODE_VERSION = "20"
ENABLE_MP_UPLOAD = "true"构建脚本 (scripts/netlify-build.js) #
import { execSync } from 'node:child_process'
async function main() {
// 1. 构建 H5
console.log('📦 [1/2] 正在构建 H5...')
execSync('pnpm build:h5:prod', { stdio: 'inherit' })
// 2. 可选:上传微信小程序
if (process.env.ENABLE_MP_UPLOAD === 'true') {
console.log('📱 [2/2] 正在上传微信小程序...')
execSync('pnpm upload:mp', { stdio: 'inherit' })
}
console.log('✅ 构建完成!')
}
main()常见问题 #
1. EPERM: operation not permitted #
原因:pages.json 文件被其他进程占用(如开发服务器、HBuilderX)
解决:关闭开发服务器后重新上传
# 先停止 pnpm dev:mp-weixin
pnpm upload:mp2. 上传失败:无权限 #
解决:
- 登录微信公众平台 → 开发 → 开发设置
- 检查代码上传密钥是否正确
- 检查 IP 白名单是否包含当前 IP
3. 私钥文件找不到 #
解决:确保私钥文件命名正确并放在项目根目录
项目根目录/
├── private.wxf97542ac5367bcb2.key ← 私钥文件
├── package.json
└── ...参数说明 #
| 参数 | 说明 | 默认值 |
|---|---|---|
--version | 版本号 | 读取 package.json 的 version |
--desc | 版本描述 | 最新 Git commit 信息 |
--robot | 机器人编号(1-30) | 1 |
Git Commit 格式说明 #
脚本使用以下命令获取最新 commit:
git log -1 --pretty="%an: %s"| 占位符 | 含义 | 示例 |
|---|---|---|
%an | 作者名字 | 张三 |
%s | 提交标题 | feat: 新增登录功能 |
输出示例:张三: feat: 新增登录功能
总结 #
通过 miniprogram-ci,我们实现了:
- ✅ 命令行一键构建上传
- ✅ 自动读取
package.json版本号 - ✅ 自动使用 Git commit 作为版本描述
- ✅ 支持 CI/CD 自动化部署
- ✅ 可选打开开发者工具
这大大简化了微信小程序的发布流程,提高了开发效率!