ai-engineering-init 1.17.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -117,6 +117,7 @@ const instructions = `## 强制技能激活流程(必须执行)
117
117
  - skill-creator: 创建技能模板/技能脚手架/skill scaffold
118
118
  - leniu-report-scenario: 报表/报表开发/报表查询/报表导出/合计行/totalLine/汇总报表/定制报表/report_order_info/金额处理/分转元/餐次/mealtime/ReportBaseTotalVO/CustomNumberConverter
119
119
  - leniu-marketing-scenario: 营销/营销规则/消费规则/计价规则/充值规则/扣款规则/就餐规则/折扣/满减/限额/限次/补贴/赠送/满赠/管理费/MarketApi/RulePriceHandler/RuleRechargeHandler/RulePayHandler/MarketRule/规则定制
120
+ - jenkins-deploy: 打包/部署/Jenkins/构建/Portainer/发布到dev/发布到test/更新环境/自动部署
120
121
 
121
122
  ### 步骤 2 - 激活(逐个调用,等待每个完成)
122
123
 
@@ -0,0 +1,207 @@
1
+ ---
2
+ name: jenkins-deploy
3
+ description: |
4
+ Jenkins + Portainer 自动打包部署技能。通过 Python 脚本调用 Jenkins API 构建项目,并触发 Portainer Webhook/Update 完成容器更新。
5
+
6
+ 触发场景:
7
+ - 需要将代码打包部署到 dev/test 环境
8
+ - 需要触发 Jenkins 构建 core 或 api 项目
9
+ - 需要更新 Portainer 容器服务
10
+ - 需要查看或修改构建配置(分支、环境、模式)
11
+ - 定制项目的打包部署
12
+
13
+ 触发词:打包、部署、Jenkins、构建、Portainer、发布到dev、发布到test、更新环境、自动部署
14
+ ---
15
+
16
+ # Jenkins + Portainer 自动打包部署
17
+
18
+ ## 概述
19
+
20
+ 项目使用 `jenkins/` 目录下的 Python 脚本实现自动化构建部署,流程:
21
+
22
+ ```
23
+ Jenkins 构建 core → Jenkins 构建 api → Portainer 更新容器
24
+ ```
25
+
26
+ ## 文件位置
27
+
28
+ | 文件 | 作用 |
29
+ |------|------|
30
+ | `jenkins/jk_build.py` | 主构建脚本 |
31
+ | `jenkins/env_param.json` | 环境配置(Jenkins/Portainer 连接参数) |
32
+ | `jenkins/last_cd_env.json` | 上次构建参数(自动保存) |
33
+
34
+ ## 构建模式
35
+
36
+ | 模式 | 说明 | 执行步骤 |
37
+ |------|------|---------|
38
+ | `0` | 只构建 | 构建 core + api,不更新 Portainer |
39
+ | `1` | 全构建+更新 | 构建 core + api → 触发 Portainer 更新 |
40
+ | `2` | 构建 api+更新 | 跳过 core,构建 api → 触发 Portainer 更新 |
41
+ | `3` | 只更新 | 不构建,直接触发 Portainer 更新 |
42
+
43
+ ## 环境支持
44
+
45
+ | 环境 | 前缀 | Jenkins Job | Portainer |
46
+ |------|------|-------------|-----------|
47
+ | 开发环境 | `dev` + 编号 | `dev-tengyun-core` / `dev-tengyun-yunshitang-api` | devops-dev.xnzn.net |
48
+ | 测试环境 | `test` + 编号 | `test-tengyun-core` / `test-tengyun-yunshitang-api` | devops-test.xnzn.net |
49
+ | dev16+ | `dev16` ~ `dev43` | 同 dev | xnzn-dev.xnzn.net(使用 forceupdateservice) |
50
+ | dev44+ | `dev44` 及以上 | 只支持模式 0 | 需手动更新 |
51
+
52
+ ## 定制项目支持
53
+
54
+ 当指定 `api_param_folder`(定制工程文件夹名)时,Jenkins Job 路径变为:
55
+
56
+ ```
57
+ {folder_name}/dev-后端-core # 替代 dev-tengyun-core
58
+ {folder_name}/dev-后端-api # 替代 dev-tengyun-yunshitang-api
59
+ ```
60
+
61
+ ## 使用方式
62
+
63
+ ### 直接运行脚本
64
+
65
+ ```bash
66
+ cd jenkins && python jk_build.py
67
+ ```
68
+
69
+ 脚本会交互式询问:
70
+ 1. **模式**(0/1/2/3)
71
+ 2. **环境**(dev1, dev2, test1 等)
72
+ 3. **core 分支**(如 release_5.56.0)
73
+ 4. **api 分支**(如 master)
74
+ 5. **定制工程文件夹**(可选,空格或 None 跳过)
75
+
76
+ ### 通过 Claude 辅助部署
77
+
78
+ 当用户说"打包到 devX"时:
79
+
80
+ 1. **读取** `jenkins/last_cd_env.json` 获取上次构建参数
81
+ 2. **确认参数**:环境、分支、模式、是否定制项目
82
+ 3. **修改** `last_cd_env.json` 写入新参数
83
+ 4. **执行** `cd jenkins && python jk_build.py`(脚本读取预设值,用户直接回车即可)
84
+
85
+ ## 配置文件说明
86
+
87
+ ### last_cd_env.json(构建参数)
88
+
89
+ ```json
90
+ {
91
+ "build_mode": "0",
92
+ "cd_env": "dev63",
93
+ "core_param_branch": "release_5.56.0",
94
+ "api_param_branch": "master",
95
+ "api_param_folder": null
96
+ }
97
+ ```
98
+
99
+ | 字段 | 说明 |
100
+ |------|------|
101
+ | `build_mode` | 构建模式(0/1/2/3) |
102
+ | `cd_env` | 目标环境(如 dev1, test2) |
103
+ | `core_param_branch` | core 仓库分支 |
104
+ | `api_param_branch` | api 仓库分支 |
105
+ | `api_param_folder` | 定制项目文件夹(null 表示标准项目) |
106
+
107
+ ### env_param.json(环境连接配置)
108
+
109
+ 包含 Jenkins 和 Portainer 的连接参数(用户名、API Token、服务地址等),按 dev/test 环境分组。
110
+
111
+ > **注意**:此文件包含敏感凭证,不要将内容输出到对话中。
112
+
113
+ ## 构建流程详解
114
+
115
+ ```
116
+ 1. 连接 Jenkins(ci.xnzn.net)
117
+ 2. 构建 core(参数:BRANCH + VERSION=环境名)
118
+ └─ 轮询构建进度,超时 10 分钟
119
+ 3. 构建 api(参数同上)
120
+ └─ 轮询构建进度,超时 5 分钟
121
+ 4. 更新 Portainer:
122
+ ├─ dev1~15:Webhook 触发
123
+ │ └─ 获取 JWT → 查 Service ID → 获取/创建 Webhook → POST 触发
124
+ └─ dev16+:Force Update
125
+ └─ 获取 JWT → 查 Service ID → PUT forceupdateservice
126
+ ```
127
+
128
+ ## 常见操作
129
+
130
+ ### 只改了 api 代码,快速部署
131
+
132
+ ```bash
133
+ # 修改 last_cd_env.json 的 build_mode 为 "2",然后运行
134
+ cd jenkins && python jk_build.py
135
+ ```
136
+
137
+ ### 只需要更新容器(已在 Jenkins 手动构建完)
138
+
139
+ ```bash
140
+ # build_mode 设为 "3"
141
+ cd jenkins && python jk_build.py
142
+ ```
143
+
144
+ ### 部署定制项目
145
+
146
+ ```json
147
+ // last_cd_env.json
148
+ {
149
+ "api_param_folder": "leniu-tengyun-wuhanxieheyiyuan"
150
+ }
151
+ ```
152
+
153
+ ## 首次初始化(团队成员)
154
+
155
+ 当用户说"初始化部署环境"或检测到 `jenkins/` 目录不存在时,执行以下步骤:
156
+
157
+ ### 自动初始化流程
158
+
159
+ ```bash
160
+ # 1. 创建 jenkins 目录
161
+ mkdir -p jenkins
162
+
163
+ # 2. 从技能模板复制文件(SKILL_DIR 为技能目录路径)
164
+ cp .claude/skills/jenkins-deploy/assets/jk_build.py jenkins/jk_build.py
165
+ cp .claude/skills/jenkins-deploy/assets/last_cd_env.template.json jenkins/last_cd_env.json
166
+
167
+ # 3. 环境配置需要用户提供(含敏感凭证)
168
+ cp .claude/skills/jenkins-deploy/assets/env_param.template.json jenkins/env_param.json
169
+
170
+ # 4. 安装依赖
171
+ pip install python-jenkins requests
172
+ ```
173
+
174
+ ### 配置凭证
175
+
176
+ 初始化后,`jenkins/env_param.json` 中的占位符需要替换为真实值:
177
+
178
+ | 占位符 | 说明 | 获取方式 |
179
+ |--------|------|---------|
180
+ | `__JENKINS_DEV_USER__` | Jenkins dev 账号 | 向团队负责人获取 |
181
+ | `__JENKINS_DEV_TOKEN__` | Jenkins dev API Token | Jenkins → 用户 → 设置 → API Token |
182
+ | `__JENKINS_TEST_USER__` | Jenkins test 账号 | 同上 |
183
+ | `__JENKINS_TEST_TOKEN__` | Jenkins test API Token | 同上 |
184
+ | `__PORTAINER_*_USER__` | Portainer 用户名 | 向团队负责人获取 |
185
+ | `__PORTAINER_*_PWD__` | Portainer 密码 | 向团队负责人获取 |
186
+
187
+ **快捷方式**:如果团队已有成员配置好,可直接拷贝对方的 `env_param.json` 文件。
188
+
189
+ ### AI 初始化行为
190
+
191
+ 当技能被触发但 `jenkins/` 不存在时:
192
+ 1. 提示用户"检测到尚未初始化部署环境"
193
+ 2. 询问是否执行初始化
194
+ 3. 执行上述复制步骤
195
+ 4. 提示用户填写凭证(或拷贝已有配置)
196
+
197
+ ## 依赖
198
+
199
+ ```bash
200
+ pip install python-jenkins requests
201
+ ```
202
+
203
+ ## 注意
204
+
205
+ - 本技能用于 dev/test 环境部署,**不涉及生产环境**
206
+ - 如果是 Git 提交/分支管理,请使用 `git-workflow` 技能
207
+ - 如果是代码构建错误排查,请使用 `bug-detective` 技能
@@ -0,0 +1,51 @@
1
+ {
2
+ "portainer_api_url": {
3
+ "dev": "https://devops-dev.xnzn.net/api",
4
+ "dev16": "https://xnzn-dev.xnzn.net/api",
5
+ "test": "https://devops-test.xnzn.net/api"
6
+ },
7
+ "portainer_user_name": {
8
+ "dev": "__PORTAINER_DEV_USER__",
9
+ "dev16": "__PORTAINER_DEV16_USER__",
10
+ "test": "__PORTAINER_TEST_USER__"
11
+ },
12
+ "portainer_user_pwd": {
13
+ "dev": "__PORTAINER_DEV_PWD__",
14
+ "dev16": "__PORTAINER_DEV16_PWD__",
15
+ "test": "__PORTAINER_TEST_PWD__"
16
+ },
17
+ "portainer_endpoints": {
18
+ "dev": "2",
19
+ "dev16": "1",
20
+ "test": "2"
21
+ },
22
+ "portainer_server_suffix": {
23
+ "dev": "_yunshitang-api",
24
+ "dev16": "_yunshitang-api-offline",
25
+ "test": "_yunshitang-api"
26
+ },
27
+ "core_job": {
28
+ "dev": "dev-tengyun-core",
29
+ "test": "test-tengyun-core"
30
+ },
31
+ "core_job_custom": {
32
+ "dev": "dev-后端-core",
33
+ "test": "test-后端-core"
34
+ },
35
+ "api_job": {
36
+ "dev": "dev-tengyun-yunshitang-api",
37
+ "test": "test-tengyun-yunshitang-api"
38
+ },
39
+ "api_job_custom": {
40
+ "dev": "dev-后端-api",
41
+ "test": "test-后端-api"
42
+ },
43
+ "jenkins_user_id": {
44
+ "dev": "__JENKINS_DEV_USER__",
45
+ "test": "__JENKINS_TEST_USER__"
46
+ },
47
+ "jenkins_api_token": {
48
+ "dev": "__JENKINS_DEV_TOKEN__",
49
+ "test": "__JENKINS_TEST_TOKEN__"
50
+ }
51
+ }
@@ -0,0 +1,362 @@
1
+ # 简单调用jenkins api,构建项目并触发webhook
2
+ import html
3
+ import json
4
+ import os
5
+ import time
6
+
7
+ import jenkins
8
+ import requests
9
+
10
+ # 预设默认值(可修改)
11
+ cd_env = '' # dev或test
12
+
13
+ # dev和test环境独立配置
14
+ jenkins_server_url = 'https://ci.xnzn.net' # jenkins地址
15
+ portainer_server_suffix = '' # portainer服务名后缀
16
+ core_timeout = 60 * 10 # core构建超时时间
17
+ api_timeout = 60 * 5 # api构建超时时间
18
+ portainer_user_name = '' # portainer用户名
19
+ portainer_user_pwd = '' # portainer密码
20
+ portainer_api_url = ''
21
+ portainer_endpoints = ''
22
+ jenkins_user_id = ''
23
+ jenkins_api_token = ''
24
+ core_job = ''
25
+ api_job = ''
26
+
27
+ # 执行job方法,通过等待时间来判断是否构建成功
28
+ def do_job_by_time(server, job_name, param_branch, param_env, wait_time):
29
+ queue_num = server.build_job(name=job_name,
30
+ parameters={'BRANCH': param_branch, 'VERSION': param_env},
31
+ token=jenkins_api_token)
32
+ print('创建queue... {queue_num}')
33
+ time.sleep(wait_time)
34
+ queue_info = server.get_queue_item(queue_num)
35
+
36
+ print('等待时间到,queue信息:' + str(queue_info))
37
+
38
+
39
+ # 执行job方法,通过请求判断结束,进度更准确
40
+ def do_job(server, job_name, param_branch, param_env, wait_time):
41
+
42
+ queue_num = server.build_job(name=job_name,
43
+ parameters={'BRANCH': param_branch, 'VERSION': param_env},
44
+ token=jenkins_api_token)
45
+ print(f'创建队列... {queue_num}')
46
+ job_number = None
47
+ # 记录开始构建时间
48
+ start_time = time.time()
49
+
50
+ while True:
51
+ time.sleep(10)
52
+ if time.time() - start_time > wait_time:
53
+ print("构建超时,结束")
54
+ exit(1)
55
+ queue_info = server.get_queue_item(queue_num)
56
+ if queue_info['why'] is not None:
57
+ why = queue_info['why']
58
+ print(f'队列等待执行器...{why}')
59
+ elif 'executable' in queue_info:
60
+ job_number = queue_info['executable']['number']
61
+ print(f'开始执行任务...{job_number}')
62
+ break
63
+ else:
64
+ print('执行结束')
65
+ break
66
+
67
+ # 间隔x秒查询构建结果
68
+ while True:
69
+ time.sleep(10)
70
+
71
+ # 如果超过3分钟,直接结束
72
+ if time.time() - start_time > wait_time:
73
+ print("构建超时,结束")
74
+ exit(1)
75
+ headers = {
76
+ "n": str(job_number),
77
+ }
78
+ # 如果job_name包含 '/',则在把'/'替换为/job/
79
+ if '/' in job_name:
80
+ job_name_url = job_name.replace('/', '/job/')
81
+ url = f'{jenkins_server_url}/job/{job_name_url}/buildHistory/ajax'
82
+ else:
83
+ url = f'{jenkins_server_url}/job/{job_name}/buildHistory/ajax'
84
+
85
+ req = requests.Request(method='POST', url=url, headers=headers)
86
+ response = server.jenkins_open(req)
87
+ # 解析response包含的html
88
+ html_str = html.unescape(response)
89
+ if 'Expected build number' in html_str: # 正在等待中
90
+ print(f'\r等待中...', end='')
91
+ continue
92
+ elif ('预计剩余时间' in html_str) or ('Estimated remaining time' in html_str): # 是否包含 预计剩余时间
93
+ try:
94
+ # 取出 '<td style="width:' 和 '%;" class="progress-bar-done"></td>' 之间的字符串
95
+ progress = html_str.split('<td style="width:')[1].split('%;" class="progress-bar-done"></td>')[0]
96
+ # 打印进度更新最后一行
97
+ print(f'\r构建中...{progress}%', end='')
98
+ except IndexError:
99
+ print("无法从HTML字符串中提取进度信息")
100
+ except Exception as e:
101
+ print(f"发生错误: {e}")
102
+ continue
103
+ else:
104
+ print(f"\r构建结束,耗时 {(time.time() - start_time):.2f} 秒", end='\n')
105
+ # 查询构建结果
106
+ build_info = server.get_build_info(job_name, job_number)
107
+ if build_info['result'] == 'SUCCESS':
108
+ print(f"构建成功 {html.unescape(build_info['url'])}")
109
+ else:
110
+ print(f"构建失败({build_info['result']}) {html.unescape(build_info['url'])}")
111
+ print(f"原始 build_info:{build_info}")
112
+ print(f"原始 html:{html_str}")
113
+ return False
114
+ break
115
+ return True
116
+
117
+
118
+ # 获取 Portainer JWT Token
119
+ def get_jwt_token():
120
+ login_url = f"{portainer_api_url}/auth"
121
+ login_data = {
122
+ "Username": portainer_user_name,
123
+ "Password": portainer_user_pwd,
124
+ }
125
+ response = requests.post(login_url, data=json.dumps(login_data))
126
+ token = response.json()["jwt"]
127
+ print('获取token成功')
128
+ return token
129
+
130
+
131
+ # 获取 Service ID
132
+ def get_server_id(token):
133
+ headers = {
134
+ "Authorization": f"Bearer {token}",
135
+ "Content-Type": "application/json"
136
+ }
137
+ # Get list of services
138
+ # services_response = requests.get(portainer_api_url + "/endpoints/1/docker/services", headers=headers) // dev改成了2
139
+ url = portainer_api_url + f'/endpoints/{portainer_endpoints}/docker/services'
140
+ services_response = requests.get(url, headers=headers)
141
+ services = services_response.json()
142
+
143
+ service_name = cd_env + portainer_server_suffix
144
+ # Find service with matching name
145
+ target_service_id = None
146
+ for service in services:
147
+ if service["Spec"]["Name"] == service_name:
148
+ target_service_id = service["ID"]
149
+ break
150
+
151
+ print(f'查询{service_name}的service_id:{target_service_id}')
152
+ return target_service_id
153
+
154
+
155
+ # 获取 Service 的 WebHook
156
+ def get_service_web_hook():
157
+ pt_token = get_jwt_token()
158
+ service_id = get_server_id(pt_token)
159
+
160
+ headers = {
161
+ "Authorization": f"Bearer {pt_token}",
162
+ "Content-Type": "application/json"
163
+ }
164
+
165
+ # 查询service的webhook
166
+ # url = portainer_api_url + f'/webhooks?filters=%7B%22ResourceID%22:%22{service_id}%22,%22EndpointID%22:1%7D' // dev改成了2
167
+ url = portainer_api_url + f'/webhooks?filters=%7B%22ResourceID%22:%22{service_id}%22,%22EndpointID%22:{portainer_endpoints}%7D'
168
+ services_response = requests.get(url, headers=headers)
169
+ webhook_json = services_response.json()
170
+ target_webhook_token = None
171
+ # 是否存在webhook
172
+ if len(webhook_json) > 0:
173
+ target_webhook_token = webhook_json[0]['Token']
174
+ print('获取webhook_token:' + target_webhook_token)
175
+
176
+ # 尝试调用请求创建webhook
177
+ if target_webhook_token is None:
178
+ payload = {"ResourceID": service_id, "EndpointID": 2, "WebhookType": 1, "registryID": 0}
179
+ services_response = requests.post(portainer_api_url + "/webhooks", headers=headers, data=json.dumps(payload))
180
+ webhook_json = services_response.json()
181
+ if webhook_json is not None and 'Token' in webhook_json:
182
+ target_webhook_token = webhook_json['Token']
183
+ print('创建webhook_token结果:' + target_webhook_token)
184
+
185
+ return target_webhook_token
186
+
187
+
188
+ # 启动webhook(dev1-15)
189
+ def trigger_webhook():
190
+
191
+ wh_token = get_service_web_hook()
192
+
193
+ if wh_token is None:
194
+ print(f"{cd_env}环境没有配置webhook,结束")
195
+ exit(0)
196
+ else:
197
+ print("开始触发webhook")
198
+ r = requests.post(portainer_api_url + '/webhooks/' + wh_token)
199
+ print("webhook触发结果:" + str(r.status_code))
200
+
201
+ # 通过service id 更新
202
+ def update_portainer():
203
+
204
+ pt_token = get_jwt_token()
205
+ service_id = get_server_id(pt_token)
206
+
207
+ headers = {
208
+ "Authorization": f"Bearer {pt_token}",
209
+ "Content-Type": "application/json"
210
+ }
211
+
212
+ # 调用更新(PUT请求):
213
+ url = portainer_api_url + f'/endpoints/{portainer_endpoints}/forceupdateservice'
214
+ payload = {
215
+ "serviceID": service_id,
216
+ "pullImage": True,
217
+ }
218
+ services_response = requests.put(url, headers=headers, data=json.dumps(payload))
219
+ update_rsp_json = services_response.json()
220
+ print('更新结果:' + str(update_rsp_json))
221
+
222
+ # ~~~~~~~~~~~~~~~~~~ 开始输入 ~~~~~~~~~~~~~~~~~~~~~~~
223
+
224
+ # 打印欢迎信息
225
+ print('\n~~~~~~~~~~~~~~')
226
+ print('正在使用jenkins+portainer构建工具,目前支持dev和test环境的自动化构建+更新')
227
+ print('~~~~~~~~~~~~~~\n')
228
+
229
+ # 获取当前目录
230
+ current_dir = os.getcwd()
231
+ # 读取上一次构建的环境
232
+ with open(current_dir + '/last_cd_env.json', 'r', encoding='utf-8') as f:
233
+ cd_env_json = json.load(f)
234
+
235
+ # 从终端获取用户输入模式
236
+ build_mode = input(f"请输入模式 0-只构建 1-全构建+更新 2-构建api+更新 3-只更新(预设{cd_env_json['build_mode']}):", ) or cd_env_json['build_mode']
237
+
238
+ # 从终端获取用户输入cd_env
239
+ cd_env = input(f"请输入环境(预设:{cd_env_json['cd_env']}):", ) or cd_env_json['cd_env']
240
+
241
+ # 从终端获取用户输入的分支
242
+ core_param_branch = input(f"请输入core分支(预设:{cd_env_json['core_param_branch']}):") or cd_env_json['core_param_branch']
243
+
244
+ # 从终端输入api分支
245
+ api_param_branch = input(f"请输入api分支(预设:{cd_env_json['api_param_branch']}):") or cd_env_json['api_param_branch']
246
+
247
+ # 从终端输入api文件夹名
248
+ # 读取上一次构建的分支
249
+ api_param_folder = input(f"请输入定制工程文件夹名(空格或None表示不需要,预设:{cd_env_json['api_param_folder']}):") or cd_env_json['api_param_folder']
250
+
251
+ # 校验模式:
252
+ if build_mode is None or build_mode not in ('0', '1', '2', '3'):
253
+ print("模式错误,结束")
254
+ exit(1)
255
+ # 校验环境,如果不是dev或test开头的,直接退出
256
+ if cd_env is None or not cd_env.startswith('dev') and not cd_env.startswith('test'):
257
+ print("环境错误,结束")
258
+ exit(1)
259
+ # 校验分支
260
+ if core_param_branch is None:
261
+ print("分支错误,结束")
262
+ exit(1)
263
+ if api_param_branch is None:
264
+ print("分支错误,结束")
265
+ exit(1)
266
+ if api_param_folder is None or api_param_folder == '' or api_param_folder == ' ' or api_param_folder == 'none' or api_param_folder == 'None':
267
+ api_param_folder = None
268
+ # 如果dev44及以上环境,只能使用模式0:
269
+ if cd_env.startswith('dev') and int(cd_env[3:]) >= 44 and build_mode != '0':
270
+ print("dev43及以上环境只能使用模式0,结束")
271
+ exit(1)
272
+
273
+ # 保存到本地环境
274
+ cd_env_json['cd_env'] = cd_env
275
+ cd_env_json['core_param_branch'] = core_param_branch
276
+ cd_env_json['api_param_branch'] = api_param_branch
277
+ cd_env_json['api_param_folder'] = api_param_folder
278
+ cd_env_json['build_mode'] = build_mode
279
+ json.dump(cd_env_json, open(current_dir + '/last_cd_env.json', 'w', encoding='utf-8'), ensure_ascii=False)
280
+
281
+ # exit(0)
282
+
283
+ # 根据环境输入,设置前缀
284
+ env_prefix = 'dev' if cd_env.startswith('dev') else 'test'
285
+
286
+ # 读取上一次构建的环境
287
+ with open(current_dir + '/env_param.json', 'r', encoding='utf-8') as f:
288
+ env_param = json.load(f)
289
+
290
+ jenkins_user_id = env_param['jenkins_user_id'][env_prefix]
291
+ jenkins_api_token = env_param['jenkins_api_token'][env_prefix]
292
+ core_job = env_param['core_job'][env_prefix]
293
+ api_job = env_param['api_job'][env_prefix]
294
+ if api_param_folder is not None:
295
+ core_job = api_param_folder + '/' + env_param['core_job_custom'][env_prefix]
296
+ api_job = api_param_folder + '/' + env_param['api_job_custom'][env_prefix]
297
+
298
+ # portainer配置(如果cd_env > dev15,使用另一套配置)
299
+ if cd_env.startswith('dev') and int(cd_env[3:]) > 15:
300
+ portainer_user_name = env_param['portainer_user_name']['dev16']
301
+ portainer_user_pwd = env_param['portainer_user_pwd']['dev16']
302
+ portainer_api_url = env_param['portainer_api_url']['dev16']
303
+ portainer_endpoints = env_param['portainer_endpoints']['dev16']
304
+ portainer_server_suffix = env_param['portainer_server_suffix']['dev16']
305
+ else:
306
+ portainer_api_url = env_param['portainer_api_url'][env_prefix]
307
+ portainer_user_name = env_param['portainer_user_name'][env_prefix]
308
+ portainer_user_pwd = env_param['portainer_user_pwd'][env_prefix]
309
+ portainer_endpoints = env_param['portainer_endpoints'][env_prefix]
310
+ portainer_server_suffix = env_param['portainer_server_suffix'][env_prefix]
311
+
312
+ # ~~~~~~~~~~~~~~~~~ 开始打包 ~~~~~~~~~~~~~~~~~~~~~~~~
313
+
314
+ print('\n~~~~~~~~~~~~~~\n')
315
+ print('开始打包')
316
+ print("链接jenkins:" + jenkins_server_url)
317
+
318
+ # 实例化jenkins对象,连接远程的jenkins master server
319
+ j_server = jenkins.Jenkins(url=jenkins_server_url, username=jenkins_user_id, password=jenkins_api_token)
320
+ print("jenkins信息:" + j_server.get_whoami()['fullName'])
321
+
322
+ # 通过参数构建core
323
+ if int(build_mode) < 2:
324
+ print('开始构建core:' + core_job + ' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
325
+ rs = do_job(j_server, core_job, core_param_branch, cd_env, core_timeout)
326
+ if rs is False:
327
+ print('构建core失败,结束')
328
+ exit(1)
329
+ time.sleep(10)
330
+
331
+ if int(build_mode) < 3:
332
+ print('开始构建api:' + api_job + ' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
333
+ rs = do_job(j_server, api_job, api_param_branch, cd_env, api_timeout)
334
+ # 如果构建api失败,再次构建
335
+ # if rs is False:
336
+ # print('第一次构建api失败,再次构建')
337
+ # rs = do_job_1(j_server, api_job, api_param_branch, cd_env, api_timeout)
338
+ if rs is False:
339
+ print('构建api失败,结束')
340
+ exit(1)
341
+ time.sleep(2)
342
+
343
+ print('结束打包' + ' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
344
+
345
+ # ~~~~~~~~~~~~~~~~~ 开始更新 ~~~~~~~~~~~~~~~~~~~~~~~~
346
+
347
+ # 开始更新portainer
348
+ if int(build_mode) == 0:
349
+ print('\n~~~~~~~ 请自行更新portainer ~~~~~~~\n')
350
+ else:
351
+ print('\n~~~~~~~~~~~~~~\n')
352
+ print('开始更新portainer:' + portainer_api_url + ' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
353
+ # portainer配置(如果cd_env > dev15,使用update)
354
+ if cd_env.startswith('dev') and int(cd_env[3:]) > 15:
355
+ # 通过update更新
356
+ update_portainer()
357
+ else:
358
+ # 触发webhook
359
+ trigger_webhook()
360
+
361
+
362
+ exit(0)
@@ -0,0 +1,7 @@
1
+ {
2
+ "build_mode": "1",
3
+ "cd_env": "dev1",
4
+ "core_param_branch": "master",
5
+ "api_param_branch": "master",
6
+ "api_param_folder": null
7
+ }
@@ -0,0 +1,207 @@
1
+ ---
2
+ name: jenkins-deploy
3
+ description: |
4
+ Jenkins + Portainer 自动打包部署技能。通过 Python 脚本调用 Jenkins API 构建项目,并触发 Portainer Webhook/Update 完成容器更新。
5
+
6
+ 触发场景:
7
+ - 需要将代码打包部署到 dev/test 环境
8
+ - 需要触发 Jenkins 构建 core 或 api 项目
9
+ - 需要更新 Portainer 容器服务
10
+ - 需要查看或修改构建配置(分支、环境、模式)
11
+ - 定制项目的打包部署
12
+
13
+ 触发词:打包、部署、Jenkins、构建、Portainer、发布到dev、发布到test、更新环境、自动部署
14
+ ---
15
+
16
+ # Jenkins + Portainer 自动打包部署
17
+
18
+ ## 概述
19
+
20
+ 项目使用 `jenkins/` 目录下的 Python 脚本实现自动化构建部署,流程:
21
+
22
+ ```
23
+ Jenkins 构建 core → Jenkins 构建 api → Portainer 更新容器
24
+ ```
25
+
26
+ ## 文件位置
27
+
28
+ | 文件 | 作用 |
29
+ |------|------|
30
+ | `jenkins/jk_build.py` | 主构建脚本 |
31
+ | `jenkins/env_param.json` | 环境配置(Jenkins/Portainer 连接参数) |
32
+ | `jenkins/last_cd_env.json` | 上次构建参数(自动保存) |
33
+
34
+ ## 构建模式
35
+
36
+ | 模式 | 说明 | 执行步骤 |
37
+ |------|------|---------|
38
+ | `0` | 只构建 | 构建 core + api,不更新 Portainer |
39
+ | `1` | 全构建+更新 | 构建 core + api → 触发 Portainer 更新 |
40
+ | `2` | 构建 api+更新 | 跳过 core,构建 api → 触发 Portainer 更新 |
41
+ | `3` | 只更新 | 不构建,直接触发 Portainer 更新 |
42
+
43
+ ## 环境支持
44
+
45
+ | 环境 | 前缀 | Jenkins Job | Portainer |
46
+ |------|------|-------------|-----------|
47
+ | 开发环境 | `dev` + 编号 | `dev-tengyun-core` / `dev-tengyun-yunshitang-api` | devops-dev.xnzn.net |
48
+ | 测试环境 | `test` + 编号 | `test-tengyun-core` / `test-tengyun-yunshitang-api` | devops-test.xnzn.net |
49
+ | dev16+ | `dev16` ~ `dev43` | 同 dev | xnzn-dev.xnzn.net(使用 forceupdateservice) |
50
+ | dev44+ | `dev44` 及以上 | 只支持模式 0 | 需手动更新 |
51
+
52
+ ## 定制项目支持
53
+
54
+ 当指定 `api_param_folder`(定制工程文件夹名)时,Jenkins Job 路径变为:
55
+
56
+ ```
57
+ {folder_name}/dev-后端-core # 替代 dev-tengyun-core
58
+ {folder_name}/dev-后端-api # 替代 dev-tengyun-yunshitang-api
59
+ ```
60
+
61
+ ## 使用方式
62
+
63
+ ### 直接运行脚本
64
+
65
+ ```bash
66
+ cd jenkins && python jk_build.py
67
+ ```
68
+
69
+ 脚本会交互式询问:
70
+ 1. **模式**(0/1/2/3)
71
+ 2. **环境**(dev1, dev2, test1 等)
72
+ 3. **core 分支**(如 release_5.56.0)
73
+ 4. **api 分支**(如 master)
74
+ 5. **定制工程文件夹**(可选,空格或 None 跳过)
75
+
76
+ ### 通过 Claude 辅助部署
77
+
78
+ 当用户说"打包到 devX"时:
79
+
80
+ 1. **读取** `jenkins/last_cd_env.json` 获取上次构建参数
81
+ 2. **确认参数**:环境、分支、模式、是否定制项目
82
+ 3. **修改** `last_cd_env.json` 写入新参数
83
+ 4. **执行** `cd jenkins && python jk_build.py`(脚本读取预设值,用户直接回车即可)
84
+
85
+ ## 配置文件说明
86
+
87
+ ### last_cd_env.json(构建参数)
88
+
89
+ ```json
90
+ {
91
+ "build_mode": "0",
92
+ "cd_env": "dev63",
93
+ "core_param_branch": "release_5.56.0",
94
+ "api_param_branch": "master",
95
+ "api_param_folder": null
96
+ }
97
+ ```
98
+
99
+ | 字段 | 说明 |
100
+ |------|------|
101
+ | `build_mode` | 构建模式(0/1/2/3) |
102
+ | `cd_env` | 目标环境(如 dev1, test2) |
103
+ | `core_param_branch` | core 仓库分支 |
104
+ | `api_param_branch` | api 仓库分支 |
105
+ | `api_param_folder` | 定制项目文件夹(null 表示标准项目) |
106
+
107
+ ### env_param.json(环境连接配置)
108
+
109
+ 包含 Jenkins 和 Portainer 的连接参数(用户名、API Token、服务地址等),按 dev/test 环境分组。
110
+
111
+ > **注意**:此文件包含敏感凭证,不要将内容输出到对话中。
112
+
113
+ ## 构建流程详解
114
+
115
+ ```
116
+ 1. 连接 Jenkins(ci.xnzn.net)
117
+ 2. 构建 core(参数:BRANCH + VERSION=环境名)
118
+ └─ 轮询构建进度,超时 10 分钟
119
+ 3. 构建 api(参数同上)
120
+ └─ 轮询构建进度,超时 5 分钟
121
+ 4. 更新 Portainer:
122
+ ├─ dev1~15:Webhook 触发
123
+ │ └─ 获取 JWT → 查 Service ID → 获取/创建 Webhook → POST 触发
124
+ └─ dev16+:Force Update
125
+ └─ 获取 JWT → 查 Service ID → PUT forceupdateservice
126
+ ```
127
+
128
+ ## 常见操作
129
+
130
+ ### 只改了 api 代码,快速部署
131
+
132
+ ```bash
133
+ # 修改 last_cd_env.json 的 build_mode 为 "2",然后运行
134
+ cd jenkins && python jk_build.py
135
+ ```
136
+
137
+ ### 只需要更新容器(已在 Jenkins 手动构建完)
138
+
139
+ ```bash
140
+ # build_mode 设为 "3"
141
+ cd jenkins && python jk_build.py
142
+ ```
143
+
144
+ ### 部署定制项目
145
+
146
+ ```json
147
+ // last_cd_env.json
148
+ {
149
+ "api_param_folder": "leniu-tengyun-wuhanxieheyiyuan"
150
+ }
151
+ ```
152
+
153
+ ## 首次初始化(团队成员)
154
+
155
+ 当用户说"初始化部署环境"或检测到 `jenkins/` 目录不存在时,执行以下步骤:
156
+
157
+ ### 自动初始化流程
158
+
159
+ ```bash
160
+ # 1. 创建 jenkins 目录
161
+ mkdir -p jenkins
162
+
163
+ # 2. 从技能模板复制文件(SKILL_DIR 为技能目录路径)
164
+ cp .claude/skills/jenkins-deploy/assets/jk_build.py jenkins/jk_build.py
165
+ cp .claude/skills/jenkins-deploy/assets/last_cd_env.template.json jenkins/last_cd_env.json
166
+
167
+ # 3. 环境配置需要用户提供(含敏感凭证)
168
+ cp .claude/skills/jenkins-deploy/assets/env_param.template.json jenkins/env_param.json
169
+
170
+ # 4. 安装依赖
171
+ pip install python-jenkins requests
172
+ ```
173
+
174
+ ### 配置凭证
175
+
176
+ 初始化后,`jenkins/env_param.json` 中的占位符需要替换为真实值:
177
+
178
+ | 占位符 | 说明 | 获取方式 |
179
+ |--------|------|---------|
180
+ | `__JENKINS_DEV_USER__` | Jenkins dev 账号 | 向团队负责人获取 |
181
+ | `__JENKINS_DEV_TOKEN__` | Jenkins dev API Token | Jenkins → 用户 → 设置 → API Token |
182
+ | `__JENKINS_TEST_USER__` | Jenkins test 账号 | 同上 |
183
+ | `__JENKINS_TEST_TOKEN__` | Jenkins test API Token | 同上 |
184
+ | `__PORTAINER_*_USER__` | Portainer 用户名 | 向团队负责人获取 |
185
+ | `__PORTAINER_*_PWD__` | Portainer 密码 | 向团队负责人获取 |
186
+
187
+ **快捷方式**:如果团队已有成员配置好,可直接拷贝对方的 `env_param.json` 文件。
188
+
189
+ ### AI 初始化行为
190
+
191
+ 当技能被触发但 `jenkins/` 不存在时:
192
+ 1. 提示用户"检测到尚未初始化部署环境"
193
+ 2. 询问是否执行初始化
194
+ 3. 执行上述复制步骤
195
+ 4. 提示用户填写凭证(或拷贝已有配置)
196
+
197
+ ## 依赖
198
+
199
+ ```bash
200
+ pip install python-jenkins requests
201
+ ```
202
+
203
+ ## 注意
204
+
205
+ - 本技能用于 dev/test 环境部署,**不涉及生产环境**
206
+ - 如果是 Git 提交/分支管理,请使用 `git-workflow` 技能
207
+ - 如果是代码构建错误排查,请使用 `bug-detective` 技能
@@ -417,6 +417,10 @@ const skillMap = [
417
417
  {
418
418
  name: 'skill-creator',
419
419
  keywords: ['创建技能', '技能模板', '技能脚手架', 'skill scaffold', '新建skill']
420
+ },
421
+ {
422
+ name: 'jenkins-deploy',
423
+ keywords: ['打包', '部署', 'Jenkins', '构建', 'Portainer', '发布到dev', '发布到test', '更新环境', '自动部署']
420
424
  }
421
425
  ];
422
426
 
@@ -87,6 +87,7 @@ alwaysApply: true
87
87
  | 多租户/租户隔离/TenantEntity/tenantId/SaaS | `.cursor/skills/tenant-management/SKILL.md` |
88
88
  | Element UI/前端组件/el-table/el-form/管理页面 | `.cursor/skills/ui-pc/SKILL.md` |
89
89
  | Vuex/store/mapState/mapActions/状态管理 | `.cursor/skills/store-pc/SKILL.md` |
90
+ | 打包/部署/Jenkins/构建/Portainer/发布到dev/发布到test/自动部署 | `.cursor/skills/jenkins-deploy/SKILL.md` |
90
91
 
91
92
  ### 执行规则
92
93
 
@@ -0,0 +1,207 @@
1
+ ---
2
+ name: jenkins-deploy
3
+ description: |
4
+ Jenkins + Portainer 自动打包部署技能。通过 Python 脚本调用 Jenkins API 构建项目,并触发 Portainer Webhook/Update 完成容器更新。
5
+
6
+ 触发场景:
7
+ - 需要将代码打包部署到 dev/test 环境
8
+ - 需要触发 Jenkins 构建 core 或 api 项目
9
+ - 需要更新 Portainer 容器服务
10
+ - 需要查看或修改构建配置(分支、环境、模式)
11
+ - 定制项目的打包部署
12
+
13
+ 触发词:打包、部署、Jenkins、构建、Portainer、发布到dev、发布到test、更新环境、自动部署
14
+ ---
15
+
16
+ # Jenkins + Portainer 自动打包部署
17
+
18
+ ## 概述
19
+
20
+ 项目使用 `jenkins/` 目录下的 Python 脚本实现自动化构建部署,流程:
21
+
22
+ ```
23
+ Jenkins 构建 core → Jenkins 构建 api → Portainer 更新容器
24
+ ```
25
+
26
+ ## 文件位置
27
+
28
+ | 文件 | 作用 |
29
+ |------|------|
30
+ | `jenkins/jk_build.py` | 主构建脚本 |
31
+ | `jenkins/env_param.json` | 环境配置(Jenkins/Portainer 连接参数) |
32
+ | `jenkins/last_cd_env.json` | 上次构建参数(自动保存) |
33
+
34
+ ## 构建模式
35
+
36
+ | 模式 | 说明 | 执行步骤 |
37
+ |------|------|---------|
38
+ | `0` | 只构建 | 构建 core + api,不更新 Portainer |
39
+ | `1` | 全构建+更新 | 构建 core + api → 触发 Portainer 更新 |
40
+ | `2` | 构建 api+更新 | 跳过 core,构建 api → 触发 Portainer 更新 |
41
+ | `3` | 只更新 | 不构建,直接触发 Portainer 更新 |
42
+
43
+ ## 环境支持
44
+
45
+ | 环境 | 前缀 | Jenkins Job | Portainer |
46
+ |------|------|-------------|-----------|
47
+ | 开发环境 | `dev` + 编号 | `dev-tengyun-core` / `dev-tengyun-yunshitang-api` | devops-dev.xnzn.net |
48
+ | 测试环境 | `test` + 编号 | `test-tengyun-core` / `test-tengyun-yunshitang-api` | devops-test.xnzn.net |
49
+ | dev16+ | `dev16` ~ `dev43` | 同 dev | xnzn-dev.xnzn.net(使用 forceupdateservice) |
50
+ | dev44+ | `dev44` 及以上 | 只支持模式 0 | 需手动更新 |
51
+
52
+ ## 定制项目支持
53
+
54
+ 当指定 `api_param_folder`(定制工程文件夹名)时,Jenkins Job 路径变为:
55
+
56
+ ```
57
+ {folder_name}/dev-后端-core # 替代 dev-tengyun-core
58
+ {folder_name}/dev-后端-api # 替代 dev-tengyun-yunshitang-api
59
+ ```
60
+
61
+ ## 使用方式
62
+
63
+ ### 直接运行脚本
64
+
65
+ ```bash
66
+ cd jenkins && python jk_build.py
67
+ ```
68
+
69
+ 脚本会交互式询问:
70
+ 1. **模式**(0/1/2/3)
71
+ 2. **环境**(dev1, dev2, test1 等)
72
+ 3. **core 分支**(如 release_5.56.0)
73
+ 4. **api 分支**(如 master)
74
+ 5. **定制工程文件夹**(可选,空格或 None 跳过)
75
+
76
+ ### 通过 Claude 辅助部署
77
+
78
+ 当用户说"打包到 devX"时:
79
+
80
+ 1. **读取** `jenkins/last_cd_env.json` 获取上次构建参数
81
+ 2. **确认参数**:环境、分支、模式、是否定制项目
82
+ 3. **修改** `last_cd_env.json` 写入新参数
83
+ 4. **执行** `cd jenkins && python jk_build.py`(脚本读取预设值,用户直接回车即可)
84
+
85
+ ## 配置文件说明
86
+
87
+ ### last_cd_env.json(构建参数)
88
+
89
+ ```json
90
+ {
91
+ "build_mode": "0",
92
+ "cd_env": "dev63",
93
+ "core_param_branch": "release_5.56.0",
94
+ "api_param_branch": "master",
95
+ "api_param_folder": null
96
+ }
97
+ ```
98
+
99
+ | 字段 | 说明 |
100
+ |------|------|
101
+ | `build_mode` | 构建模式(0/1/2/3) |
102
+ | `cd_env` | 目标环境(如 dev1, test2) |
103
+ | `core_param_branch` | core 仓库分支 |
104
+ | `api_param_branch` | api 仓库分支 |
105
+ | `api_param_folder` | 定制项目文件夹(null 表示标准项目) |
106
+
107
+ ### env_param.json(环境连接配置)
108
+
109
+ 包含 Jenkins 和 Portainer 的连接参数(用户名、API Token、服务地址等),按 dev/test 环境分组。
110
+
111
+ > **注意**:此文件包含敏感凭证,不要将内容输出到对话中。
112
+
113
+ ## 构建流程详解
114
+
115
+ ```
116
+ 1. 连接 Jenkins(ci.xnzn.net)
117
+ 2. 构建 core(参数:BRANCH + VERSION=环境名)
118
+ └─ 轮询构建进度,超时 10 分钟
119
+ 3. 构建 api(参数同上)
120
+ └─ 轮询构建进度,超时 5 分钟
121
+ 4. 更新 Portainer:
122
+ ├─ dev1~15:Webhook 触发
123
+ │ └─ 获取 JWT → 查 Service ID → 获取/创建 Webhook → POST 触发
124
+ └─ dev16+:Force Update
125
+ └─ 获取 JWT → 查 Service ID → PUT forceupdateservice
126
+ ```
127
+
128
+ ## 常见操作
129
+
130
+ ### 只改了 api 代码,快速部署
131
+
132
+ ```bash
133
+ # 修改 last_cd_env.json 的 build_mode 为 "2",然后运行
134
+ cd jenkins && python jk_build.py
135
+ ```
136
+
137
+ ### 只需要更新容器(已在 Jenkins 手动构建完)
138
+
139
+ ```bash
140
+ # build_mode 设为 "3"
141
+ cd jenkins && python jk_build.py
142
+ ```
143
+
144
+ ### 部署定制项目
145
+
146
+ ```json
147
+ // last_cd_env.json
148
+ {
149
+ "api_param_folder": "leniu-tengyun-wuhanxieheyiyuan"
150
+ }
151
+ ```
152
+
153
+ ## 首次初始化(团队成员)
154
+
155
+ 当用户说"初始化部署环境"或检测到 `jenkins/` 目录不存在时,执行以下步骤:
156
+
157
+ ### 自动初始化流程
158
+
159
+ ```bash
160
+ # 1. 创建 jenkins 目录
161
+ mkdir -p jenkins
162
+
163
+ # 2. 从技能模板复制文件(SKILL_DIR 为技能目录路径)
164
+ cp .claude/skills/jenkins-deploy/assets/jk_build.py jenkins/jk_build.py
165
+ cp .claude/skills/jenkins-deploy/assets/last_cd_env.template.json jenkins/last_cd_env.json
166
+
167
+ # 3. 环境配置需要用户提供(含敏感凭证)
168
+ cp .claude/skills/jenkins-deploy/assets/env_param.template.json jenkins/env_param.json
169
+
170
+ # 4. 安装依赖
171
+ pip install python-jenkins requests
172
+ ```
173
+
174
+ ### 配置凭证
175
+
176
+ 初始化后,`jenkins/env_param.json` 中的占位符需要替换为真实值:
177
+
178
+ | 占位符 | 说明 | 获取方式 |
179
+ |--------|------|---------|
180
+ | `__JENKINS_DEV_USER__` | Jenkins dev 账号 | 向团队负责人获取 |
181
+ | `__JENKINS_DEV_TOKEN__` | Jenkins dev API Token | Jenkins → 用户 → 设置 → API Token |
182
+ | `__JENKINS_TEST_USER__` | Jenkins test 账号 | 同上 |
183
+ | `__JENKINS_TEST_TOKEN__` | Jenkins test API Token | 同上 |
184
+ | `__PORTAINER_*_USER__` | Portainer 用户名 | 向团队负责人获取 |
185
+ | `__PORTAINER_*_PWD__` | Portainer 密码 | 向团队负责人获取 |
186
+
187
+ **快捷方式**:如果团队已有成员配置好,可直接拷贝对方的 `env_param.json` 文件。
188
+
189
+ ### AI 初始化行为
190
+
191
+ 当技能被触发但 `jenkins/` 不存在时:
192
+ 1. 提示用户"检测到尚未初始化部署环境"
193
+ 2. 询问是否执行初始化
194
+ 3. 执行上述复制步骤
195
+ 4. 提示用户填写凭证(或拷贝已有配置)
196
+
197
+ ## 依赖
198
+
199
+ ```bash
200
+ pip install python-jenkins requests
201
+ ```
202
+
203
+ ## 注意
204
+
205
+ - 本技能用于 dev/test 环境部署,**不涉及生产环境**
206
+ - 如果是 Git 提交/分支管理,请使用 `git-workflow` 技能
207
+ - 如果是代码构建错误排查,请使用 `bug-detective` 技能
package/AGENTS.md CHANGED
@@ -94,6 +94,7 @@
94
94
  | `collaborating-with-gemini` | 与 Gemini 协同设计 |
95
95
  | `codex-code-review` | 代码审查 |
96
96
  | `leniu-marketing-scenario` | 营销规则开发(折扣/满减/限额/限次/补贴/充值赠送/扣款/就餐规则) |
97
+ | `jenkins-deploy` | 打包部署(Jenkins 构建 + Portainer 更新,dev/test 环境) |
97
98
 
98
99
  ### OpenSpec 规格驱动开发技能(SDD)
99
100
 
package/bin/index.js CHANGED
@@ -586,6 +586,24 @@ function showDoneHint(toolKey) {
586
586
  console.log(` 2. 在 Codex 中使用 .codex/skills/ 下的技能`);
587
587
  console.log('');
588
588
  }
589
+ showJenkinsHint();
590
+ }
591
+
592
+ /** 检测 jenkins/ 是否已初始化,提示用户配置部署环境 */
593
+ function showJenkinsHint() {
594
+ const jenkinsDir = path.join(targetDir, 'jenkins');
595
+ const envParamFile = path.join(jenkinsDir, 'env_param.json');
596
+ const skillAssetsDir = path.join(targetDir, '.claude', 'skills', 'jenkins-deploy', 'assets');
597
+
598
+ // 技能 assets 存在但 jenkins/ 未初始化
599
+ if (fs.existsSync(skillAssetsDir) && !fs.existsSync(envParamFile)) {
600
+ console.log(fmt('yellow', fmt('bold', '📦 Jenkins 部署环境未初始化')));
601
+ console.log(` 项目包含 ${fmt('bold', 'jenkins-deploy')} 技能,但 jenkins/ 目录尚未配置。`);
602
+ console.log(` 初始化方式:`);
603
+ console.log(` 方式 1:在 AI 对话中说 ${fmt('bold', '"初始化部署环境"')}`);
604
+ console.log(` 方式 2:从团队成员处拷贝 ${fmt('bold', 'jenkins/')} 目录`);
605
+ console.log('');
606
+ }
589
607
  }
590
608
 
591
609
  function run(selectedTool) {
@@ -748,6 +766,7 @@ function runUpdate(selectedTool) {
748
766
  console.log(` 强制更新保留文件: ${fmt('bold', hintCmd('update --force'))}`);
749
767
  }
750
768
  console.log('');
769
+ showJenkinsHint();
751
770
 
752
771
  if (totalFailed > 0) process.exitCode = 1;
753
772
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-engineering-init",
3
- "version": "1.17.0",
3
+ "version": "1.17.1",
4
4
  "description": "AI 工程化配置初始化工具 — 一键为 Claude Code、OpenAI Codex 等 AI 工具初始化 Skills 和项目规范",
5
5
  "keywords": [
6
6
  "claude-code",