befly 3.10.0 → 3.10.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 (79) hide show
  1. package/.gitignore +0 -0
  2. package/README.md +10 -13
  3. package/configs/presetFields.ts +10 -0
  4. package/configs/presetRegexp.ts +225 -0
  5. package/docs/README.md +17 -11
  6. package/docs/api/api.md +15 -1
  7. package/docs/guide/quickstart.md +19 -5
  8. package/docs/infra/redis.md +23 -11
  9. package/docs/quickstart.md +5 -335
  10. package/docs/reference/addon.md +0 -15
  11. package/docs/reference/config.md +1 -1
  12. package/docs/reference/logger.md +3 -3
  13. package/docs/reference/sync.md +99 -73
  14. package/docs/reference/table.md +1 -1
  15. package/package.json +15 -16
  16. package/docs/cipher.md +0 -582
  17. package/docs/database.md +0 -1176
  18. package/tests/_mocks/mockSqliteDb.ts +0 -204
  19. package/tests/addonHelper-cache.test.ts +0 -32
  20. package/tests/api-integration-array-number.test.ts +0 -282
  21. package/tests/apiHandler-routePath-only.test.ts +0 -32
  22. package/tests/befly-config-env.test.ts +0 -78
  23. package/tests/cacheHelper.test.ts +0 -323
  24. package/tests/cacheKeys.test.ts +0 -41
  25. package/tests/checkApi-routePath-strict.test.ts +0 -166
  26. package/tests/checkMenu.test.ts +0 -346
  27. package/tests/checkTable-smoke.test.ts +0 -157
  28. package/tests/cipher.test.ts +0 -249
  29. package/tests/dbDialect-cache.test.ts +0 -23
  30. package/tests/dbDialect.test.ts +0 -46
  31. package/tests/dbHelper-advanced.test.ts +0 -723
  32. package/tests/dbHelper-all-array-types.test.ts +0 -316
  33. package/tests/dbHelper-array-serialization.test.ts +0 -258
  34. package/tests/dbHelper-batch-write.test.ts +0 -90
  35. package/tests/dbHelper-columns.test.ts +0 -234
  36. package/tests/dbHelper-execute.test.ts +0 -187
  37. package/tests/dbHelper-joins.test.ts +0 -221
  38. package/tests/fields-redis-cache.test.ts +0 -127
  39. package/tests/fields-validate.test.ts +0 -99
  40. package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
  41. package/tests/fixtures/scanFilesApis/a.ts +0 -3
  42. package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
  43. package/tests/getClientIp.test.ts +0 -54
  44. package/tests/integration.test.ts +0 -189
  45. package/tests/jwt.test.ts +0 -65
  46. package/tests/loadPlugins-order-smoke.test.ts +0 -75
  47. package/tests/logger.test.ts +0 -325
  48. package/tests/redisHelper.test.ts +0 -495
  49. package/tests/redisKeys.test.ts +0 -9
  50. package/tests/scanConfig.test.ts +0 -144
  51. package/tests/scanFiles-routePath.test.ts +0 -46
  52. package/tests/smoke-sql.test.ts +0 -24
  53. package/tests/sqlBuilder-advanced.test.ts +0 -608
  54. package/tests/sqlBuilder.test.ts +0 -209
  55. package/tests/sync-connection.test.ts +0 -183
  56. package/tests/sync-init-guard.test.ts +0 -105
  57. package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
  58. package/tests/syncApi-obsolete-records.test.ts +0 -69
  59. package/tests/syncApi-type-compat.test.ts +0 -72
  60. package/tests/syncDev-permissions.test.ts +0 -81
  61. package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
  62. package/tests/syncMenu-duplicate-path.test.ts +0 -122
  63. package/tests/syncMenu-obsolete-records.test.ts +0 -161
  64. package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
  65. package/tests/syncMenu-paths.test.ts +0 -59
  66. package/tests/syncTable-apply.test.ts +0 -279
  67. package/tests/syncTable-array-number.test.ts +0 -160
  68. package/tests/syncTable-constants.test.ts +0 -101
  69. package/tests/syncTable-db-integration.test.ts +0 -237
  70. package/tests/syncTable-ddl.test.ts +0 -245
  71. package/tests/syncTable-helpers.test.ts +0 -99
  72. package/tests/syncTable-schema.test.ts +0 -99
  73. package/tests/syncTable-testkit.test.ts +0 -25
  74. package/tests/syncTable-types.test.ts +0 -122
  75. package/tests/tableRef-and-deserialize.test.ts +0 -67
  76. package/tests/util.test.ts +0 -100
  77. package/tests/validator-array-number.test.ts +0 -310
  78. package/tests/validator-default.test.ts +0 -373
  79. package/tests/validator.test.ts +0 -679
