Overview
This skill helps generate directly runnable Node.js scripts for operations such as automatic preview, dependency packaging, and upload of Mini Program code. The scripts are generated parameterized based on the user's project configuration and support CI/CD pipeline integration (GitHub Actions, GitLab CI, etc.).
Core responsibilities: Generate repeatable command-line scripts based on the user's project information; the user executes the generated scripts to complete the actual deployment tasks.
Step 1: Collect necessary information
Before execution, first confirm the following information (if the user has not provided it, ask item by item):
1.1 Operation type
Ask the user: "Which capability do you need?"
| Operation | Description | Applicable scenario |
|---|---|---|
| Package dependencies (pack-npm) | Build npm dependencies into the miniprogram_npm directory | Must be executed first when the project uses npm modules |
| Preview (preview) | Generate a preview QR code for development/testing scan-code trial | Quick verification during development |
| Upload (upload) | Upload code to WeChat backend version management | Submit for testing, release a new version |
| Multiple combinations | Generate multiple scripts at the same time | Complete CI process (pack-npm first, then preview/upload) |
1.2 Compilation artifact directory
The output directory path after Mini Program compilation,miniprogram-ci needs to point to this directory.
Common paths:
- Taro project:
dist/ - Native project: project root directory or
miniprogram/ - uni-app:
dist/build/mp-weixin/
Ask the user: "Please confirm the Mini Program compilation artifact directory (default dist/):"
Check whether the directory exists:
ls <编译产物目录>/project.config.json 2>/dev/null || echo "目录不存在或缺少 project.config.json"
1.3 Package manager
Ask the user (if it cannot be determined): "Which package manager does the project use?"
It can also be automatically determined in the following ways:
[ -f pnpm-lock.yaml ] && echo "pnpm" || \
[ -f yarn.lock ] && echo "yarn" || \
echo "npm"
1.4 Existing script check
Check whether related scripts already exist under the scripts/ directory:
ls scripts/preview.js scripts/upload.js scripts/ci-*.js 2>/dev/null || echo "需要创建"
Step 2: Prerequisite check
2.1 Install miniprogram-ci
ls node_modules/miniprogram-ci 2>/dev/null && echo "已安装" || echo "未安装"
If not installed, install according to the package manager:
# pnpm
pnpm add miniprogram-ci --save-dev
# npm
npm install miniprogram-ci --save-dev
# yarn
yarn add miniprogram-ci --dev
2.2 Obtain the upload key
Tell the user the path to obtain it:
- Log in to WeChat Official Accounts Platform
- Go to: Development Management → Development Settings → Mini Program Code Upload
- Click "Generate" to download the key file (
private.*.key)
Security reminder:
- ❌ Key fileabsolutely must notbe committed to the code repository
- ✅ In
.gitignoreadd*.keyandprivate.*.key - ✅ Use secrets in CI/CD to manage key contents
2.3 Configure the IP whitelist
Tell the user:
- WeChat Official Accounts Platform → Development Settings → Mini Program Code Upload → IP Whitelist
- Add the egress IP of the CI server
- For local development, the whitelist can be temporarily disabled, but for the production environmentit is strongly recommended to enable it
Step 3: Create the script
Create the corresponding script according to the operation type selected by the user. The following template uses environment variables to read sensitive configurations, supporting CI/CD integration.
3.1 Packaging Dependencies Script Template
If the project uses npm modules and the user needspackaging dependenciescapability, create scripts/pack-npm.js:
#!/usr/bin/env node
/**
* 微信小程序 NPM 打包脚本
* 使用 miniprogram-ci 构建 npm 依赖至 miniprogram_npm 目录
*
* 环境变量:
* MP_APPID - 小程序 AppID(必填)
* MP_PROJECT_PATH - 编译产物目录(必填)
*
* 用法:
* node scripts/pack-npm.js
*/
const ci = require('miniprogram-ci');
const fs = require('fs');
const path = require('path');
// ─────────────────────────────────────────────────────────────────────────────
// 配置
// ─────────────────────────────────────────────────────────────────────────────
const CONFIG = {
appid: process.env.MP_APPID,
projectPath: process.env.MP_PROJECT_PATH,
};
// ─────────────────────────────────────────────────────────────────────────────
// 工具函数
// ─────────────────────────────────────────────────────────────────────────────
function validateConfig() {
const required = { MP_APPID: CONFIG.appid, MP_PROJECT_PATH: CONFIG.projectPath };
const missing = Object.entries(required).filter(([, v]) => !v).map(([k]) => k);
if (missing.length) {
console.error(`❌ 缺少环境变量: ${missing.join(', ')}`);
process.exit(1);
}
if (!fs.existsSync(path.resolve(CONFIG.projectPath))) {
console.error(`❌ 项目路径不存在: ${CONFIG.projectPath}`);
process.exit(1);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 主流程
// ─────────────────────────────────────────────────────────────────────────────
async function main() {
console.log('🔍 校验配置...');
validateConfig();
console.log('\n📋 NPM 打包配置:');
console.log(` AppID: ${CONFIG.appid}`);
console.log(` 项目路径: ${path.resolve(CONFIG.projectPath)}`);
const project = new ci.Project({
appid: CONFIG.appid,
type: 'miniProgram',
projectPath: path.resolve(CONFIG.projectPath),
ignores: ['node_modules/**/*'],
});
console.log('\n🚀 打包依赖...');
try {
const result = await ci.packNpm(project, {
reporter: (msg) => console.log(` ${msg}`),
});
console.log('\n✅ NPM 打包完成!');
console.log(`📦 结果: ${JSON.stringify(result)}`);
} catch (err) {
console.error(`\n❌ NPM 打包失败: ${err.message}`);
process.exit(1);
}
}
main();
3.2 Preview Script Template
If the user needspreviewcapability, create scripts/preview.js:
#!/usr/bin/env node
/**
* 微信小程序预览脚本
* 使用 miniprogram-ci 生成预览二维码
*
* 环境变量:
* MP_APPID - 小程序 AppID(必填)
* MP_PRIVATE_KEY_PATH - 上传密钥路径(必填)
* MP_PROJECT_PATH - 编译产物目录(必填)
* MP_ROBOT - 机器人编号 1-30(默认 1)
*
* 可选环境变量:
* MP_PAGE_PATH - 预览打开的页面路径
* MP_SEARCH_QUERY - 页面查询参数
*
* 用法:
* node scripts/preview.js
* MP_PAGE_PATH=pages/detail/index MP_SEARCH_QUERY="id=123" node scripts/preview.js
*/
const ci = require('miniprogram-ci');
const fs = require('fs');
const path = require('path');
// ─────────────────────────────────────────────────────────────────────────────
// 配置
// ─────────────────────────────────────────────────────────────────────────────
const CONFIG = {
appid: process.env.MP_APPID,
privateKeyPath: process.env.MP_PRIVATE_KEY_PATH,
projectPath: process.env.MP_PROJECT_PATH,
robot: parseInt(process.env.MP_ROBOT, 10) || 1,
pagePath: process.env.MP_PAGE_PATH || '',
searchQuery: process.env.MP_SEARCH_QUERY || '',
outputDir: path.resolve(process.cwd(), 'ci-artifacts/previews'),
};
// ─────────────────────────────────────────────────────────────────────────────
// 工具函数
// ─────────────────────────────────────────────────────────────────────────────
function validateConfig() {
const required = { MP_APPID: CONFIG.appid, MP_PRIVATE_KEY_PATH: CONFIG.privateKeyPath, MP_PROJECT_PATH: CONFIG.projectPath };
const missing = Object.entries(required).filter(([, v]) => !v).map(([k]) => k);
if (missing.length) {
console.error(`❌ 缺少环境变量: ${missing.join(', ')}`);
process.exit(1);
}
if (CONFIG.robot < 1 || CONFIG.robot > 30) {
console.error('❌ MP_ROBOT 必须在 1-30 之间');
process.exit(1);
}
if (!fs.existsSync(path.resolve(CONFIG.privateKeyPath))) {
console.error(`❌ 密钥文件不存在: ${CONFIG.privateKeyPath}`);
process.exit(1);
}
if (!fs.existsSync(path.resolve(CONFIG.projectPath))) {
console.error(`❌ 项目路径不存在: ${CONFIG.projectPath}`);
process.exit(1);
}
}
function ensureDir(dir) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
function timestamp() {
return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
}
// ─────────────────────────────────────────────────────────────────────────────
// 主流程
// ─────────────────────────────────────────────────────────────────────────────
async function main() {
console.log('🔍 校验配置...');
validateConfig();
ensureDir(CONFIG.outputDir);
const qrcodePath = path.join(CONFIG.outputDir, `preview-${timestamp()}.png`);
console.log('\n📋 预览配置:');
console.log(` AppID: ${CONFIG.appid}`);
console.log(` 项目路径: ${path.resolve(CONFIG.projectPath)}`);
console.log(` 机器人编号: ${CONFIG.robot}`);
if (CONFIG.pagePath) console.log(` 页面路径: ${CONFIG.pagePath}`);
if (CONFIG.searchQuery) console.log(` 查询参数: ${CONFIG.searchQuery}`);
console.log(` 二维码输出: ${qrcodePath}`);
const project = new ci.Project({
appid: CONFIG.appid,
type: 'miniProgram',
projectPath: path.resolve(CONFIG.projectPath),
privateKeyPath: path.resolve(CONFIG.privateKeyPath),
ignores: ['node_modules/**/*'],
});
console.log('\n🚀 生成预览...');
try {
const result = await ci.preview({
project,
desc: `Preview by robot ${CONFIG.robot} at ${new Date().toLocaleString()}`,
setting: { es6: true, es7: true, minify: true, autoPrefixWXSS: true },
qrcodeFormat: 'image',
qrcodeOutputDest: qrcodePath,
robot: CONFIG.robot,
...(CONFIG.pagePath && { pagePath: CONFIG.pagePath }),
...(CONFIG.searchQuery && { searchQuery: CONFIG.searchQuery }),
});
console.log('\n✅ 预览成功!');
console.log(`📱 二维码: ${qrcodePath}`);
if (result?.subPackageInfo) {
console.log('\n📦 包大小:');
result.subPackageInfo.forEach(p => console.log(` ${p.name || '主包'}: ${(p.size / 1024 / 1024).toFixed(2)} MB`));
}
} catch (err) {
console.error(`\n❌ 预览失败: ${err.message}`);
if (err.message.includes('invalid ip')) console.error('💡 请将当前 IP 添加到微信后台白名单');
process.exit(1);
}
}
main();
3.3 Upload Script Template
If the user needsuploadcapability, create scripts/upload.js:
#!/usr/bin/env node
/**
* 微信小程序上传脚本
* 使用 miniprogram-ci 上传代码至微信后台
*
* 环境变量:
* MP_APPID - 小程序 AppID(必填)
* MP_PRIVATE_KEY_PATH - 上传密钥路径(必填)
* MP_PROJECT_PATH - 编译产物目录(必填)
* MP_ROBOT - 机器人编号 1-30(默认 1)
*
* 命令行参数:
* --version <版本号> 必填
* --desc <描述> 必填
* --pack-npm 可选,上传前执行 npm 构建
*
* 用法:
* node scripts/upload.js --version 1.0.0 --desc "修复登录问题"
* node scripts/upload.js --version 1.0.0 --desc "新功能" --pack-npm
*/
const ci = require('miniprogram-ci');
const fs = require('fs');
const path = require('path');
// ─────────────────────────────────────────────────────────────────────────────
// 配置
// ─────────────────────────────────────────────────────────────────────────────
const CONFIG = {
appid: process.env.MP_APPID,
privateKeyPath: process.env.MP_PRIVATE_KEY_PATH,
projectPath: process.env.MP_PROJECT_PATH,
robot: parseInt(process.env.MP_ROBOT, 10) || 1,
outputDir: path.resolve(process.cwd(), 'ci-artifacts/uploads'),
};
// ─────────────────────────────────────────────────────────────────────────────
// 命令行解析
// ─────────────────────────────────────────────────────────────────────────────
function parseArgs() {
const args = process.argv.slice(2);
const result = { version: null, desc: null, packNpm: false };
for (let i = 0; i < args.length; i++) {
if (args[i] === '--version' && args[i + 1]) result.version = args[++i];
else if (args[i] === '--desc' && args[i + 1]) result.desc = args[++i];
else if (args[i] === '--pack-npm') result.packNpm = true;
else if (args[i] === '--help' || args[i] === '-h') { printHelp(); process.exit(0); }
}
return result;
}
function printHelp() {
console.log(`
用法: node scripts/upload.js [选项]
选项:
--version <版本号> 必填,如 1.0.0
--desc <描述> 必填,版本描述
--pack-npm 上传前执行 npm 构建
--help 显示帮助
`);
}
// ─────────────────────────────────────────────────────────────────────────────
// 工具函数
// ─────────────────────────────────────────────────────────────────────────────
function validateConfig() {
const required = { MP_APPID: CONFIG.appid, MP_PRIVATE_KEY_PATH: CONFIG.privateKeyPath, MP_PROJECT_PATH: CONFIG.projectPath };
const missing = Object.entries(required).filter(([, v]) => !v).map(([k]) => k);
if (missing.length) {
console.error(`❌ 缺少环境变量: ${missing.join(', ')}`);
process.exit(1);
}
if (CONFIG.robot < 1 || CONFIG.robot > 30) {
console.error('❌ MP_ROBOT 必须在 1-30 之间');
process.exit(1);
}
if (!fs.existsSync(path.resolve(CONFIG.privateKeyPath))) {
console.error(`❌ 密钥文件不存在: ${CONFIG.privateKeyPath}`);
process.exit(1);
}
if (!fs.existsSync(path.resolve(CONFIG.projectPath))) {
console.error(`❌ 项目路径不存在: ${CONFIG.projectPath}`);
process.exit(1);
}
}
function ensureDir(dir) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
function timestamp() {
return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
}
// ─────────────────────────────────────────────────────────────────────────────
// 上传(含超时重试)
// ─────────────────────────────────────────────────────────────────────────────
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 5000;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* 上传并在超时时自动重试
* 微信上传服务器在 GitHub Actions 等 CI 环境下可能因跨境网络超时,
* err.message 可为 "timeout"、"undefined" 或空字符串。
*/
async function uploadWithRetry(project, args) {
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
if (attempt > 1) {
console.log(`\n🔄 第 ${attempt} 次重试上传...`);
await sleep(RETRY_DELAY_MS);
}
return await ci.upload({
project,
version: args.version,
desc: args.desc,
robot: CONFIG.robot,
setting: { es6: true, es7: true, minify: true, autoPrefixWXSS: true },
onProgressUpdate: (info) => { if (typeof info === 'string') console.log(` ${info}`); },
});
} catch (err) {
const errMsg = err.message || String(err);
// 超时的 err.message 可能是 "timeout"、"undefined" 或空字符串
const isTimeout = errMsg === 'timeout' || errMsg === 'undefined' || !errMsg;
if (isTimeout && attempt < MAX_RETRIES) {
console.warn(`\n⚠️ 上传超时(第 ${attempt}/${MAX_RETRIES} 次),${RETRY_DELAY_MS / 1000}s 后重试...`);
continue;
}
throw err;
}
}
}
function saveResult(result, args) {
ensureDir(CONFIG.outputDir);
const filename = `upload-${args.version}-${timestamp()}.json`;
const filepath = path.join(CONFIG.outputDir, filename);
const data = {
timestamp: new Date().toISOString(),
version: args.version,
desc: args.desc,
robot: CONFIG.robot,
result,
};
fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
console.log(`📄 结果已保存: ${filepath}`);
}
// ─────────────────────────────────────────────────────────────────────────────
// 主流程
// ─────────────────────────────────────────────────────────────────────────────
async function main() {
const args = parseArgs();
if (!args.version) { console.error('❌ 必须指定 --version'); process.exit(1); }
if (!args.desc) { console.error('❌ 必须指定 --desc'); process.exit(1); }
console.log('🔍 校验配置...');
validateConfig();
console.log('\n📋 上传配置:');
console.log(` AppID: ${CONFIG.appid}`);
console.log(` 项目路径: ${path.resolve(CONFIG.projectPath)}`);
console.log(` 机器人编号: ${CONFIG.robot}`);
console.log(` 版本号: ${args.version}`);
console.log(` 版本描述: ${args.desc}`);
console.log(` packNpm: ${args.packNpm ? '是' : '否'}`);
const project = new ci.Project({
appid: CONFIG.appid,
type: 'miniProgram',
projectPath: path.resolve(CONFIG.projectPath),
privateKeyPath: path.resolve(CONFIG.privateKeyPath),
ignores: ['node_modules/**/*'],
});
if (args.packNpm) {
console.log('\n📦 执行 npm 构建...');
try {
await ci.packNpm(project, { reporter: console.log });
console.log('✅ npm 构建完成');
} catch (err) {
console.error(`❌ npm 构建失败: ${err.message}`);
process.exit(1);
}
}
console.log('\n🚀 上传代码...');
try {
const result = await uploadWithRetry(project, args);
console.log('\n✅ 上传成功!');
if (result?.subPackageInfo) {
console.log('\n📦 包大小:');
result.subPackageInfo.forEach(p => console.log(` ${p.name || '主包'}: ${(p.size / 1024 / 1024).toFixed(2)} MB`));
}
saveResult({ success: true, ...result }, args);
} catch (err) {
console.error(`\n❌ 上传失败: ${err.message}`);
if (err.message.includes('invalid ip')) console.error('💡 请将当前 IP 添加到微信后台白名单');
saveResult({ success: false, error: err.message }, args);
process.exit(1);
}
}
main();
Step 4: Register npm scripts
In package.json of scripts add (according to the operation selected by the user):
{
"scripts": {
"ci:pack-npm": "node scripts/pack-npm.js",
"ci:preview": "node scripts/preview.js",
"ci:upload": "node scripts/upload.js",
"ci:upload:npm": "node scripts/upload.js --pack-npm"
}
}
Step 5: Environment Variable Configuration Guide
Local Development
Create the .env file (requires dotenv or shell source):
MP_APPID=wx1234567890abcdef
MP_PRIVATE_KEY_PATH=./private.wxXXXX.key
MP_PROJECT_PATH=./dist
MP_ROBOT=1
Remind the user: Add .env and *.key to .gitignore。
CI/CD Configuration (GitHub Actions Example)
Two versions are provided below, choose according to your package manager:
npm / yarn project (concise version)
name: Deploy Mini Program
on:
push:
branches:
- main
workflow_dispatch:
jobs:
upload:
runs-on: ubuntu-latest
# ⚠️ 创建 GitHub Release 必须声明,否则报 HTTP 403
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Generate version
id: version
run: echo "version=$(date +%Y%m%d).$(git rev-parse HEAD | cut -c1-6)" >> "$GITHUB_OUTPUT"
- name: Write private key
run: |
echo "${{ secrets.MP_PRIVATE_KEY }}" > private.key
chmod 600 private.key
- name: Upload to WeChat
env:
MP_APPID: ${{ secrets.MP_APPID }}
MP_PRIVATE_KEY_PATH: ./private.key
MP_PROJECT_PATH: ./dist
MP_ROBOT: 1
run: npm run ci:upload -- --version "${{ steps.version.outputs.version }}" --desc "CI 自动上传"
- name: Cleanup
if: always()
run: rm -f private.key
pnpm project (full version, including retry and Release)
This template has been verified in a real project (pnpm + Taro) and includes solutions to all pitfalls.
name: WeChat Mini Program CI
on:
push:
branches:
- main
# 支持手动触发并自定义描述
workflow_dispatch:
inputs:
desc:
description: '版本描述(留空则自动生成)'
required: false
default: ''
jobs:
deploy:
name: 测试 → 构建 → 上传微信后台
runs-on: ubuntu-latest
# ⚠️ 必须声明,否则创建 GitHub Release 会报 HTTP 403
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
# ⚠️ pnpm 必须先于 setup-node 安装,否则 cache: 'pnpm' 会报错
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
# ⚠️ 需要先提交 pnpm-lock.yaml,否则 --frozen-lockfile 会失败
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm test
- name: Generate version
id: version
run: |
DATE=$(date +%Y%m%d)
COMMIT=$(git rev-parse HEAD | cut -c1-6)
VERSION="${DATE}.${COMMIT}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Generated version: ${VERSION}"
- name: Build mini program
run: pnpm build:weapp
- name: Write private key
run: |
echo "${{ secrets.MP_PRIVATE_KEY }}" > private.key
chmod 600 private.key
- name: Upload to WeChat
env:
MP_APPID: ${{ secrets.MP_APPID }}
MP_PRIVATE_KEY_PATH: ./private.key
MP_PROJECT_PATH: ./dist
MP_ROBOT: 1
run: |
VERSION="${{ steps.version.outputs.version }}"
DESC="${{ github.event.inputs.desc }}"
if [ -z "$DESC" ]; then DESC="CI 自动发布 ${VERSION}"; fi
pnpm ci:upload -- --version "$VERSION" --desc "$DESC"
- name: Cleanup private key
if: always()
run: rm -f private.key
- name: Create GitHub Release
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.version.outputs.version }}"
COMMIT_MSG=$(git log -1 --pretty=format:"%s")
gh release create "v${VERSION}" \
--title "v${VERSION}" \
--notes "## 发布内容
- 版本号: \`${VERSION}\`
- 提交信息: ${COMMIT_MSG}
- 提交哈希: \`${{ github.sha }}\`
> 已自动上传至微信小程序后台,请前往微信公众平台提交审核。" \
--latest
GitHub Secrets configuration:
| Secret name | Value | Description |
|---|---|---|
MP_PRIVATE_KEY |
Full contents of the key file | cat private.wxXXXX.key output |
MP_APPID |
wxXXXXXXXXXXXXXXXX |
Mini Program AppID |
Common Error Handling
| Error symptom | Cause | Solution |
|---|---|---|
invalid ip |
IP is not in the whitelist | Add the IP in the WeChat backend or temporarily disable the whitelist |
permission denied |
The key is invalid or has no permission | Regenerate the key; check whether it has upload permission |
project.config.json not found |
Incorrect project path | Confirm that MP_PROJECT_PATH points to the build output directory |
Error: getaddrinfo ENOTFOUND |
Network issue | Check the proxy settings or network connection |
| The version does not appear after upload | Robot number conflict | Use different robot numbers for different tasks |
Cannot find module 'picocolors'(or nanoid/non-secure) |
pnpm strict dependency isolation causes the internal dependency chain of miniprogram-ci to break | See "Special Configuration for pnpm Projects" below |
Upload failed: undefined / timeout |
Cross-border network between the GitHub Actions runner and the WeChat upload server is unstable (60s timeout) | In upload.js add retry logic, see the template below |
| Failed to create Release: HTTP 403 | By default, GitHub Actions GITHUB_TOKEN has no contents: write permission |
Explicitly declare in the job permissions: contents: write |
Special Configuration for pnpm Projects
pnpm enables strict dependency isolation by default (symlink node_modules), which causes miniprogram-ci internal dependencies (such as picocolors、nanoid/non-secure、cssnano) cannot be resolved correctly, even if they have been installed indirectly.
Solution: Create (or modify) in the project root directory.npmrc, add:
shamefully-hoist=true
Thenthe lockfile must be regenerated for it to take effect (modifying only .npmrc is not enough):
rm pnpm-lock.yaml
CI=true pnpm install
Note: This configuration hoists all packages to the root
node_modules, behaving like npm/yarn. If you are worried about affecting other dependencies, you can use the more fine-grainedpublic-hoist-pattern[]configuration, but in practice miniprogram-ci requires a large number of entries, so it is better to just useshamefully-hoist=true。
Script parameter quick reference
preview.js
| Environment variables | Required | Description |
|---|---|---|
MP_APPID |
✅ | Mini Program AppID |
MP_PRIVATE_KEY_PATH |
✅ | Key file path |
MP_PROJECT_PATH |
✅ | Build output directory |
MP_ROBOT |
❌ | Robot number (default 1) |
MP_PAGE_PATH |
❌ | Page opened in preview |
MP_SEARCH_QUERY |
❌ | Page query parameters |
upload.js
| Parameter | Required | Description |
|---|---|---|
--version <v> |
✅ | Version number |
--desc <d> |
✅ | Version description |
--pack-npm |
❌ | Run npm build before upload |
Security checklist
Before delivering the script, remind the user to confirm:
-
*.keyand.envhave been added to.gitignore - IP whitelist is enabled in the production environment (or CI uses a self-hosted runner with a fixed egress IP)
- In CI/CD, secrets are managed via secrets rather than in plaintext
-
ci-artifacts/directory has been added to.gitignore(if it contains sensitive logs) - pnpm project:
.npmrchas addedshamefully-hoist=trueand regenerated the lockfile (to resolve missing internal dependencies of miniprogram-ci) - GitHub Actions workflow has declared
permissions: contents: write(if a Release needs to be created) -
upload.jsincludes timeout retry logic (to handle unstable cross-border networks; on timeout, err.message may be"timeout"、"undefined"or an empty string)