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.
Files changed (223) hide show
  1. package/README.md +83 -307
  2. package/dist/befly.config.d.ts +7 -0
  3. package/{befly.config.ts → dist/befly.config.js} +11 -36
  4. package/dist/befly.js +15621 -0
  5. package/dist/befly.min.js +21 -0
  6. package/dist/checks/checkApi.d.ts +1 -0
  7. package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
  8. package/dist/checks/checkHook.d.ts +1 -0
  9. package/dist/checks/checkHook.js +86 -0
  10. package/dist/checks/checkMenu.d.ts +7 -0
  11. package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
  12. package/dist/checks/checkPlugin.d.ts +1 -0
  13. package/dist/checks/checkPlugin.js +86 -0
  14. package/dist/checks/checkTable.d.ts +6 -0
  15. package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
  16. package/dist/configs/presetFields.d.ts +4 -0
  17. package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
  18. package/dist/configs/presetRegexp.d.ts +145 -0
  19. package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
  20. package/dist/hooks/auth.d.ts +7 -0
  21. package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
  22. package/dist/hooks/cors.d.ts +11 -0
  23. package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
  24. package/dist/hooks/parser.d.ts +14 -0
  25. package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
  26. package/dist/hooks/permission.d.ts +14 -0
  27. package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
  28. package/dist/hooks/validator.d.ts +11 -0
  29. package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
  30. package/dist/index.d.ts +26 -0
  31. package/{main.ts → dist/index.js} +61 -100
  32. package/dist/lib/asyncContext.d.ts +21 -0
  33. package/dist/lib/asyncContext.js +27 -0
  34. package/dist/lib/cacheHelper.d.ts +95 -0
  35. package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
  36. package/dist/lib/cacheKeys.d.ts +23 -0
  37. package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
  38. package/dist/lib/cipher.d.ts +153 -0
  39. package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
  40. package/dist/lib/connect.d.ts +91 -0
  41. package/{lib/connect.ts → dist/lib/connect.js} +47 -88
  42. package/dist/lib/dbDialect.d.ts +87 -0
  43. package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
  44. package/dist/lib/dbHelper.d.ts +204 -0
  45. package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
  46. package/dist/lib/dbUtils.d.ts +68 -0
  47. package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
  48. package/dist/lib/jwt.d.ts +13 -0
  49. package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
  50. package/dist/lib/logger.d.ts +42 -0
  51. package/dist/lib/logger.js +1144 -0
  52. package/dist/lib/redisHelper.d.ts +185 -0
  53. package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
  54. package/dist/lib/sqlBuilder.d.ts +160 -0
  55. package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
  56. package/dist/lib/sqlCheck.d.ts +23 -0
  57. package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
  58. package/dist/lib/validator.d.ts +45 -0
  59. package/{lib/validator.ts → dist/lib/validator.js} +44 -61
  60. package/dist/loader/loadApis.d.ts +12 -0
  61. package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
  62. package/dist/loader/loadHooks.d.ts +7 -0
  63. package/dist/loader/loadHooks.js +35 -0
  64. package/dist/loader/loadPlugins.d.ts +8 -0
  65. package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
  66. package/dist/paths.d.ts +93 -0
  67. package/{paths.ts → dist/paths.js} +6 -19
  68. package/dist/plugins/cache.d.ts +16 -0
  69. package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
  70. package/dist/plugins/cipher.d.ts +12 -0
  71. package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
  72. package/dist/plugins/config.d.ts +12 -0
  73. package/dist/plugins/config.js +8 -0
  74. package/dist/plugins/db.d.ts +16 -0
  75. package/{plugins/db.ts → dist/plugins/db.js} +11 -17
  76. package/dist/plugins/jwt.d.ts +12 -0
  77. package/dist/plugins/jwt.js +12 -0
  78. package/dist/plugins/logger.d.ts +32 -0
  79. package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
  80. package/dist/plugins/redis.d.ts +16 -0
  81. package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
  82. package/dist/plugins/tool.d.ts +81 -0
  83. package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
  84. package/dist/router/api.d.ts +14 -0
  85. package/dist/router/api.js +107 -0
  86. package/dist/router/static.d.ts +9 -0
  87. package/{router/static.ts → dist/router/static.js} +20 -34
  88. package/dist/scripts/ensureDist.d.ts +1 -0
  89. package/dist/scripts/ensureDist.js +296 -0
  90. package/dist/sync/syncApi.d.ts +3 -0
  91. package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
  92. package/dist/sync/syncCache.d.ts +2 -0
  93. package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
  94. package/dist/sync/syncDev.d.ts +6 -0
  95. package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
  96. package/dist/sync/syncMenu.d.ts +14 -0
  97. package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
  98. package/dist/sync/syncTable.d.ts +151 -0
  99. package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
  100. package/{types → dist/types}/api.d.ts +12 -51
  101. package/dist/types/api.js +4 -0
  102. package/{types → dist/types}/befly.d.ts +32 -227
  103. package/dist/types/befly.js +4 -0
  104. package/{types → dist/types}/cache.d.ts +7 -15
  105. package/dist/types/cache.js +4 -0
  106. package/dist/types/cipher.d.ts +27 -0
  107. package/dist/types/cipher.js +7 -0
  108. package/{types → dist/types}/common.d.ts +8 -33
  109. package/dist/types/common.js +5 -0
  110. package/{types → dist/types}/context.d.ts +3 -5
  111. package/dist/types/context.js +4 -0
  112. package/{types → dist/types}/crypto.d.ts +0 -3
  113. package/dist/types/crypto.js +4 -0
  114. package/dist/types/database.d.ts +138 -0
  115. package/dist/types/database.js +4 -0
  116. package/dist/types/hook.d.ts +17 -0
  117. package/dist/types/hook.js +6 -0
  118. package/dist/types/jwt.d.ts +75 -0
  119. package/dist/types/jwt.js +4 -0
  120. package/dist/types/logger.d.ts +59 -0
  121. package/dist/types/logger.js +6 -0
  122. package/dist/types/plugin.d.ts +16 -0
  123. package/dist/types/plugin.js +6 -0
  124. package/dist/types/redis.d.ts +71 -0
  125. package/dist/types/redis.js +4 -0
  126. package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
  127. package/dist/types/roleApisCache.js +8 -0
  128. package/dist/types/sync.d.ts +92 -0
  129. package/dist/types/sync.js +4 -0
  130. package/dist/types/table.d.ts +34 -0
  131. package/dist/types/table.js +4 -0
  132. package/dist/types/validate.d.ts +67 -0
  133. package/dist/types/validate.js +4 -0
  134. package/dist/utils/calcPerfTime.d.ts +4 -0
  135. package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
  136. package/dist/utils/convertBigIntFields.d.ts +11 -0
  137. package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
  138. package/dist/utils/cors.d.ts +8 -0
  139. package/{utils/cors.ts → dist/utils/cors.js} +1 -3
  140. package/dist/utils/disableMenusGlob.d.ts +13 -0
  141. package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
  142. package/dist/utils/fieldClear.d.ts +11 -0
  143. package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
  144. package/dist/utils/getClientIp.d.ts +6 -0
  145. package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
  146. package/dist/utils/importDefault.d.ts +1 -0
  147. package/dist/utils/importDefault.js +29 -0
  148. package/dist/utils/isDirentDirectory.d.ts +2 -0
  149. package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
  150. package/dist/utils/loadMenuConfigs.d.ts +29 -0
  151. package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
  152. package/dist/utils/mergeAndConcat.d.ts +7 -0
  153. package/dist/utils/mergeAndConcat.js +72 -0
  154. package/dist/utils/processAtSymbol.d.ts +4 -0
  155. package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
  156. package/dist/utils/processInfo.d.ts +24 -0
  157. package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
  158. package/dist/utils/response.d.ts +20 -0
  159. package/{utils/response.ts → dist/utils/response.js} +28 -49
  160. package/dist/utils/scanAddons.d.ts +17 -0
  161. package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
  162. package/dist/utils/scanConfig.d.ts +26 -0
  163. package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
  164. package/dist/utils/scanCoreBuiltins.d.ts +3 -0
  165. package/dist/utils/scanCoreBuiltins.js +65 -0
  166. package/dist/utils/scanFiles.d.ts +30 -0
  167. package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
  168. package/dist/utils/scanSources.d.ts +10 -0
  169. package/dist/utils/scanSources.js +46 -0
  170. package/dist/utils/sortModules.d.ts +28 -0
  171. package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
  172. package/dist/utils/util.d.ts +84 -0
  173. package/dist/utils/util.js +262 -0
  174. package/package.json +26 -34
  175. package/.gitignore +0 -0
  176. package/bunfig.toml +0 -3
  177. package/checks/checkHook.ts +0 -48
  178. package/checks/checkPlugin.ts +0 -48
  179. package/configs/presetRegexp.ts +0 -225
  180. package/docs/README.md +0 -98
  181. package/docs/api/api.md +0 -1921
  182. package/docs/guide/examples.md +0 -926
  183. package/docs/guide/quickstart.md +0 -354
  184. package/docs/hooks/auth.md +0 -38
  185. package/docs/hooks/cors.md +0 -28
  186. package/docs/hooks/hook.md +0 -838
  187. package/docs/hooks/parser.md +0 -19
  188. package/docs/hooks/rateLimit.md +0 -47
  189. package/docs/infra/redis.md +0 -628
  190. package/docs/plugins/cipher.md +0 -61
  191. package/docs/plugins/database.md +0 -189
  192. package/docs/plugins/plugin.md +0 -986
  193. package/docs/reference/addon.md +0 -510
  194. package/docs/reference/config.md +0 -573
  195. package/docs/reference/logger.md +0 -495
  196. package/docs/reference/sync.md +0 -478
  197. package/docs/reference/table.md +0 -763
  198. package/docs/reference/validator.md +0 -620
  199. package/lib/asyncContext.ts +0 -43
  200. package/lib/logger.ts +0 -811
  201. package/loader/loadHooks.ts +0 -51
  202. package/plugins/config.ts +0 -13
  203. package/plugins/jwt.ts +0 -15
  204. package/router/api.ts +0 -130
  205. package/tsconfig.json +0 -8
  206. package/types/database.d.ts +0 -541
  207. package/types/hook.d.ts +0 -25
  208. package/types/jwt.d.ts +0 -118
  209. package/types/logger.d.ts +0 -65
  210. package/types/plugin.d.ts +0 -19
  211. package/types/redis.d.ts +0 -83
  212. package/types/sync.d.ts +0 -398
  213. package/types/table.d.ts +0 -216
  214. package/types/validate.d.ts +0 -69
  215. package/utils/arrayKeysToCamel.ts +0 -18
  216. package/utils/configTypes.ts +0 -3
  217. package/utils/genShortId.ts +0 -12
  218. package/utils/importDefault.ts +0 -21
  219. package/utils/keysToCamel.ts +0 -22
  220. package/utils/keysToSnake.ts +0 -22
  221. package/utils/pickFields.ts +0 -19
  222. package/utils/scanSources.ts +0 -64
  223. package/utils/sqlLog.ts +0 -37
@@ -0,0 +1 @@
1
+ export declare function checkApi(apis: any[]): Promise<void>;
@@ -1,11 +1,7 @@
1
- import { isPlainObject } from "es-toolkit/compat";
2
- import { omit } from "es-toolkit/object";
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
- } else {
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
- } catch (error: any) {
82
- Logger.error(
83
- {
84
- err: error,
85
- item: api
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 type { MenuConfig } from "../types/sync.ts";
2
- import type { AddonInfo } from "../utils/scanAddons.ts";
3
-
4
- import { Logger } from "../lib/logger.ts";
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 (menu as any)?.path === "string" ? String((menu as any).path).trim() : "";
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
- (menu as any).children = nextChildren;
51
- } else {
52
- delete (menu as any).children;
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() as { menu: any; depth: number };
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
- } else {
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
+ }
@@ -0,0 +1,6 @@
1
+ import type { ScanFileResult } from "../utils/scanFiles";
2
+ /**
3
+ * 检查表定义文件
4
+ * @throws 当检查失败时抛出异常
5
+ */
6
+ export declare function checkTable(tables: ScanFileResult[]): Promise<void>;