@@ -1,346 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
-
5
- import { checkMenu } from "../checks/checkMenu.js";
6
-
7
- describe("checkMenu", () => {
8
- test("重复 path 应阻断菜单同步", async () => {
9
- const originalCwd = process.cwd();
10
- const projectDir = join(originalCwd, "temp", `checkMenu-dup-${Date.now()}-${Math.random().toString(16).slice(2)}`);
11
- const menusJsonPath = join(projectDir, "menus.json");
12
-
13
- try {
14
- mkdirSync(projectDir, { recursive: true });
15
- process.chdir(projectDir);
16
-
17
- writeFileSync(
18
- menusJsonPath,
19
- JSON.stringify(
20
- [
21
- { name: "A", path: "/a", sort: 1 },
22
- { name: "B", path: "/a", sort: 2 }
23
- ],
24
- null,
25
- 4
26
- ),
27
- { encoding: "utf8" }
28
- );
29
-
30
- let thrown: any = null;
31
- try {
32
- await checkMenu([]);
33
- } catch (error: any) {
34
- thrown = error;
35
- }
36
-
37
- expect(thrown).toBeTruthy();
38
- expect(thrown.message).toBe("菜单结构检查失败");
39
- } finally {
40
- process.chdir(originalCwd);
41
- rmSync(projectDir, { recursive: true, force: true });
42
- }
43
- });
44
-
45
- test("缺失父级 path 应阻断菜单同步", async () => {
46
- const originalCwd = process.cwd();
47
- const projectDir = join(originalCwd, "temp", `checkMenu-parent-${Date.now()}-${Math.random().toString(16).slice(2)}`);
48
- const menusJsonPath = join(projectDir, "menus.json");
49
-
50
- try {
51
- mkdirSync(projectDir, { recursive: true });
52
- process.chdir(projectDir);
53
-
54
- writeFileSync(menusJsonPath, JSON.stringify([{ name: "B", path: "/a/b", sort: 1 }], null, 4), { encoding: "utf8" });
55
-
56
- // 菜单层级应以配置树(children)为准,而非按 URL path 分段强制推导父级。
57
- // 因此单个菜单(即使 path 含多段)也允许作为根级菜单存在。
58
- const menus = await checkMenu([]);
59
- expect(Array.isArray(menus)).toBe(true);
60
- expect(menus).toHaveLength(1);
61
- expect(menus[0]?.path).toBe("/a/b");
62
- } finally {
63
- process.chdir(originalCwd);
64
- rmSync(projectDir, { recursive: true, force: true });
65
- }
66
- });
67
-
68
- test("disableMenus(精确)应过滤指定菜单", async () => {
69
- const originalCwd = process.cwd();
70
- const projectDir = join(originalCwd, "temp", `checkMenu-disableMenus-exact-${Date.now()}-${Math.random().toString(16).slice(2)}`);
71
- const menusJsonPath = join(projectDir, "menus.json");
72
-
73
- try {
74
- mkdirSync(projectDir, { recursive: true });
75
- process.chdir(projectDir);
76
-
77
- writeFileSync(
78
- menusJsonPath,
79
- JSON.stringify(
80
- [
81
- {
82
- name: "A",
83
- path: "/a",
84
- sort: 1,
85
- children: [
86
- {
87
- name: "B",
88
- path: "/a/b",
89
- sort: 2
90
- }
91
- ]
92
- },
93
- {
94
- name: "C",
95
- path: "/c",
96
- sort: 3
97
- }
98
- ],
99
- null,
100
- 4
101
- ),
102
- { encoding: "utf8" }
103
- );
104
-
105
- const menus = await checkMenu([], { disableMenus: ["/a/b"] });
106
- expect(menus).toHaveLength(2);
107
- expect(menus[0]?.path).toBe("/a");
108
- expect(menus[1]?.path).toBe("/c");
109
- expect(Array.isArray((menus[0] as any)?.children)).toBe(false);
110
- } finally {
111
- process.chdir(originalCwd);
112
- rmSync(projectDir, { recursive: true, force: true });
113
- }
114
- });
115
-
116
- test("disableMenus(glob)应按 Bun.Glob 语义过滤匹配的菜单", async () => {
117
- const originalCwd = process.cwd();
118
- const projectDir = join(originalCwd, "temp", `checkMenu-disableMenus-glob-${Date.now()}-${Math.random().toString(16).slice(2)}`);
119
- const menusJsonPath = join(projectDir, "menus.json");
120
-
121
- try {
122
- mkdirSync(projectDir, { recursive: true });
123
- process.chdir(projectDir);
124
-
125
- writeFileSync(
126
- menusJsonPath,
127
- JSON.stringify(
128
- [
129
- { name: "A", path: "/a", sort: 1 },
130
- { name: "A-1", path: "/a/1", sort: 2 },
131
- { name: "B", path: "/b", sort: 3 }
132
- ],
133
- null,
134
- 4
135
- ),
136
- { encoding: "utf8" }
137
- );
138
-
139
- // 注意:此处完全遵循 Bun.Glob 的 match 语义。
140
- // 通常 "*" 不跨路径分隔符,因此 "/a/*" 仅匹配 "/a/1",不会匹配 "/a"。
141
- const menus = await checkMenu([], { disableMenus: ["/a/*"] });
142
- const paths = menus.map((m) => m.path);
143
- expect(paths).toContain("/a");
144
- expect(paths).toContain("/b");
145
- expect(paths).not.toContain("/a/1");
146
- } finally {
147
- process.chdir(originalCwd);
148
- rmSync(projectDir, { recursive: true, force: true });
149
- }
150
- });
151
-
152
- test("disableMenus 规则不合法应阻断启动", async () => {
153
- const originalCwd = process.cwd();
154
- const projectDir = join(originalCwd, "temp", `checkMenu-disableMenus-invalid-${Date.now()}-${Math.random().toString(16).slice(2)}`);
155
- const menusJsonPath = join(projectDir, "menus.json");
156
-
157
- try {
158
- mkdirSync(projectDir, { recursive: true });
159
- process.chdir(projectDir);
160
-
161
- writeFileSync(menusJsonPath, JSON.stringify([{ name: "A", path: "/a", sort: 1 }], null, 4), { encoding: "utf8" });
162
-
163
- // 1) disableMenus 必须是数组
164
- {
165
- let thrown: any = null;
166
- try {
167
- await checkMenu([], { disableMenus: "not-array" as any });
168
- } catch (error: any) {
169
- thrown = error;
170
- }
171
- expect(thrown).toBeTruthy();
172
- expect(String(thrown.message).includes("disableMenus")).toBe(true);
173
- }
174
-
175
- // 2) 数组元素必须是 string
176
- {
177
- let thrown: any = null;
178
- try {
179
- await checkMenu([], { disableMenus: [123 as any] });
180
- } catch (error: any) {
181
- thrown = error;
182
- }
183
- expect(thrown).toBeTruthy();
184
- expect(String(thrown.message).includes("disableMenus")).toBe(true);
185
- }
186
-
187
- // 3) 不允许空字符串
188
- {
189
- let thrown: any = null;
190
- try {
191
- await checkMenu([], { disableMenus: [" "] });
192
- } catch (error: any) {
193
- thrown = error;
194
- }
195
- expect(thrown).toBeTruthy();
196
- expect(String(thrown.message).includes("disableMenus")).toBe(true);
197
- }
198
- } finally {
199
- process.chdir(originalCwd);
200
- rmSync(projectDir, { recursive: true, force: true });
201
- }
202
- });
203
-
204
- test("默认应屏蔽 /404 /403 /500 以及所有以 /login 结尾的菜单路由", async () => {
205
- const originalCwd = process.cwd();
206
- const projectDir = join(originalCwd, "temp", `checkMenu-default-disable-${Date.now()}-${Math.random().toString(16).slice(2)}`);
207
- const menusJsonPath = join(projectDir, "menus.json");
208
-
209
- try {
210
- mkdirSync(projectDir, { recursive: true });
211
- process.chdir(projectDir);
212
-
213
- writeFileSync(
214
- menusJsonPath,
215
- JSON.stringify(
216
- [
217
- { name: "Login", path: "/login", sort: 1 },
218
- { name: "AddonLogin", path: "/addon/admin/login", sort: 2 },
219
- { name: "404", path: "/404", sort: 3 },
220
- { name: "403", path: "/403", sort: 4 },
221
- { name: "500", path: "/500", sort: 5 },
222
- { name: "A", path: "/a", sort: 6 }
223
- ],
224
- null,
225
- 4
226
- ),
227
- { encoding: "utf8" }
228
- );
229
-
230
- const menus = await checkMenu([], { disableMenus: ["**/404", "**/403", "**/500", "**/login"] });
231
- expect(Array.isArray(menus)).toBe(true);
232
- expect(menus).toHaveLength(1);
233
- expect(menus[0]?.path).toBe("/a");
234
- } finally {
235
- process.chdir(originalCwd);
236
- rmSync(projectDir, { recursive: true, force: true });
237
- }
238
- });
239
-
240
- test("合法 menus.json 应通过检查", async () => {
241
- const originalCwd = process.cwd();
242
- const projectDir = join(originalCwd, "temp", `checkMenu-ok-${Date.now()}-${Math.random().toString(16).slice(2)}`);
243
- const menusJsonPath = join(projectDir, "menus.json");
244
-
245
- try {
246
- mkdirSync(projectDir, { recursive: true });
247
- process.chdir(projectDir);
248
-
249
- writeFileSync(menusJsonPath, JSON.stringify([{ name: "A", path: "/a", sort: 1, children: [{ name: "B", path: "/a/b", sort: 2 }] }], null, 4), { encoding: "utf8" });
250
-
251
- const menus = await checkMenu([]);
252
- expect(Array.isArray(menus)).toBe(true);
253
- expect(menus.length).toBe(1);
254
- } finally {
255
- process.chdir(originalCwd);
256
- rmSync(projectDir, { recursive: true, force: true });
257
- }
258
- });
259
-
260
- test("超过三级菜单应阻断同步", async () => {
261
- const originalCwd = process.cwd();
262
- const projectDir = join(originalCwd, "temp", `checkMenu-depth-${Date.now()}-${Math.random().toString(16).slice(2)}`);
263
- const menusJsonPath = join(projectDir, "menus.json");
264
-
265
- try {
266
- mkdirSync(projectDir, { recursive: true });
267
- process.chdir(projectDir);
268
-
269
- writeFileSync(
270
- menusJsonPath,
271
- JSON.stringify(
272
- [
273
- {
274
- name: "A",
275
- path: "/a",
276
- sort: 1,
277
- children: [
278
- {
279
- name: "B",
280
- path: "/a/b",
281
- sort: 2,
282
- children: [
283
- {
284
- name: "C",
285
- path: "/a/b/c",
286
- sort: 3,
287
- children: [
288
- {
289
- name: "D",
290
- path: "/a/b/c/d",
291
- sort: 4
292
- }
293
- ]
294
- }
295
- ]
296
- }
297
- ]
298
- }
299
- ],
300
- null,
301
- 4
302
- ),
303
- { encoding: "utf8" }
304
- );
305
-
306
- let thrown: any = null;
307
- try {
308
- await checkMenu([]);
309
- } catch (error: any) {
310
- thrown = error;
311
- }
312
-
313
- expect(thrown).toBeTruthy();
314
- expect(thrown.message).toBe("菜单结构检查失败");
315
- } finally {
316
- process.chdir(originalCwd);
317
- rmSync(projectDir, { recursive: true, force: true });
318
- }
319
- });
320
-
321
- test("sort 最小值应为 1(sort=0 应阻断启动)", async () => {
322
- const originalCwd = process.cwd();
323
- const projectDir = join(originalCwd, "temp", `checkMenu-sort-min-${Date.now()}-${Math.random().toString(16).slice(2)}`);
324
- const menusJsonPath = join(projectDir, "menus.json");
325
-
326
- try {
327
- mkdirSync(projectDir, { recursive: true });
328
- process.chdir(projectDir);
329
-
330
- writeFileSync(menusJsonPath, JSON.stringify([{ name: "A", path: "/a", sort: 0 }], null, 4), { encoding: "utf8" });
331
-
332
- let thrown: any = null;
333
- try {
334
- await checkMenu([]);
335
- } catch (error: any) {
336
- thrown = error;
337
- }
338
-
339
- expect(thrown).toBeTruthy();
340
- expect(thrown.message).toBe("菜单结构检查失败");
341
- } finally {
342
- process.chdir(originalCwd);
343
- rmSync(projectDir, { recursive: true, force: true });
344
- }
345
- });
346
- });
@@ -1,157 +0,0 @@
1
- import type { ScanFileResult } from "../utils/scanFiles.js";
2
-
3
- import { describe, expect, test } from "bun:test";
4
-
5
- import { checkTable } from "../checks/checkTable.js";
6
- import { Logger } from "../lib/logger.js";
7
-
8
- describe("checkTable - smoke", () => {
9
- test("应忽略非 table 项;合法表定义不应抛错", async () => {
10
- const items: ScanFileResult[] = [
11
- {
12
- type: "api",
13
- source: "app",
14
- sourceName: "项目",
15
- filePath: "DUMMY",
16
- relativePath: "DUMMY",
17
- fileName: "dummy",
18
- moduleName: "app_dummy",
19
- addonName: "",
20
- content: {}
21
- } as any,
22
- {
23
- type: "table",
24
- source: "app",
25
- sourceName: "项目",
26
- filePath: "DUMMY",
27
- relativePath: "testCustomers",
28
- fileName: "testCustomers",
29
- moduleName: "app_testCustomers",
30
- addonName: "",
31
- content: {
32
- customerName: { name: "客户名", type: "string", max: 32 }
33
- }
34
- } as any
35
- ];
36
-
37
- await checkTable(items);
38
- expect(true).toBe(true);
39
- });
40
-
41
- test("unique 和 index 同时为 true 时应阻断启动(抛错)", async () => {
42
- const items: ScanFileResult[] = [
43
- {
44
- type: "table",
45
- source: "app",
46
- sourceName: "项目",
47
- filePath: "DUMMY",
48
- relativePath: "testMenu",
49
- fileName: "testMenu",
50
- moduleName: "app_testMenu",
51
- addonName: "",
52
- content: {
53
- path: { name: "路径", type: "string", max: 128, unique: true, index: true }
54
- }
55
- } as any
56
- ];
57
-
58
- let thrownError: any = null;
59
- try {
60
- await checkTable(items);
61
- } catch (error: any) {
62
- thrownError = error;
63
- }
64
-
65
- expect(Boolean(thrownError)).toBe(true);
66
- expect(String(thrownError?.message || "")).toContain("表结构检查失败");
67
- });
68
-
69
- test("sourceName 缺失时:日志不应出现 undefined表(允许前缀为空)", async () => {
70
- const calls: Array<{ level: string; args: unknown[] }> = [];
71
- const mockLogger = {
72
- info(...args: unknown[]) {
73
- calls.push({ level: "info", args: args });
74
- },
75
- warn(...args: unknown[]) {
76
- calls.push({ level: "warn", args: args });
77
- },
78
- error(...args: unknown[]) {
79
- calls.push({ level: "error", args: args });
80
- },
81
- debug(...args: unknown[]) {
82
- calls.push({ level: "debug", args: args });
83
- }
84
- } as any;
85
-
86
- Logger.setMock(mockLogger);
87
-
88
- try {
89
- await checkTable([
90
- {
91
- type: "table",
92
- source: "app",
93
- filePath: "DUMMY",
94
- relativePath: "TestCustomers",
95
- fileName: "TestCustomers",
96
- moduleName: "app_TestCustomers",
97
- addonName: "",
98
- content: {}
99
- } as any
100
- ]);
101
- } catch {
102
- // 触发 hasError 后会抛错:这里只验证日志前缀
103
- } finally {
104
- Logger.setMock(null);
105
- }
106
-
107
- const warnMessages = calls.filter((item) => item.level === "warn").map((item) => String(item.args[0]));
108
-
109
- expect(warnMessages.some((msg) => msg.includes("表 TestCustomers"))).toBe(true);
110
- expect(warnMessages.some((msg) => msg.includes("undefined表"))).toBe(false);
111
- });
112
-
113
- test("sourceName 非字符串时:日志不应出现 undefined表(允许前缀为空)", async () => {
114
- const calls: Array<{ level: string; args: unknown[] }> = [];
115
- const mockLogger = {
116
- info(...args: unknown[]) {
117
- calls.push({ level: "info", args: args });
118
- },
119
- warn(...args: unknown[]) {
120
- calls.push({ level: "warn", args: args });
121
- },
122
- error(...args: unknown[]) {
123
- calls.push({ level: "error", args: args });
124
- },
125
- debug(...args: unknown[]) {
126
- calls.push({ level: "debug", args: args });
127
- }
128
- } as any;
129
-
130
- Logger.setMock(mockLogger);
131
-
132
- try {
133
- await checkTable([
134
- {
135
- type: "table",
136
- source: "app",
137
- sourceName: 123,
138
- filePath: "DUMMY",
139
- relativePath: "TestCustomers",
140
- fileName: "TestCustomers",
141
- moduleName: "app_TestCustomers",
142
- addonName: "",
143
- content: {}
144
- } as any
145
- ]);
146
- } catch {
147
- // 触发 hasError 后会抛错:这里只验证日志前缀
148
- } finally {
149
- Logger.setMock(null);
150
- }
151
-
152
- const warnMessages = calls.filter((item) => item.level === "warn").map((item) => String(item.args[0]));
153
-
154
- expect(warnMessages.some((msg) => msg.includes("表 TestCustomers"))).toBe(true);
155
- expect(warnMessages.some((msg) => msg.includes("undefined表"))).toBe(false);
156
- });
157
- });