befly 3.5.7 → 3.6.0

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.
Files changed (80) hide show
  1. package/lib/addon.ts +77 -0
  2. package/lib/logger.ts +6 -15
  3. package/lifecycle/checker.ts +20 -49
  4. package/lifecycle/lifecycle.ts +7 -5
  5. package/lifecycle/loader.ts +5 -5
  6. package/main.ts +10 -1
  7. package/package.json +2 -9
  8. package/paths.ts +5 -54
  9. package/util.ts +1 -83
  10. package/apis/admin/del.ts +0 -35
  11. package/apis/admin/info.ts +0 -50
  12. package/apis/admin/ins.ts +0 -61
  13. package/apis/admin/list.ts +0 -20
  14. package/apis/admin/roleDetail.ts +0 -35
  15. package/apis/admin/roleSave.ts +0 -40
  16. package/apis/admin/upd.ts +0 -51
  17. package/apis/api/all.ts +0 -37
  18. package/apis/auth/login.ts +0 -78
  19. package/apis/auth/logout.ts +0 -23
  20. package/apis/auth/register.ts +0 -50
  21. package/apis/auth/sendSmsCode.ts +0 -36
  22. package/apis/cache/refresh.ts +0 -34
  23. package/apis/dashboard/addonList.ts +0 -47
  24. package/apis/dashboard/changelog.ts +0 -37
  25. package/apis/dashboard/configStatus.ts +0 -54
  26. package/apis/dashboard/environmentInfo.ts +0 -46
  27. package/apis/dashboard/performanceMetrics.ts +0 -23
  28. package/apis/dashboard/permissionStats.ts +0 -31
  29. package/apis/dashboard/serviceStatus.ts +0 -82
  30. package/apis/dashboard/systemInfo.ts +0 -26
  31. package/apis/dashboard/systemOverview.ts +0 -32
  32. package/apis/dashboard/systemResources.ts +0 -119
  33. package/apis/dict/all.ts +0 -25
  34. package/apis/dict/del.ts +0 -19
  35. package/apis/dict/detail.ts +0 -21
  36. package/apis/dict/ins.ts +0 -27
  37. package/apis/dict/list.ts +0 -18
  38. package/apis/dict/upd.ts +0 -31
  39. package/apis/menu/all.ts +0 -68
  40. package/apis/menu/del.ts +0 -37
  41. package/apis/menu/ins.ts +0 -20
  42. package/apis/menu/list.ts +0 -21
  43. package/apis/menu/upd.ts +0 -29
  44. package/apis/role/apiDetail.ts +0 -30
  45. package/apis/role/apiSave.ts +0 -41
  46. package/apis/role/del.ts +0 -44
  47. package/apis/role/detail.ts +0 -24
  48. package/apis/role/ins.ts +0 -39
  49. package/apis/role/list.ts +0 -14
  50. package/apis/role/menuDetail.ts +0 -30
  51. package/apis/role/menuSave.ts +0 -38
  52. package/apis/role/save.ts +0 -44
  53. package/apis/role/upd.ts +0 -40
  54. package/bin/index.ts +0 -34
  55. package/checks/conflict.ts +0 -351
  56. package/checks/table.ts +0 -250
  57. package/commands/index.ts +0 -73
  58. package/commands/sync.ts +0 -88
  59. package/commands/syncApi.ts +0 -316
  60. package/commands/syncDb/apply.ts +0 -171
  61. package/commands/syncDb/constants.ts +0 -77
  62. package/commands/syncDb/ddl.ts +0 -191
  63. package/commands/syncDb/helpers.ts +0 -173
  64. package/commands/syncDb/index.ts +0 -217
  65. package/commands/syncDb/schema.ts +0 -199
  66. package/commands/syncDb/sqlite.ts +0 -50
  67. package/commands/syncDb/state.ts +0 -112
  68. package/commands/syncDb/table.ts +0 -214
  69. package/commands/syncDb/tableCreate.ts +0 -149
  70. package/commands/syncDb/types.ts +0 -92
  71. package/commands/syncDb/version.ts +0 -73
  72. package/commands/syncDb.ts +0 -34
  73. package/commands/syncDev.ts +0 -237
  74. package/commands/syncMenu.ts +0 -349
  75. package/commands/util.ts +0 -58
  76. package/tables/admin.json +0 -14
  77. package/tables/api.json +0 -8
  78. package/tables/dict.json +0 -8
  79. package/tables/menu.json +0 -8
  80. package/tables/role.json +0 -8
