befly 3.10.1 → 3.10.3

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 (66) hide show
  1. package/.gitignore +0 -0
  2. package/configs/presetFields.ts +10 -0
  3. package/configs/presetRegexp.ts +225 -0
  4. package/package.json +15 -16
  5. package/tests/_mocks/mockSqliteDb.ts +0 -204
  6. package/tests/addonHelper-cache.test.ts +0 -32
  7. package/tests/api-integration-array-number.test.ts +0 -282
  8. package/tests/apiHandler-routePath-only.test.ts +0 -32
  9. package/tests/befly-config-env.test.ts +0 -78
  10. package/tests/cacheHelper.test.ts +0 -323
  11. package/tests/cacheKeys.test.ts +0 -41
  12. package/tests/checkApi-routePath-strict.test.ts +0 -166
  13. package/tests/checkMenu.test.ts +0 -346
  14. package/tests/checkTable-smoke.test.ts +0 -157
  15. package/tests/cipher.test.ts +0 -249
  16. package/tests/dbDialect-cache.test.ts +0 -23
  17. package/tests/dbDialect.test.ts +0 -46
  18. package/tests/dbHelper-advanced.test.ts +0 -723
  19. package/tests/dbHelper-all-array-types.test.ts +0 -316
  20. package/tests/dbHelper-array-serialization.test.ts +0 -258
  21. package/tests/dbHelper-batch-write.test.ts +0 -90
  22. package/tests/dbHelper-columns.test.ts +0 -234
  23. package/tests/dbHelper-execute.test.ts +0 -187
  24. package/tests/dbHelper-joins.test.ts +0 -221
  25. package/tests/fields-redis-cache.test.ts +0 -127
  26. package/tests/fields-validate.test.ts +0 -99
  27. package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
  28. package/tests/fixtures/scanFilesApis/a.ts +0 -3
  29. package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
  30. package/tests/getClientIp.test.ts +0 -54
  31. package/tests/integration.test.ts +0 -189
  32. package/tests/jwt.test.ts +0 -65
  33. package/tests/loadPlugins-order-smoke.test.ts +0 -75
  34. package/tests/logger.test.ts +0 -325
  35. package/tests/redisHelper.test.ts +0 -495
  36. package/tests/redisKeys.test.ts +0 -9
  37. package/tests/scanConfig.test.ts +0 -144
  38. package/tests/scanFiles-routePath.test.ts +0 -46
  39. package/tests/smoke-sql.test.ts +0 -24
  40. package/tests/sqlBuilder-advanced.test.ts +0 -608
  41. package/tests/sqlBuilder.test.ts +0 -209
  42. package/tests/sync-connection.test.ts +0 -183
  43. package/tests/sync-init-guard.test.ts +0 -105
  44. package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
  45. package/tests/syncApi-obsolete-records.test.ts +0 -69
  46. package/tests/syncApi-type-compat.test.ts +0 -72
  47. package/tests/syncDev-permissions.test.ts +0 -81
  48. package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
  49. package/tests/syncMenu-duplicate-path.test.ts +0 -122
  50. package/tests/syncMenu-obsolete-records.test.ts +0 -161
  51. package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
  52. package/tests/syncMenu-paths.test.ts +0 -59
  53. package/tests/syncTable-apply.test.ts +0 -279
  54. package/tests/syncTable-array-number.test.ts +0 -160
  55. package/tests/syncTable-constants.test.ts +0 -101
  56. package/tests/syncTable-db-integration.test.ts +0 -237
  57. package/tests/syncTable-ddl.test.ts +0 -245
  58. package/tests/syncTable-helpers.test.ts +0 -99
  59. package/tests/syncTable-schema.test.ts +0 -99
  60. package/tests/syncTable-testkit.test.ts +0 -25
  61. package/tests/syncTable-types.test.ts +0 -122
  62. package/tests/tableRef-and-deserialize.test.ts +0 -67
  63. package/tests/util.test.ts +0 -100
  64. package/tests/validator-array-number.test.ts +0 -310
  65. package/tests/validator-default.test.ts +0 -373
  66. package/tests/validator.test.ts +0 -679
