befly 3.8.1 → 3.8.3

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/util.ts CHANGED
@@ -5,11 +5,10 @@ import { isEmpty, isPlainObject } from 'es-toolkit/compat';
5
5
  import { snakeCase, camelCase, kebabCase } from 'es-toolkit/string';
6
6
  import { Env } from './env.js';
7
7
  import { Logger } from './lib/logger.js';
8
- import { projectDir } from './paths.js';
8
+ import { projectDir, projectAddonsDir } from './paths.js';
9
9
  import type { KeyValue } from './types/common.js';
10
10
  import type { JwtPayload, JwtSignOptions, JwtVerifyOptions } from './types/jwt';
11
11
  import type { Plugin } from './types/plugin.js';
12
- import type { ParsedFieldRule } from './types/common.js';
13
12
 
14
13
  // ========================================
15
14
  // API 响应工具
@@ -39,6 +38,10 @@ export const No = <T = any>(msg: string = '', data: T | {} = {}, other: KeyValue
39
38
  };
40
39
  };
41
40
 
41
+ // ========================================
42
+ // 动态导入工具
43
+ // ========================================
44
+
42
45
  // ========================================
43
46
  // 字段转换工具(重新导出 lib/convert.ts)
44
47
  // ========================================
@@ -170,48 +173,85 @@ export const calcPerfTime = (startTime: number, endTime: number = Bun.nanosecond
170
173
  };
171
174
 
172
175
  // ========================================
173
- // 表定义工具
176
+ // Addon 工具函数
174
177
  // ========================================
175
178
 
176
179
  /**
177
- * 解析字段规则字符串
178
- * 格式:"字段名|类型|最小值|最大值|默认值|必填|正则"
179
- * 注意:只分割前6个|,第7个|之后的所有内容(包括|)都属于正则表达式
180
+ * 扫描所有可用的 addon
181
+ * 优先从本地 addons/ 目录加载,其次从 node_modules/@befly-addon/ 加载
182
+ * @returns addon 名称数组
180
183
  */