@@ -1,349 +0,0 @@
1
- /**
2
- * SyncMenu 命令 - 同步菜单数据到数据库
3
- * 说明:根据 menu.json 配置文件增量同步菜单数据(最多2级:父级和子级)
4
- *
5
- * 流程:
6
- * 1. 读取 core/config/menu.json 和项目根目录的 menu.json 配置文件
7
- * 2. core 配置优先覆盖项目配置(根据 path 匹配)
8
- * 3. 文件不存在或格式错误时默认为空数组
9
- * 4. 子级菜单自动追加父级路径作为前缀
10
- * 5. 根据菜单的 path 字段检查是否存在
11
- * 6. 存在则更新其他字段(name、icon、sort、type、pid)
12
- * 7. 不存在则新增菜单记录
13
- * 8. 强制删除配置中不存在的菜单记录
14
- * 注:state 字段由框架自动管理(1=正常,2=禁用,0=删除)
15
- */
16
-
17
- import { Logger } from '../lib/logger.js';
18
- import { Database } from '../lib/database.js';
19
- import { RedisHelper } from '../lib/redisHelper.js';
20
- import { join } from 'pathe';
21
- import { existsSync } from 'node:fs';
22
- import { coreDir, projectDir } from '../paths.js';
23
-
24
- interface SyncMenuOptions {
25
- plan?: boolean;
26
- }
27
-
28
- interface MenuConfig {
29
- name: string;
30
- path: string;
31
- icon?: string;
32
- sort?: number;
33
- type?: number;
34
- children?: MenuConfig[];
35
- }
36
-
37
- export interface SyncMenuStats {
38
- totalMenus: number;
39
- parentMenus: number;
40
- childMenus: number;
41
- created: number;
42
- updated: number;
43
- deleted: number;
44
- }
45
-
46
- /**
47
- * 读取菜单配置文件
48
- * 如果文件不存在或不是数组格式,返回空数组
49
- */
50
- async function readMenuConfig(filePath: string): Promise<MenuConfig[]> {
51
- try {
52
- if (!existsSync(filePath)) {
53
- return [];
54
- }
55
-
56
- const file = Bun.file(filePath);
57
- const content = await file.json();
58
-
59
- // 验证是否为数组
60
- if (!Array.isArray(content)) {
61
- return [];
62
- }
63
-
64
- return content;
65
- } catch (error: any) {
66
- return [];
67
- }
68
- }
69
-
70
- /**
71
- * 合并菜单配置(core 优先覆盖项目)
72
- * 支持二级菜单结构:父级和子级
73
- */
74
- function mergeMenuConfigs(projectMenus: MenuConfig[], coreMenus: MenuConfig[]): MenuConfig[] {
75
- const menuMap = new Map<string, MenuConfig>();
76
-
77
- // 1. 先添加项目菜单
78
- for (const menu of projectMenus) {
79
- if (menu.path) {
80
- menuMap.set(menu.path, { ...menu });
81
- }
82
- }
83
-
84
- // 2. core 菜单覆盖同 path 的项目菜单
85
- for (const menu of coreMenus) {
86
- if (menu.path) {
87
- menuMap.set(menu.path, { ...menu });
88
- }
89
- }
90
-
91
- // 3. 转换为数组并处理子菜单
92
- const result: MenuConfig[] = [];
93
- for (const menu of menuMap.values()) {
94
- const mergedMenu = { ...menu };
95
-
96
- // 处理子菜单合并
97
- if (menu.children && menu.children.length > 0) {
98
- const childMap = new Map<string, MenuConfig>();
99
-
100
- // 先添加项目的子菜单
101
- const projectMenu = projectMenus.find((m) => m.path === menu.path);
102
- if (projectMenu?.children) {
103
- for (const child of projectMenu.children) {
104
- if (child.path) {
105
- childMap.set(child.path, { ...child });
106
- }
107
- }
108
- }
109
-
110
- // core 子菜单覆盖
111
- const coreMenu = coreMenus.find((m) => m.path === menu.path);
112
- if (coreMenu?.children) {
113
- for (const child of coreMenu.children) {
114
- if (child.path) {
115
- childMap.set(child.path, { ...child });
116
- }
117
- }
118
- }
119
-
120
- mergedMenu.children = Array.from(childMap.values());
121
- }
122
-
123
- result.push(mergedMenu);
124
- }
125
-
126
- return result;
127
- }
128
-
129
- /**
130
- * 收集配置文件中所有菜单的 path(最多2级)
131
- * 子级菜单自动追加父级路径前缀
132
- */
133
- function collectPaths(menus: MenuConfig[]): Set<string> {
134
- const paths = new Set<string>();
135
-
136
- for (const menu of menus) {
137
- if (menu.path) {
138
- paths.add(menu.path);
139
- }
140
- if (menu.children && menu.children.length > 0) {
141
- for (const child of menu.children) {
142
- if (child.path) {
143
- // 子级菜单追加父级路径前缀
144
- const fullPath = menu.path + child.path;
145
- paths.add(fullPath);
146
- }
147
- }
148
- }
149
- }
150
-
151
- return paths;
152
- }
153
-
154
- /**
155
- * 同步菜单(两层结构:父级和子级)
156
- * 子级菜单路径自动追加父级路径前缀
157
- */
158
- async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: number; updated: number }> {
159
- const stats = { created: 0, updated: 0 };
160
-
161
- for (const menu of menus) {
162
- try {
163
- // 1. 同步父级菜单
164
- const existingParent = await helper.getOne({
165
- table: 'core_menu',
166
- where: { path: menu.path || '' }
167
- });
168
-
169
- let parentId: number;
170
-
171
- if (existingParent) {
172
- await helper.updData({
173
- table: 'core_menu',
174
- where: { id: existingParent.id },
175
- data: {
176
- pid: 0,
177
- name: menu.name,
178
- icon: menu.icon || '',
179
- sort: menu.sort || 0,
180
- type: menu.type || 1
181
- }
182
- });
183
- parentId = existingParent.id;
184
- stats.updated++;
185
- } else {
186
- parentId = await helper.insData({
187
- table: 'core_menu',
188
- data: {
189
- pid: 0,
190
- name: menu.name,
191
- path: menu.path || '',
192
- icon: menu.icon || '',
193
- sort: menu.sort || 0,
194
- type: menu.type || 1
195
- }
196
- });
197
- stats.created++;
198
- }
199
-
200
- // 2. 同步子级菜单(自动追加父级路径前缀)
201
- if (menu.children && menu.children.length > 0) {
202
- for (const child of menu.children) {
203
- // 子级菜单完整路径 = 父级路径 + 子级路径
204
- const childFullPath = menu.path + child.path;
205
-
206
- const existingChild = await helper.getOne({
207
- table: 'core_menu',
208
- where: { path: childFullPath }
209
- });
210
-
211
- if (existingChild) {
212
- await helper.updData({
213
- table: 'core_menu',
214
- where: { id: existingChild.id },
215
- data: {
216
- pid: parentId,
217
- name: child.name,
218
- icon: child.icon || '',
219
- sort: child.sort || 0,
220
- type: child.type || 1
221
- }
222
- });
223
- stats.updated++;
224
- } else {
225
- await helper.insData({
226
- table: 'core_menu',
227
- data: {
228
- pid: parentId,
229
- name: child.name,
230
- path: childFullPath,
231
- icon: child.icon || '',
232
- sort: child.sort || 0,
233
- type: child.type || 1
234
- }
235
- });
236
- stats.created++;
237
- }
238
- }
239
- }
240
- } catch (error: any) {
241
- Logger.error(`同步菜单 "${menu.name}" 失败:`, error.message || String(error));
242
- throw error;
243
- }
244
- }
245
-
246
- return stats;
247
- }
248
-
249
- /**
250
- * 删除配置中不存在的菜单(强制删除)
251
- */
252
- async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Promise<number> {
253
- const allRecords = await helper.getAll({
254
- table: 'core_menu',
255
- fields: ['id', 'path', 'name'],
256
- where: { state$gte: 0 } // 查询所有状态(包括软删除的 state=0)
257
- });
258
-
259
- let deletedCount = 0;
260
- for (const record of allRecords) {
261
- if (record.path && !configPaths.has(record.path)) {
262
- await helper.delForce({
263
- table: 'core_menu',
264
- where: { id: record.id }
265
- });
266
- deletedCount++;
267
- }
268
- }
269
-
270
- return deletedCount;
271
- }
272
-
273
- /**
274
- * SyncMenu 命令主函数
275
- */
276
- export async function syncMenuCommand(options: SyncMenuOptions = {}): Promise<SyncMenuStats> {
277
- try {
278
- if (options.plan) {
279
- Logger.info('[计划] 同步菜单配置到数据库(plan 模式不执行)');
280
- return { totalMenus: 0, parentMenus: 0, childMenus: 0, created: 0, updated: 0, deleted: 0 };
281
- }
282
-
283
- // 1. 读取两个配置文件
284
- const projectMenuPath = join(projectDir, 'menu.json');
285
- const coreMenuPath = join(coreDir, 'menu.json');
286
-
287
- const projectMenus = await readMenuConfig(projectMenuPath);
288
- const coreMenus = await readMenuConfig(coreMenuPath);
289
-
290
- // 2. 合并菜单配置
291
- const mergedMenus = mergeMenuConfigs(projectMenus, coreMenus);
292
-
293
- // 连接数据库(SQL + Redis)
294
- await Database.connect();
295
-
296
- const helper = Database.getDbHelper();
297
-
298
- // 3. 检查表是否存在
299
- const exists = await helper.tableExists('core_menu');
300
-
301
- if (!exists) {
302
- Logger.error(`❌ 表 core_menu 不存在,请先运行 befly syncDb 同步数据库`);
303
- process.exit(1);
304
- }
305
-
306
- // 4. 收集配置文件中所有菜单的 path
307
- const configPaths = collectPaths(mergedMenus);
308
-
309
- // 5. 同步菜单
310
- const stats = await syncMenus(helper, mergedMenus);
311
-
312
- // 6. 删除文件中不存在的菜单(强制删除)
313
- const deletedCount = await deleteObsoleteRecords(helper, configPaths);
314
-
315
- // 7. 获取最终菜单数据
316
- const allMenus = await helper.getAll({
317
- table: 'core_menu',
318
- fields: ['id', 'pid', 'name', 'path', 'type'],
319
- orderBy: ['pid#ASC', 'sort#ASC', 'id#ASC']
320
- });
321
-
322
- // 8. 缓存菜单数据到 Redis
323
- try {
324
- const menus = await helper.getAll({
325
- table: 'core_menu',
326
- fields: ['id', 'pid', 'name', 'path', 'icon', 'type', 'sort'],
327
- orderBy: ['sort#ASC', 'id#ASC']
328
- });
329
-
330
- await RedisHelper.setObject('menus:all', menus);
331
- } catch (error: any) {
332
- // 忽略缓存错误
333
- }
334
-
335
- return {
336
- totalMenus: allMenus.length,
337
- parentMenus: allMenus.filter((m: any) => m.pid === 0).length,
338
- childMenus: allMenus.filter((m: any) => m.pid !== 0).length,
339
- created: stats.created,
340
- updated: stats.updated,
341
- deleted: deletedCount
342
- };
343
- } catch (error: any) {
344
- Logger.error('菜单同步失败:', error);
345
- process.exit(1);
346
- } finally {
347
- await Database?.disconnect();
348
- }
349
- }
package/commands/util.ts DELETED
@@ -1,58 +0,0 @@
1
- /**
2
- * Commands 工具函数
3
- * 提供命令间可复用的通用功能
4
- */
5
-
6
- import { join, parse, dirname } from 'pathe';
7
- import { existsSync, readFileSync } from 'node:fs';
8
-
9
- /**
10
- * 获取项目根目录
11
- * 向上查找包含 package.json 的目录
12
- *
13
- * @returns 项目根目录路径
14
- */
15
- export function getProjectRoot(): string {
16
- let current = process.cwd();
17
- const root = parse(current).root;
18
-
19
- while (current !== root) {
20
- if (existsSync(join(current, 'package.json'))) {
21
- return current;
22
- }
23
- current = dirname(current);
24
- }
25
-
26
- return process.cwd();
27
- }
28
-
29
- /**
30
- * 读取 package.json 文件内容
31
- *
32
- * @param pkgPath package.json 文件路径
33
- * @returns package.json 的内容对象
34
- */
35
- export function readPackageJson(pkgPath: string): Record<string, any> {
36
- try {
37
- const content = readFileSync(pkgPath, 'utf-8');
38
- return JSON.parse(content);
39
- } catch (error) {
40
- throw new Error(`读取 package.json 失败: ${error}`);
41
- }
42
- }
43
-
44
- /**
45
- * 获取指定目录的 package.json 版本号
46
- *
47
- * @param dir 目录路径
48
- * @returns 版本号字符串
49
- */
50
- export function getPackageVersion(dir: string): string {
51
- try {
52
- const pkgPath = join(dir, 'package.json');
53
- const pkg = readPackageJson(pkgPath);
54
- return pkg.version || '0.0.0';
55
- } catch (error) {
56
- return '0.0.0';
57
- }
58
- }
package/tables/admin.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "name": "姓名|string|2|50|null|0|null",
3
- "nickname": "昵称|string|2|50|null|0|null",
4
- "email": "邮箱|string|5|100|null|1|^[\\w.-]+@[\\w.-]+\\.\\w+$",
5
- "phone": "手机号|string|11|11|null|0|^1[3-9]\\d{9}$",
6
- "username": "用户名|string|3|30|null|0|^[a-zA-Z0-9_]+$",
7
- "password": "密码|string|6|500|null|0|null",
8
- "avatar": "头像|string|0|500|null|0|null",
9
- "roleId": "角色ID|number|1|999999999999999|null|1|null",
10
- "roleCode": "角色编码|string|2|50|null|0|^[a-zA-Z0-9_]+$",
11
- "roleType": "角色类型|string|4|5|user|1|^(admin|user)$",
12
- "lastLoginTime": "最后登录时间|number|0|null|0|0|null",
13
- "lastLoginIp": "最后登录IP|string|0|50|null|0|null"
14
- }
package/tables/api.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "name": "接口名称|string|2|100|null|1|null",
3
- "path": "接口路径|string|1|200|null|1|null",
4
- "method": "请求方法|string|3|10|POST|1|^(GET|POST|PUT|DELETE|PATCH)$",
5
- "description": "接口描述|string|0|500|null|0|null",
6
- "addonName": "所属插件|string|0|50|null|0|null",
7
- "addonTitle": "插件标题|string|0|100|null|0|null"
8
- }
package/tables/dict.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "name": "字典名称|string|2|50|null|1|null",
3
- "code": "字典代码|string|2|50|null|1|^[a-zA-Z0-9_]+$",
4
- "value": "字典值|string|0|200|null|1|null",
5
- "sort": "排序|number|0|9999|0|0|null",
6
- "pid": "父级ID|number|0|999999999999999|0|1|null",
7
- "description": "描述|string|0|200|null|0|null"
8
- }
package/tables/menu.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "name": "菜单名称|string|2|50|null|1|null",
3
- "path": "路由路径|string|1|200|null|1|null",
4
- "icon": "图标名称|string|0|50|null|0|null",
5
- "sort": "排序|number|0|9999|0|0|null",
6
- "pid": "父级ID|number|0|999999999999999|0|1|null",
7
- "type": "菜单类型|number|1|2|1|1|^(1|2)$"
8
- }
package/tables/role.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "name": "角色名称|string|2|50|null|1|null",
3
- "code": "角色编码|string|2|50|null|1|^[a-zA-Z0-9_]+$",
4
- "description": "角色描述|string|0|200|null|0|null",
5
- "menus": "菜单权限|array_text|null|null|null|0|null",
6
- "apis": "接口权限|array_text|null|null|null|0|null",
7
- "sort": "排序|number|0|9999|0|0|null"
8
- }