befly 3.16.11 → 3.17.2

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 (156) hide show
  1. package/README.md +6 -0
  2. package/apis/admin/cacheRefresh.js +122 -0
  3. package/apis/admin/del.js +34 -0
  4. package/apis/admin/detail.js +23 -0
  5. package/apis/admin/ins.js +69 -0
  6. package/apis/admin/list.js +28 -0
  7. package/apis/admin/upd.js +95 -0
  8. package/apis/api/all.js +24 -0
  9. package/apis/api/list.js +31 -0
  10. package/apis/auth/login.js +123 -0
  11. package/apis/auth/sendSmsCode.js +24 -0
  12. package/apis/dashboard/configStatus.js +39 -0
  13. package/apis/dashboard/environmentInfo.js +43 -0
  14. package/apis/dashboard/performanceMetrics.js +20 -0
  15. package/apis/dashboard/permissionStats.js +27 -0
  16. package/apis/dashboard/serviceStatus.js +75 -0
  17. package/apis/dashboard/systemInfo.js +19 -0
  18. package/apis/dashboard/systemOverview.js +30 -0
  19. package/apis/dashboard/systemResources.js +106 -0
  20. package/apis/dict/all.js +23 -0
  21. package/apis/dict/del.js +16 -0
  22. package/apis/dict/detail.js +27 -0
  23. package/apis/dict/ins.js +51 -0
  24. package/apis/dict/items.js +30 -0
  25. package/apis/dict/list.js +36 -0
  26. package/apis/dict/upd.js +74 -0
  27. package/apis/dictType/all.js +16 -0
  28. package/apis/dictType/del.js +38 -0
  29. package/apis/dictType/detail.js +20 -0
  30. package/apis/dictType/ins.js +37 -0
  31. package/apis/dictType/list.js +26 -0
  32. package/apis/dictType/upd.js +51 -0
  33. package/apis/email/config.js +25 -0
  34. package/apis/email/logList.js +23 -0
  35. package/apis/email/send.js +66 -0
  36. package/apis/email/verify.js +21 -0
  37. package/apis/loginLog/list.js +23 -0
  38. package/apis/menu/all.js +41 -0
  39. package/apis/menu/list.js +25 -0
  40. package/apis/operateLog/list.js +23 -0
  41. package/apis/role/all.js +21 -0
  42. package/apis/role/apiSave.js +43 -0
  43. package/apis/role/apis.js +22 -0
  44. package/apis/role/del.js +49 -0
  45. package/apis/role/detail.js +32 -0
  46. package/apis/role/ins.js +46 -0
  47. package/apis/role/list.js +27 -0
  48. package/apis/role/menuSave.js +42 -0
  49. package/apis/role/menus.js +22 -0
  50. package/apis/role/save.js +40 -0
  51. package/apis/role/upd.js +50 -0
  52. package/apis/sysConfig/all.js +16 -0
  53. package/apis/sysConfig/del.js +36 -0
  54. package/apis/sysConfig/get.js +49 -0
  55. package/apis/sysConfig/ins.js +50 -0
  56. package/apis/sysConfig/list.js +24 -0
  57. package/apis/sysConfig/upd.js +62 -0
  58. package/checks/api.js +55 -0
  59. package/checks/config.js +107 -0
  60. package/checks/hook.js +38 -0
  61. package/checks/menu.js +58 -0
  62. package/checks/plugin.js +38 -0
  63. package/checks/table.js +78 -0
  64. package/configs/beflyConfig.json +61 -0
  65. package/configs/beflyMenus.json +85 -0
  66. package/configs/constConfig.js +34 -0
  67. package/configs/regexpAlias.json +55 -0
  68. package/hooks/auth.js +34 -0
  69. package/hooks/cors.js +39 -0
  70. package/hooks/parser.js +90 -0
  71. package/hooks/permission.js +71 -0
  72. package/hooks/validator.js +43 -0
  73. package/index.js +326 -0
  74. package/lib/cacheHelper.js +483 -0
  75. package/lib/cacheKeys.js +42 -0
  76. package/lib/connect.js +120 -0
  77. package/lib/dbHelper/builders.js +698 -0
  78. package/lib/dbHelper/context.js +131 -0
  79. package/lib/dbHelper/dataOps.js +505 -0
  80. package/lib/dbHelper/execute.js +65 -0
  81. package/lib/dbHelper/index.js +27 -0
  82. package/lib/dbHelper/transaction.js +43 -0
  83. package/lib/dbHelper/util.js +58 -0
  84. package/lib/dbHelper/validate.js +549 -0
  85. package/lib/emailHelper.js +110 -0
  86. package/lib/logger.js +604 -0
  87. package/lib/redisHelper.js +684 -0
  88. package/lib/sqlBuilder/batch.js +113 -0
  89. package/lib/sqlBuilder/check.js +150 -0
  90. package/lib/sqlBuilder/compiler.js +347 -0
  91. package/lib/sqlBuilder/errors.js +60 -0
  92. package/lib/sqlBuilder/index.js +218 -0
  93. package/lib/sqlBuilder/parser.js +296 -0
  94. package/lib/sqlBuilder/util.js +260 -0
  95. package/lib/validator.js +303 -0
  96. package/package.json +20 -12
  97. package/paths.js +112 -0
  98. package/plugins/cache.js +16 -0
  99. package/plugins/config.js +11 -0
  100. package/plugins/email.js +27 -0
  101. package/plugins/logger.js +20 -0
  102. package/plugins/mysql.js +36 -0
  103. package/plugins/redis.js +34 -0
  104. package/plugins/tool.js +155 -0
  105. package/router/api.js +140 -0
  106. package/router/static.js +71 -0
  107. package/scripts/syncDb/context.js +99 -0
  108. package/scripts/syncDb/diff.js +133 -0
  109. package/scripts/syncDb/index.js +70 -0
  110. package/scripts/syncDb/query.js +26 -0
  111. package/scripts/syncDb/report.js +190 -0
  112. package/scripts/syncDb/transform.js +111 -0
  113. package/sql/admin.sql +18 -0
  114. package/sql/api.sql +12 -0
  115. package/sql/dict.sql +13 -0
  116. package/sql/dictType.sql +12 -0
  117. package/sql/emailLog.sql +20 -0
  118. package/sql/loginLog.sql +25 -0
  119. package/sql/menu.sql +12 -0
  120. package/sql/operateLog.sql +22 -0
  121. package/sql/role.sql +14 -0
  122. package/sql/sysConfig.sql +16 -0
  123. package/sync/api.js +93 -0
  124. package/sync/cache.js +13 -0
  125. package/sync/dev.js +171 -0
  126. package/sync/menu.js +99 -0
  127. package/tables/admin.json +56 -0
  128. package/tables/api.json +26 -0
  129. package/tables/dict.json +30 -0
  130. package/tables/dictType.json +24 -0
  131. package/tables/emailLog.json +61 -0
  132. package/tables/loginLog.json +86 -0
  133. package/tables/menu.json +24 -0
  134. package/tables/operateLog.json +68 -0
  135. package/tables/role.json +32 -0
  136. package/tables/sysConfig.json +43 -0
  137. package/utils/calcPerfTime.js +13 -0
  138. package/utils/cors.js +17 -0
  139. package/utils/deepMerge.js +78 -0
  140. package/utils/fieldClear.js +65 -0
  141. package/utils/formatYmdHms.js +23 -0
  142. package/utils/formatZodIssues.js +109 -0
  143. package/utils/getClientIp.js +47 -0
  144. package/utils/importDefault.js +51 -0
  145. package/utils/is.js +462 -0
  146. package/utils/loggerUtils.js +185 -0
  147. package/utils/processInfo.js +39 -0
  148. package/utils/regexpUtil.js +52 -0
  149. package/utils/response.js +114 -0
  150. package/utils/scanFiles.js +124 -0
  151. package/utils/scanSources.js +68 -0
  152. package/utils/sortModules.js +75 -0
  153. package/utils/toSessionTtlSeconds.js +14 -0
  154. package/utils/util.js +374 -0
  155. package/befly.js +0 -12769
  156. package/befly.min.js +0 -47
