befly 3.10.18 → 3.11.1
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 +83 -307
- package/dist/befly.config.d.ts +7 -0
- package/{befly.config.ts → dist/befly.config.js} +11 -36
- package/dist/befly.js +15621 -0
- package/dist/befly.min.js +21 -0
- package/dist/checks/checkApi.d.ts +1 -0
- package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
- package/dist/checks/checkHook.d.ts +1 -0
- package/dist/checks/checkHook.js +86 -0
- package/dist/checks/checkMenu.d.ts +7 -0
- package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
- package/dist/checks/checkPlugin.d.ts +1 -0
- package/dist/checks/checkPlugin.js +86 -0
- package/dist/checks/checkTable.d.ts +6 -0
- package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
- package/dist/configs/presetFields.d.ts +4 -0
- package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
- package/dist/configs/presetRegexp.d.ts +145 -0
- package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
- package/dist/hooks/auth.d.ts +7 -0
- package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
- package/dist/hooks/cors.d.ts +11 -0
- package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
- package/dist/hooks/parser.d.ts +14 -0
- package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
- package/dist/hooks/permission.d.ts +14 -0
- package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
- package/dist/hooks/validator.d.ts +11 -0
- package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
- package/dist/index.d.ts +26 -0
- package/{main.ts → dist/index.js} +61 -100
- package/dist/lib/asyncContext.d.ts +21 -0
- package/dist/lib/asyncContext.js +27 -0
- package/dist/lib/cacheHelper.d.ts +95 -0
- package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
- package/dist/lib/cacheKeys.d.ts +23 -0
- package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
- package/dist/lib/cipher.d.ts +153 -0
- package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
- package/dist/lib/connect.d.ts +91 -0
- package/{lib/connect.ts → dist/lib/connect.js} +47 -88
- package/dist/lib/dbDialect.d.ts +87 -0
- package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
- package/dist/lib/dbHelper.d.ts +204 -0
- package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
- package/dist/lib/dbUtils.d.ts +68 -0
- package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
- package/dist/lib/jwt.d.ts +13 -0
- package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
- package/dist/lib/logger.d.ts +42 -0
- package/dist/lib/logger.js +1144 -0
- package/dist/lib/redisHelper.d.ts +185 -0
- package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
- package/dist/lib/sqlBuilder.d.ts +160 -0
- package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
- package/dist/lib/sqlCheck.d.ts +23 -0
- package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
- package/dist/lib/validator.d.ts +45 -0
- package/{lib/validator.ts → dist/lib/validator.js} +44 -61
- package/dist/loader/loadApis.d.ts +12 -0
- package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
- package/dist/loader/loadHooks.d.ts +7 -0
- package/dist/loader/loadHooks.js +35 -0
- package/dist/loader/loadPlugins.d.ts +8 -0
- package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
- package/dist/paths.d.ts +93 -0
- package/{paths.ts → dist/paths.js} +6 -19
- package/dist/plugins/cache.d.ts +16 -0
- package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
- package/dist/plugins/cipher.d.ts +12 -0
- package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
- package/dist/plugins/config.d.ts +12 -0
- package/dist/plugins/config.js +8 -0
- package/dist/plugins/db.d.ts +16 -0
- package/{plugins/db.ts → dist/plugins/db.js} +11 -17
- package/dist/plugins/jwt.d.ts +12 -0
- package/dist/plugins/jwt.js +12 -0
- package/dist/plugins/logger.d.ts +32 -0
- package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
- package/dist/plugins/redis.d.ts +16 -0
- package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
- package/dist/plugins/tool.d.ts +81 -0
- package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
- package/dist/router/api.d.ts +14 -0
- package/dist/router/api.js +107 -0
- package/dist/router/static.d.ts +9 -0
- package/{router/static.ts → dist/router/static.js} +20 -34
- package/dist/scripts/ensureDist.d.ts +1 -0
- package/dist/scripts/ensureDist.js +296 -0
- package/dist/sync/syncApi.d.ts +3 -0
- package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
- package/dist/sync/syncCache.d.ts +2 -0
- package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
- package/dist/sync/syncDev.d.ts +6 -0
- package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
- package/dist/sync/syncMenu.d.ts +14 -0
- package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
- package/dist/sync/syncTable.d.ts +151 -0
- package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
- package/{types → dist/types}/api.d.ts +12 -51
- package/dist/types/api.js +4 -0
- package/{types → dist/types}/befly.d.ts +32 -227
- package/dist/types/befly.js +4 -0
- package/{types → dist/types}/cache.d.ts +7 -15
- package/dist/types/cache.js +4 -0
- package/dist/types/cipher.d.ts +27 -0
- package/dist/types/cipher.js +7 -0
- package/{types → dist/types}/common.d.ts +8 -33
- package/dist/types/common.js +5 -0
- package/{types → dist/types}/context.d.ts +3 -5
- package/dist/types/context.js +4 -0
- package/{types → dist/types}/crypto.d.ts +0 -3
- package/dist/types/crypto.js +4 -0
- package/dist/types/database.d.ts +138 -0
- package/dist/types/database.js +4 -0
- package/dist/types/hook.d.ts +17 -0
- package/dist/types/hook.js +6 -0
- package/dist/types/jwt.d.ts +75 -0
- package/dist/types/jwt.js +4 -0
- package/dist/types/logger.d.ts +59 -0
- package/dist/types/logger.js +6 -0
- package/dist/types/plugin.d.ts +16 -0
- package/dist/types/plugin.js +6 -0
- package/dist/types/redis.d.ts +71 -0
- package/dist/types/redis.js +4 -0
- package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
- package/dist/types/roleApisCache.js +8 -0
- package/dist/types/sync.d.ts +92 -0
- package/dist/types/sync.js +4 -0
- package/dist/types/table.d.ts +34 -0
- package/dist/types/table.js +4 -0
- package/dist/types/validate.d.ts +67 -0
- package/dist/types/validate.js +4 -0
- package/dist/utils/calcPerfTime.d.ts +4 -0
- package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
- package/dist/utils/convertBigIntFields.d.ts +11 -0
- package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
- package/dist/utils/cors.d.ts +8 -0
- package/{utils/cors.ts → dist/utils/cors.js} +1 -3
- package/dist/utils/disableMenusGlob.d.ts +13 -0
- package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
- package/dist/utils/fieldClear.d.ts +11 -0
- package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
- package/dist/utils/getClientIp.d.ts +6 -0
- package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
- package/dist/utils/importDefault.d.ts +1 -0
- package/dist/utils/importDefault.js +29 -0
- package/dist/utils/isDirentDirectory.d.ts +2 -0
- package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
- package/dist/utils/loadMenuConfigs.d.ts +29 -0
- package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
- package/dist/utils/mergeAndConcat.d.ts +7 -0
- package/dist/utils/mergeAndConcat.js +72 -0
- package/dist/utils/processAtSymbol.d.ts +4 -0
- package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
- package/dist/utils/processInfo.d.ts +24 -0
- package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
- package/dist/utils/response.d.ts +20 -0
- package/{utils/response.ts → dist/utils/response.js} +28 -49
- package/dist/utils/scanAddons.d.ts +17 -0
- package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
- package/dist/utils/scanConfig.d.ts +26 -0
- package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
- package/dist/utils/scanCoreBuiltins.d.ts +3 -0
- package/dist/utils/scanCoreBuiltins.js +65 -0
- package/dist/utils/scanFiles.d.ts +30 -0
- package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
- package/dist/utils/scanSources.d.ts +10 -0
- package/dist/utils/scanSources.js +46 -0
- package/dist/utils/sortModules.d.ts +28 -0
- package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
- package/dist/utils/util.d.ts +84 -0
- package/dist/utils/util.js +262 -0
- package/package.json +26 -34
- package/.gitignore +0 -0
- package/bunfig.toml +0 -3
- package/checks/checkHook.ts +0 -48
- package/checks/checkPlugin.ts +0 -48
- package/configs/presetRegexp.ts +0 -225
- package/docs/README.md +0 -98
- package/docs/api/api.md +0 -1921
- package/docs/guide/examples.md +0 -926
- package/docs/guide/quickstart.md +0 -354
- package/docs/hooks/auth.md +0 -38
- package/docs/hooks/cors.md +0 -28
- package/docs/hooks/hook.md +0 -838
- package/docs/hooks/parser.md +0 -19
- package/docs/hooks/rateLimit.md +0 -47
- package/docs/infra/redis.md +0 -628
- package/docs/plugins/cipher.md +0 -61
- package/docs/plugins/database.md +0 -189
- package/docs/plugins/plugin.md +0 -986
- package/docs/reference/addon.md +0 -510
- package/docs/reference/config.md +0 -573
- package/docs/reference/logger.md +0 -495
- package/docs/reference/sync.md +0 -478
- package/docs/reference/table.md +0 -763
- package/docs/reference/validator.md +0 -620
- package/lib/asyncContext.ts +0 -43
- package/lib/logger.ts +0 -811
- package/loader/loadHooks.ts +0 -51
- package/plugins/config.ts +0 -13
- package/plugins/jwt.ts +0 -15
- package/router/api.ts +0 -130
- package/tsconfig.json +0 -8
- package/types/database.d.ts +0 -541
- package/types/hook.d.ts +0 -25
- package/types/jwt.d.ts +0 -118
- package/types/logger.d.ts +0 -65
- package/types/plugin.d.ts +0 -19
- package/types/redis.d.ts +0 -83
- package/types/sync.d.ts +0 -398
- package/types/table.d.ts +0 -216
- package/types/validate.d.ts +0 -69
- package/utils/arrayKeysToCamel.ts +0 -18
- package/utils/configTypes.ts +0 -3
- package/utils/genShortId.ts +0 -12
- package/utils/importDefault.ts +0 -21
- package/utils/keysToCamel.ts +0 -22
- package/utils/keysToSnake.ts +0 -22
- package/utils/pickFields.ts +0 -19
- package/utils/scanSources.ts +0 -64
- package/utils/sqlLog.ts +0 -37
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkApi(apis: any[]): Promise<void>;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { omit } from "
|
|
3
|
-
|
|
4
|
-
import { Logger } from "../lib/logger.ts";
|
|
5
|
-
|
|
6
|
-
export async function checkApi(apis: any[]): Promise<void> {
|
|
1
|
+
import { Logger } from "../lib/logger";
|
|
2
|
+
import { isPlainObject, omit } from "../utils/util";
|
|
3
|
+
export async function checkApi(apis) {
|
|
7
4
|
let hasError = false;
|
|
8
|
-
|
|
9
5
|
for (const api of apis) {
|
|
10
6
|
try {
|
|
11
7
|
if (typeof api?.name !== "string" || api.name.trim() === "") {
|
|
@@ -13,83 +9,69 @@ export async function checkApi(apis: any[]): Promise<void> {
|
|
|
13
9
|
hasError = true;
|
|
14
10
|
continue;
|
|
15
11
|
}
|
|
16
|
-
|
|
17
12
|
if (typeof api?.handler !== "function") {
|
|
18
13
|
Logger.warn(omit(api, ["handler"]), "接口的 handler 属性必须是函数");
|
|
19
14
|
hasError = true;
|
|
20
15
|
continue;
|
|
21
16
|
}
|
|
22
|
-
|
|
23
17
|
// routePath / routePrefix 由 scanFiles 系统生成:必须是严格的 pathname
|
|
24
18
|
if (typeof api?.routePath !== "string" || api.routePath.trim() === "") {
|
|
25
19
|
Logger.warn(omit(api, ["handler"]), "接口的 routePath 属性必须是非空字符串(由系统生成)");
|
|
26
20
|
hasError = true;
|
|
27
|
-
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
28
23
|
const routePath = api.routePath.trim();
|
|
29
|
-
|
|
30
24
|
// 不允许出现 "POST/api/..." 等 method 前缀
|
|
31
25
|
if (/^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/i.test(routePath)) {
|
|
32
26
|
Logger.warn(omit(api, ["handler"]), "接口的 routePath 不允许包含 method 前缀,应为 url.pathname(例如 /api/app/xxx)");
|
|
33
27
|
hasError = true;
|
|
34
28
|
}
|
|
35
|
-
|
|
36
29
|
if (!routePath.startsWith("/api/")) {
|
|
37
30
|
Logger.warn(omit(api, ["handler"]), "接口的 routePath 必须以 /api/ 开头");
|
|
38
31
|
hasError = true;
|
|
39
32
|
}
|
|
40
|
-
|
|
41
33
|
if (routePath.includes(" ")) {
|
|
42
34
|
Logger.warn(omit(api, ["handler"]), "接口的 routePath 不允许包含空格");
|
|
43
35
|
hasError = true;
|
|
44
36
|
}
|
|
45
|
-
|
|
46
37
|
if (routePath.includes("/api//")) {
|
|
47
38
|
Logger.warn(omit(api, ["handler"]), "接口的 routePath 不允许出现 /api//(重复斜杠)");
|
|
48
39
|
hasError = true;
|
|
49
40
|
}
|
|
50
41
|
}
|
|
51
|
-
|
|
52
42
|
if (typeof api?.routePrefix !== "string" || api.routePrefix.trim() === "") {
|
|
53
43
|
Logger.warn(omit(api, ["handler"]), "接口的 routePrefix 属性必须是非空字符串(由系统生成)");
|
|
54
44
|
hasError = true;
|
|
55
45
|
}
|
|
56
|
-
|
|
57
46
|
if (api.method && !["GET", "POST", "GET,POST", "POST,GET"].includes(String(api.method).toUpperCase())) {
|
|
58
47
|
Logger.warn(omit(api, ["handler"]), "接口的 method 属性必须是有效的 HTTP 方法 (GET, POST, GET,POST, POST,GET)");
|
|
59
48
|
hasError = true;
|
|
60
49
|
}
|
|
61
|
-
|
|
62
50
|
if (api.auth !== undefined && typeof api.auth !== "boolean") {
|
|
63
51
|
Logger.warn(omit(api, ["handler"]), "接口的 auth 属性必须是布尔值 (true=需登录, false=公开)");
|
|
64
52
|
hasError = true;
|
|
65
53
|
}
|
|
66
|
-
|
|
67
54
|
if (api.fields && !isPlainObject(api.fields)) {
|
|
68
55
|
Logger.warn(omit(api, ["handler"]), "接口的 fields 属性必须是对象");
|
|
69
56
|
hasError = true;
|
|
70
57
|
}
|
|
71
|
-
|
|
72
58
|
if (api.required && !Array.isArray(api.required)) {
|
|
73
59
|
Logger.warn(omit(api, ["handler"]), "接口的 required 属性必须是数组");
|
|
74
60
|
hasError = true;
|
|
75
61
|
}
|
|
76
|
-
|
|
77
|
-
if (api.required && api.required.some((reqItem: any) => typeof reqItem !== "string")) {
|
|
62
|
+
if (api.required && api.required.some((reqItem) => typeof reqItem !== "string")) {
|
|
78
63
|
Logger.warn(omit(api, ["handler"]), "接口的 required 属性必须是字符串数组");
|
|
79
64
|
hasError = true;
|
|
80
65
|
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"接口解析失败"
|
|
88
|
-
);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
Logger.error({
|
|
69
|
+
err: error,
|
|
70
|
+
item: api
|
|
71
|
+
}, "接口解析失败");
|
|
89
72
|
hasError = true;
|
|
90
73
|
}
|
|
91
74
|
}
|
|
92
|
-
|
|
93
75
|
if (hasError) {
|
|
94
76
|
throw new Error("接口结构检查失败");
|
|
95
77
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkHook(hooks: any[]): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Logger } from "../lib/logger";
|
|
2
|
+
import { isPlainObject, omit } from "../utils/util";
|
|
3
|
+
export async function checkHook(hooks) {
|
|
4
|
+
let hasError = false;
|
|
5
|
+
const coreBuiltinNameRegexp = /^[a-z]+(?:_[a-z]+)*$/;
|
|
6
|
+
for (const hook of hooks) {
|
|
7
|
+
try {
|
|
8
|
+
if (!isPlainObject(hook)) {
|
|
9
|
+
Logger.warn(omit(hook, ["handler"]), "钩子导出必须是对象(export default { deps, handler })");
|
|
10
|
+
hasError = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
// moduleName 必须存在(用于依赖排序与运行时挂载)。
|
|
14
|
+
if (typeof hook.moduleName !== "string" || hook.moduleName.trim() === "") {
|
|
15
|
+
Logger.warn(omit(hook, ["handler"]), "钩子的 moduleName 必须是非空字符串(由系统生成,用于 deps 与运行时挂载)");
|
|
16
|
+
hasError = true;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
// enable 必须显式声明且只能为 boolean(true/false),不允许 0/1 等其他类型。
|
|
20
|
+
if (!Object.prototype.hasOwnProperty.call(hook, "enable")) {
|
|
21
|
+
Logger.warn(omit(hook, ["handler"]), "钩子的 enable 属性是必填项,且必须显式声明为 true 或 false");
|
|
22
|
+
hasError = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (typeof hook.enable !== "boolean") {
|
|
26
|
+
Logger.warn(omit(hook, ["handler"]), "钩子的 enable 属性必须是 boolean(true/false),不允许 0/1 等其他类型");
|
|
27
|
+
hasError = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// core 内置钩子:必须来自静态注册(filePath 以 core:hook: 开头),且 name 必须显式指定并与 moduleName 一致。
|
|
31
|
+
if (hook.source === "core") {
|
|
32
|
+
const name = typeof hook.name === "string" ? hook.name : "";
|
|
33
|
+
if (name === "") {
|
|
34
|
+
Logger.warn(omit(hook, ["handler"]), "core 内置钩子必须显式设置 name(string),用于确定钩子名称");
|
|
35
|
+
hasError = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// name 必须满足:小写字母 + 下划线(不允许空格、驼峰、数字等)。
|
|
39
|
+
if (!coreBuiltinNameRegexp.test(name)) {
|
|
40
|
+
Logger.warn(omit(hook, ["handler"]), "core 内置钩子的 name 必须满足小写字母+下划线格式(例如 auth / rate_limit),不允许空格、驼峰或其他字符");
|
|
41
|
+
hasError = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!coreBuiltinNameRegexp.test(hook.moduleName)) {
|
|
45
|
+
Logger.warn(omit(hook, ["handler"]), "core 内置钩子的 moduleName 必须满足小写字母+下划线格式(由系统生成,且必须与 name 一致)");
|
|
46
|
+
hasError = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (name !== hook.moduleName) {
|
|
50
|
+
Logger.warn(omit(hook, ["handler"]), "core 内置钩子的 name 必须与 moduleName 完全一致");
|
|
51
|
+
hasError = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (typeof hook.filePath !== "string" || !hook.filePath.startsWith(`core:hook:${name}`)) {
|
|
55
|
+
Logger.warn(omit(hook, ["handler"]), "core 内置钩子必须来自静态注册(filePath 必须以 core:hook:<name> 开头),不允许通过扫描目录加载");
|
|
56
|
+
hasError = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!Array.isArray(hook.deps)) {
|
|
61
|
+
Logger.warn(omit(hook, ["handler"]), "钩子的 deps 属性必须是字符串数组");
|
|
62
|
+
hasError = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (hook.deps.some((depItem) => typeof depItem !== "string")) {
|
|
66
|
+
Logger.warn(omit(hook, ["handler"]), "钩子的 deps 属性必须是字符串数组");
|
|
67
|
+
hasError = true;
|
|
68
|
+
}
|
|
69
|
+
if (typeof hook.handler !== "function") {
|
|
70
|
+
Logger.warn(omit(hook, ["handler"]), "钩子的 handler 属性必须是函数");
|
|
71
|
+
hasError = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
Logger.error({
|
|
77
|
+
err: error,
|
|
78
|
+
item: hook
|
|
79
|
+
}, "钩子解析失败");
|
|
80
|
+
hasError = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (hasError) {
|
|
84
|
+
throw new Error("钩子结构检查失败");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MenuConfig } from "../types/sync";
|
|
2
|
+
import type { AddonInfo } from "../utils/scanAddons";
|
|
3
|
+
type CheckMenuOptions = {
|
|
4
|
+
disableMenus?: string[];
|
|
5
|
+
};
|
|
6
|
+
export declare const checkMenu: (addons: AddonInfo[], options?: CheckMenuOptions) => Promise<MenuConfig[]>;
|
|
7
|
+
export {};
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { compileDisableMenuGlobRules, isMenuPathDisabledByGlobRules } from "../utils/disableMenusGlob.ts";
|
|
6
|
-
import { loadMenuConfigs } from "../utils/loadMenuConfigs.ts";
|
|
7
|
-
|
|
8
|
-
function isValidMenuPath(path: string): { ok: boolean; reason: string } {
|
|
1
|
+
import { Logger } from "../lib/logger";
|
|
2
|
+
import { compileDisableMenuGlobRules, isMenuPathDisabledByGlobRules } from "../utils/disableMenusGlob";
|
|
3
|
+
import { loadMenuConfigs } from "../utils/loadMenuConfigs";
|
|
4
|
+
function isValidMenuPath(path) {
|
|
9
5
|
if (!path) {
|
|
10
6
|
return { ok: false, reason: "path 不能为空" };
|
|
11
7
|
}
|
|
@@ -23,96 +19,74 @@ function isValidMenuPath(path: string): { ok: boolean; reason: string } {
|
|
|
23
19
|
}
|
|
24
20
|
return { ok: true, reason: "" };
|
|
25
21
|
}
|
|
26
|
-
|
|
27
|
-
type CheckMenuOptions = {
|
|
28
|
-
disableMenus?: string[];
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
type DisableMenuRule = ReturnType<typeof compileDisableMenuGlobRules>[number];
|
|
32
|
-
|
|
33
|
-
function filterMenusByDisableRules(mergedMenus: MenuConfig[], rules: DisableMenuRule[]): MenuConfig[] {
|
|
22
|
+
function filterMenusByDisableRules(mergedMenus, rules) {
|
|
34
23
|
if (rules.length === 0) {
|
|
35
24
|
return mergedMenus;
|
|
36
25
|
}
|
|
37
|
-
|
|
38
|
-
const filtered: MenuConfig[] = [];
|
|
39
|
-
|
|
26
|
+
const filtered = [];
|
|
40
27
|
for (const menu of mergedMenus) {
|
|
41
|
-
const menuPath = typeof
|
|
28
|
+
const menuPath = typeof menu?.path === "string" ? String(menu.path).trim() : "";
|
|
42
29
|
if (menuPath && isMenuPathDisabledByGlobRules(menuPath, rules)) {
|
|
43
30
|
continue;
|
|
44
31
|
}
|
|
45
|
-
|
|
46
|
-
const children = Array.isArray((menu as any)?.children) ? ((menu as any).children as MenuConfig[]) : null;
|
|
32
|
+
const children = Array.isArray(menu?.children) ? menu.children : null;
|
|
47
33
|
if (children && children.length > 0) {
|
|
48
34
|
const nextChildren = filterMenusByDisableRules(children, rules);
|
|
49
35
|
if (nextChildren.length > 0) {
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
36
|
+
menu.children = nextChildren;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
delete menu.children;
|
|
53
40
|
}
|
|
54
41
|
}
|
|
55
|
-
|
|
56
42
|
filtered.push(menu);
|
|
57
43
|
}
|
|
58
|
-
|
|
59
44
|
return filtered;
|
|
60
45
|
}
|
|
61
|
-
|
|
62
|
-
export const checkMenu = async (addons: AddonInfo[], options: CheckMenuOptions = {}): Promise<MenuConfig[]> => {
|
|
46
|
+
export const checkMenu = async (addons, options = {}) => {
|
|
63
47
|
let hasError = false;
|
|
64
|
-
|
|
65
48
|
const mergedMenus = await loadMenuConfigs(addons);
|
|
66
|
-
|
|
67
49
|
const disableRules = compileDisableMenuGlobRules(options.disableMenus);
|
|
68
50
|
const filteredMenus = filterMenusByDisableRules(mergedMenus, disableRules);
|
|
69
|
-
|
|
70
|
-
const stack: Array<{ menu: any; depth: number }> = [];
|
|
51
|
+
const stack = [];
|
|
71
52
|
for (const m of filteredMenus) {
|
|
72
53
|
stack.push({ menu: m, depth: 1 });
|
|
73
54
|
}
|
|
74
|
-
|
|
75
|
-
const pathSet = new Set<string>();
|
|
76
|
-
|
|
55
|
+
const pathSet = new Set();
|
|
77
56
|
while (stack.length > 0) {
|
|
78
|
-
const current = stack.pop()
|
|
57
|
+
const current = stack.pop();
|
|
79
58
|
const menu = current?.menu;
|
|
80
59
|
const depth = typeof current?.depth === "number" ? current.depth : 0;
|
|
81
|
-
|
|
82
60
|
if (menu === null || typeof menu !== "object") {
|
|
83
61
|
hasError = true;
|
|
84
62
|
Logger.warn({ menu: menu }, "菜单节点必须是对象");
|
|
85
63
|
continue;
|
|
86
64
|
}
|
|
87
|
-
|
|
88
65
|
if (depth > 3) {
|
|
89
66
|
hasError = true;
|
|
90
67
|
Logger.warn({ path: menu?.path, depth: depth }, "菜单层级超过 3 级(最多三级)");
|
|
91
68
|
continue;
|
|
92
69
|
}
|
|
93
|
-
|
|
94
70
|
const children = menu.children;
|
|
95
71
|
if (typeof children !== "undefined" && !Array.isArray(children)) {
|
|
96
72
|
hasError = true;
|
|
97
73
|
Logger.warn({ path: menu?.path, childrenType: typeof children }, "菜单 children 必须是数组");
|
|
98
74
|
continue;
|
|
99
75
|
}
|
|
100
|
-
|
|
101
76
|
if (Array.isArray(children) && children.length > 0) {
|
|
102
77
|
if (depth >= 3) {
|
|
103
78
|
hasError = true;
|
|
104
79
|
Logger.warn({ path: menu?.path, depth: depth }, "菜单层级超过 3 级(最多三级)");
|
|
105
|
-
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
106
82
|
for (const child of children) {
|
|
107
83
|
stack.push({ menu: child, depth: depth + 1 });
|
|
108
84
|
}
|
|
109
85
|
}
|
|
110
86
|
}
|
|
111
|
-
|
|
112
87
|
const path = typeof menu.path === "string" ? menu.path.trim() : "";
|
|
113
88
|
const name = typeof menu.name === "string" ? menu.name.trim() : "";
|
|
114
89
|
const sort = typeof menu.sort === "number" ? menu.sort : 999999;
|
|
115
|
-
|
|
116
90
|
// 标准化输出(用于后续 syncMenu 直接使用)
|
|
117
91
|
if (typeof menu.path === "string") {
|
|
118
92
|
menu.path = path;
|
|
@@ -123,46 +97,37 @@ export const checkMenu = async (addons: AddonInfo[], options: CheckMenuOptions =
|
|
|
123
97
|
if (typeof menu.sort === "undefined") {
|
|
124
98
|
menu.sort = sort;
|
|
125
99
|
}
|
|
126
|
-
|
|
127
100
|
if (!path) {
|
|
128
101
|
hasError = true;
|
|
129
102
|
Logger.warn({ menu: menu }, "菜单缺少 path(必须是非空字符串)");
|
|
130
103
|
continue;
|
|
131
104
|
}
|
|
132
|
-
|
|
133
105
|
const pathCheck = isValidMenuPath(path);
|
|
134
106
|
if (!pathCheck.ok) {
|
|
135
107
|
hasError = true;
|
|
136
108
|
Logger.warn({ path: path, reason: pathCheck.reason }, "菜单 path 不合法");
|
|
137
109
|
}
|
|
138
|
-
|
|
139
110
|
if (!name) {
|
|
140
111
|
hasError = true;
|
|
141
112
|
Logger.warn({ path: path, menu: menu }, "菜单缺少 name(必须是非空字符串)");
|
|
142
113
|
}
|
|
143
|
-
|
|
144
114
|
if (typeof menu.sort !== "undefined" && typeof menu.sort !== "number") {
|
|
145
115
|
hasError = true;
|
|
146
116
|
Logger.warn({ path: path, sort: menu.sort }, "菜单 sort 必须是 number");
|
|
147
117
|
}
|
|
148
|
-
|
|
149
118
|
if (typeof menu.sort === "number" && (!Number.isFinite(menu.sort) || menu.sort < 1)) {
|
|
150
119
|
hasError = true;
|
|
151
120
|
Logger.warn({ path: path, sort: menu.sort }, "菜单 sort 最小值为 1");
|
|
152
121
|
}
|
|
153
|
-
|
|
154
122
|
if (pathSet.has(path)) {
|
|
155
123
|
hasError = true;
|
|
156
124
|
Logger.warn({ path: path }, "菜单 path 重复(严格模式禁止重复 path)");
|
|
157
125
|
continue;
|
|
158
126
|
}
|
|
159
|
-
|
|
160
127
|
pathSet.add(path);
|
|
161
128
|
}
|
|
162
|
-
|
|
163
129
|
if (hasError) {
|
|
164
130
|
throw new Error("菜单结构检查失败");
|
|
165
131
|
}
|
|
166
|
-
|
|
167
132
|
return filteredMenus;
|
|
168
133
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkPlugin(plugins: any[]): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Logger } from "../lib/logger";
|
|
2
|
+
import { isPlainObject, omit } from "../utils/util";
|
|
3
|
+
export async function checkPlugin(plugins) {
|
|
4
|
+
let hasError = false;
|
|
5
|
+
const coreBuiltinNameRegexp = /^[a-z]+(?:_[a-z]+)*$/;
|
|
6
|
+
for (const plugin of plugins) {
|
|
7
|
+
try {
|
|
8
|
+
if (!isPlainObject(plugin)) {
|
|
9
|
+
Logger.warn(omit(plugin, ["handler"]), "插件导出必须是对象(export default { deps, handler })");
|
|
10
|
+
hasError = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
// moduleName 必须存在(用于依赖排序与运行时挂载)。
|
|
14
|
+
if (typeof plugin.moduleName !== "string" || plugin.moduleName.trim() === "") {
|
|
15
|
+
Logger.warn(omit(plugin, ["handler"]), "插件的 moduleName 必须是非空字符串(由系统生成,用于 deps 与运行时挂载)");
|
|
16
|
+
hasError = true;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
// enable 必须显式声明且只能为 boolean(true/false),不允许 0/1 等其他类型。
|
|
20
|
+
if (!Object.prototype.hasOwnProperty.call(plugin, "enable")) {
|
|
21
|
+
Logger.warn(omit(plugin, ["handler"]), "插件的 enable 属性是必填项,且必须显式声明为 true 或 false");
|
|
22
|
+
hasError = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (typeof plugin.enable !== "boolean") {
|
|
26
|
+
Logger.warn(omit(plugin, ["handler"]), "插件的 enable 属性必须是 boolean(true/false),不允许 0/1 等其他类型");
|
|
27
|
+
hasError = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// core 内置插件:必须来自静态注册(filePath 以 core:plugin: 开头),且 name 必须显式指定并与 moduleName 一致。
|
|
31
|
+
if (plugin.source === "core") {
|
|
32
|
+
const name = typeof plugin.name === "string" ? plugin.name : "";
|
|
33
|
+
if (name === "") {
|
|
34
|
+
Logger.warn(omit(plugin, ["handler"]), "core 内置插件必须显式设置 name(string),用于确定插件名称");
|
|
35
|
+
hasError = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// name 必须满足:小写字母 + 下划线(不允许空格、驼峰、数字等)。
|
|
39
|
+
if (!coreBuiltinNameRegexp.test(name)) {
|
|
40
|
+
Logger.warn(omit(plugin, ["handler"]), "core 内置插件的 name 必须满足小写字母+下划线格式(例如 logger / redis_cache),不允许空格、驼峰或其他字符");
|
|
41
|
+
hasError = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!coreBuiltinNameRegexp.test(plugin.moduleName)) {
|
|
45
|
+
Logger.warn(omit(plugin, ["handler"]), "core 内置插件的 moduleName 必须满足小写字母+下划线格式(由系统生成,且必须与 name 一致)");
|
|
46
|
+
hasError = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (name !== plugin.moduleName) {
|
|
50
|
+
Logger.warn(omit(plugin, ["handler"]), "core 内置插件的 name 必须与 moduleName 完全一致");
|
|
51
|
+
hasError = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (typeof plugin.filePath !== "string" || !plugin.filePath.startsWith(`core:plugin:${name}`)) {
|
|
55
|
+
Logger.warn(omit(plugin, ["handler"]), "core 内置插件必须来自静态注册(filePath 必须以 core:plugin:<name> 开头),不允许通过扫描目录加载");
|
|
56
|
+
hasError = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!Array.isArray(plugin.deps)) {
|
|
61
|
+
Logger.warn(omit(plugin, ["handler"]), "插件的 deps 属性必须是字符串数组");
|
|
62
|
+
hasError = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (plugin.deps.some((depItem) => typeof depItem !== "string")) {
|
|
66
|
+
Logger.warn(omit(plugin, ["handler"]), "插件的 deps 属性必须是字符串数组");
|
|
67
|
+
hasError = true;
|
|
68
|
+
}
|
|
69
|
+
if (typeof plugin.handler !== "function") {
|
|
70
|
+
Logger.warn(omit(plugin, ["handler"]), "插件的 handler 属性必须是函数");
|
|
71
|
+
hasError = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
Logger.error({
|
|
77
|
+
err: error,
|
|
78
|
+
item: plugin
|
|
79
|
+
}, "插件解析失败");
|
|
80
|
+
hasError = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (hasError) {
|
|
84
|
+
throw new Error("插件结构检查失败");
|
|
85
|
+
}
|
|
86
|
+
}
|