befly 3.8.19 → 3.8.21
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/README.md +7 -6
- package/bunfig.toml +1 -1
- package/checks/checkApi.ts +92 -0
- package/checks/checkApp.ts +31 -0
- package/{check.ts → checks/checkTable.ts} +28 -159
- package/config.ts +71 -0
- package/hooks/auth.ts +30 -0
- package/hooks/cors.ts +48 -0
- package/hooks/errorHandler.ts +23 -0
- package/hooks/parser.ts +67 -0
- package/hooks/permission.ts +54 -0
- package/hooks/rateLimit.ts +70 -0
- package/hooks/requestId.ts +24 -0
- package/hooks/requestLogger.ts +25 -0
- package/hooks/responseFormatter.ts +64 -0
- package/hooks/validator.ts +34 -0
- package/lib/database.ts +28 -25
- package/lib/dbHelper.ts +3 -3
- package/lib/jwt.ts +90 -99
- package/lib/logger.ts +44 -23
- package/lib/redisHelper.ts +19 -22
- package/loader/loadApis.ts +69 -114
- package/loader/loadHooks.ts +65 -0
- package/loader/loadPlugins.ts +50 -219
- package/main.ts +106 -133
- package/package.json +23 -14
- package/paths.ts +20 -0
- package/plugins/cache.ts +1 -3
- package/plugins/db.ts +8 -11
- package/plugins/logger.ts +5 -3
- package/plugins/redis.ts +10 -14
- package/router/api.ts +60 -106
- package/router/root.ts +15 -12
- package/router/static.ts +54 -58
- package/sync/syncAll.ts +58 -0
- package/sync/syncApi.ts +264 -0
- package/sync/syncDb/apply.ts +194 -0
- package/sync/syncDb/constants.ts +76 -0
- package/sync/syncDb/ddl.ts +194 -0
- package/sync/syncDb/helpers.ts +200 -0
- package/sync/syncDb/index.ts +164 -0
- package/sync/syncDb/schema.ts +201 -0
- package/sync/syncDb/sqlite.ts +50 -0
- package/sync/syncDb/table.ts +321 -0
- package/sync/syncDb/tableCreate.ts +146 -0
- package/sync/syncDb/version.ts +72 -0
- package/sync/syncDb.ts +19 -0
- package/sync/syncDev.ts +206 -0
- package/sync/syncMenu.ts +331 -0
- package/tests/cipher.test.ts +248 -0
- package/tests/dbHelper-advanced.test.ts +717 -0
- package/tests/dbHelper-columns.test.ts +266 -0
- package/tests/dbHelper-execute.test.ts +240 -0
- package/tests/fields-redis-cache.test.ts +123 -0
- package/tests/fields-validate.test.ts +99 -0
- package/tests/integration.test.ts +202 -0
- package/tests/jwt.test.ts +122 -0
- package/tests/logger.test.ts +94 -0
- package/tests/redisHelper.test.ts +231 -0
- package/tests/sqlBuilder-advanced.test.ts +593 -0
- package/tests/sqlBuilder.test.ts +184 -0
- package/tests/util.test.ts +95 -0
- package/tests/validator-advanced.test.ts +653 -0
- package/tests/validator.test.ts +148 -0
- package/tests/xml.test.ts +101 -0
- package/tsconfig.json +2 -4
- package/types/api.d.ts +6 -0
- package/types/befly.d.ts +152 -28
- package/types/context.d.ts +29 -3
- package/types/hook.d.ts +35 -0
- package/types/index.ts +14 -1
- package/types/plugin.d.ts +6 -7
- package/types/sync.d.ts +403 -0
- package/env.ts +0 -106
- package/lib/middleware.ts +0 -275
- package/types/env.ts +0 -65
- package/types/util.d.ts +0 -45
- package/util.ts +0 -257
package/loader/loadApis.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API 加载器
|
|
3
|
-
* 负责扫描和加载所有 API
|
|
3
|
+
* 负责扫描和加载所有 API 路由(组件、项目)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// 内部依赖
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
// 外部依赖
|
|
10
|
+
import { relative, basename, join } from 'pathe';
|
|
8
11
|
import { isPlainObject } from 'es-toolkit/compat';
|
|
12
|
+
import { calcPerfTime, scanFiles, scanAddons, getAddonDir, addonDirExists } from 'befly-util';
|
|
13
|
+
|
|
14
|
+
// 相对导入
|
|
9
15
|
import { Logger } from '../lib/logger.js';
|
|
10
|
-
import { calcPerfTime } from '../util.js';
|
|
11
16
|
import { projectApiDir } from '../paths.js';
|
|
12
|
-
import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
|
|
13
|
-
import type { ApiRoute } from '../types/api.js';
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
// 类型导入
|
|
19
|
+
import type { ApiRoute } from '../types/api.js';
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* API 默认字段定义
|
|
@@ -52,103 +56,6 @@ const DEFAULT_API_FIELDS = {
|
|
|
52
56
|
}
|
|
53
57
|
} as const;
|
|
54
58
|
|
|
55
|
-
/**
|
|
56
|
-
* 扫描用户 API 文件
|
|
57
|
-
*/
|
|
58
|
-
async function scanUserApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
|
|
59
|
-
const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
|
|
60
|
-
|
|
61
|
-
if (!existsSync(projectApiDir)) {
|
|
62
|
-
return apis;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const glob = new Bun.Glob(API_GLOB_PATTERN);
|
|
66
|
-
|
|
67
|
-
for await (const file of glob.scan({
|
|
68
|
-
cwd: projectApiDir,
|
|
69
|
-
onlyFiles: true,
|
|
70
|
-
absolute: true
|
|
71
|
-
})) {
|
|
72
|
-
if (file.endsWith('.d.ts')) {
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const apiPath = relative(projectApiDir, file).replace(/\.(ts|js)$/, '');
|
|
76
|
-
if (apiPath.indexOf('_') !== -1) continue;
|
|
77
|
-
|
|
78
|
-
apis.push({
|
|
79
|
-
file: file,
|
|
80
|
-
routePrefix: '',
|
|
81
|
-
displayName: '用户'
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return apis;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* 扫描组件 API 文件
|
|
90
|
-
*/
|
|
91
|
-
async function scanAddonApis(): Promise<Array<{ file: string; routePrefix: string; displayName: string }>> {
|
|
92
|
-
const apis: Array<{ file: string; routePrefix: string; displayName: string }> = [];
|
|
93
|
-
const glob = new Bun.Glob(API_GLOB_PATTERN);
|
|
94
|
-
const addons = scanAddons();
|
|
95
|
-
|
|
96
|
-
for (const addon of addons) {
|
|
97
|
-
if (!addonDirExists(addon, 'apis')) continue;
|
|
98
|
-
|
|
99
|
-
const addonApiDir = getAddonDir(addon, 'apis');
|
|
100
|
-
for await (const file of glob.scan({
|
|
101
|
-
cwd: addonApiDir,
|
|
102
|
-
onlyFiles: true,
|
|
103
|
-
absolute: true
|
|
104
|
-
})) {
|
|
105
|
-
if (file.endsWith('.d.ts')) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const apiPath = relative(addonApiDir, file).replace(/\.(ts|js)$/, '');
|
|
109
|
-
if (apiPath.indexOf('_') !== -1) continue;
|
|
110
|
-
|
|
111
|
-
apis.push({
|
|
112
|
-
file: file,
|
|
113
|
-
routePrefix: `addon/${addon}`,
|
|
114
|
-
displayName: `组件${addon}`
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return apis;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 初始化单个 API
|
|
124
|
-
*/
|
|
125
|
-
async function initApi(apiRoutes: Map<string, ApiRoute>, apiInfo: { file: string; routePrefix: string; displayName: string }): Promise<void> {
|
|
126
|
-
const { file, routePrefix, displayName } = apiInfo;
|
|
127
|
-
const apiDir = routePrefix === '' ? projectApiDir : getAddonDir(routePrefix.replace('addon/', ''), 'apis');
|
|
128
|
-
const apiPath = relative(apiDir, file).replace(/\.(ts|js)$/, '');
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
// Windows 下路径需要转换为正斜杠格式
|
|
132
|
-
const filePath = file.replace(/\\/g, '/');
|
|
133
|
-
const apiImport = await import(filePath);
|
|
134
|
-
const api = apiImport.default;
|
|
135
|
-
|
|
136
|
-
// 设置默认值
|
|
137
|
-
api.method = api.method || 'POST';
|
|
138
|
-
api.auth = api.auth !== undefined ? api.auth : true;
|
|
139
|
-
// 合并默认字段:默认字段作为基础,API 自定义字段优先级更高
|
|
140
|
-
api.fields = { ...DEFAULT_API_FIELDS, ...(api.fields || {}) };
|
|
141
|
-
api.required = api.required || [];
|
|
142
|
-
|
|
143
|
-
// 构建路由
|
|
144
|
-
api.route = `${api.method.toUpperCase()}/api/${routePrefix ? routePrefix + '/' : ''}${apiPath}`;
|
|
145
|
-
apiRoutes.set(api.route, api);
|
|
146
|
-
} catch (error: any) {
|
|
147
|
-
Logger.error(`[${displayName}] 接口 ${apiPath} 加载失败`, error);
|
|
148
|
-
process.exit(1);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
59
|
/**
|
|
153
60
|
* 加载所有 API 路由
|
|
154
61
|
* @param apiRoutes - API 路由映射表
|
|
@@ -157,19 +64,67 @@ export async function loadApis(apiRoutes: Map<string, ApiRoute>): Promise<void>
|
|
|
157
64
|
try {
|
|
158
65
|
const loadStartTime = Bun.nanoseconds();
|
|
159
66
|
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
67
|
+
// 1. 扫描项目 API
|
|
68
|
+
const projectApiFiles = await scanFiles(projectApiDir);
|
|
69
|
+
const projectApiList = projectApiFiles.map((file) => ({
|
|
70
|
+
filePath: file.filePath,
|
|
71
|
+
relativePath: file.relativePath,
|
|
72
|
+
type: 'project' as const,
|
|
73
|
+
routePrefix: '',
|
|
74
|
+
typeName: '项目'
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
// 2. 扫描组件 API
|
|
78
|
+
const addonApiList: Array<{
|
|
79
|
+
filePath: string;
|
|
80
|
+
relativePath: string;
|
|
81
|
+
type: 'addon';
|
|
82
|
+
routePrefix: string;
|
|
83
|
+
typeName: string;
|
|
84
|
+
}> = [];
|
|
85
|
+
const addons = scanAddons();
|
|
86
|
+
for (const addon of addons) {
|
|
87
|
+
if (!addonDirExists(addon, 'apis')) continue;
|
|
88
|
+
|
|
89
|
+
const addonApiDir = getAddonDir(addon, 'apis');
|
|
90
|
+
const addonApiFiles = await scanFiles(addonApiDir);
|
|
91
|
+
|
|
92
|
+
for (const file of addonApiFiles) {
|
|
93
|
+
addonApiList.push({
|
|
94
|
+
filePath: file.filePath,
|
|
95
|
+
relativePath: file.relativePath,
|
|
96
|
+
type: 'addon' as const,
|
|
97
|
+
routePrefix: `addon/${addon}`,
|
|
98
|
+
typeName: `组件${addon}`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
168
101
|
}
|
|
169
102
|
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
103
|
+
// 3. 合并所有 API 文件
|
|
104
|
+
const allApiFiles = [...projectApiList, ...addonApiList];
|
|
105
|
+
|
|
106
|
+
// 4. 遍历处理所有 API 文件
|
|
107
|
+
for (const apiFile of allApiFiles) {
|
|
108
|
+
try {
|
|
109
|
+
// Windows 下路径需要转换为正斜杠格式
|
|
110
|
+
const normalizedFilePath = apiFile.filePath.replace(/\\/g, '/');
|
|
111
|
+
const apiImport = await import(normalizedFilePath);
|
|
112
|
+
const api = apiImport.default;
|
|
113
|
+
|
|
114
|
+
// 设置默认值
|
|
115
|
+
api.method = api.method || 'POST';
|
|
116
|
+
api.auth = api.auth !== undefined ? api.auth : true;
|
|
117
|
+
// 合并默认字段:默认字段作为基础,API 自定义字段优先级更高
|
|
118
|
+
api.fields = { ...DEFAULT_API_FIELDS, ...(api.fields || {}) };
|
|
119
|
+
api.required = api.required || [];
|
|
120
|
+
|
|
121
|
+
// 构建路由
|
|
122
|
+
api.route = `${api.method.toUpperCase()}/api/${apiFile.routePrefix ? apiFile.routePrefix + '/' : ''}${apiFile.relativePath}`;
|
|
123
|
+
apiRoutes.set(api.route, api);
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
Logger.error(`[${apiFile.typeName}] 接口 ${apiFile.relativePath} 加载失败`, error);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
173
128
|
}
|
|
174
129
|
|
|
175
130
|
const totalLoadTime = calcPerfTime(loadStartTime);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 钩子加载器
|
|
3
|
+
* 负责扫描和初始化所有钩子(核心、组件、项目)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 外部依赖
|
|
7
|
+
import { scanAddons, getAddonDir } from 'befly-util';
|
|
8
|
+
|
|
9
|
+
// 相对导入
|
|
10
|
+
import { Logger } from '../lib/logger.js';
|
|
11
|
+
import { coreHookDir, projectHookDir } from '../paths.js';
|
|
12
|
+
import { sortModules, scanModules } from '../util.js';
|
|
13
|
+
|
|
14
|
+
// 类型导入
|
|
15
|
+
import type { Hook } from '../types/hook.js';
|
|
16
|
+
|
|
17
|
+
export async function loadHooks(befly: {
|
|
18
|
+
//
|
|
19
|
+
hookLists: Hook[];
|
|
20
|
+
pluginsConfig?: Record<string, any>;
|
|
21
|
+
}): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
const allHooks: Hook[] = [];
|
|
24
|
+
|
|
25
|
+
// 1. 扫描核心钩子
|
|
26
|
+
const coreHooks = await scanModules<Hook>(coreHookDir, 'core', '钩子', befly.pluginsConfig);
|
|
27
|
+
|
|
28
|
+
// 2. 扫描组件钩子
|
|
29
|
+
const addonHooks: Hook[] = [];
|
|
30
|
+
const addons = scanAddons();
|
|
31
|
+
for (const addon of addons) {
|
|
32
|
+
const dir = getAddonDir(addon, 'hooks');
|
|
33
|
+
const hooks = await scanModules<Hook>(dir, 'addon', '钩子', befly.pluginsConfig, addon);
|
|
34
|
+
addonHooks.push(...hooks);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 3. 扫描项目钩子
|
|
38
|
+
const appHooks = await scanModules<Hook>(projectHookDir, 'app', '钩子', befly.pluginsConfig);
|
|
39
|
+
|
|
40
|
+
// 4. 合并所有钩子
|
|
41
|
+
allHooks.push(...coreHooks);
|
|
42
|
+
allHooks.push(...addonHooks);
|
|
43
|
+
allHooks.push(...appHooks);
|
|
44
|
+
|
|
45
|
+
// 5. 过滤禁用的钩子
|
|
46
|
+
const disableHooks = (befly as any).config?.disableHooks || [];
|
|
47
|
+
const enabledHooks = allHooks.filter((hook) => !disableHooks.includes(hook.name));
|
|
48
|
+
|
|
49
|
+
if (disableHooks.length > 0) {
|
|
50
|
+
Logger.info(`禁用钩子: ${disableHooks.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 6. 排序
|
|
54
|
+
const sortedHooks = sortModules(enabledHooks);
|
|
55
|
+
if (sortedHooks === false) {
|
|
56
|
+
Logger.error('钩子依赖关系错误,请检查 after 属性');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
befly.hookLists.push(...sortedHooks);
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
Logger.error('加载钩子时发生错误', error);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/loader/loadPlugins.ts
CHANGED
|
@@ -1,249 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 插件加载器
|
|
3
|
-
*
|
|
3
|
+
* 负责扫描和初始化所有插件(核心、组件、项目)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import { camelCase } from 'es-toolkit/string';
|
|
6
|
+
import { scanAddons, getAddonDir } from 'befly-util';
|
|
7
|
+
|
|
9
8
|
import { Logger } from '../lib/logger.js';
|
|
10
|
-
import { calcPerfTime } from '../util.js';
|
|
11
9
|
import { corePluginDir, projectPluginDir } from '../paths.js';
|
|
12
|
-
import {
|
|
10
|
+
import { sortModules, scanModules } from '../util.js';
|
|
11
|
+
|
|
13
12
|
import type { Plugin } from '../types/plugin.js';
|
|
14
13
|
import type { BeflyContext } from '../types/befly.js';
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
visiting.add(name);
|
|
37
|
-
(plugin.after || []).forEach(visit);
|
|
38
|
-
visiting.delete(name);
|
|
39
|
-
visited.add(name);
|
|
40
|
-
result.push(plugin);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
plugins.forEach((p) => visit(p.pluginName || p.name));
|
|
44
|
-
return isPass ? result : false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 扫描核心插件
|
|
49
|
-
*/
|
|
50
|
-
async function scanCorePlugins(loadedPluginNames: Set<string>): Promise<Plugin[]> {
|
|
51
|
-
const plugins: Plugin[] = [];
|
|
52
|
-
const glob = new Bun.Glob('*.{ts,js}');
|
|
53
|
-
|
|
54
|
-
for await (const file of glob.scan({
|
|
55
|
-
cwd: corePluginDir,
|
|
56
|
-
onlyFiles: true,
|
|
57
|
-
absolute: true
|
|
58
|
-
})) {
|
|
59
|
-
if (file.endsWith('.d.ts')) continue;
|
|
60
|
-
const fileName = basename(file).replace(/\.(ts|js)$/, '');
|
|
61
|
-
if (fileName.startsWith('_')) continue;
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const pluginImport = await import(file);
|
|
65
|
-
const plugin = pluginImport.default;
|
|
66
|
-
plugin.pluginName = fileName;
|
|
67
|
-
plugins.push(plugin);
|
|
68
|
-
loadedPluginNames.add(fileName);
|
|
69
|
-
} catch (err: any) {
|
|
70
|
-
Logger.error(`核心插件 ${fileName} 导入失败`, err);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return plugins;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 扫描组件插件
|
|
80
|
-
*/
|
|
81
|
-
async function scanAddonPlugins(loadedPluginNames: Set<string>): Promise<Plugin[]> {
|
|
82
|
-
const plugins: Plugin[] = [];
|
|
83
|
-
const glob = new Bun.Glob('*.{ts,js}');
|
|
84
|
-
const addons = scanAddons();
|
|
85
|
-
|
|
86
|
-
for (const addon of addons) {
|
|
87
|
-
if (!addonDirExists(addon, 'plugins')) continue;
|
|
88
|
-
|
|
89
|
-
const addonPluginsDir = getAddonDir(addon, 'plugins');
|
|
90
|
-
for await (const file of glob.scan({
|
|
91
|
-
cwd: addonPluginsDir,
|
|
92
|
-
onlyFiles: true,
|
|
93
|
-
absolute: true
|
|
94
|
-
})) {
|
|
95
|
-
if (file.endsWith('.d.ts')) continue;
|
|
96
|
-
const fileName = basename(file).replace(/\.(ts|js)$/, '');
|
|
97
|
-
if (fileName.startsWith('_')) continue;
|
|
98
|
-
|
|
99
|
-
const addonCamelCase = camelCase(addon);
|
|
100
|
-
const fileNameCamelCase = camelCase(fileName);
|
|
101
|
-
const pluginFullName = `addon${addonCamelCase.charAt(0).toUpperCase() + addonCamelCase.slice(1)}_${fileNameCamelCase}`;
|
|
102
|
-
|
|
103
|
-
if (loadedPluginNames.has(pluginFullName)) {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const normalizedFilePath = file.replace(/\\/g, '/');
|
|
109
|
-
const pluginImport = await import(normalizedFilePath);
|
|
110
|
-
const plugin = pluginImport.default;
|
|
111
|
-
plugin.pluginName = pluginFullName;
|
|
112
|
-
plugins.push(plugin);
|
|
113
|
-
loadedPluginNames.add(pluginFullName);
|
|
114
|
-
} catch (err: any) {
|
|
115
|
-
Logger.error(`组件${addon} ${fileName} 导入失败`, err);
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return plugins;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* 扫描用户插件
|
|
126
|
-
*/
|
|
127
|
-
async function scanUserPlugins(loadedPluginNames: Set<string>): Promise<Plugin[]> {
|
|
128
|
-
const plugins: Plugin[] = [];
|
|
129
|
-
|
|
130
|
-
if (!existsSync(projectPluginDir)) {
|
|
131
|
-
return plugins;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const glob = new Bun.Glob('*.{ts,js}');
|
|
135
|
-
for await (const file of glob.scan({
|
|
136
|
-
cwd: projectPluginDir,
|
|
137
|
-
onlyFiles: true,
|
|
138
|
-
absolute: true
|
|
139
|
-
})) {
|
|
140
|
-
if (file.endsWith('.d.ts')) continue;
|
|
141
|
-
const fileName = basename(file).replace(/\.(ts|js)$/, '');
|
|
142
|
-
if (fileName.startsWith('_')) continue;
|
|
143
|
-
|
|
144
|
-
const fileNameCamelCase = camelCase(fileName);
|
|
145
|
-
const pluginFullName = `app${fileNameCamelCase.charAt(0).toUpperCase() + fileNameCamelCase.slice(1)}`;
|
|
146
|
-
|
|
147
|
-
if (loadedPluginNames.has(pluginFullName)) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const normalizedFilePath = file.replace(/\\/g, '/');
|
|
153
|
-
const pluginImport = await import(normalizedFilePath);
|
|
154
|
-
const plugin = pluginImport.default;
|
|
155
|
-
plugin.pluginName = pluginFullName;
|
|
156
|
-
plugins.push(plugin);
|
|
157
|
-
loadedPluginNames.add(pluginFullName);
|
|
158
|
-
} catch (err: any) {
|
|
159
|
-
Logger.error(`用户插件 ${fileName} 导入失败`, err);
|
|
160
|
-
process.exit(1);
|
|
15
|
+
export async function loadPlugins(befly: {
|
|
16
|
+
//
|
|
17
|
+
pluginLists: Plugin[];
|
|
18
|
+
appContext: BeflyContext;
|
|
19
|
+
pluginsConfig?: Record<string, any>;
|
|
20
|
+
}): Promise<void> {
|
|
21
|
+
try {
|
|
22
|
+
const allPlugins: Plugin[] = [];
|
|
23
|
+
|
|
24
|
+
// 1. 扫描核心插件
|
|
25
|
+
const corePlugins = await scanModules<Plugin>(corePluginDir, 'core', '插件', befly.pluginsConfig);
|
|
26
|
+
|
|
27
|
+
// 2. 扫描组件插件
|
|
28
|
+
const addonPlugins: Plugin[] = [];
|
|
29
|
+
const addons = scanAddons();
|
|
30
|
+
for (const addon of addons) {
|
|
31
|
+
const dir = getAddonDir(addon, 'plugins');
|
|
32
|
+
const plugins = await scanModules<Plugin>(dir, 'addon', '插件', befly.pluginsConfig, addon);
|
|
33
|
+
addonPlugins.push(...plugins);
|
|
161
34
|
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return plugins;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 初始化单个插件
|
|
169
|
-
*/
|
|
170
|
-
async function initPlugin(befly: { pluginLists: Plugin[]; appContext: BeflyContext }, plugin: Plugin): Promise<void> {
|
|
171
|
-
befly.pluginLists.push(plugin);
|
|
172
|
-
|
|
173
|
-
if (typeof plugin?.onInit === 'function') {
|
|
174
|
-
befly.appContext[plugin.pluginName] = await plugin?.onInit(befly.appContext);
|
|
175
|
-
} else {
|
|
176
|
-
befly.appContext[plugin.pluginName] = {};
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
35
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
* @param befly - Befly实例(需要访问 pluginLists 和 appContext)
|
|
183
|
-
*/
|
|
184
|
-
export async function loadPlugins(befly: { pluginLists: Plugin[]; appContext: BeflyContext }): Promise<void> {
|
|
185
|
-
try {
|
|
186
|
-
const loadStartTime = Bun.nanoseconds();
|
|
187
|
-
const loadedPluginNames = new Set<string>();
|
|
36
|
+
// 3. 扫描项目插件
|
|
37
|
+
const appPlugins = await scanModules<Plugin>(projectPluginDir, 'app', '插件', befly.pluginsConfig);
|
|
188
38
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
39
|
+
// 4. 合并所有插件
|
|
40
|
+
allPlugins.push(...corePlugins);
|
|
41
|
+
allPlugins.push(...addonPlugins);
|
|
42
|
+
allPlugins.push(...appPlugins);
|
|
193
43
|
|
|
194
|
-
//
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
Logger.error('核心插件依赖关系错误,请检查插件的 after 属性');
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
44
|
+
// 5. 过滤禁用的插件
|
|
45
|
+
const disablePlugins = (befly as any).config?.disablePlugins || [];
|
|
46
|
+
const enabledPlugins = allPlugins.filter((plugin) => !disablePlugins.includes(plugin.name));
|
|
200
47
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
Logger.error('组件插件依赖关系错误,请检查插件的 after 属性');
|
|
204
|
-
process.exit(1);
|
|
48
|
+
if (disablePlugins.length > 0) {
|
|
49
|
+
Logger.info(`禁用插件: ${disablePlugins.join(', ')}`);
|
|
205
50
|
}
|
|
206
51
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
52
|
+
// 6. 排序与初始化
|
|
53
|
+
const sortedPlugins = sortModules(enabledPlugins);
|
|
54
|
+
if (sortedPlugins === false) {
|
|
55
|
+
Logger.error('插件依赖关系错误,请检查 after 属性');
|
|
210
56
|
process.exit(1);
|
|
211
57
|
}
|
|
212
58
|
|
|
213
|
-
|
|
214
|
-
// 3.1 初始化核心插件
|
|
215
|
-
for (const plugin of sortedCorePlugins) {
|
|
59
|
+
for (const plugin of sortedPlugins) {
|
|
216
60
|
try {
|
|
217
|
-
|
|
218
|
-
} catch (error: any) {
|
|
219
|
-
Logger.error(`核心插件 ${plugin.pluginName} 初始化失败`, error);
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
61
|
+
befly.pluginLists.push(plugin);
|
|
223
62
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
63
|
+
if (typeof plugin.handler === 'function') {
|
|
64
|
+
befly.appContext[plugin.name!] = await plugin.handler(befly.appContext);
|
|
65
|
+
} else {
|
|
66
|
+
befly.appContext[plugin.name!] = {};
|
|
67
|
+
}
|
|
228
68
|
} catch (error: any) {
|
|
229
|
-
Logger.error(
|
|
69
|
+
Logger.error(`插件 ${plugin.name} 初始化失败`, error);
|
|
230
70
|
process.exit(1);
|
|
231
71
|
}
|
|
232
72
|
}
|
|
233
|
-
|
|
234
|
-
// 3.3 初始化用户插件
|
|
235
|
-
for (const plugin of sortedUserPlugins) {
|
|
236
|
-
try {
|
|
237
|
-
await initPlugin(befly, plugin);
|
|
238
|
-
} catch (error: any) {
|
|
239
|
-
Logger.error(`用户插件 ${plugin.pluginName} 初始化失败`, error);
|
|
240
|
-
process.exit(1);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const totalLoadTime = calcPerfTime(loadStartTime);
|
|
245
73
|
} catch (error: any) {
|
|
246
74
|
Logger.error('加载插件时发生错误', error);
|
|
247
75
|
process.exit(1);
|
|
248
76
|
}
|
|
249
77
|
}
|
|
78
|
+
|
|
79
|
+
// ==================== 钩子加载逻辑 ====================
|
|
80
|
+
// 已移动到 loadHooks.ts
|