@@ -0,0 +1,50 @@
1
+ import adminRoleTable from "../../tables/role.json";
2
+
3
+ export default {
4
+ name: "更新角色",
5
+ method: "POST",
6
+ body: "none",
7
+ auth: true,
8
+ fields: {
9
+ name: adminRoleTable.name,
10
+ code: adminRoleTable.code,
11
+ description: adminRoleTable.description,
12
+ menus: adminRoleTable.menus,
13
+ apis: adminRoleTable.apis,
14
+ sort: adminRoleTable.sort
15
+ },
16
+ required: [],
17
+ handler: async (befly, ctx) => {
18
+ const apiPaths = ctx.body.apis || [];
19
+ const menuPaths = ctx.body.menus || [];
20
+
21
+ const existing = await befly.mysql.getList({
22
+ table: "beflyRole",
23
+ where: {
24
+ code: ctx.body.code,
25
+ id$ne: ctx.body.id
26
+ }
27
+ });
28
+
29
+ if (existing.data.total > 0) {
30
+ return befly.tool.No("角色代码已被其他角色使用");
31
+ }
32
+
33
+ await befly.mysql.updData({
34
+ table: "beflyRole",
35
+ where: { id: ctx.body.id },
36
+ data: {
37
+ name: ctx.body.name,
38
+ code: ctx.body.code,
39
+ description: ctx.body.description,
40
+ menus: menuPaths,
41
+ apis: apiPaths,
42
+ sort: ctx.body.sort
43
+ }
44
+ });
45
+
46
+ await befly.cache.refreshRoleApiPermissions(ctx.body.code, apiPaths);
47
+
48
+ return befly.tool.Yes("操作成功");
49
+ }
50
+ };
@@ -0,0 +1,16 @@
1
+ export default {
2
+ name: "获取所有系统配置",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {},
7
+ required: [],
8
+ handler: async (befly) => {
9
+ const result = await befly.mysql.getAll({
10
+ table: "beflySysConfig",
11
+ orderBy: ["id#ASC"]
12
+ });
13
+
14
+ return befly.tool.Yes("操作成功", { lists: result.data.lists });
15
+ }
16
+ };
@@ -0,0 +1,36 @@
1
+ export default {
2
+ name: "删除系统配置",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ id: { name: "ID", input: "integer", min: 1, max: null }
8
+ },
9
+ required: ["id"],
10
+ handler: async (befly, ctx) => {
11
+ try {
12
+ const config = await befly.mysql.getOne({
13
+ table: "beflySysConfig",
14
+ where: { id: ctx.body.id }
15
+ });
16
+
17
+ if (!config.data || !config.data.id) {
18
+ return befly.tool.No("配置不存在");
19
+ }
20
+
21
+ if (config.data.isSystem === 1) {
22
+ return befly.tool.No("系统配置不允许删除");
23
+ }
24
+
25
+ await befly.mysql.delData({
26
+ table: "beflySysConfig",
27
+ where: { id: ctx.body.id }
28
+ });
29
+
30
+ return befly.tool.Yes("操作成功");
31
+ } catch (error) {
32
+ befly.logger.error("删除系统配置失败", error);
33
+ return befly.tool.No("操作失败");
34
+ }
35
+ }
36
+ };
@@ -0,0 +1,49 @@
1
+ import { isString } from "../../utils/is.js";
2
+ import sysConfigTable from "../../tables/sysConfig.json";
3
+
4
+ export default {
5
+ name: "根据代码获取配置值",
6
+ method: "POST",
7
+ body: "none",
8
+ auth: false,
9
+ fields: {
10
+ code: sysConfigTable.code
11
+ },
12
+ required: [],
13
+ handler: async (befly, ctx) => {
14
+ const config = await befly.mysql.getOne({
15
+ table: "beflySysConfig",
16
+ where: { code: ctx.body.code }
17
+ });
18
+
19
+ if (!config.data || !config.data.id) {
20
+ return befly.tool.No("配置不存在");
21
+ }
22
+
23
+ const valueType = config.data.valueType;
24
+ const rawValue = config.data.value;
25
+ if (!isString(valueType) || !isString(rawValue)) {
26
+ return befly.tool.No("配置数据不完整");
27
+ }
28
+
29
+ const code = config.data.code;
30
+ if (!isString(code)) {
31
+ return befly.tool.No("配置数据不完整");
32
+ }
33
+
34
+ let value = rawValue;
35
+ if (valueType === "number") {
36
+ value = Number(rawValue);
37
+ } else if (valueType === "boolean") {
38
+ value = rawValue === "true" || rawValue === "1";
39
+ } else if (valueType === "json") {
40
+ try {
41
+ value = JSON.parse(rawValue);
42
+ } catch {
43
+ value = rawValue;
44
+ }
45
+ }
46
+
47
+ return befly.tool.Yes("操作成功", { code: code, value: value });
48
+ }
49
+ };
@@ -0,0 +1,50 @@
1
+ import sysConfigTable from "../../tables/sysConfig.json";
2
+
3
+ export default {
4
+ name: "添加系统配置",
5
+ method: "POST",
6
+ body: "none",
7
+ auth: true,
8
+ fields: {
9
+ name: sysConfigTable.name,
10
+ code: sysConfigTable.code,
11
+ value: sysConfigTable.value,
12
+ valueType: sysConfigTable.valueType,
13
+ group: sysConfigTable.group,
14
+ sort: sysConfigTable.sort,
15
+ isSystem: sysConfigTable.isSystem,
16
+ description: sysConfigTable.description
17
+ },
18
+ required: [],
19
+ handler: async (befly, ctx) => {
20
+ try {
21
+ const existing = await befly.mysql.getOne({
22
+ table: "beflySysConfig",
23
+ where: { code: ctx.body.code }
24
+ });
25
+
26
+ if (existing.data && existing.data.id) {
27
+ return befly.tool.No("配置代码已存在");
28
+ }
29
+
30
+ const configId = await befly.mysql.insData({
31
+ table: "beflySysConfig",
32
+ data: {
33
+ name: ctx.body.name,
34
+ code: ctx.body.code,
35
+ value: ctx.body.value,
36
+ valueType: ctx.body.valueType || "string",
37
+ group: ctx.body.group || "",
38
+ sort: ctx.body.sort || 0,
39
+ isSystem: ctx.body.isSystem || 0,
40
+ description: ctx.body.description || ""
41
+ }
42
+ });
43
+
44
+ return befly.tool.Yes("操作成功", { id: configId.data });
45
+ } catch (error) {
46
+ befly.logger.error("添加系统配置失败", error);
47
+ return befly.tool.No("操作失败");
48
+ }
49
+ }
50
+ };
@@ -0,0 +1,24 @@
1
+ export default {
2
+ name: "获取系统配置列表",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ page: { name: "页码", input: "integer", min: 1, max: 9999 },
8
+ limit: { name: "每页数量", input: "integer", min: 1, max: 100 },
9
+ keyword: { name: "关键词", input: "string", min: 0, max: 50 },
10
+ state: { name: "状态", input: "integer", min: 0, max: 2 }
11
+ },
12
+ required: [],
13
+ handler: async (befly, ctx) => {
14
+ const result = await befly.mysql.getList({
15
+ table: "beflySysConfig",
16
+ fields: ["id", "name", "code", "value", "valueType", "group", "sort", "isSystem", "description", "state", "createdAt", "updatedAt"],
17
+ page: ctx.body.page,
18
+ limit: ctx.body.limit,
19
+ orderBy: ["group#ASC", "sort#ASC", "id#ASC"]
20
+ });
21
+
22
+ return befly.tool.Yes("操作成功", result.data);
23
+ }
24
+ };
@@ -0,0 +1,62 @@
1
+ import sysConfigTable from "../../tables/sysConfig.json";
2
+
3
+ export default {
4
+ name: "更新系统配置",
5
+ method: "POST",
6
+ body: "none",
7
+ auth: true,
8
+ fields: {
9
+ id: { name: "ID", input: "integer", min: 1, max: null },
10
+ name: sysConfigTable.name,
11
+ code: sysConfigTable.code,
12
+ value: sysConfigTable.value,
13
+ valueType: sysConfigTable.valueType,
14
+ group: sysConfigTable.group,
15
+ sort: sysConfigTable.sort,
16
+ description: sysConfigTable.description,
17
+ state: { name: "状态", input: "integer", min: 0, max: 2 }
18
+ },
19
+ required: ["id"],
20
+ handler: async (befly, ctx) => {
21
+ try {
22
+ const config = await befly.mysql.getOne({
23
+ table: "beflySysConfig",
24
+ where: { id: ctx.body.id }
25
+ });
26
+
27
+ if (!config.data || !config.data.id) {
28
+ return befly.tool.No("配置不存在");
29
+ }
30
+
31
+ if (config.data.isSystem === 1) {
32
+ await befly.mysql.updData({
33
+ table: "beflySysConfig",
34
+ data: {
35
+ value: ctx.body.value
36
+ },
37
+ where: { id: ctx.body.id }
38
+ });
39
+ } else {
40
+ await befly.mysql.updData({
41
+ table: "beflySysConfig",
42
+ data: {
43
+ name: ctx.body.name,
44
+ code: ctx.body.code,
45
+ value: ctx.body.value,
46
+ valueType: ctx.body.valueType,
47
+ group: ctx.body.group,
48
+ sort: ctx.body.sort,
49
+ description: ctx.body.description,
50
+ state: ctx.body.state
51
+ },
52
+ where: { id: ctx.body.id }
53
+ });
54
+ }
55
+
56
+ return befly.tool.Yes("操作成功");
57
+ } catch (error) {
58
+ befly.logger.error("更新系统配置失败", error);
59
+ return befly.tool.No("操作失败");
60
+ }
61
+ }
62
+ };
package/checks/api.js ADDED
@@ -0,0 +1,55 @@
1
+ import * as z from "zod";
2
+
3
+ import { Logger } from "../lib/logger.js";
4
+ import { formatZodIssues } from "../utils/formatZodIssues.js";
5
+ import { isNoTrimStringAllowEmpty } from "../utils/is.js";
6
+
7
+ z.config(z.locales.zhCN());
8
+
9
+ const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
10
+ const lowerCamelRegex = /^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
11
+ const apiPathRegex = /^\/api\/(core|app)\/[a-z][a-zA-Z0-9]*(?:\/[a-z][a-zA-Z0-9]*)*$/;
12
+
13
+ const fieldSchema = z
14
+ .object({
15
+ name: noTrimString.min(1),
16
+ input: noTrimString.min(1),
17
+ min: z.number().nullable().optional(),
18
+ max: z.number().nullable().optional()
19
+ })
20
+ .strict();
21
+
22
+ const fieldsSchema = z.record(z.string().regex(lowerCamelRegex), fieldSchema);
23
+
24
+ const apiSchema = z
25
+ .object({
26
+ source: noTrimString.min(1),
27
+ type: noTrimString.min(1),
28
+ filePath: noTrimString.min(1),
29
+ relativePath: noTrimString.min(1),
30
+ fileName: z.string().min(1).regex(lowerCamelRegex),
31
+ name: noTrimString.min(1).max(100),
32
+ handler: z.custom((value) => typeof value === "function"),
33
+ apiPath: noTrimString.regex(apiPathRegex),
34
+ method: z.enum(["GET", "POST", "ALL"]),
35
+ body: z.enum(["none", "raw"]),
36
+ auth: z.union([z.boolean(), z.array(noTrimString).min(1)]),
37
+ fields: fieldsSchema,
38
+ required: z.array(noTrimString)
39
+ })
40
+ .strict();
41
+
42
+ const apiListSchema = z.array(apiSchema);
43
+
44
+ export async function checkApi(apis) {
45
+ let hasError = false;
46
+
47
+ const schemaResult = apiListSchema.safeParse(apis);
48
+ if (!schemaResult.success) {
49
+ const errors = formatZodIssues(schemaResult.error.issues, { items: apis, itemLabel: "api" });
50
+ Logger.warn("接口校验失败", { errors: errors }, false);
51
+ hasError = true;
52
+ }
53
+
54
+ return hasError;
55
+ }
@@ -0,0 +1,107 @@
1
+ import * as z from "zod";
2
+
3
+ import { Logger } from "../lib/logger.js";
4
+ import { DB_ID_MODE_VALUES, RUN_MODE_VALUES } from "../configs/constConfig.js";
5
+ import { formatZodIssues } from "../utils/formatZodIssues.js";
6
+ import { isNoTrimStringAllowEmpty, isValidTimeZone } from "../utils/is.js";
7
+
8
+ z.config(z.locales.zhCN());
9
+
10
+ const boolIntSchema = z.union([z.literal(0), z.literal(1), z.literal(true), z.literal(false)]);
11
+ const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
12
+
13
+ const configSchema = z
14
+ .object({
15
+ runMode: z.enum(RUN_MODE_VALUES),
16
+ appName: noTrimString,
17
+ appPort: z.int().min(1).max(65535),
18
+ appHost: noTrimString,
19
+ devEmail: z.union([z.literal(""), z.email()]),
20
+ devPassword: z.string().min(6),
21
+ bodyLimit: z.int().min(1),
22
+ tz: z.string().refine((value) => isValidTimeZone(value), "无效的时区"),
23
+
24
+ logger: z
25
+ .object({
26
+ debug: boolIntSchema,
27
+ excludeFields: z.array(noTrimString),
28
+ dir: noTrimString,
29
+ console: boolIntSchema,
30
+ maxSize: z.int().min(1),
31
+ maxStringLen: z.int().min(1),
32
+ maxArrayItems: z.int().min(1)
33
+ })
34
+ .strict(),
35
+
36
+ mysql: z
37
+ .object({
38
+ idMode: z.enum(DB_ID_MODE_VALUES),
39
+ hostname: noTrimString,
40
+ port: z.int().min(1).max(65535),
41
+ username: noTrimString,
42
+ password: noTrimString,
43
+ database: noTrimString,
44
+ max: z.number().min(1)
45
+ })
46
+ .strict(),
47
+
48
+ redis: z
49
+ .object({
50
+ hostname: noTrimString,
51
+ port: z.int().min(1).max(65535),
52
+ username: noTrimString,
53
+ password: noTrimString,
54
+ db: z.int().min(0).max(16),
55
+ prefix: noTrimString.regex(/^[^:]*$/)
56
+ })
57
+ .strict(),
58
+
59
+ session: z
60
+ .object({
61
+ expireDays: z.int().min(1)
62
+ })
63
+ .strict(),
64
+
65
+ cors: z
66
+ .object({
67
+ origin: noTrimString,
68
+ methods: noTrimString,
69
+ allowedHeaders: noTrimString,
70
+ exposedHeaders: noTrimString,
71
+ maxAge: z.int().min(0),
72
+ credentials: z.union([z.literal("true"), z.literal("false"), z.literal(true), z.literal(false)])
73
+ })
74
+ .strict(),
75
+
76
+ rateLimit: z
77
+ .object({
78
+ enable: boolIntSchema,
79
+ defaultLimit: z.int().min(1),
80
+ defaultWindow: z.int().min(1),
81
+ key: noTrimString,
82
+ skipRoutes: z.array(noTrimString),
83
+ rules: z.array(z.object({}).passthrough())
84
+ })
85
+ .strict()
86
+ })
87
+ .strict();
88
+
89
+ /**
90
+ * 配置检查(启动期强校验)
91
+ *
92
+ * 说明:
93
+ * - checkTable/checkApi 等校验的是“扫描到的源码/表定义”。
94
+ * - checkConfig 校验的是“最终合并后的运行时配置对象”,用于阻断错误配置带病启动。
95
+ */
96
+ export async function checkConfig(config) {
97
+ let hasError = false;
98
+
99
+ const schemaResult = configSchema.safeParse(config);
100
+ if (!schemaResult.success) {
101
+ const errors = formatZodIssues(schemaResult.error.issues, { item: config, itemLabel: "config" });
102
+ Logger.warn("配置校验失败", { errors: errors }, false);
103
+ hasError = true;
104
+ }
105
+
106
+ return hasError;
107
+ }
package/checks/hook.js ADDED
@@ -0,0 +1,38 @@
1
+ import * as z from "zod";
2
+
3
+ import { Logger } from "../lib/logger.js";
4
+ import { formatZodIssues } from "../utils/formatZodIssues.js";
5
+ import { isNoTrimStringAllowEmpty } from "../utils/is.js";
6
+
7
+ z.config(z.locales.zhCN());
8
+
9
+ const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
10
+
11
+ const hookSchema = z
12
+ .object({
13
+ source: noTrimString.min(1),
14
+ type: noTrimString.min(1),
15
+ filePath: noTrimString.min(1),
16
+ relativePath: noTrimString.min(1),
17
+ fileName: noTrimString.min(1),
18
+ apiPath: noTrimString.min(1),
19
+ handler: z.custom((value) => typeof value === "function"),
20
+ deps: z.array(noTrimString)
21
+ })
22
+ .strict();
23
+
24
+ export async function checkHook(hooks) {
25
+ let hasError = false;
26
+
27
+ for (const hook of hooks) {
28
+ const schemaResult = hookSchema.safeParse(hook);
29
+ if (!schemaResult.success) {
30
+ const errors = formatZodIssues(schemaResult.error.issues, { item: hook, itemLabel: "hook" });
31
+ Logger.warn("钩子校验失败", { errors: errors }, false);
32
+ hasError = true;
33
+ continue;
34
+ }
35
+ }
36
+
37
+ return hasError;
38
+ }
package/checks/menu.js ADDED
@@ -0,0 +1,58 @@
1
+ import * as z from "zod";
2
+
3
+ import { Logger } from "../lib/logger.js";
4
+ import { formatZodIssues } from "../utils/formatZodIssues.js";
5
+
6
+ z.config(z.locales.zhCN());
7
+
8
+ const fullMenuPathRegex = /^\/(?:app|core)(?:\/[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*)*$/;
9
+ const childMenuPathRegex = /^\/[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
10
+
11
+ const childMenuSchema = z
12
+ .object({
13
+ path: z.string().regex(childMenuPathRegex),
14
+ name: z.string().regex(/^\S(?:.*\S)?$/),
15
+ sort: z.number(),
16
+ children: z.array(z.any()).max(0).optional()
17
+ })
18
+ .strict();
19
+
20
+ const menuSchema = z
21
+ .object({
22
+ path: z.string().regex(fullMenuPathRegex),
23
+ name: z.string().regex(/^\S(?:.*\S)?$/),
24
+ sort: z.number(),
25
+ children: z.array(childMenuSchema).optional()
26
+ })
27
+ .strict();
28
+
29
+ const menuListSchema = z.array(menuSchema).superRefine((menuList, refineCtx) => {
30
+ for (let menuIndex = 0; menuIndex < menuList.length; menuIndex++) {
31
+ const menu = menuList[menuIndex];
32
+ const children = Array.isArray(menu.children) ? menu.children : [];
33
+ for (let childIndex = 0; childIndex < children.length; childIndex++) {
34
+ const child = children[childIndex];
35
+ const childFullPath = `${menu.path}${child.path}`;
36
+ if (!fullMenuPathRegex.test(childFullPath)) {
37
+ refineCtx.addIssue({
38
+ code: z.ZodIssueCode.custom,
39
+ message: "菜单完整路径必须是 /app/xxx 或 /core/xxx",
40
+ path: [menuIndex, "children", childIndex, "path"]
41
+ });
42
+ }
43
+ }
44
+ }
45
+ });
46
+
47
+ export const checkMenu = async (menus) => {
48
+ let hasError = false;
49
+
50
+ const schemaResult = menuListSchema.safeParse(menus);
51
+ if (!schemaResult.success) {
52
+ const errors = formatZodIssues(schemaResult.error.issues, { items: menus, itemLabel: "menus" });
53
+ Logger.warn("菜单校验失败", { errors: errors }, false);
54
+ hasError = true;
55
+ }
56
+
57
+ return hasError;
58
+ };
@@ -0,0 +1,38 @@
1
+ import * as z from "zod";
2
+
3
+ import { Logger } from "../lib/logger.js";
4
+ import { formatZodIssues } from "../utils/formatZodIssues.js";
5
+ import { isNoTrimStringAllowEmpty } from "../utils/is.js";
6
+
7
+ z.config(z.locales.zhCN());
8
+
9
+ const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
10
+
11
+ const pluginSchema = z
12
+ .object({
13
+ source: noTrimString.min(1),
14
+ type: noTrimString.min(1),
15
+ filePath: noTrimString.min(1),
16
+ relativePath: noTrimString.min(1),
17
+ fileName: noTrimString.min(1),
18
+ apiPath: noTrimString.min(1),
19
+ handler: z.custom((value) => typeof value === "function"),
20
+ deps: z.array(noTrimString)
21
+ })
22
+ .strict();
23
+
24
+ export async function checkPlugin(plugins) {
25
+ let hasError = false;
26
+
27
+ for (const plugin of plugins) {
28
+ const schemaResult = pluginSchema.safeParse(plugin);
29
+ if (!schemaResult.success) {
30
+ const errors = formatZodIssues(schemaResult.error.issues, { item: plugin, itemLabel: "plugin" });
31
+ Logger.warn("插件校验失败", { errors: errors }, false);
32
+ hasError = true;
33
+ continue;
34
+ }
35
+ }
36
+
37
+ return hasError;
38
+ }
@@ -0,0 +1,78 @@
1
+ import * as z from "zod";
2
+
3
+ import { Logger } from "../lib/logger.js";
4
+ import { formatZodIssues } from "../utils/formatZodIssues.js";
5
+ import { isNoTrimStringAllowEmpty, isNullable } from "../utils/is.js";
6
+
7
+ z.config(z.locales.zhCN());
8
+
9
+ const lowerCamelRegex = /^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
10
+ const noTrimString = z.string().refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
11
+ const FIELD_NAME_REGEX_SOURCE = "^[\\u4e00-\\u9fa5a-zA-Z0-9_]+$";
12
+ const INPUT_TYPES = ["number", "integer", "string", "char", "array", "array_number", "array_integer", "json", "json_number", "json_integer"];
13
+
14
+ const fieldNameRegex = new RegExp(FIELD_NAME_REGEX_SOURCE);
15
+ const inputRegexLiteral = /^\/.*?\/[gimsuy]*$/;
16
+ const inputEnumRegex = /^[^/].*\|.*$/;
17
+ const inputAliasRegex = /^@.+$/;
18
+
19
+ const inputSchema = z.union([z.enum(INPUT_TYPES), z.string().regex(inputRegexLiteral), z.string().regex(inputEnumRegex), z.string().regex(inputAliasRegex)]).refine(isNoTrimStringAllowEmpty, "不允许首尾空格");
20
+ const DEFAULT_MIN = 0;
21
+ const DEFAULT_MAX = Number.MAX_SAFE_INTEGER;
22
+
23
+ function addIssue(context, path, message) {
24
+ context.addIssue({
25
+ path: path,
26
+ message: message
27
+ });
28
+ }
29
+
30
+ const fieldDefSchema = z
31
+ .object({
32
+ name: z.string().min(1).regex(fieldNameRegex),
33
+ input: inputSchema,
34
+ min: z.number().nullable().optional(),
35
+ max: z.number().nullable().optional()
36
+ })
37
+ .strict()
38
+ .superRefine((value, context) => {
39
+ const min = isNullable(value.min) ? DEFAULT_MIN : value.min;
40
+ const max = isNullable(value.max) ? DEFAULT_MAX : value.max;
41
+ if (min > max) {
42
+ addIssue(context, ["min"], "min 不能大于 max");
43
+ }
44
+ });
45
+
46
+ const tableContentSchema = z.record(z.string().regex(lowerCamelRegex), fieldDefSchema);
47
+
48
+ const tableItemSchema = z
49
+ .object({
50
+ source: noTrimString.min(1),
51
+ type: noTrimString.min(1),
52
+ filePath: noTrimString.min(1),
53
+ relativePath: noTrimString.min(1),
54
+ apiPath: noTrimString.min(1),
55
+ fileName: z.string().min(1).regex(lowerCamelRegex),
56
+ fieldsDef: tableContentSchema
57
+ })
58
+ .strict();
59
+
60
+ const tableListSchema = z.array(tableItemSchema);
61
+
62
+ /**
63
+ * 检查表定义文件
64
+ * @throws 当检查失败时抛出异常
65
+ */
66
+ export async function checkTable(tables) {
67
+ // 收集所有表文件
68
+ let hasError = false;
69
+
70
+ const schemaResult = tableListSchema.safeParse(tables);
71
+ if (!schemaResult.success) {
72
+ const errors = formatZodIssues(schemaResult.error.issues, { items: tables, itemLabel: "table" });
73
+ Logger.warn("表结构校验失败", { errors: errors }, false);
74
+ hasError = true;
75
+ }
76
+
77
+ return hasError;
78
+ }