Przeglądaj źródła

feat: 添加 Claude Skills 和微信小程序发布脚本

- 添加 miniprogram-ci Skill:微信小程序 CI/CD 自动化工具
- 添加 skill-creator Skill:用于创建新 Skill 的工具
- 添加 weapp-publish Skill:专门用于发布企业小程序和人才小程序
- 添加 publish-weapp.js 脚本:自动化构建和发布微信小程序
- 添加发布快捷命令:publish:enterprise, publish:talent 等

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 2 dni temu
rodzic
commit
6aa12ca263

+ 285 - 0
.claude/skills/miniprogram-ci/SKILL.md

@@ -0,0 +1,285 @@
+---
+name: miniprogram-ci
+description: 微信小程序 CI/CD 自动化工具。用于小程序代码上传、预览、构建 npm、代码质量检查等自动化操作。当用户需要自动化微信小程序部署流程时应使用此技能。
+license: MIT
+---
+
+# miniprogram-ci Skill
+
+miniprogram-ci 是从微信开发者工具中抽离的关于小程序/小游戏项目代码的编译模块,提供 CI/CD 自动化能力。
+
+## 核心功能
+
+| 功能 | 说明 |
+|------|------|
+| **上传代码** | 对应小程序开发者工具的上传功能 |
+| **预览代码** | 生成预览二维码,支持多种格式 |
+| **构建 npm** | 对应开发者工具"菜单-工具-构建 npm" |
+| **上传云函数** | 上传云开发云函数代码 |
+| **代码质量检查** | 检查包大小、代码压缩、未使用组件等 |
+| **获取 sourceMap** | 支持获取最近上传版本的 sourceMap |
+
+---
+
+## 使用前准备
+
+### 1. 获取上传密钥
+
+使用小程序管理员身份访问"微信公众平台 > 开发 > 开发设置",下载代码上传密钥。
+
+### 2. 配置 IP 白名单
+
+未配置 IP 白名单的,将无法使用 miniprogram-ci 进行预览和上传。
+
+### 3. 安装依赖
+
+```bash
+# 项目依赖
+npm install miniprogram-ci --save
+
+# 或全局安装(命令行使用)
+npm install -g miniprogram-ci
+```
+
+---
+
+## Node.js API 使用
+
+### 1. 创建项目实例
+
+```javascript
+const ci = require('miniprogram-ci')
+
+const project = new ci.Project({
+  appid: 'wxsomeappid',                    // 必填:小程序 appid
+  type: 'miniProgram',                     // 项目类型
+  projectPath: 'the/project/path',         // 必填:项目路径
+  privateKeyPath: 'the/privatekey/path',   // 密钥路径(与 privateKey 二选一)
+  privateKey: fs.readFileSync('...'),      // 密钥内容(与 privateKeyPath 二选一)
+  ignores: ['node_modules/**/*'],          // 排除规则
+})
+```
+
+**项目类型有效值:**
+- `miniProgram` - 小程序(默认)
+- `miniProgramPlugin` - 小程序插件
+- `miniGame` - 小游戏
+- `miniGamePlugin` - 小游戏插件
+
+### 2. 上传代码
+
+```javascript
+const uploadResult = await ci.upload({
+  project,
+  version: '1.1.1',              // 必填:自定义版本号
+  desc: 'update features',       // 可选:自定义备注
+  setting: {
+    useProjectConfig: true,      // 使用 project.config.json 配置
+    es7: true,                   // 将 JS 编译为 ES5
+    minify: true,                // 压缩所有代码
+    codeProtect: false,          // 代码保护
+    autoPrefixWXSS: true,        // 样式自动补全
+  },
+  onProgressUpdate: console.log, // 进度回调
+  robot: 1,                      // CI 机器人编号 (1-30)
+})
+```
+
+### 3. 预览代码
+
+```javascript
+const previewResult = await ci.preview({
+  project,
+  desc: 'preview test',
+  setting: { useProjectConfig: true },
+  qrcodeFormat: 'image',         // 'image' | 'base64' | 'terminal'
+  qrcodeOutputDest: '/path/to/qrcode.jpg',
+  onProgressUpdate: console.log,
+  pagePath: 'pages/index/index', // 预览页面路径
+  searchQuery: 'a=1&b=2',         // 预览参数
+  scene: 1011,                    // 场景值
+})
+```
+
+### 4. 构建 npm
+
+```javascript
+const warning = await ci.packNpm(project, {
+  ignores: ['pack_npm_ignore_list'],
+  reporter: (infos) => { console.log(infos) }
+})
+```
+
+### 5. 代码质量检查
+
+```javascript
+const res = await ci.checkCodeQuality(project)
+// 检查项包括:
+// - PACKAGE_SIZE_LIMIT - 包大小限制
+// - JS_COMPRESS_OPEN - JS 压缩
+// - WXML_COMPRESS_OPEN - WXML 压缩
+// - WXSS_COMPRESS_OPEN - WXSS 压缩
+// - CONTAINS_UNUSED_CODES - 无依赖文件检查
+// - CONTAINS_UNUSED_COMPONENTS - 未使用组件检查
+// - CONTAINS_OTHER_PKG_JS - 主包中仅被分包依赖的 JS
+```
+
+### 6. 获取编译结果
+
+```javascript
+const compiledResult = await ci.getCompiledResult({
+  project,
+  desc: 'compile test',
+  setting: { useProjectConfig: true },
+}, '/path/to/save.zip')  // 编译结果 zip 保存路径
+```
+
+---
+
+## 命令行使用
+
+### 上传代码
+```bash
+miniprogram-ci upload \
+  --pp ./mini/ \
+  --pkp ./private.YOUR_APPID.key \
+  --appid YOUR_APPID \
+  --uv 1.0.0 \
+  --ud "描述信息" \
+  -r 1 \
+  --use-project-config true
+```
+
+### 预览代码
+```bash
+miniprogram-ci preview \
+  --pp ./mini/ \
+  --pkp ./private.YOUR_APPID.key \
+  --appid YOUR_APPID \
+  --uv 1.0.0 \
+  -r 1 \
+  --use-project-config true \
+  --qrcode-format image \
+  --qrcode-output-dest './qrcode.jpg'
+```
+
+### 构建 npm
+```bash
+miniprogram-ci pack-npm \
+  --pp ./mini/ \
+  --pkp ./private.YOUR_APPID.key \
+  --appid YOUR_APPID
+```
+
+### 代码质量检查
+```bash
+miniprogram-ci check-code-quality \
+  --pp ./mini/ \
+  --pkp ./private.YOUR_APPID.key \
+  --appid YOUR_APPID \
+  --save-path ./report.json
+```
+
+---
+
+## 参数说明
+
+| 参数 | 说明 |
+|------|------|
+| `--pp` | projectPath,项目路径 |
+| `--pkp` | privateKeyPath,私钥路径 |
+| `--appid` | 小程序 appid |
+| `--uv` | uploadVersion,上传版本号 |
+| `--ud` | uploadDesc,上传描述 |
+| `-r` | robot,机器人编号 (1-30) |
+| `--use-project-config` | 是否使用 project.config.json 中的配置 |
+
+---
+
+## 编译设置 (setting)
+
+| 配置项 | 类型 | 默认值 | 说明 |
+|--------|------|--------|------|
+| `useProjectConfig` | boolean | false | 使用 project.config.json 中的设置 |
+| `es7` | boolean | - | 将 JS 编译为 ES5 |
+| `disableUseStrict` | boolean | - | 禁用 JS 严格模式 |
+| `minifyJS` | boolean | - | 压缩 JS 代码 |
+| `minifyWXML` | boolean | - | 压缩 WXML 代码 |
+| `minifyWXSS` | boolean | - | 压缩 WXSS 代码 |
+| `minify` | boolean | - | 压缩所有代码 |
+| `codeProtect` | boolean | - | 代码保护 |
+| `autoPrefixWXSS` | boolean | - | 样式自动补全 |
+| `compileWorklet` | boolean | - | 编译 worklet 代码 |
+
+---
+
+## 注意事项
+
+1. 代码上传密钥拥有预览、上传代码的权限,请妥善保管
+2. 密钥不会明文存储在微信公众平台上,一旦遗失必须重置
+3. 未配置 IP 白名单的,将无法使用 miniprogram-ci 进行预览和上传
+4. 从 1.0.28 开始支持第三方平台开发的上传和预览
+
+---
+
+## 本项目集成示例
+
+对于当前项目(企业小程序 `mini` 和人才小程序 `mini-talent`),可以使用以下脚本:
+
+```javascript
+// scripts/ci/upload-wechat-mini.js
+const ci = require('miniprogram-ci')
+const path = require('path')
+
+async function upload(options) {
+  const { appid, projectPath, version, desc, robot = 1 } = options
+
+  const project = new ci.Project({
+    appid,
+    type: 'miniProgram',
+    projectPath: path.resolve(__dirname, '../../', projectPath),
+    privateKeyPath: path.resolve(__dirname, '../../keys/private.${appid}.key'),
+    ignores: ['node_modules/**/*'],
+  })
+
+  const result = await ci.upload({
+    project,
+    version,
+    desc,
+    setting: {
+      useProjectConfig: true,
+      minify: true,
+    },
+    onProgressUpdate: (progress) => {
+      console.log(`上传进度: ${progress}%`)
+    },
+    robot,
+  })
+
+  console.log('上传成功:', result)
+}
+
+// 企业小程序
+upload({
+  appid: 'YOUR_ENTERPRISE_APPID',
+  projectPath: 'mini',
+  version: process.env.VERSION || '1.0.0',
+  desc: process.env.DESC || '自动化构建',
+})
+
+// 人才小程序
+upload({
+  appid: 'YOUR_TALENT_APPID',
+  projectPath: 'mini-talent',
+  version: process.env.VERSION || '1.0.0',
+  desc: process.env.DESC || '自动化构建',
+})
+```
+
+---
+
+## 官方资源
+
+- **NPM 包**: https://www.npmjs.com/package/miniprogram-ci
+- **官方 GitHub Action**: https://github.com/marketplace/actions/action-miniprogram-ci
+- **最新版本**: 2.0.10

