跳到主要内容

CI/CD

本项目使用 GitLab CI/CD 自动化构建、测试、质量检查和上传流程。

Pipeline 总览​

整个 Pipeline 由 6 个阶段顺序执行:

阶段的顺序是刻意设计的:越早的阶段越轻量,发现问题的成本越低。

各阶段说明​

1. install — 安装依赖​

Job说明
setup使用 npm ci 安装项目依赖,并将 ~/.npm 和 node_modules/ 写入缓存
# .gitlab-ci.yml
install:
stage: install
image: node:lts
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
script:
- npm ci --cache .npm --prefer-offline
artifacts:
paths:
- node_modules/

2. lint — 代码规范检查​

Job工具说明
eslintESLint(Airbnb 规范)检查 JavaScript / TypeScript 代码规范
stylelintstylelint(twbs-bootstrap 规范)检查 WXSS / SCSS 样式代码规范
lint:
stage: lint
image: node:lts
script:
- npm run lint
- npm run lint:style

lint 失败时 Pipeline 中止,不会进入后续阶段。

提示

Lint 规范与提交前的 husky + lint-staged 钩子保持一致,本地提交和 CI 两道关卡用同一套规则。

3. test — 测试​

Job工具触发条件
unit testsJest + miniprogram-simulate每次 Pipeline
coverageJest(coverage 模式)MR 和默认分支
test:
stage: test
image: node:lts
script:
- npm run test
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml

详见 测试.

4. build — 构建​

Job说明
build通过微信开发者工具 CLI 构建 npm,产物输出到 miniprogram_npm/
build:
stage: build
image: node:lts
script:
# 构建小程序 npm
- npm run build:npm
artifacts:
paths:
- miniprogram_npm/
- dist/
注意

小程序的"构建 npm"是将 node_modules 中的包构建到 miniprogram_npm/ 目录,与 Web 的打包构建不同。详见 npm 支持。

5. upload — 上传​

使用 miniprogram-ci 上传代码到微信服务器。

upload:
stage: upload
image: node:lts
rules:
- if: $CI_COMMIT_BRANCH == "develop"
variables:
VERSION: "体验版"
- if: $CI_COMMIT_BRANCH == "main"
variables:
VERSION: "正式版"
script:
- node scripts/upload.mjs --version="$VERSION"

上传脚本​

// scripts/upload.mjs
import ci from 'miniprogram-ci';
import path from 'node:path';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const args = process.argv.slice(2);
const versionArg = args.find((a) => a.startsWith('--version='));
const version = versionArg?.split('=')[1] || '体验版';

const { version: pkgVersion } = JSON.parse(
fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'),
);

const project = new ci.Project({
appid: process.env.WX_APPID,
type: 'miniProgram',
projectPath: path.resolve(__dirname, '../'),
privateKey: process.env.WX_PRIVATE_KEY,
ignores: ['node_modules/**/*'],
});

try {
const uploadResult = await ci.upload({
project,
version: pkgVersion,
desc: `${version} - ${new Date().toLocaleString()}`,
setting: {
es6: true,
es7: true,
minify: true,
autoPrefixWXSS: true,
},
onProgressUpdate: console.log,
});

console.log('上传成功', uploadResult);
} catch (error) {
console.error('上传失败', error);
process.exit(1);
}
提示

脚本使用 ESM 语法,文件后缀为 .mjs,无需在 package.json 中设置 "type": "module"(避免影响小程序运行时的 CommonJS 模块解析)。Node LTS 原生支持 ESM 与顶层 await。

CI 密钥配置​

上传脚本中的 WX_APPID 与 WX_PRIVATE_KEY 均来自 GitLab CI/CD 变量(Settings → CI/CD → Variables),无需在脚本中硬编码,也无需将密钥写入文件:

变量说明掩码/保护
WX_APPID小程序 AppID可不掩码
WX_PRIVATE_KEY上传密钥的完整内容(从微信公众平台下载的 .key 文件内容)掩码 + 仅保护分支

脚本通过 privateKey: process.env.WX_PRIVATE_KEY 直接将密钥内容传给 miniprogram-ci,无需 privateKeyPath,也不必在 CI 中 echo 写盘。

安全

上传密钥拥有预览、上传代码的权限,严禁提交到代码仓库。务必在 GitLab 中将 WX_PRIVATE_KEY 标记为 Masked,并限制仅在 develop/main 分支可用(Protected)。同时建议在微信公众平台配置上传白名单 IP(CI Runner 出口 IP)。

6. notify — 通知​

notify:
stage: notify
script:
- echo "Pipeline 完成"
after_script:
- |
if [ $CI_PIPELINE_STATUS = "success" ]; then
curl -X POST "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=$WECOM_BOT_KEY" \
-H "Content-Type: application/json" \
-d "{\"msgtype\":\"markdown\",\"markdown\":{\"content\":\"✅ 小程序上传成功\n> 分支: $CI_COMMIT_BRANCH\n> 版本: $VERSION\"}}"
fi

完整 .gitlab-ci.yml​

stages:
- install
- lint
- test
- build
- upload
- notify

variables:
NODE_ENV: production

install:
stage: install
image: node:lts
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
script:
- npm ci
artifacts:
paths:
- node_modules/

lint:
stage: lint
image: node:lts
needs: [install]
script:
- npm run lint
- npm run lint:style

test:
stage: test
image: node:lts
needs: [install]
script:
- npm run test
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml

build:
stage: build
image: node:lts
needs: [install]
script:
- npm run build:npm
artifacts:
paths:
- miniprogram_npm/

upload:develop:
stage: upload
image: node:lts
needs: [lint, test, build]
rules:
- if: $CI_COMMIT_BRANCH == "develop"
script:
- node scripts/upload.mjs --version="体验版"

upload:main:
stage: upload
image: node:lts
needs: [lint, test, build]
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- node scripts/upload.mjs --version="正式版"

notify:
stage: notify
needs: [upload:develop, upload:main]
rules:
- if: $CI_COMMIT_BRANCH == "develop" || $CI_COMMIT_BRANCH == "main"
script:
- echo "通知完成"

体验版与正式版​

版本触发分支用途上传后操作
体验版develop内部测试自动生成体验版二维码
正式版main用户发布需在微信后台提交审核
提示

正式版上传后不会自动发布,需在微信公众平台 → 版本管理 → 提交审核,审核通过后手动发布。

参考资料​