befly 3.5.0 → 3.5.2

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.
@@ -34,6 +34,15 @@ interface MenuConfig {
34
34
  children?: MenuConfig[];
35
35
  }
36
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
+
37
46
  /**
38
47
  * 读取菜单配置文件
39
48
  * 如果文件不存在或不是数组格式,返回空数组
@@ -41,7 +50,6 @@ interface MenuConfig {
41
50
  async function readMenuConfig(filePath: string): Promise<MenuConfig[]> {
42
51
  try {
43
52
  if (!existsSync(filePath)) {
44
- Logger.warn(`菜单配置文件不存在: ${filePath},使用空数组`);
45
53
  return [];
46
54
  }
47
55
 
@@ -50,13 +58,11 @@ async function readMenuConfig(filePath: string): Promise<MenuConfig[]> {
50
58
 
51
59
  // 验证是否为数组
52
60
  if (!Array.isArray(content)) {
53
- Logger.warn(`菜单配置文件格式错误(非数组): ${filePath},使用空数组`);
54
61
  return [];
55
62
  }
56
63
 
57
64
  return content;
58
65
  } catch (error: any) {
59
- Logger.warn(`读取菜单配置失败: ${filePath},使用空数组`, error.message);
60
66
  return [];
61
67
  }
62
68
  }
@@ -176,7 +182,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
176
182
  });
177
183
  parentId = existingParent.id;
178
184
  stats.updated++;
179
- Logger.info(` └ 更新父级菜单: ${menu.name} (ID: ${parentId}, Path: ${menu.path})`);
180
185
  } else {
181
186
  parentId = await helper.insData({
182
187
  table: 'core_menu',
@@ -190,7 +195,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
190
195
  }
191
196
  });
192
197
  stats.created++;
193
- Logger.info(` └ 新增父级菜单: ${menu.name} (ID: ${parentId}, Path: ${menu.path})`);
194
198
  }
195
199
 
196
200
  // 2. 同步子级菜单(自动追加父级路径前缀)
@@ -217,9 +221,8 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
217
221
  }
218
222
  });
219
223
  stats.updated++;
220
- Logger.info(` └ 更新子级菜单: ${child.name} (ID: ${existingChild.id}, PID: ${parentId}, Path: ${childFullPath})`);
221
224
  } else {
222
- const childId = await helper.insData({
225
+ await helper.insData({
223
226
  table: 'core_menu',
224
227
  data: {
225
228
  pid: parentId,
@@ -231,7 +234,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
231
234
  }
232
235
  });
233
236
  stats.created++;
234
- Logger.info(` └ 新增子级菜单: ${child.name} (ID: ${childId}, PID: ${parentId}, Path: ${childFullPath})`);
235
237
  }
236
238
  }
237
239
  }
@@ -248,8 +250,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
248
250
  * 删除配置中不存在的菜单(强制删除)
249
251
  */
250
252
  async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Promise<number> {
251
- Logger.info(`\n=== 删除配置中不存在的记录 ===`);
252
-
253
253
  const allRecords = await helper.getAll({
254
254
  table: 'core_menu',
255
255
  fields: ['id', 'path', 'name'],
@@ -264,60 +264,31 @@ async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Pro
264
264
  where: { id: record.id }
265
265
  });
266
266
  deletedCount++;
267
- Logger.info(` └ 删除记录: ${record.name} (ID: ${record.id}, path: ${record.path})`);
268
267
  }
269
268
  }
270
269
 
271
- if (deletedCount === 0) {
272
- Logger.info(' ✅ 无需删除的记录');
273
- }
274
-
275
270
  return deletedCount;
276
271
  }
277
272
 
278
273
  /**
279
274
  * SyncMenu 命令主函数
280
275
  */