+ 446 - 0
.claude/skills/skill-creator/SKILL.md

@@ -0,0 +1,446 @@
+---
+name: skill-creator
+description: Skill 编写指南和模板生成工具。用于创建新的 Claude Code Skill,提供完整的编写指南、最佳实践、文件结构说明、YAML frontmatter 配置、模板示例和常见问题解答。
+license: MIT
+version: 1.0.0
+author: Claude Code
+tags: [skill, template, guide, documentation]
+---
+
+# skill-creator Skill
+
+本 Skill 提供完整的 Skill 编写指南和模板,帮助快速创建高质量、规范的 Claude Code Skill。
+
+---
+
+## 目录
+
+1. [Skill 概述](#skill-概述)
+2. [文件结构](#文件结构)
+3. [YAML Frontmatter 配置](#yaml-frontmatter-配置)
+4. [编写指南](#编写指南)
+5. [最佳实践](#最佳实践)
+6. [完整模板](#完整模板)
+7. [常见问题](#常见问题)
+
+---
+
+## Skill 概述
+
+### 什么是 Skill
+
+Skill 是 Claude Code 的可扩展功能模块,通过定义特定的技能集,让 AI 代理能够执行特定领域的任务。
+
+### Skill 的特点
+
+| 特点 | 说明 |
+|------|------|
+| **模块化** | 每个 Skill 是独立的功能单元 |
+| **可复用** | 可被多个代理和场景调用 |
+| **渐进加载** | 只在需要时加载,避免干扰普通对话 |
+| **元数据驱动** | 通过 YAML frontmatter 定义行为 |
+
+---
+
+## 文件结构
+
+### 标准目录结构
+
+```
+.claude/skills/<skill-name>/
+├── SKILL.md           # 必需:Skill 定义文件(主要入口)
+├── references/        # 可选:参考文档目录
+│   ├── doc1.md
+│   └── doc2.md
+└── assets/           # 可选:静态资源(图片、配置文件等)
+    ├── config.json
+    └── diagram.png
+```
+
+### 文件说明
+
+| 文件/目录 | 必需 | 说明 |
+|-----------|------|------|
+| `SKILL.md` | 是 | Skill 主定义文件,包含 frontmatter 和内容 |
+| `references/` | 否 | 额外参考文档,加载时一并读取 |
+| `assets/` | 否 | 静态资源文件 |
+
+---
+
+## YAML Frontmatter 配置
+
+### 必需字段
+
+```yaml
+---
+name: my-skill
+description: 简短描述此 Skill 的功能
+---
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `name` | string | Skill 唯一标识符(kebab-case) |
+| `description` | string | 简短描述,用于触发决策 |
+
+### 可选字段
+
+```yaml
+---
+name: my-skill
+description: 简短描述此 Skill 的功能
+license: MIT
+version: 1.0.0
+author: Your Name
+tags: [category1, category2]
+parameters:
+  - name: input
+    type: string
+    required: true
+    description: 输入参数说明
+---
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `license` | string | 许可证类型(如 MIT、Apache-2.0) |
+| `version` | string | 版本号(语义化版本) |
+| `author` | string | 作者信息 |
+| `tags` | array | 分类标签,便于检索和分组 |
+| `parameters` | array | 参数定义列表 |
+
+### 参数定义结构
+
+```yaml
+parameters:
+  - name: parameterName      # 参数名
+    type: string             # 类型: string, number, boolean, array, object
+    required: true           # 是否必需
+    description: 参数说明     # 描述
+    default: "default_value" # 默认值(可选)
+```
+
+---
+
+## 编写指南
+
+### 1. 描述字段写作规范
+
+`description` 字段是 Skill 触发决策的关键依据:
+
+```yaml
+# 好的描述
+description: 数据库迁移工具。用于执行 schema 变更、数据迁移、回滚操作等。
+
+# 不好的描述
+description: 数据库工具
+```
+
+**原则:**
+- 明确说明功能领域
+- 列出主要使用场景
+- 包含触发关键词
+- 长度适中(50-100 字)
+
+### 2. 内容组织结构
+
+建议按以下结构组织 Skill 内容:
+
+```markdown
+# Skill 名称
+
+简要介绍(1-2 段)
+
+## 核心功能
+功能列表或表格
+
+## 使用场景
+何时使用此 Skill
+
+## 快速开始
+最简使用示例
+
+## 详细说明
+分章节详细说明
+
+## 注意事项
+重要提醒
+
+## 参考资源
+外部链接
+```
+
+### 3. 代码示例规范
+
+```markdown
+### 示例:基本用法
+
+\`\`\`javascript
+const result = await myFunction({
+  option1: 'value1',
+  option2: 'value2',
+})
+\`\`\`
+
+**参数说明:**
+- `option1`: 参数1的说明
+- `option2`: 参数2的说明
+```
+
+**原则:**
+- 代码块必须指定语言
+- 关键参数添加注释
+- 复杂逻辑提供文字说明
+- 示例应可直接运行
+
+---
+
+## 最佳实践
+
+### 1. 命名规范
+
+| 元素 | 规范 | 示例 |
+|------|------|------|
+| 目录名 | kebab-case | `database-migrator` |
+| Skill 名 | kebab-case | `name: database-migrator` |
+| 参数名 | camelCase | `connectionString` |
+
+### 2. 渐进式加载原则
+
+Skill 应设计为"按需加载",避免对普通对话造成干扰:
+
+```yaml
+# 描述中明确触发场景
+description: 数据库迁移工具。当用户需要执行 schema 变更、数据迁移时使用。
+
+# 添加使用场景章节
+## 使用场景
+- 执行数据库 schema 迁移
+- 回滚迁移
+- 查看迁移历史
+```
+
+### 3. 内容分层
+
+- **第一层(概览)**:是什么、为什么用、核心功能
+- **第二层(示例)**:快速开始、常见用法
+- **第三层(详解)**:参数说明、高级用法
+- **第四层(参考)**:API 文档、外部资源
+
+### 4. 可维护性
+
+- 使用表格展示结构化数据
+- 代码示例保持最新
+- 添加版本更新日志
+- 预留 references 目录存放补充文档
+
+---
+
+## 完整模板
+
+以下是一个完整的 Skill 模板,可以直接复制使用:
+
+```markdown
+---
+name: my-skill
+description: 简短描述此 Skill 的功能。明确说明使用场景,以便系统正确触发调用。
+license: MIT
+version: 1.0.0
+author: Your Name
+tags: [category1, category2]
+parameters:
+  - name: input
+    type: string
+    required: true
+    description: 输入参数说明
+  - name: options
+    type: object
+    required: false
+    description: 可选配置项
+---
+
+# my-skill Skill
+
+简要介绍此 Skill 的用途和价值(1-2 段话)。
+
+---
+
+## 核心功能
+
+| 功能 | 说明 |
+|------|------|
+| **功能1** | 功能1的描述 |
+| **功能2** | 功能2的描述 |
+| **功能3** | 功能3的描述 |
+
+---
+
+## 使用场景
+
+- 场景1:明确说明何时使用
+- 场景2:明确说明何时使用
+- 场景3:明确说明何时使用
+
+---
+
+## 快速开始
+
+### 最简示例
+
+\`\`\`javascript
+// 引入模块
+const myModule = require('my-module')
+
+// 基本使用
+const result = myModule({
+  input: 'value'
+})
+\`\`\`
+
+---
+
+## 详细说明
+
+### 参数配置
+
+| 参数 | 类型 | 必需 | 默认值 | 说明 |
+|------|------|------|--------|------|
+| `input` | string | 是 | - | 输入值 |
+| `options` | object | 否 | `{}` | 配置选项 |
+
+### 配置选项 (options)
+
+| 选项 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `option1` | boolean | `true` | 选项1说明 |
+| `option2` | number | `100` | 选项2说明 |
+
+---
+
+## 高级用法
+
+### 场景1:复杂配置
+
+\`\`\`javascript
+const result = myModule({
+  input: 'value',
+  options: {
+    option1: true,
+    option2: 200
+  }
+})
+\`\`\`
+
+### 场景2:批量处理
+
+\`\`\`javascript
+const items = ['item1', 'item2', 'item3']
+const results = await Promise.all(
+  items.map(item => myModule({ input: item }))
+)
+\`\`\`
+
+---
+
+## 注意事项
+
+1. 注意事项第一条
+2. 注意事项第二条
+3. 注意事项第三条
+
+---
+
+## 项目集成示例
+
+\`\`\`javascript
+// 项目中的实际使用示例
+const { mySkill } = require('./skills/my-skill')
+
+async function handleUserRequest(data) {
+  return mySkill({
+    input: data.input,
+    options: {
+      ...data.options
+    }
+  })
+}
+\`\`\`
+
+---
+
+## 参考资源
+
+- **官方文档**: https://example.com/docs
+- **API 参考**: https://example.com/api
+- **示例代码**: https://github.com/example/examples
+
+---
+
+## 更新日志
+
+### 1.0.0 (2024-01-01)
+- 初始版本发布
+- 支持基础功能
+```
+
+---
+
+## 常见问题
+
+### Q1: Skill 什么时候会被触发?
+
+A: 系统会分析用户的请求内容,与 Skill 的 `name` 和 `description` 进行匹配。确保描述中包含清晰的关键词和使用场景。
+
+### Q2: 一个文件可以定义多个 Skill 吗?
+
+A: 不可以。每个 SKILL.md 文件只能定义一个 Skill。多个 Skill 需要创建多个目录。
+
+### Q3: references 目录中的文档如何使用?
+
+A: references 目录中的所有 .md 文件会在 Skill 加载时一并读取,适合存放详细的技术文档、API 说明等补充材料。
+
+### Q4: 如何处理 Skill 版本更新?
+
+A: 在 YAML frontmatter 中更新 `version` 字段,并在内容中添加更新日志章节。
+
+### Q5: Skill 可以调用其他 Skill 吗?
+
+A: 可以。在 Skill 内容中可以引用其他 Skill,但要注意避免循环依赖。
+
+### Q6: 参数定义是必需的吗?
+
+A: 不是必需的。但如果 Skill 有明确的输入参数,建议在 YAML 中定义,便于系统理解和验证。
+
+---
+
+## 检查清单
+
+发布 Skill 前请确认:
+
+- [ ] 目录命名符合 kebab-case 规范
+- [ ] SKILL.md 文件位于正确位置
+- [ ] YAML frontmatter 包含必需字段(name, description)
+- [ ] description 清晰描述功能和使用场景
+- [ ] 代码示例可运行且有语言标注
+- [ ] 表格格式正确
+- [ ] 所有链接有效
+- [ ] 版本号已更新
+
+---
+
+## 现有 Skill 参考
+
+项目中已有的 Skill 可以作为参考:
+
+- **miniprogram-ci**: 微信小程序 CI/CD 自动化
+- **command**: 命令执行工具
+
+查看现有 Skill 的最佳方式:
+
+```bash
+# 查看所有 Skill
+ls -la .claude/skills/
+
+# 查看特定 Skill
+cat .claude/skills/<skill-name>/SKILL.md
+```

+ 355 - 0
.claude/skills/weapp-publish/SKILL.md

@@ -0,0 +1,355 @@
+---
+name: weapp-publish
+description: 微信小程序发布工具。用于发布企业小程序和人才小程序的开发版和体验版,支持自动化构建、上传和预览流程。
+license: MIT
+---
+
+# 微信小程序发布 Skill
+
+本 Skill 专门用于本项目的企业小程序和人才小程序的自动化发布流程。
+
+---
+
+## 支持的小程序
+
+| 小程序 | 目录 | AppID | 说明 |
+|--------|------|-------|------|
+| **企业小程序** | `mini/` | `wx1e791ed2e0229eb8` | 用人方小程序(企业管理端) |
+| **人才小程序** | `mini-talent/` | `wx3c47dbce1ea7d43c` | 人才小程序(求职者端) |
+
+---
+
+## 发布前准备
+
+### 1. 安装 miniprogram-ci
+
+```bash
+pnpm add -D miniprogram-ci
+# 或全局安装
+pnpm add -g miniprogram-ci
+```
+
+### 2. 获取上传密钥
+
+使用小程序管理员身份访问"微信公众平台 > 开发 > 开发设置",下载代码上传密钥。
+
+**企业小程序密钥文件**: `keys/private.wx1e791ed2e0229eb8.key`
+**人才小程序密钥文件**: `keys/private.wx3c47dbce1ea7d43c.key`
+
+### 3. 配置 IP 白名单
+
+在微信公众平台配置服务器的 IP 白名单。
+
+---
+
+## 发布流程
+
+### 完整发布步骤
+
+1. **构建项目**: 运行 Taro 构建命令生成小程序代码
+2. **上传代码**: 使用 miniprogram-ci 上传到微信服务器
+3. **设置体验版**(可选): 在小程序后台将上传的版本设置为体验版
+
+### 发布命令
+
+```bash
+# 企业小程序 - 开发版上传
+pnpm run publish:enterprise:dev
+
+# 企业小程序 - 生产版上传
+pnpm run publish:enterprise:prod
+
+# 人才小程序 - 开发版上传
+pnpm run publish:talent:dev
+
+# 人才小程序 - 生产版上传
+pnpm run publish:talent:prod
+```
+
+---
+
+## 脚本实现
+
+### 创建发布脚本
+
+在项目根目录创建 `scripts/publish-weapp.js`:
+
+```javascript
+const ci = require('miniprogram-ci')
+const path = require('path')
+const { execSync } = require('child_process')
+
+// 小程序配置
+const MINI_CONFIGS = {
+  enterprise: {
+    name: '企业小程序',
+    appid: 'wx1e791ed2e0229eb8',
+    projectPath: path.resolve(__dirname, '../mini'),
+    privateKeyPath: path.resolve(__dirname, '../keys/private.wx1e791ed2e0229eb8.key'),
+    buildCmd: 'cd mini && pnpm build:weapp',
+    distPath: 'dist/weapp',
+  },
+  talent: {
+    name: '人才小程序',
+    appid: 'wx3c47dbce1ea7d43c',
+    projectPath: path.resolve(__dirname, '../mini-talent'),
+    privateKeyPath: path.resolve(__dirname, '../keys/private.wx3c47dbce1ea7d43c.key'),
+    buildCmd: 'cd mini-talent && pnpm build:weapp',
+    distPath: 'dist/weapp',
+  },
+}
+
+/**
+ * 构建小程序
+ */
+function buildMiniProject(config) {
+  console.log(`\n🔨 正在构建 ${config.name}...`)
+  try {
+    execSync(config.buildCmd, { stdio: 'inherit' })
+    console.log(`✅ ${config.name} 构建完成`)
+  } catch (error) {
+    console.error(`❌ ${config.name} 构建失败`)
+    throw error
+  }
+}
+
+/**
+ * 上传小程序到微信服务器
+ */
+async function uploadMiniProject(config, options) {
+  const { version, desc, robot = 1 } = options
+
+  console.log(`\n📤 正在上传 ${config.name}...`)
+  console.log(`   版本: ${version}`)
+  console.log(`   描述: ${desc}`)
+
+  const project = new ci.Project({
+    appid: config.appid,
+    type: 'miniProgram',
+    projectPath: config.projectPath,
+    privateKeyPath: config.privateKeyPath,
+    ignores: ['node_modules/**/*'],
+  })
+
+  try {
+    const result = await ci.upload({
+      project,
+      version,
+      desc,
+      setting: {
+        useProjectConfig: true,
+        es7: true,
+        minify: true,
+        minifyJS: true,
+        minifyWXML: true,
+        minifyWXSS: true,
+        autoPrefixWXSS: true,
+      },
+      onProgressUpdate: (progress) => {
+        process.stdout.write(`\r   上传进度: ${progress}%`)
+      },
+      robot,
+    })
+
+    console.log(`\n✅ ${config.name} 上传成功!`)
+    console.log(`   时间: ${result.time}`)
+    console.log(`   版本: ${result.version}`)
+    console.log(`   请前往小程序后台查看: https://mp.weixin.qq.com/`)
+  } catch (error) {
+    console.error(`\n❌ ${config.name} 上传失败`)
+    console.error(error.message)
+    throw error
+  }
+}
+
+/**
+ * 预览小程序(生成二维码)
+ */
+async function previewMiniProject(config, options) {
+  const { desc, qrcodePath, robot = 1 } = options
+
+  console.log(`\n👀 正在生成 ${config.name} 预览二维码...`)
+
+  const project = new ci.Project({
+    appid: config.appid,
+    type: 'miniProgram',
+    projectPath: config.projectPath,
+    privateKeyPath: config.privateKeyPath,
+    ignores: ['node_modules/**/*'],
+  })
+
+  try {
+    const result = await ci.preview({
+      project,
+      desc,
+      setting: { useProjectConfig: true },
+      qrcodeFormat: 'image',
+      qrcodeOutputDest: qrcodePath,
+      onProgressUpdate: (progress) => {
+        process.stdout.write(`\r   生成进度: ${progress}%`)
+      },
+      robot,
+    })
+
+    console.log(`\n✅ 预览二维码已生成: ${qrcodePath}`)
+  } catch (error) {
+    console.error(`\n❌ 预览二维码生成失败`)
+    console.error(error.message)
+    throw error
+  }
+}
+
+/**
+ * 主函数
+ */
+async function main() {
+  const args = process.argv.slice(2)
+  const [miniType, action = 'upload'] = args
+
+  // 解析参数
+  const options = {
+    version: process.env.VERSION || '1.0.0',
+    desc: process.env.DESC || '自动化发布',
+    robot: parseInt(process.env.ROBOT || '1'),
+  }
+
+  if (!miniType || !MINI_CONFIGS[miniType]) {
+    console.error('❌ 请指定小程序类型: enterprise 或 talent')
+    console.error('示例: pnpm run publish:enterprise')
+    process.exit(1)
+  }
+
+  const config = MINI_CONFIGS[miniType]
+
+  try {
+    // 构建项目
+    buildMiniProject(config)
+
+    // 执行操作
+    if (action === 'upload') {
+      await uploadMiniProject(config, options)
+    } else if (action === 'preview') {
+      await previewMiniProject(config, {
+        desc: options.desc,
+        qrcodePath: path.resolve(__dirname, `../qrcode-${miniType}.jpg`),
+        robot: options.robot,
+      })
+    } else {
+      console.error(`❌ 未知操作: ${action}`)
+      process.exit(1)
+    }
+  } catch (error) {
+    process.exit(1)
+  }
+}
+
+main()
+```
+
+---
+
+## package.json 配置
+
+在根目录 `package.json` 中添加以下脚本:
+
+```json
+{
+  "scripts": {
+    "publish:enterprise": "node scripts/publish-weapp.js enterprise upload",
+    "publish:enterprise:preview": "node scripts/publish-weapp.js enterprise preview",
+    "publish:talent": "node scripts/publish-weapp.js talent upload",
+    "publish:talent:preview": "node scripts/publish-weapp.js talent preview"
+  }
+}
+```
+
+---
+
+## 环境变量配置
+
+创建 `.env.local` 文件:
+
+```bash
+# 发布版本号
+VERSION=1.0.0
+
+# 发布描述
+DESC=修复了一些问题并优化了用户体验
+
+# CI 机器人编号 (1-30)
+ROBOT=1
+```
+
+---
+
+## 使用示例
+
+### 发布企业小程序
+
+```bash
+# 使用默认版本号和描述
+pnpm run publish:enterprise
+
+# 自定义版本号和描述
+VERSION=1.2.0 DESC="新增订单管理功能" pnpm run publish:enterprise
+
+# 生成预览二维码
+pnpm run publish:enterprise:preview
+```
+
+### 发布人才小程序
+
+```bash
+# 使用默认版本号和描述
+pnpm run publish:talent
+
+# 自定义版本号和描述
+VERSION=2.0.0 DESC="全新改版,优化求职流程" pnpm run publish:talent
+
+# 生成预览二维码
+pnpm run publish:talent:preview
+```
+
+---
+
+## 快捷命令建议
+
+用户可以直接询问 Claude 执行以下操作:
+
+| 用户指令 | Claude 行为 |
+|----------|-------------|
+| "发布企业小程序" | 运行 `pnpm run publish:enterprise` |
+| "发布人才小程序" | 运行 `pnpm run publish:talent` |
+| "生成企业小程序预览二维码" | 运行 `pnpm run publish:enterprise:preview` |
+| "发布两个小程序" | 依次运行企业小程序和人才小程序的发布命令 |
+
+---
+
+## 注意事项
+
+1. **密钥安全**: 上传密钥拥有预览、上传权限,请勿提交到代码仓库
+2. **构建依赖**: 发布前确保项目依赖已安装 (`pnpm install`)
+3. **版本管理**: 建议使用语义化版本号 (Semantic Versioning)
+4. **IP 白名单**: 确保服务器 IP 已在微信公众平台配置白名单
+5. **机器人编号**: 多个 CI 并发时使用不同的 robot 编号 (1-30)
+
+---
+
+## 常见问题
+
+### Q: 上传失败提示 IP 不在白名单
+**A**: 请在微信公众平台"开发 > 开发设置"中添加服务器 IP 到白名单。
+
+### Q: 构建成功但上传失败
+**A**: 检查密钥文件路径是否正确,密钥是否匹配对应的 AppID。
+
+### Q: 如何设置体验版
+**A**: 上传成功后,登录小程序后台"版本管理",将指定版本设置为体验版。
+
+---
+
+## 相关资源
+
+- [miniprogram-ci 官方文档](https://www.npmjs.com/package/miniprogram-ci)
+- [微信公众平台](https://mp.weixin.qq.com/)
+- [miniprogram-ci Skill](.claude/skills/miniprogram-ci/SKILL.md)

+ 5 - 1
package.json

@@ -63,7 +63,11 @@
     "test:e2e:ui": "cd web && pnpm test:e2e:ui",
     "test:e2e:debug": "cd web && pnpm test:e2e:debug",
     "test:e2e:report": "cd web && pnpm exec playwright show-report",
-    "prepare": "husky"
+    "prepare": "husky",
+    "publish:enterprise": "node scripts/publish-weapp.js enterprise upload",
+    "publish:enterprise:preview": "node scripts/publish-weapp.js enterprise preview",
+    "publish:talent": "node scripts/publish-weapp.js talent upload",
+    "publish:talent:preview": "node scripts/publish-weapp.js talent preview"
   },
   "keywords": [],
   "author": "",

+ 169 - 0
scripts/publish-weapp.js

@@ -0,0 +1,169 @@
+const ci = require('miniprogram-ci')
+const path = require('path')
+const { execSync } = require('child_process')
+
+// 小程序配置
+const MINI_CONFIGS = {
+  enterprise: {
+    name: '企业小程序',
+    appid: 'wx1e791ed2e0229eb8',
+    projectPath: path.resolve(__dirname, '../mini'),
+    privateKeyPath: path.resolve(__dirname, '../keys/private.wx1e791ed2e0229eb8.key'),
+    buildCmd: 'cd mini && pnpm build:weapp',
+    distPath: 'dist/weapp',
+  },
+  talent: {
+    name: '人才小程序',
+    appid: 'wx3c47dbce1ea7d43c',
+    projectPath: path.resolve(__dirname, '../mini-talent'),
+    privateKeyPath: path.resolve(__dirname, '../keys/private.wx3c47dbce1ea7d43c.key'),
+    buildCmd: 'cd mini-talent && pnpm build:weapp',
+    distPath: 'dist/weapp',
+  },
+}
+
+/**
+ * 构建小程序
+ */
+function buildMiniProject(config) {
+  console.log(`\n🔨 正在构建 ${config.name}...`)
+  try {
+    execSync(config.buildCmd, { stdio: 'inherit' })
+    console.log(`✅ ${config.name} 构建完成`)
+  } catch (error) {
+    console.error(`❌ ${config.name} 构建失败`)
+    throw error
+  }
+}
+
+/**
+ * 上传小程序到微信服务器
+ */
+async function uploadMiniProject(config, options) {
+  const { version, desc, robot = 1 } = options
+
+  console.log(`\n📤 正在上传 ${config.name}...`)
+  console.log(`   版本: ${version}`)
+  console.log(`   描述: ${desc}`)
+
+  const project = new ci.Project({
+    appid: config.appid,
+    type: 'miniProgram',
+    projectPath: config.projectPath,
+    privateKeyPath: config.privateKeyPath,
+    ignores: ['node_modules/**/*'],
+  })
+
+  try {
+    const result = await ci.upload({
+      project,
+      version,
+      desc,
+      setting: {
+        useProjectConfig: true,
+        es7: true,
+        minify: true,
+        minifyJS: true,
+        minifyWXML: true,
+        minifyWXSS: true,
+        autoPrefixWXSS: true,
+      },
+      onProgressUpdate: (progress) => {
+        process.stdout.write(`\r   上传进度: ${progress}%`)
+      },
+      robot,
+    })
+
+    console.log(`\n✅ ${config.name} 上传成功!`)
+    console.log(`   时间: ${result.time}`)
+    console.log(`   版本: ${result.version}`)
+    console.log(`   请前往小程序后台查看: https://mp.weixin.qq.com/`)
+  } catch (error) {
+    console.error(`\n❌ ${config.name} 上传失败`)
+    console.error(error.message)
+    throw error
+  }
+}
+
+/**
+ * 预览小程序(生成二维码)
+ */
+async function previewMiniProject(config, options) {
+  const { desc, qrcodePath, robot = 1 } = options
+
+  console.log(`\n👀 正在生成 ${config.name} 预览二维码...`)
+
+  const project = new ci.Project({
+    appid: config.appid,
+    type: 'miniProgram',
+    projectPath: config.projectPath,
+    privateKeyPath: config.privateKeyPath,
+    ignores: ['node_modules/**/*'],
+  })
+
+  try {
+    const result = await ci.preview({
+      project,
+      desc,
+      setting: { useProjectConfig: true },
+      qrcodeFormat: 'image',
+      qrcodeOutputDest: qrcodePath,
+      onProgressUpdate: (progress) => {
+        process.stdout.write(`\r   生成进度: ${progress}%`)
+      },
+      robot,
+    })
+
+    console.log(`\n✅ 预览二维码已生成: ${qrcodePath}`)
+  } catch (error) {
+    console.error(`\n❌ 预览二维码生成失败`)
+    console.error(error.message)
+    throw error
+  }
+}
+
+/**
+ * 主函数
+ */
+async function main() {
+  const args = process.argv.slice(2)
+  const [miniType, action = 'upload'] = args
+
+  // 解析参数
+  const options = {
+    version: process.env.VERSION || '1.0.0',
+    desc: process.env.DESC || '自动化发布',
+    robot: parseInt(process.env.ROBOT || '1'),
+  }
+
+  if (!miniType || !MINI_CONFIGS[miniType]) {
+    console.error('❌ 请指定小程序类型: enterprise 或 talent')
+    console.error('示例: node scripts/publish-weapp.js enterprise')
+    process.exit(1)
+  }
+
+  const config = MINI_CONFIGS[miniType]
+
+  try {
+    // 构建项目
+    buildMiniProject(config)
+
+    // 执行操作
+    if (action === 'upload') {
+      await uploadMiniProject(config, options)
+    } else if (action === 'preview') {
+      await previewMiniProject(config, {
+        desc: options.desc,
+        qrcodePath: path.resolve(__dirname, `../qrcode-${miniType}.jpg`),
+        robot: options.robot,
+      })
+    } else {
+      console.error(`❌ 未知操作: ${action}`)
+      process.exit(1)
+    }
+  } catch (error) {
+    process.exit(1)
+  }
+}
+
+main()