zero-ai 1.0.85 → 1.0.87
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.
- package/.r2mo/.obsidian/plugins/terminal/manifest.json +14 -0
- package/.r2mo/.obsidian/plugins/terminal/styles.css +32 -0
- package/.r2mo/task/2026-03-07/2026-03-07.12-26-02-TASK@/345/274/200/345/217/221/346/226/260/345/221/275/344/273/244 ai ex-menu.md" +310 -0
- package/.r2mo/task/task-001.md +28 -294
- package/package.json +1 -1
- package/src/commander/auth.json +6 -0
- package/src/commander-ai/fn.ex.auth.js +136 -0
- package/src/commander-ai/fn.source.apply.js +55 -8
- package/src/commander-ai/index.js +2 -6
- package/.r2mo/.obsidian/themes/Comfort/manifest.json +0 -11
- package/.r2mo/.obsidian/themes/Comfort/theme.css +0 -218
- package/.r2mo/.obsidian/themes/Serenity/manifest.json +0 -7
- package/.r2mo/.obsidian/themes/Serenity/theme.css +0 -7258
- package/.r2mo/.obsidian/themes/W95/manifest.json +0 -8
- package/.r2mo/.obsidian/themes/W95/theme.css +0 -768
- package/src/commander/ex-api.json +0 -8
- package/src/commander/ex-crud.json +0 -1
- package/src/commander/ex-perm.json +0 -8
- package/src/commander-ai/fn.ex.api.js +0 -995
- package/src/commander-ai/fn.ex.crud.js +0 -545
- package/src/commander-ai/fn.ex.perm.js +0 -207
package/.r2mo/task/task-001.md
CHANGED
|
@@ -1,310 +1,44 @@
|
|
|
1
1
|
---
|
|
2
|
-
runAt: 2026-03-
|
|
3
|
-
|
|
4
|
-
title: 开发新命令 ai ex-menu
|
|
5
|
-
status: completed
|
|
2
|
+
runAt: 2026-03-07.12-26-02
|
|
3
|
+
title: 开发新命令 ai auth
|
|
6
4
|
---
|
|
7
|
-
|
|
8
|
-
## 任务目标
|
|
9
|
-
|
|
10
|
-
将以下参考的执行记录转换成自动化命令 ai ex-menu
|
|
11
|
-
|
|
12
|
-
## 实施结果
|
|
13
|
-
|
|
14
|
-
### 已完成功能
|
|
15
|
-
|
|
16
|
-
#### 1. 新增 `ai ex-menu` 命令
|
|
17
|
-
|
|
18
|
-
**命令文件**:
|
|
19
|
-
- `src/commander/ex-menu.json` - 命令配置
|
|
20
|
-
- `src/commander-ai/fn.ex.menu.js` - 命令实现
|
|
21
|
-
- `src/commander-ai/index.js` - 已注册 executor
|
|
22
|
-
|
|
23
|
-
**执行流程**:
|
|
24
|
-
1. 加载 `.r2mo/app.env` 环境变量
|
|
25
|
-
2. 连接数据库查询 `S_ROLE` 表
|
|
26
|
-
3. 用户单选角色(提示:菜单全开放模式,建议选择开发人员角色)
|
|
27
|
-
4. 查询 `X_MENU` 生成两个 JSON 文件:
|
|
28
|
-
- `src/main/resources/init/permission/ui.menu/{CODE}.json` - NAME 数组
|
|
29
|
-
- `src/main/resources/init/permission/ui.menu/role/{CODE}.json` - 层级文本数组(带缩进)
|
|
30
|
-
|
|
31
|
-
**特性**:
|
|
32
|
-
- 自动过滤超级管理员角色(该角色在 `ai ex-api` 中固定输出)
|
|
33
|
-
- 支持任意角色 CODE 的文件生成
|
|
34
|
-
- 层级文本按 ORDER、NAME 排序,缩进规则:每层级 4 个空格
|
|
35
|
-
|
|
36
|
-
#### 2. 增强 `ai ex-api` 命令
|
|
37
|
-
|
|
38
|
-
**修改内容**:
|
|
39
|
-
- 超级管理员角色(ID: `e501b47a-c08b-4c83-b12b-95ad82873e96`)始终自动输出到 `ZERO_MODULE` 固定路径,不需要用户选择
|
|
40
|
-
- 用户选择角色时自动过滤掉超级管理员,避免重复
|
|
41
|
-
- 汇总输出增强:
|
|
42
|
-
- 🔒 固定输出角色:超级管理员(自动包含)
|
|
43
|
-
- 👤 用户选择角色:显示每个角色的 NAME、CODE、ID
|
|
44
|
-
- 📁 输出文件路径分流:
|
|
45
|
-
- 超级管理员 → `ZERO_MODULE/.../RBAC_ROLE/ADMIN.SUPER/`
|
|
46
|
-
- 其他角色 → `src/main/resources/init/oob/role/{CODE}/`
|
|
47
|
-
|
|
48
|
-
**输出示例**:
|
|
49
|
-
```
|
|
50
|
-
[ex-api] ✅ 执行完成(幂等)
|
|
51
|
-
[ex-api] 📋 汇总:
|
|
52
|
-
[ex-api] 🔑 ACTION_ID = xxx
|
|
53
|
-
[ex-api] 🔑 RESOURCE_ID = xxx
|
|
54
|
-
[ex-api] 🔑 PERMISSION_ID = xxx
|
|
55
|
-
[ex-api] 👥 授权角色总数 = 2
|
|
56
|
-
[ex-api] 🔒 固定输出角色:
|
|
57
|
-
[ex-api] [1] 超级管理员 (CODE: ADMIN.SUPER, ID: e501b47a-...)
|
|
58
|
-
[ex-api] 👤 用户选择角色:
|
|
59
|
-
[ex-api] [1] 开发人员 (CODE: ADMIN.DEVELOPER, ID: xxx)
|
|
60
|
-
[ex-api] 📁 RBAC_RESOURCE = /path/to/...
|
|
61
|
-
[ex-api] 📁 RBAC_ROLE = ...ZERO_MODULE.../ADMIN.SUPER/...
|
|
62
|
-
[ex-api] 📁 RBAC_ROLE = .../init/oob/role/ADMIN.DEVELOPER/...
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## 前置条件
|
|
66
|
-
|
|
67
|
-
### 环境变量配置
|
|
68
|
-
数据库连接信息存储在 `.r2mo/app.env` 文件中:
|
|
69
|
-
```bash
|
|
70
|
-
export Z_DB_HOST="127.0.0.1"
|
|
71
|
-
export Z_DB_PORT="3306"
|
|
72
|
-
export Z_DB_USERNAME="root"
|
|
73
|
-
export Z_DB_PASSWORD="pl,okmijn123"
|
|
74
|
-
export Z_DBS_INSTANCE="DB_HMS_001_APP" # 业务数据库实例名
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### 数据库表结构
|
|
78
|
-
表名:`X_MENU`(位于 `DB_HMS_001_APP` 数据库)
|
|
79
|
-
|
|
80
|
-
关键字段:
|
|
81
|
-
- `ID` (varchar(36)) - 主键
|
|
82
|
-
- `NAME` (varchar(255)) - 菜单唯一标识符,如 `zero.desktop`, `hms.order.main`
|
|
83
|
-
- `TEXT` (varchar(255)) - 菜单中文显示文本,如 "工作台", "新增预定"
|
|
84
|
-
- `LEVEL` (bigint) - 菜单层级(1=一级菜单, 2=二级菜单, 3=三级菜单, 4=四级菜单)
|
|
85
|
-
- `PARENT_ID` (varchar(36)) - 父菜单 ID(NULL 表示顶级菜单)
|
|
86
|
-
- `ORDER` (bigint) - 同级菜单排序序号
|
|
87
|
-
- `SIGMA` (varchar(128)) - 租户/应用标识符
|
|
88
|
-
- `APP_ID` (varchar(36)) - 应用 ID
|
|
89
|
-
- `ACTIVE` (bit(1)) - 是否启用
|
|
90
|
-
|
|
91
5
|
## 执行步骤
|
|
92
6
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
### 步骤 2: 查询数据库获取菜单 NAME 列表
|
|
99
|
-
查询所有菜单的 NAME 字段,按 ORDER 和 NAME 排序:
|
|
100
|
-
```bash
|
|
101
|
-
mysql -h"$Z_DB_HOST" -P"$Z_DB_PORT" -u"$Z_DB_USERNAME" -p"$Z_DB_PASSWORD" \
|
|
102
|
-
DB_HMS_001_APP \
|
|
103
|
-
-e "SELECT NAME FROM X_MENU ORDER BY \`ORDER\`, LEVEL, NAME;" \
|
|
104
|
-
2>/dev/null | tail -n +2
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
**输出示例**:
|
|
108
|
-
```
|
|
109
|
-
zero.desktop
|
|
110
|
-
zero.desktop.my
|
|
111
|
-
zero.desktop.my.todo-pending
|
|
112
|
-
hms.order
|
|
113
|
-
hms.order.main
|
|
114
|
-
...
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### 步骤 3: 生成 ADMIN.DEVELOPER.json(NAME 数组)
|
|
118
|
-
将查询结果转换为 JSON 数组格式:
|
|
119
|
-
```bash
|
|
120
|
-
mysql -h"$Z_DB_HOST" -P"$Z_DB_PORT" -u"$Z_DB_USERNAME" -p"$Z_DB_PASSWORD" \
|
|
121
|
-
DB_HMS_001_APP \
|
|
122
|
-
-e "SELECT NAME FROM X_MENU ORDER BY \`ORDER\`, LEVEL, NAME;" \
|
|
123
|
-
2>/dev/null | tail -n +2 | \
|
|
124
|
-
jq -R -s -c 'split("\n") | map(select(length > 0))'
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
**生成文件格式**:
|
|
128
|
-
```json
|
|
129
|
-
{
|
|
130
|
-
"name" : [ "zero.desktop", "zero.desktop.my", "zero.desktop.my.todo-pending", ... ]
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**写入文件**:
|
|
135
|
-
```
|
|
136
|
-
src/main/resources/init/permission/ui.menu/ADMIN.DEVELOPER.json
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### 步骤 4: 查询数据库获取层级菜单结构
|
|
140
|
-
查询菜单的完整层级信息(ID, PARENT_ID, NAME, TEXT, ORDER):
|
|
141
|
-
```bash
|
|
142
|
-
mysql -h"$Z_DB_HOST" -P"$Z_DB_PORT" -u"$Z_DB_USERNAME" -p"$Z_DB_PASSWORD" \
|
|
143
|
-
DB_HMS_001_APP -B \
|
|
144
|
-
-e "SELECT ID, IFNULL(PARENT_ID,''), NAME, IFNULL(TEXT,NAME), IFNULL(\`ORDER\`,0) FROM X_MENU;" \
|
|
145
|
-
2>/dev/null
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**输出示例**:
|
|
149
|
-
```
|
|
150
|
-
ID PARENT_ID NAME TEXT ORDER
|
|
151
|
-
8aca2f19-9845-495a-bf39-b77514a20da5 zero.desktop 工作台 10000
|
|
152
|
-
613a2ae1-e066-4454-b5fb-efdcd6578f51 8aca2f19-9845-495a-bf39-b77514a20da5 zero.desktop.my 工作台 2000
|
|
153
|
-
...
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### 步骤 5: 生成 role/ADMIN.DEVELOPER.json(层级文本数组)
|
|
157
|
-
使用 Python 脚本构建父子关系树,按层级生成带缩进的中文菜单文本:
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
mysql -h"$Z_DB_HOST" -P"$Z_DB_PORT" -u"$Z_DB_USERNAME" -p"$Z_DB_PASSWORD" \
|
|
161
|
-
DB_HMS_001_APP -B \
|
|
162
|
-
-e "SELECT ID, IFNULL(PARENT_ID,''), NAME, IFNULL(TEXT,NAME), IFNULL(\`ORDER\`,0) FROM X_MENU;" \
|
|
163
|
-
2>/dev/null | python3 -c '
|
|
164
|
-
import sys, json
|
|
165
|
-
|
|
166
|
-
# 读取数据库输出
|
|
167
|
-
rows = []
|
|
168
|
-
lines = sys.stdin.read().splitlines()
|
|
169
|
-
for ln in lines[1:]: # 跳过表头
|
|
170
|
-
parts = ln.split("\t")
|
|
171
|
-
if len(parts) >= 5:
|
|
172
|
-
rows.append(parts[:5])
|
|
173
|
-
|
|
174
|
-
# 构建菜单字典(按 ID 索引)
|
|
175
|
-
by_id = {
|
|
176
|
-
r[0]: {
|
|
177
|
-
"id": r[0],
|
|
178
|
-
"pid": r[1] or None, # 父 ID(空字符串转为 None)
|
|
179
|
-
"name": r[2],
|
|
180
|
-
"text": r[3],
|
|
181
|
-
"order": int(r[4])
|
|
182
|
-
}
|
|
183
|
-
for r in rows
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
# 构建父子关系映射
|
|
187
|
-
children = {}
|
|
188
|
-
for n in by_id.values():
|
|
189
|
-
children.setdefault(n["pid"], []).append(n)
|
|
190
|
-
|
|
191
|
-
# 对每个父节点的子节点按 ORDER 和 NAME 排序
|
|
192
|
-
for k in children:
|
|
193
|
-
children[k].sort(key=lambda x: (x["order"], x["name"]))
|
|
194
|
-
|
|
195
|
-
# 递归遍历生成带缩进的文本数组
|
|
196
|
-
out = []
|
|
197
|
-
def walk(pid, depth):
|
|
198
|
-
for n in children.get(pid, []):
|
|
199
|
-
# 缩进规则:每层级增加 4 个空格(depth-1 是因为顶级菜单不缩进)
|
|
200
|
-
out.append((" " * (4 * (depth - 1))) + n["text"])
|
|
201
|
-
walk(n["id"], depth + 1)
|
|
202
|
-
|
|
203
|
-
# 从顶级菜单(pid=None)开始遍历
|
|
204
|
-
walk(None, 1)
|
|
205
|
-
|
|
206
|
-
# 输出 JSON 数组
|
|
207
|
-
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
208
|
-
'
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**生成文件格式**:
|
|
212
|
-
```json
|
|
213
|
-
[
|
|
214
|
-
"工作台",
|
|
215
|
-
" 工作台",
|
|
216
|
-
" 我的待办",
|
|
217
|
-
" 个人报表",
|
|
218
|
-
" 帮助",
|
|
219
|
-
"酒店管理",
|
|
220
|
-
" 预定",
|
|
221
|
-
" 新增预定",
|
|
222
|
-
...
|
|
223
|
-
]
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
**缩进规则**:
|
|
227
|
-
- 一级菜单(LEVEL=1):无缩进
|
|
228
|
-
- 二级菜单(LEVEL=2):4 个空格
|
|
229
|
-
- 三级菜单(LEVEL=3):8 个空格
|
|
230
|
-
- 四级菜单(LEVEL=4):12 个空格
|
|
231
|
-
|
|
232
|
-
**写入文件**:
|
|
233
|
-
```
|
|
234
|
-
src/main/resources/init/permission/ui.menu/role/ADMIN.DEVELOPER.json
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## 数据验证
|
|
238
|
-
|
|
239
|
-
### 验证点 1: NAME 数组完整性
|
|
240
|
-
检查 `ADMIN.DEVELOPER.json` 中的 name 数组是否包含数据库中所有菜单:
|
|
241
|
-
```bash
|
|
242
|
-
# 统计数据库中的菜单数量
|
|
243
|
-
mysql -h"$Z_DB_HOST" -P"$Z_DB_PORT" -u"$Z_DB_USERNAME" -p"$Z_DB_PASSWORD" \
|
|
244
|
-
DB_HMS_001_APP \
|
|
245
|
-
-e "SELECT COUNT(*) FROM X_MENU;" 2>/dev/null
|
|
246
|
-
|
|
247
|
-
# 统计 JSON 文件中的菜单数量
|
|
248
|
-
jq '.name | length' src/main/resources/init/permission/ui.menu/ADMIN.DEVELOPER.json
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### 验证点 2: 层级结构正确性
|
|
252
|
-
检查 `role/ADMIN.DEVELOPER.json` 中的缩进是否符合父子关系:
|
|
253
|
-
- 子菜单的缩进必须比父菜单多 4 个空格
|
|
254
|
-
- 同级菜单的缩进必须相同
|
|
255
|
-
|
|
256
|
-
### 验证点 3: 排序正确性
|
|
257
|
-
菜单顺序应按照数据库中的 `ORDER` 字段排序,同 ORDER 值按 NAME 字母序排序。
|
|
258
|
-
|
|
259
|
-
## 关键注意事项
|
|
7
|
+
1. 检查是否配置了 `ZERO_MODULE` 环境变量,若没有配置则直接退出。
|
|
8
|
+
2. 配置了环境变量之后,检查这个目录下所有 `zero-exmoudle-???` 中的 `src/main/resources/` 下是否包含了 `security/RBAC_ROLE/ADMIN.SUPER` 目录,若包含则将里面的 `*.yml` 文件记录。
|
|
9
|
+
3. 检查当前项目(Maven中)是否包含 `src/main/resources/init/oob/RBAC_ROLE`,若无也退出。
|
|
10
|
+
4. 枚举 `src/main/resources/init/oob/RBAC_ROLE` 下的角色目录让用户选择(默认全选),询问用户是否覆盖,若选择覆盖则直接将源头的 `*.yml` 拷贝到目标目录覆盖掉,目标目录为 `src/main/resources/init/oob/RBAC_ROLE/{用户选择的目录}`
|
|
260
11
|
|
|
261
|
-
|
|
262
|
-
- `PARENT_ID` 为 NULL 时表示顶级菜单,需转换为空字符串或 None
|
|
263
|
-
- `TEXT` 为 NULL 时使用 `NAME` 作为显示文本
|
|
264
|
-
- `ORDER` 为 NULL 时默认为 0
|
|
12
|
+
## Changes
|
|
265
13
|
|
|
266
|
-
|
|
267
|
-
- 主排序:`ORDER` 字段(升序)
|
|
268
|
-
- 次排序:`LEVEL` 字段(升序)
|
|
269
|
-
- 第三排序:`NAME` 字段(字母序)
|
|
14
|
+
### 2026-03-07
|
|
270
15
|
|
|
271
|
-
|
|
272
|
-
- 使用递归算法从顶级菜单(PARENT_ID=NULL)开始遍历
|
|
273
|
-
- 每递归一层,缩进增加 4 个空格
|
|
274
|
-
- 深度从 1 开始(顶级菜单 depth=1,无缩进)
|
|
16
|
+
**实现完成**
|
|
275
17
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
18
|
+
创建文件:
|
|
19
|
+
- `src/commander/auth.json` - 命令配置
|
|
20
|
+
- `src/commander-ai/fn.ex.auth.js` - 命令执行器
|
|
279
21
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
- 使用 `-B` 参数输出 TSV 格式(制表符分隔)
|
|
283
|
-
- 使用 `tail -n +2` 跳过表头行
|
|
22
|
+
更新文件:
|
|
23
|
+
- `src/commander-ai/index.js` - 注册 executeAuth
|
|
284
24
|
|
|
285
|
-
|
|
25
|
+
**实现细节**
|
|
286
26
|
|
|
287
|
-
|
|
288
|
-
- **ADMIN.DEVELOPER.json**:更新了 196 个菜单项的 NAME 数组
|
|
289
|
-
- **role/ADMIN.DEVELOPER.json**:更新了 196 个菜单项的中文显示文本(带层级缩进)
|
|
27
|
+
路径规律:`{ZERO_MODULE}/{mod}/{mod}-domain/src/main/resources/plugins/{mod}/security/RBAC_ROLE/ADMIN.SUPER/*.yml`
|
|
290
28
|
|
|
291
|
-
|
|
292
|
-
- 移除已废弃的菜单前缀(`hm.*` → `hms.*`)
|
|
293
|
-
- 新增流程管理模块(`zero.wm.*`)
|
|
294
|
-
- 新增权限细分配置(`zero.ssm.rbac.authority.*`)
|
|
295
|
-
- 新增仪表盘快捷入口(`zero.bsm.dash.*`, `zero.cm.dash.*`)
|
|
296
|
-
- 财务模块结构调整(`zero.fms.admin.*` → `zero.fms.process.*`)
|
|
29
|
+
例如:`zero-exmodule-rbac/zero-exmodule-rbac-domain/src/main/resources/plugins/zero-exmodule-rbac/security/RBAC_ROLE/ADMIN.SUPER/*.yml`
|
|
297
30
|
|
|
298
|
-
|
|
31
|
+
扫描逻辑:
|
|
32
|
+
1. 读取 ZERO_MODULE 下所有 `zero-exmodule-*` 目录
|
|
33
|
+
2. 按固定规律拼接路径:`{mod}/{mod}-domain/src/main/resources/plugins/{mod}/security/RBAC_ROLE/ADMIN.SUPER/`
|
|
34
|
+
3. 扫描该目录下所有 `*.yml` 文件
|
|
299
35
|
|
|
300
|
-
|
|
301
|
-
-
|
|
302
|
-
-
|
|
303
|
-
-
|
|
36
|
+
交互优化:
|
|
37
|
+
- 显示扫描统计(模块数、命中数、文件数)
|
|
38
|
+
- checkbox 多选禁用循环滚动(`loop: false`)
|
|
39
|
+
- 默认全选所有角色目录
|
|
40
|
+
- 二次确认覆盖操作
|
|
304
41
|
|
|
305
|
-
|
|
42
|
+
**BUG 修复**
|
|
306
43
|
|
|
307
|
-
|
|
308
|
-
- jq 1.6+
|
|
309
|
-
- Python 3.7+
|
|
310
|
-
- Bash 4.0+
|
|
44
|
+
初始实现使用递归扫描 + 深度限制(5层),但实际路径深度超过限制且有固定规律。改为直接按规律拼接路径,无需递归,性能更优。
|
package/package.json
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const Ec = require("../epic");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const inquirer = require("inquirer");
|
|
7
|
+
|
|
8
|
+
module.exports = async (options) => {
|
|
9
|
+
try {
|
|
10
|
+
Ec.execute("ai auth:从 ZERO_MODULE 扩展模块同步 RBAC 角色权限 YAML 文件");
|
|
11
|
+
|
|
12
|
+
// 1. 检查 ZERO_MODULE 环境变量
|
|
13
|
+
const zeroModule = process.env.ZERO_MODULE;
|
|
14
|
+
if (!zeroModule || !zeroModule.trim()) {
|
|
15
|
+
Ec.error("环境变量 ZERO_MODULE 未配置,无法执行同步操作");
|
|
16
|
+
Ec.info("请设置 ZERO_MODULE 环境变量指向扩展模块根目录");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(zeroModule)) {
|
|
21
|
+
Ec.error(`ZERO_MODULE 路径不存在:${zeroModule}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Ec.info(`✓ ZERO_MODULE: ${zeroModule}`);
|
|
26
|
+
|
|
27
|
+
// 2. 按固定规律扫描:zero-exmodule-{name}/zero-exmodule-{name}-domain/src/main/resources/plugins/zero-exmodule-{name}/security/RBAC_ROLE/ADMIN.SUPER/*.yml
|
|
28
|
+
const moduleDirEntries = fs.readdirSync(zeroModule);
|
|
29
|
+
const exModules = moduleDirEntries.filter(name => name.startsWith("zero-exmodule-") && fs.statSync(path.join(zeroModule, name)).isDirectory());
|
|
30
|
+
|
|
31
|
+
if (exModules.length === 0) {
|
|
32
|
+
Ec.error("未找到任何 zero-exmodule-* 目录");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sourceYmlFiles = [];
|
|
37
|
+
for (const mod of exModules) {
|
|
38
|
+
const securityPath = path.join(zeroModule, mod, `${mod}-domain`, "src/main/resources/plugins", mod, "security/RBAC_ROLE/ADMIN.SUPER");
|
|
39
|
+
if (fs.existsSync(securityPath)) {
|
|
40
|
+
try {
|
|
41
|
+
const files = fs.readdirSync(securityPath).filter(f => f.endsWith(".yml"));
|
|
42
|
+
files.forEach(f => {
|
|
43
|
+
sourceYmlFiles.push({
|
|
44
|
+
module: mod,
|
|
45
|
+
file: f,
|
|
46
|
+
fullPath: path.join(securityPath, f)
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// 忽略读取错误
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (sourceYmlFiles.length === 0) {
|
|
56
|
+
Ec.error("未找到任何 security/RBAC_ROLE/ADMIN.SUPER/*.yml 文件");
|
|
57
|
+
Ec.info(`已扫描 ${exModules.length} 个模块,路径规律:{mod}/{mod}-domain/src/main/resources/plugins/{mod}/security/RBAC_ROLE/ADMIN.SUPER/`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hitModules = new Set(sourceYmlFiles.map(item => item.module)).size;
|
|
62
|
+
Ec.info(`✓ 扫描完成:${exModules.length} 个模块,${hitModules} 个命中,${sourceYmlFiles.length} 个 YAML 文件`);
|
|
63
|
+
Ec.info(`✓ 找到 ${sourceYmlFiles.length} 个源 YAML 文件:`);
|
|
64
|
+
sourceYmlFiles.forEach(({ module: mod, file }) => {
|
|
65
|
+
Ec.info(` [${mod}] ${file}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// 3. 检查当前项目的目标目录
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const targetBase = path.resolve(cwd, "src/main/resources/init/oob/RBAC_ROLE");
|
|
71
|
+
if (!fs.existsSync(targetBase)) {
|
|
72
|
+
Ec.error(`目标目录不存在:${targetBase}`);
|
|
73
|
+
Ec.info("请在包含 src/main/resources/init/oob/RBAC_ROLE 的 Maven 项目根目录下执行");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. 枚举目标角色目录让用户选择
|
|
78
|
+
const roleDirs = fs.readdirSync(targetBase).filter(entry => {
|
|
79
|
+
return fs.statSync(path.join(targetBase, entry)).isDirectory();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (roleDirs.length === 0) {
|
|
83
|
+
Ec.error(`目标目录下没有角色子目录:${targetBase}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Ec.info(`\n目标目录:${targetBase}`);
|
|
88
|
+
|
|
89
|
+
const { selectedRoles } = await inquirer.prompt([
|
|
90
|
+
{
|
|
91
|
+
type: "checkbox",
|
|
92
|
+
name: "selectedRoles",
|
|
93
|
+
message: "请选择要同步的角色目录(默认全选):",
|
|
94
|
+
choices: roleDirs,
|
|
95
|
+
default: roleDirs,
|
|
96
|
+
loop: false
|
|
97
|
+
}
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
if (!selectedRoles || selectedRoles.length === 0) {
|
|
101
|
+
Ec.info("未选择任何角色目录,退出");
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { confirmOverwrite } = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: "confirm",
|
|
108
|
+
name: "confirmOverwrite",
|
|
109
|
+
message: `是否覆盖以上 ${selectedRoles.length} 个角色目录中的 YAML 文件?`,
|
|
110
|
+
default: false
|
|
111
|
+
}
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
if (!confirmOverwrite) {
|
|
115
|
+
Ec.info("取消操作,退出");
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 5. 执行拷贝
|
|
120
|
+
let copiedCount = 0;
|
|
121
|
+
for (const role of selectedRoles) {
|
|
122
|
+
const roleTargetDir = path.join(targetBase, role);
|
|
123
|
+
for (const { file, fullPath: srcPath } of sourceYmlFiles) {
|
|
124
|
+
const destPath = path.join(roleTargetDir, file);
|
|
125
|
+
fs.copyFileSync(srcPath, destPath);
|
|
126
|
+
Ec.info(` [${role}] ${file} -> 已覆盖`);
|
|
127
|
+
copiedCount++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Ec.info(`\n✓ 同步完成,共拷贝 ${copiedCount} 个文件到 ${selectedRoles.length} 个角色目录`);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
Ec.error(`ai auth 执行失败:${err.message}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
@@ -89,25 +89,72 @@ module.exports = async (options) => {
|
|
|
89
89
|
Ec.info(` ${index + 1}. ${file}`);
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
// 按前缀分组 MDC 文件
|
|
93
|
+
const prefixGroups = [
|
|
94
|
+
{ prefix: "r2-spec-", label: "核心规范" },
|
|
95
|
+
{ prefix: "r2-go-", label: "GoLang全栈开发" },
|
|
96
|
+
{ prefix: "r2-rust-", label: "Rust前端开发" },
|
|
97
|
+
{ prefix: "r2-ui", label: "AntD前端开发" },
|
|
98
|
+
{ prefix: "r2-backend-spring-", label: "Spring后端开发" },
|
|
99
|
+
{ prefix: "r2-backend-zero-", label: "Zero后端开发" },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const groupedFiles = {};
|
|
103
|
+
const matchedFiles = new Set();
|
|
104
|
+
prefixGroups.forEach(({ prefix }) => {
|
|
105
|
+
const matched = ruleFiles.filter(f => f.startsWith(prefix));
|
|
106
|
+
if (matched.length > 0) {
|
|
107
|
+
groupedFiles[prefix] = matched;
|
|
108
|
+
matched.forEach(f => matchedFiles.add(f));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const ungroupedFiles = ruleFiles.filter(f => !matchedFiles.has(f));
|
|
113
|
+
|
|
114
|
+
// 构造选择列表:前缀组(显示中文标签+数量)+ 未匹配文件并排
|
|
115
|
+
const mdcChoices = [];
|
|
116
|
+
prefixGroups.forEach(({ prefix, label }) => {
|
|
117
|
+
if (groupedFiles[prefix]) {
|
|
118
|
+
mdcChoices.push({
|
|
119
|
+
name: `${label}(${groupedFiles[prefix].length} 个文件)`,
|
|
120
|
+
value: { type: "group", prefix },
|
|
121
|
+
checked: false,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
ungroupedFiles.forEach(file => {
|
|
126
|
+
mdcChoices.push({
|
|
127
|
+
name: file,
|
|
128
|
+
value: { type: "single", file },
|
|
129
|
+
checked: false,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const { selectedChoices } = await inquirer.prompt([
|
|
93
134
|
{
|
|
94
135
|
type: "checkbox",
|
|
95
|
-
name: "
|
|
136
|
+
name: "selectedChoices",
|
|
96
137
|
message: "请选择要安装的规则文件:",
|
|
97
138
|
loop: false,
|
|
98
|
-
choices:
|
|
99
|
-
name: file,
|
|
100
|
-
value: file,
|
|
101
|
-
checked: false
|
|
102
|
-
}))
|
|
139
|
+
choices: mdcChoices,
|
|
103
140
|
}
|
|
104
141
|
]);
|
|
105
142
|
|
|
106
|
-
if (
|
|
143
|
+
if (selectedChoices.length === 0) {
|
|
107
144
|
Ec.info(`未选择任何文件,操作取消。`);
|
|
108
145
|
process.exit(0);
|
|
109
146
|
}
|
|
110
147
|
|
|
148
|
+
// 展开所选项为最终文件列表
|
|
149
|
+
const selectedFiles = [];
|
|
150
|
+
selectedChoices.forEach(choice => {
|
|
151
|
+
if (choice.type === "group") {
|
|
152
|
+
groupedFiles[choice.prefix].forEach(f => selectedFiles.push(f));
|
|
153
|
+
} else {
|
|
154
|
+
selectedFiles.push(choice.file);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
111
158
|
const { selectedTargets } = await inquirer.prompt([
|
|
112
159
|
{
|
|
113
160
|
type: "checkbox",
|
|
@@ -8,9 +8,7 @@ const executeWeb = require('./fn.source.front');
|
|
|
8
8
|
const executeSpring = require('./fn.source.spring');
|
|
9
9
|
const executeZero = require('./fn.source.zero');
|
|
10
10
|
const executeApply = require('./fn.source.apply');
|
|
11
|
-
const
|
|
12
|
-
const executeExApi = require('./fn.ex.api');
|
|
13
|
-
const executeExCrud = require('./fn.ex.crud');
|
|
11
|
+
const executeAuth = require('./fn.ex.auth');
|
|
14
12
|
const executeExApp = require('./fn.ex.app');
|
|
15
13
|
const executeExMenu = require('./fn.ex.menu');
|
|
16
14
|
const exported = {
|
|
@@ -26,9 +24,7 @@ const exported = {
|
|
|
26
24
|
executeZero, // ai zero
|
|
27
25
|
// Cursor 规则安装
|
|
28
26
|
executeApply, // ai apply
|
|
29
|
-
|
|
30
|
-
executeExApi, // ai ex-api
|
|
31
|
-
executeExCrud, // ai ex-crud
|
|
27
|
+
executeAuth, // ai auth
|
|
32
28
|
executeExApp, // ai ex-app
|
|
33
29
|
executeExMenu, // ai ex-menu
|
|
34
30
|
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "Comfort",
|
|
3
|
-
"version": "1.0.8",
|
|
4
|
-
"minAppVersion": "1.0.8",
|
|
5
|
-
"author": "carrie999",
|
|
6
|
-
"authorUrl": "https://github.com/Carrie999",
|
|
7
|
-
"fundingUrl": {
|
|
8
|
-
"Buy Me a Coffee": "https://www.buymeacoffee.com/pangyajingD",
|
|
9
|
-
"wechat pay": "https://github.com/Carrie999/wechat"
|
|
10
|
-
}
|
|
11
|
-
}
|