181
- export const parseRule = (rule: string): ParsedFieldRule => {
182
- const parts: string[] = [];
183
- let currentPart = '';
184
- let pipeCount = 0;
185
-
186
- for (let i = 0; i < rule.length; i++) {
187
- if (rule[i] === '|' && pipeCount < 6) {
188
- parts.push(currentPart);
189
- currentPart = '';
190
- pipeCount++;
191
- } else {
192
- currentPart += rule[i];
184
+ export const scanAddons = (): string[] => {
185
+ const addons = new Set<string>();
186
+
187
+ // 1. 扫描本地 addons 目录(优先级高)
188
+ // if (existsSync(projectAddonsDir)) {
189
+ // try {
190
+ // const localAddons = fs.readdirSync(projectAddonsDir).filter((name) => {
191
+ // const fullPath = join(projectAddonsDir, name);
192
+ // try {
193
+ // const stat = statSync(fullPath);
194
+ // return stat.isDirectory() && !name.startsWith('_');
195
+ // } catch {
196
+ // return false;
197
+ // }
198
+ // });
199
+ // localAddons.forEach((name) => addons.add(name));
200
+ // } catch (err) {
201
+ // // 忽略本地目录读取错误
202
+ // }
203
+ // }
204
+
205
+ // 2. 扫描 node_modules/@befly-addon 目录
206
+ const beflyDir = join(projectDir, 'node_modules', '@befly-addon');
207
+ if (existsSync(beflyDir)) {
208
+ try {
209
+ const npmAddons = fs.readdirSync(beflyDir).filter((name) => {
210
+ // 如果本地已存在,跳过 npm 包版本
211
+ if (addons.has(name)) return false;
212
+
213
+ const fullPath = join(beflyDir, name);
214
+ try {
215
+ const stat = statSync(fullPath);
216
+ return stat.isDirectory();
217
+ } catch {
218
+ return false;
219
+ }
220
+ });
221
+ npmAddons.forEach((name) => addons.add(name));
222
+ } catch {
223
+ // 忽略 npm 目录读取错误
193
224
  }
194
225
  }
195
- parts.push(currentPart);
196
226
 
197
- const [fieldName = '', fieldType = 'string', fieldMinStr = 'null', fieldMaxStr = 'null', fieldDefaultStr = 'null', fieldIndexStr = '0', fieldRegx = 'null'] = parts;
227
+ return Array.from(addons).sort();
228
+ };
198
229
 
199
- const fieldIndex = Number(fieldIndexStr) as 0 | 1;
200
- const fieldMin = fieldMinStr !== 'null' ? Number(fieldMinStr) : null;
201
- const fieldMax = fieldMaxStr !== 'null' ? Number(fieldMaxStr) : null;
230
+ /**
231
+ * 获取 addon 的指定子目录路径
232
+ * 优先返回本地 addons 目录,其次返回 node_modules 目录
233
+ * @param name - addon 名称
234
+ * @param subDir - 子目录名称
235
+ * @returns 完整路径
236
+ */
237
+ export const getAddonDir = (name: string, subDir: string): string => {
238
+ // 优先使用本地 addons 目录
239
+ // const localPath = join(projectAddonsDir, name, subDir);
240
+ // if (existsSync(localPath)) {
241
+ // return localPath;
242
+ // }
202
243
 
203
- let fieldDefault: any = fieldDefaultStr;
204
- if (fieldType === 'number' && fieldDefaultStr !== 'null') {
205
- fieldDefault = Number(fieldDefaultStr);
206
- }
244
+ // 降级使用 node_modules 目录
245
+ return join(projectDir, 'node_modules', '@befly-addon', name, subDir);
246
+ };
207
247
 
208
- return {
209
- name: fieldName,
210
- type: fieldType as 'string' | 'number' | 'text' | 'array_string' | 'array_text',
211
- min: fieldMin,
212
- max: fieldMax,
213
- default: fieldDefault,
214
- index: fieldIndex,
215
- regex: fieldRegx !== 'null' ? fieldRegx : null
216
- };
248
+ /**
249
+ * 检查 addon 子目录是否存在
250
+ * @param name - addon 名称
251
+ * @param subDir - 子目录名称
252
+ * @returns 是否存在
253
+ */
254
+ export const addonDirExists = (name: string, subDir: string): boolean => {
255
+ const dir = getAddonDir(name, subDir);
256
+ return existsSync(dir) && statSync(dir).isDirectory();
217
257
  };
package/lib/addon.ts DELETED
@@ -1,77 +0,0 @@
1
- /**
2
- * Addon 管理工具类
3
- * 提供 addon 的扫描、路径获取等功能
4
- */
5
-
6
- import fs from 'node:fs';
7
- import { join } from 'pathe';
8
- import { existsSync, statSync, readdirSync } from 'node:fs';
9
- import { projectDir } from '../paths.js';
10
-
11
- /**
12
- * Addon 管理类
13
- */
14
- export class Addon {
15
- /**
16
- * 扫描所有可用的 addon
17
- * @returns addon 名称数组
18
- */
19
- static scan(): string[] {
20
- const beflyDir = join(projectDir, 'node_modules', '@befly-addon');
21
-
22
- if (!existsSync(beflyDir)) {
23
- return [];
24
- }
25
-
26
- try {
27
- return fs
28
- .readdirSync(beflyDir)
29
- .filter((name) => {
30
- // addon 名称格式:admin, demo 等(不带 addon- 前缀)
31
- const fullPath = join(beflyDir, name);
32
- try {
33
- const stat = statSync(fullPath);
34
- return stat.isDirectory();
35
- } catch {
36
- return false;
37
- }
38
- })
39
- .sort();
40
- } catch {
41
- return [];
42
- }
43
- }
44
-
45
- /**
46
- * 获取 addon 的指定子目录路径
47
- * @param name - addon 名称
48
- * @param subDir - 子目录名称
49
- * @returns 完整路径
50
- */
51
- static getDir(name: string, subDir: string): string {
52
- return join(projectDir, 'node_modules', '@befly-addon', name, subDir);
53
- }
54
-
55
- /**
56
- * 检查 addon 子目录是否存在
57
- * @param name - addon 名称
58
- * @param subDir - 子目录名称
59
- * @returns 是否存在
60
- */
61
- static dirExists(name: string, subDir: string): boolean {
62
- const dir = this.getDir(name, subDir);
63
- return existsSync(dir) && statSync(dir).isDirectory();
64
- }
65
-
66
- /**
67
- * 获取插件目录列表
68
- * @param addonsDir - addons 根目录路径
69
- * @returns 插件名称数组
70
- */
71
- static getDirs(addonsDir: string): string[] {
72
- return readdirSync(addonsDir).filter((name) => {
73
- const addonPath = join(addonsDir, name);
74
- return statSync(addonPath).isDirectory() && !name.startsWith('_');
75
- });
76
- }
77
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * 服务启动引导器
3
- * 负责组装和启动Bun HTTP服务器
4
- */
5
-
6
- import { Logger } from '../lib/logger.js';
7
- import { calcPerfTime, No } from '../util.js';
8
- import { Env } from '../env.js';
9
- import { rootHandler } from '../router/root.js';
10
- import { apiHandler } from '../router/api.js';
11
- import { staticHandler } from '../router/static.js';
12
-
13
- import type { Server } from 'bun';
14
- import type { BeflyContext } from '../types/befly.js';
15
- import type { ApiRoute } from '../types/api.js';
16
- import type { Plugin } from '../types/plugin.js';
17
-
18
- /**
19
- * 引导器类
20
- */
21
- export class Bootstrap {
22
- /**
23
- * 启动HTTP服务器
24
- * @param befly - Befly实例(需要访问 apiRoutes, pluginLists, appContext, appOptions)
25
- * @param callback - 启动后的回调函数
26
- */
27
- static async start(
28
- befly: {
29
- apiRoutes: Map<string, ApiRoute>;
30
- pluginLists: Plugin[];
31
- appContext: BeflyContext;
32
- appOptions: any;
33
- },
34
- callback?: (server: Server) => void
35
- ): Promise<Server> {
36
- const startTime = Bun.nanoseconds();
37
-
38
- const server = Bun.serve({
39
- port: Env.APP_PORT,
40
- hostname: Env.APP_HOST,
41
- routes: {
42
- '/': rootHandler,
43
- '/api/*': apiHandler(befly.apiRoutes, befly.pluginLists, befly.appContext),
44
- '/*': staticHandler,
45
- ...(befly.appOptions.routes || {})
46
- },
47
- error: (error: Error) => {
48
- Logger.error('服务启动时发生错误', error);
49
- return Response.json(No('内部服务器错误'));
50
- }
51
- });
52
-
53
- const finalStartupTime = calcPerfTime(startTime);
54
- Logger.info(`${Env.APP_NAME} 服务器启动成功! 服务器启动耗时: ${finalStartupTime}`);
55
- Logger.info(`服务器监听地址: http://${Env.APP_HOST}:${Env.APP_PORT}`);
56
-
57
- if (callback && typeof callback === 'function') {
58
- callback(server);
59
- }
60
-
61
- return server;
62
- }
63
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * 系统检查管理器
3
- * 负责在框架启动前执行系统检查
4
- */
5
-
6
- import { join, basename } from 'pathe';
7
- import { existsSync, statSync } from 'node:fs';
8
- import { Logger } from '../lib/logger.js';
9
- import { calcPerfTime } from '../util.js';
10
- import { projectCheckDir } from '../paths.js';
11
- import { checkDefault } from '../check.js';
12
- import { Addon } from '../lib/addon.js';
13
-
14
- /**
15
- * 系统检查器类
16
- */
17
- export class Checker {
18
- /**
19
- * 执行所有系统检查
20
- */
21
- static async run(): Promise<void> {
22
- try {
23
- const checkStartTime = Bun.nanoseconds();
24
-
25
- // 先执行默认检查(有异常会自动抛出)
26
- await checkDefault();
27
-
28
- const glob = new Bun.Glob('*.{ts}');
29
-
30
- // 统计信息
31
- const stats = {
32
- totalChecks: 0,
33
- passedChecks: 0,
34
- failedChecks: 0
35
- };
36
-
37
- // 检查目录列表:先项目,后 addons
38
- const checkDirs: Array<{ path: string; type: 'app' | 'addon'; addonName?: string }> = [];
39
-
40
- // 添加项目 checks 目录(如果存在)
41
- if (existsSync(projectCheckDir) && statSync(projectCheckDir).isDirectory()) {
42
- checkDirs.push({ path: projectCheckDir, type: 'app' });
43
- }
44
-
45
- // 添加所有 addon 的 checks 目录
46
- const addons = Addon.scan();
47
- for (const addon of addons) {
48
- if (Addon.dirExists(addon, 'checks')) {
49
- checkDirs.push({
50
- path: Addon.getDir(addon, 'checks'),
51
- type: 'addon',
52
- addonName: addon
53
- });
54
- }
55
- }
56
-
57
- // 按顺序扫描并执行检查函数
58
- for (const checkConfig of checkDirs) {
59
- const { path: checkDir, type: type } = checkConfig;
60
- const addonName = 'addonName' in checkConfig ? checkConfig.addonName : undefined;
61
- const checkTypeLabel = type === 'app' ? '项目' : `组件${addonName}`;
62
-
63
- for await (const file of glob.scan({
64
- cwd: checkDir,
65
- onlyFiles: true,
66
- absolute: true
67
- })) {
68
- const fileName = basename(file);
69
- if (fileName.startsWith('_')) continue; // 跳过以下划线开头的文件
70
-
71
- try {
72
- stats.totalChecks++;
73
- const singleCheckStart = Bun.nanoseconds();
74
-
75
- // 导入检查模块
76
- const checkModule = await import(file);
77
-
78
- // 获取 default 导出的检查函数
79
- const checkFn = checkModule.default;
80
-
81
- // 执行检查函数
82
- if (typeof checkFn === 'function') {
83
- const checkResult = await checkFn();
84
- const singleCheckTime = calcPerfTime(singleCheckStart);
85
-
86
- // 检查返回值是否为 boolean
87
- if (typeof checkResult !== 'boolean') {
88
- Logger.warn(`${checkTypeLabel}检查 ${fileName} 返回值必须为 true 或 false,当前为 ${typeof checkResult}`);
89
- stats.failedChecks++;
90
- } else if (checkResult === true) {
91
- stats.passedChecks++;
92
- } else {
93
- Logger.warn(`${checkTypeLabel}检查未通过: ${fileName}`);
94
- stats.failedChecks++;
95
- }
96
- } else {
97
- Logger.warn(`${checkTypeLabel}检查文件 ${fileName} 未找到 default 导出的检查函数`);
98
- stats.failedChecks++;
99
- }
100
- } catch (error: any) {
101
- const singleCheckTime = calcPerfTime(Bun.nanoseconds());
102
- Logger.error(`${checkTypeLabel}检查失败 ${fileName},耗时: ${singleCheckTime}`, error);
103
- stats.failedChecks++;
104
- }
105
- }
106
- }
107
-
108
- const totalCheckTime = calcPerfTime(checkStartTime);
109
-
110
- // 输出检查结果统计
111
- if (stats.failedChecks > 0) {
112
- Logger.error(`系统检查失败: ${stats.failedChecks}/${stats.totalChecks},耗时: ${totalCheckTime}`);
113
- process.exit(1);
114
- } else if (stats.totalChecks > 0) {
115
- Logger.info(`系统检查通过: ${stats.passedChecks}/${stats.totalChecks},耗时: ${totalCheckTime}`);
116
- }
117
- } catch (error: any) {
118
- Logger.error('执行系统检查时发生错误', error);
119
- process.exit(1);
120
- }
121
- }
122
- }
@@ -1,104 +0,0 @@
1
- /**
2
- * 生命周期管理器
3
- * 统一管理框架的启动流程:检查、加载插件、加载API、启动服务器
4
- */
5
-
6
- import { Logger } from '../lib/logger.js';
7
- import { Database } from '../lib/database.js';
8
- import { Loader } from './loader.js';
9
- import { Checker } from './checker.js';
10
- import { Env } from '../env.js';
11
- import { calcPerfTime } from '../util.js';
12
- import { Addon } from '../lib/addon.js';
13
- import { Bootstrap } from './bootstrap.js';
14
-
15
- import type { Server } from 'bun';
16
- import type { Plugin } from '../types/plugin.js';
17
- import type { ApiRoute } from '../types/api.js';
18
- import type { BeflyContext, BeflyOptions } from '../types/befly.js';
19
-
20
- /**
21
- * 生命周期管理类
22
- */
23
- export class Lifecycle {
24
- /** API 路由映射表 */
25
- private apiRoutes: Map<string, ApiRoute> = new Map();
26
-
27
- /** 插件列表 */
28
- private pluginLists: Plugin[] = [];
29
-
30
- /** 应用配置选项 */
31
- private options: BeflyOptions;
32
-
33
- constructor(options: BeflyOptions = {}) {
34
- this.options = options;
35
- }
36
-
37
- /**
38
- * 启动完整的生命周期流程
39
- * @param appContext - 应用上下文
40
- * @param callback - 启动完成后的回调函数
41
- */
42
- async start(appContext: BeflyContext, callback?: (server: Server) => void): Promise<Server> {
43
- const serverStartTime = Bun.nanoseconds();
44
-
45
- // 1. 执行系统检查
46
- await Checker.run();
47
-
48
- // 2. 加载插件
49
- await Loader.loadPlugins({ pluginLists: this.pluginLists, appContext });
50
-
51
- // 3. 加载所有 API(addon + app)
52
- await this.loadAllApis();
53
-
54
- // 4. 启动 HTTP 服务器
55
- const totalStartupTime = calcPerfTime(serverStartTime);
56
-
57
- return await Bootstrap.start(
58
- {
59
- apiRoutes: this.apiRoutes,
60
- pluginLists: this.pluginLists,
61
- appContext,
62
- appOptions: this.options
63
- },
64
- callback
65
- );
66
- }
67
-
68
- /**
69
- * 加载所有 API 路由
70
- * 包括 core APIs、addon APIs 和 app APIs
71
- */
72
- private async loadAllApis(): Promise<void> {
73
- // 1. 加载 Core APIs
74
- try {
75
- await Loader.loadApis('core', this.apiRoutes, { where: 'core' });
76
- } catch (error: any) {
77
- Logger.error(`核心 APIs 加载失败`, error);
78
- throw error;
79
- }
80
-
81
- // 2. 加载 addon APIs
82
- const addons = Addon.scan();
83
-
84
- for (const addon of addons) {
85
- const hasApis = Addon.dirExists(addon, 'apis');
86
- if (hasApis) {
87
- try {
88
- await Loader.loadApis(addon, this.apiRoutes, { where: 'addon', addonName: addon });
89
- } catch (error: any) {
90
- Logger.error(`[组件 ${addon}] APIs 加载失败`, error);
91
- throw error;
92
- }
93
- }
94
- }
95
-
96
- // 3. 加载用户 APIs
97
- try {
98
- await Loader.loadApis('app', this.apiRoutes, { where: 'app' });
99
- } catch (error: any) {
100
- Logger.error(`用户 APIs 加载失败`, error);
101
- throw error;
102
- }
103
- }
104
- }