281
- export async function syncMenuCommand(options: SyncMenuOptions = {}) {
276
+ export async function syncMenuCommand(options: SyncMenuOptions = {}): Promise<SyncMenuStats> {
282
277
  try {
283
278
  if (options.plan) {
284
279
  Logger.info('[计划] 同步菜单配置到数据库(plan 模式不执行)');
285
- Logger.info('[计划] 1. 读取 core/config/menu.json 和项目根目录 menu.json');
286
- Logger.info('[计划] 2. 合并菜单配置(core 优先覆盖项目)');
287
- Logger.info('[计划] 3. 子级菜单自动追加父级路径前缀');
288
- Logger.info('[计划] 4. 根据 path 检查菜单是否存在');
289
- Logger.info('[计划] 5. 存在则更新,不存在则新增');
290
- Logger.info('[计划] 6. 强制删除配置中不存在的菜单');
291
- Logger.info('[计划] 7. 显示菜单结构预览');
292
- return;
280
+ return { totalMenus: 0, parentMenus: 0, childMenus: 0, created: 0, updated: 0, deleted: 0 };
293
281
  }
294
282
 
295
- Logger.info('开始同步菜单配置到数据库...\n');
296
-
297
283
  // 1. 读取两个配置文件
298
- Logger.info('=== 步骤 1: 读取菜单配置文件 ===');
299
284
  const projectMenuPath = join(projectDir, 'menu.json');
300
285
  const coreMenuPath = join(coreDir, 'menu.json');
301
286
 
302
- Logger.info(` 项目路径: ${projectMenuPath}`);
303
- Logger.info(` core 路径: ${coreMenuPath}`);
304
-
305
287
  const projectMenus = await readMenuConfig(projectMenuPath);
306
288
  const coreMenus = await readMenuConfig(coreMenuPath);
307
289
 
308
- Logger.info(`✅ 项目配置: ${projectMenus.length} 个父级菜单`);
309
- Logger.info(`✅ core 配置: ${coreMenus.length} 个父级菜单`);
310
-
311
290
  // 2. 合并菜单配置
312
- Logger.info('\n=== 步骤 2: 合并菜单配置(core 优先覆盖项目) ===');
313
291
  const mergedMenus = mergeMenuConfigs(projectMenus, coreMenus);
314
- Logger.info(`✅ 合并后共有 ${mergedMenus.length} 个父级菜单`);
315
-
316
- // 打印合并后的菜单结构
317
- for (const menu of mergedMenus) {
318
- const childCount = menu.children?.length || 0;
319
- Logger.info(` └ ${menu.name} (${menu.path}) - ${childCount} 个子菜单`);
320
- }
321
292
 
322
293
  // 连接数据库(SQL + Redis)
323
294
  await Database.connect();
@@ -325,7 +296,6 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
325
296
  const helper = Database.getDbHelper();
326
297
 
327
298
  // 3. 检查表是否存在
328
- Logger.info('\n=== 步骤 3: 检查数据表 ===');
329
299
  const exists = await helper.tableExists('core_menu');
330
300
 
331
301
  if (!exists) {
@@ -333,47 +303,23 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
333
303
  process.exit(1);
334
304
  }
335
305
 
336
- Logger.info(`✅ 表 core_menu 存在`);
337
-
338
306
  // 4. 收集配置文件中所有菜单的 path
339
- Logger.info('\n=== 步骤 4: 收集配置菜单路径 ===');
340
307
  const configPaths = collectPaths(mergedMenus);
341
- Logger.info(`✅ 配置文件中共有 ${configPaths.size} 个菜单路径`);
342
308
 
343
309
  // 5. 同步菜单
344
- Logger.info('\n=== 步骤 5: 同步菜单数据(新增/更新) ===');
345
310
  const stats = await syncMenus(helper, mergedMenus);
346
311
 
347
312
  // 6. 删除文件中不存在的菜单(强制删除)
348
313
  const deletedCount = await deleteObsoleteRecords(helper, configPaths);
349
314
 
350
- // 7. 构建树形结构预览
351
- Logger.info('\n=== 步骤 7: 菜单结构预览 ===');
315
+ // 7. 获取最终菜单数据
352
316
  const allMenus = await helper.getAll({
353
317
  table: 'core_menu',
354
318
  fields: ['id', 'pid', 'name', 'path', 'type'],
355
319
  orderBy: ['pid#ASC', 'sort#ASC', 'id#ASC']
356
320
  });
357
321
 
358
- const parentMenus = allMenus.filter((m: any) => m.pid === 0);
359
- for (const parent of parentMenus) {
360
- const children = allMenus.filter((m: any) => m.pid === parent.id);
361
- Logger.info(` └ ${parent.name} (${parent.path})`);
362
- for (const child of children) {
363
- Logger.info(` └ ${child.name} (${child.path})`);
364
- }
365
- }
366
-
367
- // 8. 输出统计信息
368
- Logger.info(`\n=== 菜单同步完成 ===`);
369
- Logger.info(`新增菜单: ${stats.created} 个`);
370
- Logger.info(`更新菜单: ${stats.updated} 个`);
371
- Logger.info(`删除菜单: ${deletedCount} 个`);
372
- Logger.info(`当前父级菜单: ${allMenus.filter((m: any) => m.pid === 0).length} 个`);
373
- Logger.info(`当前子级菜单: ${allMenus.filter((m: any) => m.pid !== 0).length} 个`);
374
-
375
- // 9. 缓存菜单数据到 Redis
376
- Logger.info('\n=== 步骤 8: 缓存菜单数据到 Redis ===');
322
+ // 8. 缓存菜单数据到 Redis
377
323
  try {
378
324
  const menus = await helper.getAll({
379
325
  table: 'core_menu',
@@ -381,16 +327,19 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
381
327
  orderBy: ['sort#ASC', 'id#ASC']
382
328
  });
383
329
 
384
- const result = await RedisHelper.setObject('menus:all', menus);
385
-
386
- if (result === null) {
387
- Logger.warn('⚠️ 菜单缓存失败');
388
- } else {
389
- Logger.info(`✅ 已缓存 ${menus.length} 个菜单到 Redis (Key: menus:all)`);
390
- }
330
+ await RedisHelper.setObject('menus:all', menus);
391
331
  } catch (error: any) {
392
- Logger.error('⚠️ 菜单缓存异常:', error);
332
+ // 忽略缓存错误
393
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
+ };
394
343
  } catch (error: any) {
395
344
  Logger.error('菜单同步失败:', error);
396
345
  process.exit(1);
package/lib/database.ts CHANGED
@@ -87,8 +87,6 @@ export class Database {
87
87
 
88
88
  const version = await Promise.race([healthCheckPromise, timeoutPromise]);
89
89
 
90
- Logger.info(`数据库连接成功,version: ${version}`);
91
-
92
90
  this.sqlClient = sql;
93
91
  return sql;
94
92
  } catch (error: any) {
@@ -110,7 +108,6 @@ export class Database {
110
108
  if (this.sqlClient) {
111
109
  try {
112
110
  await this.sqlClient.close();
113
- Logger.info('SQL 连接已关闭');
114
111
  } catch (error: any) {
115
112
  Logger.warn('关闭 SQL 连接时出错:', error.message);
116
113
  }
@@ -186,7 +183,6 @@ export class Database {
186
183
  });
187
184
 
188
185
  await redis.ping();
189
- Logger.info('Redis 连接成功');
190
186
 
191
187
  this.redisClient = redis;
192
188
  return redis;
@@ -203,7 +199,6 @@ export class Database {
203
199
  if (this.redisClient) {
204
200
  try {
205
201
  this.redisClient.close();
206
- Logger.info('Redis 连接已关闭');
207
202
  } catch (error: any) {
208
203
  Logger.warn('关闭 Redis 连接时出错:', error);
209
204
  }
@@ -233,16 +228,12 @@ export class Database {
233
228
  static async connect(options?: { sql?: SqlClientOptions; redis?: boolean }): Promise<void> {
234
229
  try {
235
230
  if (options?.sql !== false) {
236
- Logger.info('正在初始化 SQL 连接...');
237
231
  await this.connectSql(options?.sql);
238
232
  }
239
233
 
240
234
  if (options?.redis !== false) {
241
- Logger.info('正在初始化 Redis 连接...');
242
235
  await this.connectRedis();
243
236
  }
244
-
245
- Logger.info('数据库连接初始化完成');
246
237
  } catch (error: any) {
247
238
  Logger.error('数据库初始化失败', error);
248
239
  await this.disconnect();
package/lib/logger.ts CHANGED
@@ -217,10 +217,12 @@ export class Logger {
217
217
  * 用于命令开始时提示用户当前环境
218
218
  */
219
219
  static printEnv(): void {
220
- const env = process.env.NODE_ENV || 'development';
221
- const envColor = env === 'production' ? chalk.red : env === 'test' ? chalk.yellow : chalk.green;
222
- console.log(chalk.gray('────────────────────────────────────────'));
223
- console.log(chalk.bold('运行环境: ') + envColor.bold(env.toUpperCase()));
224
- console.log(chalk.gray('────────────────────────────────────────'));
220
+ console.log('========================================');
221
+ console.log('开始执行完整同步流程');
222
+ console.log(`当前环境: ${process.env.NODE_ENV || 'development'}`);
223
+ console.log(`项目名称: ${Env.APP_NAME}`);
224
+ console.log(`数据库地址: ${Env.DB_HOST}`);
225
+ console.log(`数据库名称: ${Env.DB_NAME}`);
226
+ console.log('========================================\n');
225
227
  }
226
228
  }
@@ -46,20 +46,15 @@ export class Checker {
46
46
  const conflictCheckTime = calcPerfTime(conflictCheckStart);
47
47
 
48
48
  if (typeof conflictResult !== 'boolean') {
49
- Logger.warn(`核心检查 conflict.ts 返回值必须为 true 或 false,当前为 ${typeof conflictResult},耗时: ${conflictCheckTime}`);
49
+ Logger.warn(`核心检查 conflict.ts 返回值必须为 true 或 false,当前为 ${typeof conflictResult}`);
50
50
  stats.failedChecks++;
51
51
  } else if (conflictResult === true) {
52
52
  stats.passedChecks++;
53
- Logger.info(`核心检查 conflict.ts 通过,耗时: ${conflictCheckTime}`);
54
53
  } else {
55
- Logger.warn(`核心检查未通过: conflict.ts,耗时: ${conflictCheckTime}`);
54
+ Logger.warn(`核心检查未通过: conflict.ts`);
56
55
  stats.failedChecks++;
57
56
  // 资源冲突检测失败,立即终止
58
- Logger.warn('资源冲突检测失败,无法继续启动', {
59
- totalChecks: stats.totalChecks,
60
- passedChecks: stats.passedChecks,
61
- failedChecks: stats.failedChecks
62
- });
57
+ Logger.warn('资源冲突检测失败,无法继续启动');
63
58
  process.exit(1);
64
59
  }
65
60
  }
@@ -88,8 +83,6 @@ export class Checker {
88
83
  const { path: checkDir, type } = checkConfig;
89
84
  const addonName = 'addonName' in checkConfig ? checkConfig.addonName : undefined;
90
85
  const checkTypeLabel = type === 'core' ? '核心' : type === 'project' ? '项目' : `组件${addonName}`;
91
- Logger.info(`开始执行${checkTypeLabel}检查,目录: ${checkDir}`);
92
-
93
86
  for await (const file of glob.scan({
94
87
  cwd: checkDir,
95
88
  onlyFiles: true,
@@ -118,18 +111,16 @@ export class Checker {
118
111
 
119
112
  // 检查返回值是否为 boolean
120
113
  if (typeof checkResult !== 'boolean') {
121
- Logger.warn(`${checkTypeLabel}检查 ${fileName} 返回值必须为 true 或 false,当前为 ${typeof checkResult},耗时: ${singleCheckTime}`);
114
+ Logger.warn(`${checkTypeLabel}检查 ${fileName} 返回值必须为 true 或 false,当前为 ${typeof checkResult}`);
122
115
  stats.failedChecks++;
123
116
  } else if (checkResult === true) {
124
117
  stats.passedChecks++;
125
- Logger.info(`${checkTypeLabel}检查 ${fileName} 通过,耗时: ${singleCheckTime}`);
126
118
  } else {
127
- Logger.warn(`${checkTypeLabel}检查未通过: ${fileName},耗时: ${singleCheckTime}`);
119
+ Logger.warn(`${checkTypeLabel}检查未通过: ${fileName}`);
128
120
  stats.failedChecks++;
129
121
  }
130
122
  } else {
131
- const singleCheckTime = calcPerfTime(singleCheckStart);
132
- Logger.warn(`${checkTypeLabel}检查文件 ${fileName} 未找到 default 导出的检查函数,耗时: ${singleCheckTime}`);
123
+ Logger.warn(`${checkTypeLabel}检查文件 ${fileName} 未找到 default 导出的检查函数`);
133
124
  stats.failedChecks++;
134
125
  }
135
126
  } catch (error: any) {
@@ -143,19 +134,11 @@ export class Checker {
143
134
  const totalCheckTime = calcPerfTime(checkStartTime);
144
135
 
145
136
  // 输出检查结果统计
146
- Logger.info(`系统检查完成! 总耗时: ${totalCheckTime},总检查数: ${stats.totalChecks}, 通过: ${stats.passedChecks}, 失败: ${stats.failedChecks}`);
147
-
148
137
  if (stats.failedChecks > 0) {
149
- Logger.error('统检查失败,无法继续启动', {
150
- totalChecks: stats.totalChecks,
151
- passedChecks: stats.passedChecks,
152
- failedChecks: stats.failedChecks
153
- });
138
+ Logger.error(`✗ 系统检查失败: ${stats.failedChecks}/${stats.totalChecks},耗时: ${totalCheckTime}`);
154
139
  process.exit(1);
155
140
  } else if (stats.totalChecks > 0) {
156
- Logger.info(`所有系统检查通过!`);
157
- } else {
158
- Logger.info(`未执行任何检查`);
141
+ Logger.info(`✓ 系统检查通过: ${stats.passedChecks}/${stats.totalChecks},耗时: ${totalCheckTime}`);
159
142
  }
160
143
  } catch (error: any) {
161
144
  Logger.error('执行系统检查时发生错误', error);
@@ -39,7 +39,6 @@ export class Lifecycle {
39
39
  */
40
40
  async start(appContext: BeflyContext, callback?: (server: Server) => void): Promise<Server> {
41
41
  const serverStartTime = Bun.nanoseconds();
42
- Logger.info('开始启动 Befly 服务器...');
43
42
 
44
43
  // 1. 执行系统检查
45
44
  await Checker.run();
@@ -52,7 +51,7 @@ export class Lifecycle {
52
51
 
53
52
  // 4. 启动 HTTP 服务器
54
53
  const totalStartupTime = calcPerfTime(serverStartTime);
55
- Logger.info(`服务器启动准备完成,总耗时: ${totalStartupTime}`);
54
+ Logger.info(`✓ 服务器启动准备完成,总耗时: ${totalStartupTime}`);
56
55
 
57
56
  return await Bootstrap.start(
58
57
  {
@@ -70,63 +69,35 @@ export class Lifecycle {
70
69
  * 包括 core APIs、addon APIs 和 app APIs
71
70
  */
72
71
  private async loadAllApis(): Promise<void> {
73
- Logger.info('========== 开始加载所有 API 路由 ==========');
74
- const totalLoadStart = Bun.nanoseconds();
75
-
76
72
  // 1. 加载 Core APIs
77
- Logger.info('========== 开始加载核心 APIs ==========');
78
- const coreApiLoadStart = Bun.nanoseconds();
79
73
  try {
80
74
  await Loader.loadApis('core', this.apiRoutes, { where: 'core' });
81
- const coreApiLoadTime = calcPerfTime(coreApiLoadStart);
82
- Logger.info(`========== 核心 APIs 加载完成,耗时: ${coreApiLoadTime} ==========`);
83
75
  } catch (error: any) {
84
- const coreApiLoadTime = calcPerfTime(coreApiLoadStart);
85
- Logger.error(`核心 APIs 加载失败,耗时: ${coreApiLoadTime}`, error);
76
+ Logger.error(`核心 APIs 加载失败`, error);
86
77
  throw error;
87
78
  }
88
79
 
89
80
  // 2. 加载 addon APIs
90
81
  const addons = scanAddons();
91
- Logger.info(`扫描到 ${addons.length} 个 addon: ${addons.join(', ')}`);
92
82
 
93
83
  for (const addon of addons) {
94
- const addonLoadStart = Bun.nanoseconds();
95
84
  const hasApis = addonDirExists(addon, 'apis');
96
- Logger.info(`[组件 ${addon}] APIs 目录存在: ${hasApis}`);
97
-
98
85
  if (hasApis) {
99
- Logger.info(`[组件 ${addon}] ===== 开始加载 APIs =====`);
100
86
  try {
101
87
  await Loader.loadApis(addon, this.apiRoutes, { where: 'addon', addonName: addon });
102
- const addonLoadTime = calcPerfTime(addonLoadStart);
103
- Logger.info(`[组件 ${addon}] ===== APIs 加载完成,耗时: ${addonLoadTime} =====`);
104
88
  } catch (error: any) {
105
- const addonLoadTime = calcPerfTime(addonLoadStart);
106
- Logger.error(`[组件 ${addon}] APIs 加载失败,耗时: ${addonLoadTime}`, error);
107
- throw error; // 重新抛出错误,让上层处理
89
+ Logger.error(`[组件 ${addon}] APIs 加载失败`, error);
90
+ throw error;
108
91
  }
109
92
  }
110
93
  }
111
94
 
112
- Logger.info('========== 组件 APIs 全部加载完成 ==========');
113
-
114
95
  // 3. 加载用户 APIs
115
- Logger.info('========== 开始加载用户 APIs ==========');
116
-
117
- const userApiLoadStart = Bun.nanoseconds();
118
96
  try {
119
- // 加载 app APIs
120
97
  await Loader.loadApis('app', this.apiRoutes, { where: 'app' });
121
- const userApiLoadTime = calcPerfTime(userApiLoadStart);
122
- Logger.info(`========== 用户 APIs 加载完成,耗时: ${userApiLoadTime} ==========`);
123
98
  } catch (error: any) {
124
- const userApiLoadTime = calcPerfTime(userApiLoadStart);
125
- Logger.error(`用户 APIs 加载失败,耗时: ${userApiLoadTime}`, error);
99
+ Logger.error(`用户 APIs 加载失败`, error);
126
100
  throw error;
127
101
  }
128
-
129
- const totalLoadTime = calcPerfTime(totalLoadStart);
130
- Logger.info(`========== 所有 API 路由加载完成!总耗时: ${totalLoadTime} ==========`);
131
102
  }
132
103
  }