package/.gitignore ADDED
File without changes
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 预定义的默认字段(API fields 的 @ 引用)
3
+ */
4
+ export const presetFields: Record<string, any> = {
5
+ "@id": { name: "ID", type: "number", min: 1, max: null },
6
+ "@page": { name: "页码", type: "number", min: 1, max: 9999, default: 1 },
7
+ "@limit": { name: "每页数量", type: "number", min: 1, max: 100, default: 30 },
8
+ "@keyword": { name: "关键词", type: "string", min: 0, max: 50 },
9
+ "@state": { name: "状态", type: "number", regex: "^[0-2]$" }
10
+ };
@@ -0,0 +1,225 @@
1
+ /**
2
+ * 内置正则表达式别名
3
+ * 用于表单验证和数据校验
4
+ * 命名规范:小驼峰格式
5
+ */
6
+ export const RegexAliases = {
7
+ // ============================================
8
+ // 数字类型
9
+ // ============================================
10
+ /** 正整数(不含0) */
11
+ number: "^\\d+$",
12
+ /** 整数(含负数) */
13
+ integer: "^-?\\d+$",
14
+ /** 浮点数 */
15
+ float: "^-?\\d+(\\.\\d+)?$",
16
+ /** 正整数(不含0) */
17
+ positive: "^[1-9]\\d*$",
18
+ /** 负整数 */
19
+ negative: "^-\\d+$",
20
+ /** 零 */
21
+ zero: "^0$",
22
+
23
+ // ============================================
24
+ // 字符串类型
25
+ // ============================================
26
+ /** 纯字母 */
27
+ word: "^[a-zA-Z]+$",
28
+ /** 字母和数字 */
29
+ alphanumeric: "^[a-zA-Z0-9]+$",
30
+ /** 字母、数字和下划线 */
31
+ alphanumeric_: "^[a-zA-Z0-9_]+$",
32
+ /** 字母、数字、下划线和短横线 */
33
+ alphanumericDash_: "^[a-zA-Z0-9_-]+$",
34
+ /** 小写字母 */
35
+ lowercase: "^[a-z]+$",
36
+ /** 大写字母 */
37
+ uppercase: "^[A-Z]+$",
38
+
39
+ // ============================================
40
+ // 中文
41
+ // ============================================
42
+ /** 纯中文 */
43
+ chinese: "^[\\u4e00-\\u9fa5]+$",
44
+ /** 中文和字母 */
45
+ chineseWord: "^[\\u4e00-\\u9fa5a-zA-Z]+$",
46
+
47
+ // ============================================
48
+ // 常用格式
49
+ // ============================================
50
+ /** 邮箱地址 */
51
+ email: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
52
+ /** 中国大陆手机号 */
53
+ phone: "^1[3-9]\\d{9}$",
54
+ /** 固定电话(区号-号码) */
55
+ telephone: "^0\\d{2,3}-?\\d{7,8}$",
56
+ /** URL 地址 */
57
+ url: "^https?://",
58
+ /** IPv4 地址 */
59
+ ip: "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$",
60
+ /** IPv6 地址 */
61
+ ipv6: "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$",
62
+ /** 域名 */
63
+ domain: "^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$",
64
+
65
+ // ============================================
66
+ // 特殊格式
67
+ // ============================================
68
+ /** UUID */
69
+ uuid: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
70
+ /** 十六进制字符串 */
71
+ hex: "^[0-9a-fA-F]+$",
72
+ /** Base64 编码 */
73
+ base64: "^[A-Za-z0-9+/=]+$",
74
+ /** MD5 哈希 */
75
+ md5: "^[a-f0-9]{32}$",
76
+ /** SHA1 哈希 */
77
+ sha1: "^[a-f0-9]{40}$",
78
+ /** SHA256 哈希 */
79
+ sha256: "^[a-f0-9]{64}$",
80
+
81
+ // ============================================
82
+ // 日期时间
83
+ // ============================================
84
+ /** 日期 YYYY-MM-DD */
85
+ date: "^\\d{4}-\\d{2}-\\d{2}$",
86
+ /** 时间 HH:MM:SS */
87
+ time: "^\\d{2}:\\d{2}:\\d{2}$",
88
+ /** ISO 日期时间 */
89
+ datetime: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}",
90
+ /** 年份 */
91
+ year: "^\\d{4}$",
92
+ /** 月份 01-12 */
93
+ month: "^(0[1-9]|1[0-2])$",
94
+ /** 日期 01-31 */
95
+ day: "^(0[1-9]|[12]\\d|3[01])$",
96
+
97
+ // ============================================
98
+ // 代码相关
99
+ // ============================================
100
+ /** 变量名 */
101
+ variable: "^[a-zA-Z_][a-zA-Z0-9_]*$",
102
+ /** 常量名(全大写) */
103
+ constant: "^[A-Z][A-Z0-9_]*$",
104
+ /** 包名(小写+连字符) */
105
+ package: "^[a-z][a-z0-9-]*$",
106
+
107
+ // ============================================
108
+ // 证件相关
109
+ // ============================================
110
+ /** 中国身份证号(18位) */
111
+ idCard: "^\\d{17}[\\dXx]$",
112
+ /** 护照号 */
113
+ passport: "^[a-zA-Z0-9]{5,17}$",
114
+
115
+ // ============================================
116
+ // 账号相关(国内常用)
117
+ // ============================================
118
+ /** 银行卡号(16-19位数字) */
119
+ bankCard: "^\\d{16,19}$",
120
+ /** 微信号(6-20位,字母开头,可包含字母、数字、下划线、减号) */
121
+ wechat: "^[a-zA-Z][a-zA-Z0-9_-]{5,19}$",
122
+ /** QQ号(5-11位数字,首位非0) */
123
+ qq: "^[1-9]\\d{4,10}$",
124
+ /** 支付宝账号(手机号或邮箱) */
125
+ alipay: "^(1[3-9]\\d{9}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$",
126
+ /** 用户名(4-20位,字母开头,可包含字母、数字、下划线) */
127
+ username: "^[a-zA-Z][a-zA-Z0-9_]{3,19}$",
128
+ /** 昵称(2-20位,支持中文、字母、数字) */
129
+ nickname: "^[\\u4e00-\\u9fa5a-zA-Z0-9]{2,20}$",
130
+
131
+ // ============================================
132
+ // 密码强度
133
+ // ============================================
134
+ /** 弱密码(至少6位) */
135
+ passwordWeak: "^.{6,}$",
136
+ /** 中等密码(至少8位,包含字母和数字) */
137
+ passwordMedium: "^(?=.*[a-zA-Z])(?=.*\\d).{8,}$",
138
+ /** 强密码(至少8位,包含大小写字母、数字和特殊字符) */
139
+ passwordStrong: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,}$",
140
+
141
+ // ============================================
142
+ // 其他常用
143
+ // ============================================
144
+ /** 车牌号(新能源+普通) */
145
+ licensePlate: "^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$",
146
+ /** 邮政编码 */
147
+ postalCode: "^\\d{6}$",
148
+ /** 版本号(语义化版本) */
149
+ semver: "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?(\\+[a-zA-Z0-9.]+)?$",
150
+ /** 颜色值(十六进制) */
151
+ colorHex: "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
152
+
153
+ // ============================================
154
+ // 空值
155
+ // ============================================
156
+ /** 空字符串 */
157
+ empty: "^$",
158
+ /** 非空 */
159
+ notempty: ".+"
160
+ } as const;
161
+
162
+ /**
163
+ * 正则别名类型
164
+ */
165
+ export type RegexAliasName = keyof typeof RegexAliases;
166
+
167
+ // ============================================
168
+ // 正则表达式缓存(性能优化)
169
+ // ============================================
170
+ const regexCache = new Map<string, RegExp>();
171
+
172
+ /**
173
+ * 获取正则表达式字符串
174
+ * @param name 正则别名(以 @ 开头)或自定义正则字符串
175
+ * @returns 正则表达式字符串
176
+ */
177
+ export function getRegex(name: string): string {
178
+ if (name.startsWith("@")) {
179
+ const alias = name.slice(1) as RegexAliasName;
180
+ return RegexAliases[alias] || name;
181
+ }
182
+ return name;
183
+ }
184
+
185
+ /**
186
+ * 获取编译后的正则表达式对象(带缓存)
187
+ * @param pattern 正则别名或正则字符串
188
+ * @param flags 正则标志(如 'i', 'g')
189
+ * @returns 编译后的 RegExp 对象
190
+ */
191
+ export function getCompiledRegex(pattern: string, flags?: string): RegExp {
192
+ const regexStr = getRegex(pattern);
193
+ const cacheKey = `${regexStr}:${flags || ""}`;
194
+
195
+ let cached = regexCache.get(cacheKey);
196
+ if (!cached) {
197
+ cached = new RegExp(regexStr, flags);
198
+ regexCache.set(cacheKey, cached);
199
+ }
200
+ return cached;
201
+ }
202
+
203
+ /**
204
+ * 验证值是否匹配正则(使用缓存)
205
+ * @param value 要验证的值
206
+ * @param pattern 正则别名或正则字符串
207
+ * @returns 是否匹配
208
+ */
209
+ export function matchRegex(value: string, pattern: string): boolean {
210
+ return getCompiledRegex(pattern).test(value);
211
+ }
212
+
213
+ /**
214
+ * 清除正则缓存
215
+ */
216
+ export function clearRegexCache(): void {
217
+ regexCache.clear();
218
+ }
219
+
220
+ /**
221
+ * 获取缓存大小
222
+ */
223
+ export function getRegexCacheSize(): number {
224
+ return regexCache.size;
225
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.10.1",
4
- "gitHead": "123125b9d7a6c26879a5b5c9d3ac05e2082141c6",
3
+ "version": "3.10.3",
4
+ "gitHead": "7dd54bc1ed484139fa52ce5408f1a6c166e2a928",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
7
7
  "keywords": [
@@ -20,27 +20,26 @@
20
20
  "license": "Apache-2.0",
21
21
  "author": "chensuiyi <bimostyle@qq.com>",
22
22
  "files": [
23
- ".npmrc",
23
+ ".gitignore",
24
24
  "befly.config.ts",
25
25
  "bunfig.toml",
26
- "checks",
27
- "dist",
28
- "docs",
29
- "hooks",
30
- "lib",
31
26
  "LICENSE",
32
- "loader",
33
27
  "main.ts",
34
28
  "package.json",
35
29
  "paths.ts",
36
- "plugins",
37
30
  "README.md",
38
- "router",
39
- "sync",
40
- "tests",
41
31
  "tsconfig.json",
42
- "types",
43
- "utils"
32
+ "checks/",
33
+ "configs/",
34
+ "docs/",
35
+ "hooks/",
36
+ "lib/",
37
+ "loader/",
38
+ "plugins/",
39
+ "router/",
40
+ "sync/",
41
+ "types/",
42
+ "utils/"
44
43
  ],
45
44
  "type": "module",
46
45
  "main": "main.ts",
@@ -63,7 +62,7 @@
63
62
  "typecheck": "bunx tsgo --noEmit"
64
63
  },
65
64
  "dependencies": {
66
- "befly-shared": "1.3.0",
65
+ "befly-shared": "1.3.2",
67
66
  "chalk": "^5.6.2",
68
67
  "es-toolkit": "^1.43.0",
69
68
  "fast-jwt": "^6.1.0",
@@ -1,204 +0,0 @@
1
- export type MockColumn = { name: string; type: string; notnull: 0 | 1; dflt_value: any };
2
-
3
- export type MockSqliteState = {
4
- executedSql: string[];
5
- tables: Record<
6
- string,
7
- {
8
- columns: Record<string, MockColumn>;
9
- indexes: Record<string, string[]>;
10
- }
11
- >;
12
- };
13
-
14
- function normalizeQuotedIdent(input: string): string {
15
- return String(input).trim().replace(/^`/, "").replace(/`$/, "").replace(/^"/, "").replace(/"$/, "");
16
- }
17
-
18
- function parseCreateTable(sql: string): { tableName: string; columnDefs: string } | null {
19
- const m = /^CREATE\s+TABLE\s+(.+?)\s*\((.*)\)\s*$/is.exec(sql.trim());
20
- if (!m) return null;
21
- return {
22
- tableName: normalizeQuotedIdent(m[1].trim()),
23
- columnDefs: m[2]
24
- };
25
- }
26
-
27
- function parseAlterAddColumn(sql: string): { tableName: string; colName: string; colType: string; notnull: 0 | 1; dflt: any } | null {
28
- const m = /^ALTER\s+TABLE\s+(.+?)\s+ADD\s+COLUMN\s+(?:IF\s+NOT\s+EXISTS\s+)?(.+?)\s+(.*)$/i.exec(sql.trim());
29
- if (!m) return null;
30
-
31
- const tableName = normalizeQuotedIdent(m[1].trim());
32
- const colName = normalizeQuotedIdent(m[2].trim());
33
- const rest = m[3];
34
-
35
- const upper = rest.toUpperCase();
36
- const colType = upper.includes("INTEGER") ? "INTEGER" : "TEXT";
37
- const notnull: 0 | 1 = upper.includes("NOT NULL") ? 1 : 0;
38
-
39
- let dflt: any = null;
40
- const dfltMatch = /\bDEFAULT\s+(.+?)(\s|$)/i.exec(rest);
41
- if (dfltMatch) {
42
- const raw = dfltMatch[1].trim();
43
- if (raw === "''") dflt = "";
44
- else if (raw.startsWith("'") && raw.endsWith("'")) dflt = raw.slice(1, -1).replace(/''/g, "'");
45
- else if (/^\d+$/.test(raw)) dflt = Number(raw);
46
- else dflt = raw;
47
- }
48
-
49
- return { tableName: tableName, colName: colName, colType: colType, notnull: notnull, dflt: dflt };
50
- }
51
-
52
- function parseCreateIndex(sql: string): { indexName: string; tableName: string; columns: string[] } | null {
53
- const m = /^CREATE\s+INDEX\s+(?:CONCURRENTLY\s+)?(?:IF\s+NOT\s+EXISTS\s+)?(.+?)\s+ON\s+(.+?)\s*\((.+)\)\s*$/is.exec(sql.trim());
54
- if (!m) return null;
55
-
56
- const indexName = normalizeQuotedIdent(m[1].trim());
57
- const tableName = normalizeQuotedIdent(m[2].trim());
58
- const columns = m[3]
59
- .split(",")
60
- .map((s) => normalizeQuotedIdent(s.trim()))
61
- .filter((s) => s.length > 0);
62
-
63
- return { indexName: indexName, tableName: tableName, columns: columns };
64
- }
65
-
66
- function parseDropIndex(sql: string): { indexName: string } | null {
67
- const m = /^DROP\s+INDEX\s+(?:CONCURRENTLY\s+)?(?:IF\s+EXISTS\s+)?(.+?)\s*$/i.exec(sql.trim());
68
- if (!m) return null;
69
- return { indexName: normalizeQuotedIdent(m[1].trim()) };
70
- }
71
-
72
- function extractPragmaIdent(sqlStr: string): string {
73
- const m = /\((.+)\)\s*$/i.exec(String(sqlStr).trim());
74
- return normalizeQuotedIdent(m ? m[1] : "");
75
- }
76
-
77
- export function createMockSqliteDb(state: MockSqliteState) {
78
- return {
79
- unsafe: async (sqlStr: string, params?: unknown[]) => {
80
- const sql = String(sqlStr);
81
- state.executedSql.push(sql);
82
-
83
- if (sql.includes("sqlite_version()")) {
84
- return [{ version: "3.50.1" }];
85
- }
86
-
87
- if (sql.includes("sqlite_master")) {
88
- const tableName = String(params?.[0] || "");
89
- return [{ count: state.tables[tableName] ? 1 : 0 }];
90
- }
91
-
92
- if (/^PRAGMA\s+table_info\s*\(/i.test(sql)) {
93
- const tableName = extractPragmaIdent(sql);
94
- const t = state.tables[tableName];
95
- if (!t) return [];
96
- return Object.values(t.columns).map((c) => {
97
- return {
98
- name: c.name,
99
- type: c.type,
100
- notnull: c.notnull,
101
- dflt_value: c.dflt_value
102
- };
103
- });
104
- }
105
-
106
- if (/^PRAGMA\s+index_list\s*\(/i.test(sql)) {
107
- const tableName = extractPragmaIdent(sql);
108
- const t = state.tables[tableName];
109
- if (!t) return [];
110
- return Object.keys(t.indexes).map((name) => {
111
- return { name: name };
112
- });
113
- }
114
-
115
- if (/^PRAGMA\s+index_info\s*\(/i.test(sql)) {
116
- const indexName = extractPragmaIdent(sql);
117
- for (const table of Object.values(state.tables)) {
118
- if (table.indexes[indexName]) {
119
- return table.indexes[indexName].map((col) => {
120
- return { name: col };
121
- });
122
- }
123
- }
124
- return [];
125
- }
126
-
127
- const createTable = parseCreateTable(sql);
128
- if (createTable) {
129
- const colMap: Record<string, MockColumn> = {};
130
- const parts = createTable.columnDefs
131
- .split(",")
132
- .map((s) => s.trim())
133
- .filter((s) => s.length > 0);
134
-
135
- for (const p of parts) {
136
- const colMatch = /^(.+?)\s+(.+)$/s.exec(p);
137
- if (!colMatch) continue;
138
- const colName = normalizeQuotedIdent(colMatch[1].trim());
139
- const rest = colMatch[2];
140
-
141
- const upper = rest.toUpperCase();
142
- const colType = upper.includes("INTEGER") ? "INTEGER" : upper.includes("BIGINT") ? "INTEGER" : "TEXT";
143
- const notnull: 0 | 1 = upper.includes("NOT NULL") || upper.includes("PRIMARY KEY") ? 1 : 0;
144
-
145
- let dflt: any = null;
146
- const dfltMatch = /\bDEFAULT\s+(.+?)(\s|$)/i.exec(rest);
147
- if (dfltMatch) {
148
- const raw = dfltMatch[1].trim();
149
- if (raw === "''") dflt = "";
150
- else if (raw.startsWith("'") && raw.endsWith("'")) dflt = raw.slice(1, -1).replace(/''/g, "'");
151
- else if (/^\d+$/.test(raw)) dflt = Number(raw);
152
- else dflt = raw;
153
- }
154
-
155
- colMap[colName] = { name: colName, type: colType, notnull: notnull, dflt_value: dflt };
156
- }
157
-
158
- state.tables[createTable.tableName] = {
159
- columns: colMap,
160
- indexes: {}
161
- };
162
-
163
- return [];
164
- }
165
-
166
- const addColumn = parseAlterAddColumn(sql);
167
- if (addColumn) {
168
- const t = state.tables[addColumn.tableName];
169
- if (!t) throw new Error(`mock sqlite db: 表不存在,无法 ADD COLUMN: ${addColumn.tableName}`);
170
- if (!t.columns[addColumn.colName]) {
171
- t.columns[addColumn.colName] = {
172
- name: addColumn.colName,
173
- type: addColumn.colType,
174
- notnull: addColumn.notnull,
175
- dflt_value: addColumn.dflt
176
- };
177
- }
178
- return [];
179
- }
180
-
181
- const createIndex = parseCreateIndex(sql);
182
- if (createIndex) {
183
- const t = state.tables[createIndex.tableName];
184
- if (!t) throw new Error(`mock sqlite db: 表不存在,无法 CREATE INDEX: ${createIndex.tableName}`);
185
- t.indexes[createIndex.indexName] = createIndex.columns;
186
- return [];
187
- }
188
-
189
- const dropIndex = parseDropIndex(sql);
190
- if (dropIndex) {
191
- for (const t of Object.values(state.tables)) {
192
- delete t.indexes[dropIndex.indexName];
193
- }
194
- return [];
195
- }
196
-
197
- if (/^DROP\s+TABLE/i.test(sql)) {
198
- return [];
199
- }
200
-
201
- throw new Error(`mock sqlite db: 未处理的 SQL: ${sql}`);
202
- }
203
- };
204
- }
@@ -1,32 +0,0 @@
1
- import { test, expect } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
-
4
- import { join } from "pathe";
5
-
6
- test("scanAddons - should scan node_modules @befly-addon", async () => {
7
- const originalCwd = process.cwd();
8
- const projectDir = join(originalCwd, "temp", `addonHelper-cache-test-${Date.now()}`);
9
-
10
- try {
11
- // 构造一个最小可扫描的项目结构
12
- mkdirSync(join(projectDir, "node_modules", "@befly-addon", "demo"), { recursive: true });
13
- mkdirSync(join(projectDir, "node_modules", "@befly-addon", "_ignore"), { recursive: true });
14
-
15
- // 放一个非目录项,确保扫描逻辑不会误判
16
- writeFileSync(join(projectDir, "node_modules", "@befly-addon", "README.md"), "x", { encoding: "utf8" });
17
-
18
- // scanAddons 依赖 appDir=process.cwd()(模块初始化时取值),所以先切 cwd 再动态 import
19
- process.chdir(projectDir);
20
-
21
- const mod = await import(`../utils/scanAddons.js?cacheTest=${Date.now()}`);
22
- const scanAddons = mod.scanAddons as () => any[];
23
-
24
- const addons = scanAddons();
25
- expect(addons.map((a) => a.name)).toEqual(["demo"]);
26
- expect(addons[0].fullPath.endsWith("node_modules/@befly-addon/demo")).toBe(true);
27
- expect(addons[0].camelName).toBe("demo");
28
- } finally {
29
- process.chdir(originalCwd);
30
- rmSync(projectDir, { recursive: true, force: true });
31
- }
32
- });