befly 3.8.2 → 3.8.4
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/check.ts +165 -92
- package/env.ts +3 -3
- package/lib/cacheHelper.ts +81 -0
- package/lib/database.ts +1 -1
- package/loader/loadApis.ts +172 -0
- package/{lifecycle → loader}/loadPlugins.ts +10 -10
- package/main.ts +108 -12
- package/package.json +4 -4
- package/paths.ts +7 -0
- package/util.ts +85 -1
- package/lib/addon.ts +0 -77
- package/lifecycle/bootstrap.ts +0 -49
- package/lifecycle/checker.ts +0 -122
- package/lifecycle/lifecycle.ts +0 -61
- package/lifecycle/loadApis.ts +0 -164
- package/menu.json +0 -55
- package/plugins/cache.ts +0 -22
- package/plugins/db.ts +0 -59
- package/plugins/logger.ts +0 -27
- package/plugins/redis.ts +0 -41
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 加载器
|
|
3
|
+
* 负责扫描和加载所有 API 路由(组件、用户)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { relative, basename, join } from 'pathe';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { isPlainObject } from 'es-toolkit/compat';
|
|
9
|
+
import { Logger } from '../lib/logger.js';
|
|
10
|
+
import { calcPerfTime } from '../util.js';
|
|
11
|
+
import { projectApiDir } from '../paths.js';
|
|
12
|
+
import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
|
|
13
|
+
import type { ApiRoute } from '../types/api.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* API 默认字段定义
|
|
17
|
+
* 这些字段会自动合并到所有 API 的 fields 中
|
|
18
|
+
* API 自定义的同名字段可以覆盖这些默认值
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_API_FIELDS = {
|
|
21
|
+
id: {
|
|
22
|
+
name: 'ID',
|
|
23
|
+
type: 'number',
|
|
24
|
+
min: 1,
|
|
25
|
+
max: null
|
|
26
|
+
},
|
|
27
|
+
page: {
|
|
28
|
+
name: '页码',
|
|
29
|
+
type: 'number',
|
|
30
|
+
min: 1,
|
|
31
|
+
max: 9999
|
|
32
|
+
},
|
|
33
|
+
limit: {
|
|
34
|
+
name: '每页数量',
|
|
35
|
+
type: 'number',
|
|
36
|
+
min: 1,
|
|
37
|
+
max: 100
|
|
38
|
+
},
|
|
39
|
+
keyword: {
|
|
40
|
+
name: '关键词',
|
|
41
|
+
type: 'string',
|
|
42
|
+
min: 1,
|
|
43
|
+
max: 50
|
|
44
|
+
},
|
|
45
|
+
state: {
|
|
46
|
+
name: '状态',
|
|
47
|
+
type: 'number',
|
|
48
|
+
min: 0,
|
|
49
|
+
max: 2
|
|
50
|
+
}
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 扫描用户 API 文件
|
|
55
|
+
*/
|
|
56
|
+
async function scanUserApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
|
|
57
|
+
const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
|
|
58
|
+
|
|
59
|
+
if (!existsSync(projectApiDir)) {
|
|
60
|
+
return apis;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const glob = new Bun.Glob('**/*.ts');
|
|
64
|
+
|
|
65
|
+
for await (const file of glob.scan({
|
|
66
|
+
cwd: projectApiDir,
|
|
67
|
+
onlyFiles: true,
|
|
68
|
+
absolute: true
|
|
69
|
+
})) {
|
|
70
|
+
const apiPath = relative(projectApiDir, file).replace(/\.ts$/, '');
|
|
71
|
+
if (apiPath.indexOf('_') !== -1) continue;
|
|
72
|
+
|
|
73
|
+
apis.push({
|
|
74
|
+
file: file,
|
|
75
|
+
routePrefix: '',
|
|
76
|
+
displayName: '用户'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return apis;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 扫描组件 API 文件
|
|
85
|
+
*/
|
|
86
|
+
async function scanAddonApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
|
|
87
|
+
const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
|
|
88
|
+
const glob = new Bun.Glob('**/*.ts');
|
|
89
|
+
const addons = scanAddons();
|
|
90
|
+
|
|
91
|
+
for (const addon of addons) {
|
|
92
|
+
if (!addonDirExists(addon, 'apis')) continue;
|
|
93
|
+
|
|
94
|
+
const addonApiDir = getAddonDir(addon, 'apis');
|
|
95
|
+
for await (const file of glob.scan({
|
|
96
|
+
cwd: addonApiDir,
|
|
97
|
+
onlyFiles: true,
|
|
98
|
+
absolute: true
|
|
99
|
+
})) {
|
|
100
|
+
const apiPath = relative(addonApiDir, file).replace(/\.ts$/, '');
|
|
101
|
+
if (apiPath.indexOf('_') !== -1) continue;
|
|
102
|
+
|
|
103
|
+
apis.push({
|
|
104
|
+
file: file,
|
|
105
|
+
routePrefix: `addon/${addon}`,
|
|
106
|
+
displayName: `组件${addon}`
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return apis;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 初始化单个 API
|
|
116
|
+
*/
|
|
117
|
+
async function initApi(apiRoutes: Map<string, ApiRoute>, apiInfo: { file: string; routePrefix: string; displayName: string }): Promise<void> {
|
|
118
|
+
const { file, routePrefix, displayName } = apiInfo;
|
|
119
|
+
const apiDir = routePrefix === '' ? projectApiDir : getAddonDir(routePrefix.replace('addon/', ''), 'apis');
|
|
120
|
+
const apiPath = relative(apiDir, file).replace(/\.ts$/, '');
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Windows 下路径需要转换为正斜杠格式
|
|
124
|
+
const filePath = file.replace(/\\/g, '/');
|
|
125
|
+
const apiImport = await import(filePath);
|
|
126
|
+
const api = apiImport.default;
|
|
127
|
+
|
|
128
|
+
// 设置默认值
|
|
129
|
+
api.method = api.method || 'POST';
|
|
130
|
+
api.auth = api.auth !== undefined ? api.auth : true;
|
|
131
|
+
// 合并默认字段:先设置自定义字段,再用默认字段覆盖(默认字段优先级更高)
|
|
132
|
+
api.fields = { ...(api.fields || {}), ...DEFAULT_API_FIELDS };
|
|
133
|
+
api.required = api.required || [];
|
|
134
|
+
|
|
135
|
+
// 构建路由
|
|
136
|
+
api.route = `${api.method.toUpperCase()}/api/${routePrefix ? routePrefix + '/' : ''}${apiPath}`;
|
|
137
|
+
apiRoutes.set(api.route, api);
|
|
138
|
+
} catch (error: any) {
|
|
139
|
+
Logger.error(`[${displayName}] 接口 ${apiPath} 加载失败`, error);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 加载所有 API 路由
|
|
146
|
+
* @param apiRoutes - API 路由映射表
|
|
147
|
+
*/
|
|
148
|
+
export async function loadApis(apiRoutes: Map<string, ApiRoute>): Promise<void> {
|
|
149
|
+
try {
|
|
150
|
+
const loadStartTime = Bun.nanoseconds();
|
|
151
|
+
|
|
152
|
+
// 阶段1:扫描所有 API
|
|
153
|
+
const userApis = await scanUserApis();
|
|
154
|
+
const addonApis = await scanAddonApis();
|
|
155
|
+
|
|
156
|
+
// 阶段2:初始化所有 API(用户 → 组件)
|
|
157
|
+
// 2.1 初始化用户 APIs
|
|
158
|
+
for (const apiInfo of userApis) {
|
|
159
|
+
await initApi(apiRoutes, apiInfo);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 2.2 初始化组件 APIs
|
|
163
|
+
for (const apiInfo of addonApis) {
|
|
164
|
+
await initApi(apiRoutes, apiInfo);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const totalLoadTime = calcPerfTime(loadStartTime);
|
|
168
|
+
} catch (error: any) {
|
|
169
|
+
Logger.error('加载 API 时发生错误', error);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -9,7 +9,7 @@ import { camelCase } from 'es-toolkit/string';
|
|
|
9
9
|
import { Logger } from '../lib/logger.js';
|
|
10
10
|
import { calcPerfTime } from '../util.js';
|
|
11
11
|
import { corePluginDir, projectPluginDir } from '../paths.js';
|
|
12
|
-
import {
|
|
12
|
+
import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
|
|
13
13
|
import type { Plugin } from '../types/plugin.js';
|
|
14
14
|
import type { BeflyContext } from '../types/befly.js';
|
|
15
15
|
|
|
@@ -80,12 +80,12 @@ async function scanCorePlugins(loadedPluginNames: Set<string>): Promise<Plugin[]
|
|
|
80
80
|
async function scanAddonPlugins(loadedPluginNames: Set<string>): Promise<Plugin[]> {
|
|
81
81
|
const plugins: Plugin[] = [];
|
|
82
82
|
const glob = new Bun.Glob('*.ts');
|
|
83
|
-
const addons =
|
|
83
|
+
const addons = scanAddons();
|
|
84
84
|
|
|
85
85
|
for (const addon of addons) {
|
|
86
|
-
if (!
|
|
86
|
+
if (!addonDirExists(addon, 'plugins')) continue;
|
|
87
87
|
|
|
88
|
-
const addonPluginsDir =
|
|
88
|
+
const addonPluginsDir = getAddonDir(addon, 'plugins');
|
|
89
89
|
for await (const file of glob.scan({
|
|
90
90
|
cwd: addonPluginsDir,
|
|
91
91
|
onlyFiles: true,
|
|
@@ -103,9 +103,9 @@ async function scanAddonPlugins(loadedPluginNames: Set<string>): Promise<Plugin[
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
106
|
+
const pluginImport = await import(file);
|
|
107
|
+
const plugin = pluginImport.default;
|
|
108
|
+
plugin.pluginName = pluginFullName;
|
|
109
109
|
plugins.push(pluginInstance);
|
|
110
110
|
loadedPluginNames.add(pluginFullName);
|
|
111
111
|
} catch (err: any) {
|
|
@@ -145,9 +145,9 @@ async function scanUserPlugins(loadedPluginNames: Set<string>): Promise<Plugin[]
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
try {
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
148
|
+
const pluginImport = await import(file);
|
|
149
|
+
const plugin = pluginImport.default;
|
|
150
|
+
plugin.pluginName = pluginFullName;
|
|
151
151
|
plugins.push(pluginInstance);
|
|
152
152
|
loadedPluginNames.add(pluginFullName);
|
|
153
153
|
} catch (err: any) {
|
package/main.ts
CHANGED
|
@@ -1,44 +1,136 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Befly 框架主入口文件
|
|
3
|
-
* 提供简洁的框架接口,核心逻辑已提取到
|
|
3
|
+
* 提供简洁的框架接口,核心逻辑已提取到 loader 层
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Env } from './env.js';
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import { Logger } from './lib/logger.js';
|
|
9
9
|
import { Cipher } from './lib/cipher.js';
|
|
10
10
|
import { Jwt } from './lib/jwt.js';
|
|
11
11
|
import { Database } from './lib/database.js';
|
|
12
|
-
import {
|
|
12
|
+
import { loadPlugins } from './loader/loadPlugins.js';
|
|
13
|
+
import { loadApis } from './loader/loadApis.js';
|
|
14
|
+
import { rootHandler } from './router/root.js';
|
|
15
|
+
import { apiHandler } from './router/api.js';
|
|
16
|
+
import { staticHandler } from './router/static.js';
|
|
13
17
|
import { coreDir } from './paths.js';
|
|
14
18
|
import { DbHelper } from './lib/dbHelper.js';
|
|
15
19
|
import { RedisHelper } from './lib/redisHelper.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
20
|
+
import { checkTable, checkApi, checkApp } from './check.js';
|
|
21
|
+
import {
|
|
22
|
+
//
|
|
23
|
+
Yes,
|
|
24
|
+
No,
|
|
25
|
+
keysToSnake,
|
|
26
|
+
keysToCamel,
|
|
27
|
+
arrayKeysToCamel,
|
|
28
|
+
pickFields,
|
|
29
|
+
fieldClear,
|
|
30
|
+
calcPerfTime,
|
|
31
|
+
scanAddons,
|
|
32
|
+
getAddonDir,
|
|
33
|
+
addonDirExists
|
|
34
|
+
} from './util.js';
|
|
18
35
|
|
|
19
36
|
import type { Server } from 'bun';
|
|
20
37
|
import type { BeflyContext } from './types/befly.js';
|
|
38
|
+
import type { Plugin } from './types/plugin.js';
|
|
39
|
+
import type { ApiRoute } from './types/api.js';
|
|
21
40
|
/**
|
|
22
41
|
* Befly 框架核心类
|
|
23
42
|
* 职责:管理应用上下文和生命周期
|
|
24
43
|
*/
|
|
25
44
|
export class Befly {
|
|
26
|
-
/**
|
|
27
|
-
private
|
|
45
|
+
/** API 路由映射表 */
|
|
46
|
+
private apiRoutes: Map<string, ApiRoute> = new Map();
|
|
47
|
+
|
|
48
|
+
/** 插件列表 */
|
|
49
|
+
private pluginLists: Plugin[] = [];
|
|
28
50
|
|
|
29
51
|
/** 应用上下文 */
|
|
30
52
|
public appContext: BeflyContext;
|
|
31
53
|
|
|
32
54
|
constructor() {
|
|
33
|
-
this.lifecycle = new Lifecycle();
|
|
34
55
|
this.appContext = {};
|
|
35
56
|
}
|
|
36
57
|
|
|
58
|
+
/**
|
|
59
|
+
* 启动完整的生命周期流程
|
|
60
|
+
* @returns HTTP 服务器实例
|
|
61
|
+
*/
|
|
62
|
+
private async start(): Promise<Server> {
|
|
63
|
+
const serverStartTime = Bun.nanoseconds();
|
|
64
|
+
|
|
65
|
+
// 1. 加载所有 API(动态导入必须在最前面,避免 Bun 1.3.2 的崩溃 bug)
|
|
66
|
+
await loadApis(this.apiRoutes);
|
|
67
|
+
|
|
68
|
+
// 2. 执行项目结构检查
|
|
69
|
+
const appCheckResult = await checkApp();
|
|
70
|
+
if (!appCheckResult) {
|
|
71
|
+
Logger.error('项目结构检查失败,程序退出');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. 执行表定义检查
|
|
76
|
+
const tableCheckResult = await checkTable();
|
|
77
|
+
if (!tableCheckResult) {
|
|
78
|
+
Logger.error('表定义检查失败,程序退出');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4. 执行 API 定义检查
|
|
83
|
+
const apiCheckResult = await checkApi();
|
|
84
|
+
if (!apiCheckResult) {
|
|
85
|
+
Logger.error('API 定义检查失败,程序退出');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 5. 加载插件
|
|
90
|
+
await loadPlugins({
|
|
91
|
+
pluginLists: this.pluginLists,
|
|
92
|
+
appContext: this.appContext
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 4. 启动 HTTP 服务器
|
|
96
|
+
const totalStartupTime = calcPerfTime(serverStartTime);
|
|
97
|
+
|
|
98
|
+
return await this.startServer();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 启动 HTTP 服务器
|
|
103
|
+
* @returns HTTP 服务器实例
|
|
104
|
+
*/
|
|
105
|
+
private async startServer(): Promise<Server> {
|
|
106
|
+
const startTime = Bun.nanoseconds();
|
|
107
|
+
|
|
108
|
+
const server = Bun.serve({
|
|
109
|
+
port: Env.APP_PORT,
|
|
110
|
+
hostname: Env.APP_HOST,
|
|
111
|
+
routes: {
|
|
112
|
+
'/': rootHandler,
|
|
113
|
+
'/api/*': apiHandler(this.apiRoutes, this.pluginLists, this.appContext),
|
|
114
|
+
'/*': staticHandler
|
|
115
|
+
},
|
|
116
|
+
error: (error: Error) => {
|
|
117
|
+
Logger.error('服务启动时发生错误', error);
|
|
118
|
+
return Response.json({ code: 1, msg: '内部服务器错误' });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const finalStartupTime = calcPerfTime(startTime);
|
|
123
|
+
Logger.info(`${Env.APP_NAME} 服务器启动成功! 服务器启动耗时: ${finalStartupTime}`);
|
|
124
|
+
Logger.info(`服务器监听地址: http://${Env.APP_HOST}:${Env.APP_PORT}`);
|
|
125
|
+
|
|
126
|
+
return server;
|
|
127
|
+
}
|
|
128
|
+
|
|
37
129
|
/**
|
|
38
130
|
* 启动服务器并注册优雅关闭处理
|
|
39
131
|
*/
|
|
40
132
|
async listen(): Promise<Server> {
|
|
41
|
-
const server = await this.
|
|
133
|
+
const server = await this.start();
|
|
42
134
|
|
|
43
135
|
const gracefulShutdown = async (signal: string) => {
|
|
44
136
|
// 1. 停止接收新请求
|
|
@@ -76,9 +168,10 @@ export {
|
|
|
76
168
|
Database,
|
|
77
169
|
DbHelper,
|
|
78
170
|
RedisHelper,
|
|
79
|
-
Addon,
|
|
80
171
|
coreDir,
|
|
81
|
-
|
|
172
|
+
checkTable,
|
|
173
|
+
checkApi,
|
|
174
|
+
checkApp
|
|
82
175
|
};
|
|
83
176
|
|
|
84
177
|
// 工具函数命名空间导出
|
|
@@ -88,5 +181,8 @@ export const utils = {
|
|
|
88
181
|
arrayKeysToCamel: arrayKeysToCamel,
|
|
89
182
|
pickFields: pickFields,
|
|
90
183
|
fieldClear: fieldClear,
|
|
91
|
-
calcPerfTime: calcPerfTime
|
|
184
|
+
calcPerfTime: calcPerfTime,
|
|
185
|
+
scanAddons: scanAddons,
|
|
186
|
+
getAddonDir: getAddonDir,
|
|
187
|
+
addonDirExists: addonDirExists
|
|
92
188
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.4",
|
|
4
4
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
40
|
"files": [
|
|
41
41
|
"lib/",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
42
|
+
"loader/",
|
|
43
|
+
"middleware/",
|
|
44
44
|
"router/",
|
|
45
45
|
"types/",
|
|
46
46
|
".gitignore",
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"es-toolkit": "^1.41.0",
|
|
70
70
|
"pathe": "^2.0.3"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "40b4c3e6591454ff060416a6275142792e7de66c"
|
|
73
73
|
}
|
package/paths.ts
CHANGED
|
@@ -89,3 +89,10 @@ export const projectApiDir = join(projectDir, 'apis');
|
|
|
89
89
|
* @usage 存放用户业务表定义(JSON 格式)
|
|
90
90
|
*/
|
|
91
91
|
export const projectTableDir = join(projectDir, 'tables');
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 项目组件目录
|
|
95
|
+
* @description {projectDir}/addons/
|
|
96
|
+
* @usage 存放本地组件(优先级高于 node_modules 中的组件)
|
|
97
|
+
*/
|
|
98
|
+
export const projectAddonsDir = join(projectDir, 'addons');
|
package/util.ts
CHANGED
|
@@ -5,7 +5,7 @@ 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';
|
|
@@ -171,3 +171,87 @@ export const calcPerfTime = (startTime: number, endTime: number = Bun.nanosecond
|
|
|
171
171
|
return `${elapsedSeconds.toFixed(2)} 秒`;
|
|
172
172
|
}
|
|
173
173
|
};
|
|
174
|
+
|
|
175
|
+
// ========================================
|
|
176
|
+
// Addon 工具函数
|
|
177
|
+
// ========================================
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 扫描所有可用的 addon
|
|
181
|
+
* 优先从本地 addons/ 目录加载,其次从 node_modules/@befly-addon/ 加载
|
|
182
|
+
* @returns addon 名称数组
|
|
183
|
+
*/
|
|
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 目录读取错误
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return Array.from(addons).sort();
|
|
228
|
+
};
|
|
229
|
+
|
|
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
|
+
// }
|
|
243
|
+
|
|
244
|
+
// 降级使用 node_modules 目录
|
|
245
|
+
return join(projectDir, 'node_modules', '@befly-addon', name, subDir);
|
|
246
|
+
};
|
|
247
|
+
|
|
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();
|
|
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
|
-
}
|
package/lifecycle/bootstrap.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 服务启动引导器
|
|
3
|
-
* 负责组装和启动Bun HTTP服务器
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Logger } from '../lib/logger.js';
|
|
7
|
-
import { calcPerfTime } 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)
|
|
25
|
-
*/
|
|
26
|
-
static async start(befly: { apiRoutes: Map<string, ApiRoute>; pluginLists: Plugin[]; appContext: BeflyContext }): Promise<Server> {
|
|
27
|
-
const startTime = Bun.nanoseconds();
|
|
28
|
-
|
|
29
|
-
const server = Bun.serve({
|
|
30
|
-
port: Env.APP_PORT,
|
|
31
|
-
hostname: Env.APP_HOST,
|
|
32
|
-
routes: {
|
|
33
|
-
'/': rootHandler,
|
|
34
|
-
'/api/*': apiHandler(befly.apiRoutes, befly.pluginLists, befly.appContext),
|
|
35
|
-
'/*': staticHandler
|
|
36
|
-
},
|
|
37
|
-
error: (error: Error) => {
|
|
38
|
-
Logger.error('服务启动时发生错误', error);
|
|
39
|
-
return Response.json({ code: 1, msg: '内部服务器错误' });
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const finalStartupTime = calcPerfTime(startTime);
|
|
44
|
-
Logger.info(`${Env.APP_NAME} 服务器启动成功! 服务器启动耗时: ${finalStartupTime}`);
|
|
45
|
-
Logger.info(`服务器监听地址: http://${Env.APP_HOST}:${Env.APP_PORT}`);
|
|
46
|
-
|
|
47
|
-
return server;
|
|
48
|
-
}
|
|
49
|
-
}
|