befly-shared 1.2.7 → 1.3.0

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 (94) hide show
  1. package/package.json +13 -30
  2. package/utils/arrayToTree.ts +135 -0
  3. package/utils/buildTreeByParentPath.ts +127 -0
  4. package/utils/scanViewsDir.ts +148 -0
  5. package/README.md +0 -439
  6. package/dist/addonHelper.js +0 -83
  7. package/dist/arrayKeysToCamel.js +0 -18
  8. package/dist/arrayToTree.js +0 -23
  9. package/dist/calcPerfTime.js +0 -13
  10. package/dist/configTypes.js +0 -1
  11. package/dist/constants.js +0 -46
  12. package/dist/fieldClear.js +0 -57
  13. package/dist/genShortId.js +0 -12
  14. package/dist/hashPassword.js +0 -22
  15. package/dist/index.js +0 -25
  16. package/dist/keysToCamel.js +0 -21
  17. package/dist/keysToSnake.js +0 -21
  18. package/dist/layouts.js +0 -59
  19. package/dist/pickFields.js +0 -16
  20. package/dist/redisKeys.js +0 -34
  21. package/dist/regex.js +0 -202
  22. package/dist/scanConfig.js +0 -83
  23. package/dist/scanFiles.js +0 -39
  24. package/dist/scanViews.js +0 -48
  25. package/dist/withDefaultColumns.js +0 -32
  26. package/src/addonHelper.ts +0 -88
  27. package/src/arrayKeysToCamel.ts +0 -18
  28. package/src/arrayToTree.ts +0 -31
  29. package/src/calcPerfTime.ts +0 -13
  30. package/src/configTypes.ts +0 -29
  31. package/src/constants.ts +0 -60
  32. package/src/fieldClear.ts +0 -75
  33. package/src/genShortId.ts +0 -12
  34. package/src/hashPassword.ts +0 -27
  35. package/src/index.ts +0 -28
  36. package/src/keysToCamel.ts +0 -22
  37. package/src/keysToSnake.ts +0 -22
  38. package/src/layouts.ts +0 -90
  39. package/src/pickFields.ts +0 -19
  40. package/src/redisKeys.ts +0 -44
  41. package/src/regex.ts +0 -225
  42. package/src/scanConfig.ts +0 -106
  43. package/src/scanFiles.ts +0 -49
  44. package/src/scanViews.ts +0 -55
  45. package/src/withDefaultColumns.ts +0 -36
  46. package/tests/addonHelper.test.ts +0 -55
  47. package/tests/arrayKeysToCamel.test.ts +0 -21
  48. package/tests/arrayToTree.test.ts +0 -98
  49. package/tests/calcPerfTime.test.ts +0 -19
  50. package/tests/fieldClear.test.ts +0 -39
  51. package/tests/keysToCamel.test.ts +0 -22
  52. package/tests/keysToSnake.test.ts +0 -22
  53. package/tests/layouts.test.ts +0 -93
  54. package/tests/pickFields.test.ts +0 -22
  55. package/tests/regex.test.ts +0 -308
  56. package/tests/scanFiles.test.ts +0 -58
  57. package/tests/types.test.ts +0 -289
  58. package/types/addon.d.ts +0 -50
  59. package/types/addonConfigMerge.d.ts +0 -17
  60. package/types/addonHelper.d.ts +0 -24
  61. package/types/api.d.ts +0 -63
  62. package/types/arrayKeysToCamel.d.ts +0 -13
  63. package/types/arrayToTree.d.ts +0 -8
  64. package/types/calcPerfTime.d.ts +0 -4
  65. package/types/common.d.ts +0 -8
  66. package/types/configMerge.d.ts +0 -49
  67. package/types/configTypes.d.ts +0 -28
  68. package/types/constants.d.ts +0 -48
  69. package/types/context.d.ts +0 -38
  70. package/types/crypto.d.ts +0 -23
  71. package/types/database.d.ts +0 -55
  72. package/types/fieldClear.d.ts +0 -16
  73. package/types/genShortId.d.ts +0 -10
  74. package/types/hashPassword.d.ts +0 -11
  75. package/types/index.d.ts +0 -22
  76. package/types/jwt.d.ts +0 -99
  77. package/types/keysToCamel.d.ts +0 -10
  78. package/types/keysToSnake.d.ts +0 -10
  79. package/types/layouts.d.ts +0 -29
  80. package/types/loadAndMergeConfig.d.ts +0 -7
  81. package/types/logger.d.ts +0 -22
  82. package/types/menu.d.ts +0 -49
  83. package/types/mergeConfig.d.ts +0 -7
  84. package/types/pickFields.d.ts +0 -4
  85. package/types/redisKeys.d.ts +0 -34
  86. package/types/regex.d.ts +0 -145
  87. package/types/scanConfig.d.ts +0 -7
  88. package/types/scanFiles.d.ts +0 -12
  89. package/types/scanViews.d.ts +0 -11
  90. package/types/table.d.ts +0 -49
  91. package/types/tool.d.ts +0 -67
  92. package/types/types.d.ts +0 -44
  93. package/types/validate.d.ts +0 -69
  94. package/types/withDefaultColumns.d.ts +0 -7
package/package.json CHANGED
@@ -1,40 +1,23 @@
1
1
  {
2
2
  "name": "befly-shared",
3
- "version": "1.2.7",
4
- "type": "module",
5
- "exports": {
6
- ".": {
7
- "bun": "./src/index.ts",
8
- "import": "./dist/index.js",
9
- "types": "./types/index.d.ts"
10
- },
11
- "./*": {
12
- "bun": "./src/*.ts",
13
- "import": "./dist/*.js",
14
- "types": "./types/*.d.ts"
15
- }
16
- },
3
+ "version": "1.3.0",
4
+ "private": false,
5
+ "description": "Befly 纯函数与纯常量共享包(不包含 Node/浏览器特定实现)",
6
+ "license": "Apache-2.0",
7
+ "author": "chensuiyi <bimostyle@qq.com>",
17
8
  "files": [
18
- "dist",
19
- "src",
20
- "types",
21
- "tests",
22
9
  "package.json",
23
- "README.md"
10
+ "utils"
24
11
  ],
12
+ "type": "module",
13
+ "exports": {
14
+ "./utils/*": "./utils/*.ts"
15
+ },
25
16
  "scripts": {
26
- "build": "rimraf dist && tsc",
27
17
  "test": "bun test"
28
18
  },
29
- "devDependencies": {
30
- "@types/bun": "latest",
31
- "rimraf": "^6.1.2",
32
- "typescript": "^5.7.2"
33
- },
34
- "dependencies": {
35
- "es-toolkit": "^1.42.0",
36
- "merge-anything": "^6.0.6",
37
- "pathe": "^2.0.3"
19
+ "engines": {
20
+ "bun": ">=1.3.0"
38
21
  },
39
- "gitHead": "8725d04017ae530017c1d400d0fc7b4e2b3ffdb0"
22
+ "gitHead": "123125b9d7a6c26879a5b5c9d3ac05e2082141c6"
40
23
  }
@@ -0,0 +1,135 @@
1
+ export type ArrayToTreeResult<T extends Record<string, any>> = {
2
+ flat: Array<T>;
3
+ tree: Array<T>;
4
+ map: Map<string, T>;
5
+ };
6
+
7
+ /**
8
+ * 将一维数组按 { id, pid } 组装为树形结构(纯函数 / 无副作用)。
9
+ *
10
+ * - 默认字段:id / pid / children / sort
11
+ * - pid 为空字符串或父节点不存在时,视为根节点
12
+ * - 内部会 clone 一份节点对象,并写入 children: []
13
+ * - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 id 自然序1
14
+ */
15
+ export function arrayToTree<T extends Record<string, any>>(items: T[], id: string = "id", pid: string = "pid", children: string = "children", sort: string = "sort"): ArrayToTreeResult<T> {
16
+ const idKey = typeof id === "string" && id.length > 0 ? id : "id";
17
+ const pidKey = typeof pid === "string" && pid.length > 0 ? pid : "pid";
18
+ const childrenKey = typeof children === "string" && children.length > 0 ? children : "children";
19
+ const sortKey = typeof sort === "string" && sort.length > 0 ? sort : "sort";
20
+
21
+ const map = new Map<string, T>();
22
+ const flat: T[] = [];
23
+
24
+ const safeItems = Array.isArray(items) ? items : [];
25
+
26
+ const normalizeKey = (value: unknown): string => {
27
+ if (typeof value === "string") {
28
+ return value;
29
+ }
30
+ if (typeof value === "number" && Number.isFinite(value)) {
31
+ return String(value);
32
+ }
33
+ return "";
34
+ };
35
+
36
+ for (const item of safeItems) {
37
+ const rawId = item ? (item as any)[idKey] : undefined;
38
+ const rawPid = item ? (item as any)[pidKey] : undefined;
39
+
40
+ const normalizedId = normalizeKey(rawId);
41
+ const normalizedPid = normalizeKey(rawPid);
42
+
43
+ const nextNode = Object.assign({}, item) as T;
44
+ (nextNode as any)[idKey] = normalizedId;
45
+ (nextNode as any)[pidKey] = normalizedPid;
46
+ (nextNode as any)[childrenKey] = [];
47
+
48
+ flat.push(nextNode);
49
+
50
+ if (normalizedId.length > 0) {
51
+ map.set(normalizedId, nextNode);
52
+ }
53
+ }
54
+
55
+ const tree: T[] = [];
56
+
57
+ for (const node of flat) {
58
+ const selfId = normalizeKey(node ? (node as any)[idKey] : undefined);
59
+ const parentId = normalizeKey(node ? (node as any)[pidKey] : undefined);
60
+
61
+ if (parentId.length > 0 && parentId !== selfId) {
62
+ const parent = map.get(parentId);
63
+ if (parent && Array.isArray((parent as any)[childrenKey])) {
64
+ (parent as any)[childrenKey].push(node);
65
+ continue;
66
+ }
67
+ }
68
+
69
+ tree.push(node);
70
+ }
71
+
72
+ const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
73
+
74
+ const getSortValue = (node: T): number => {
75
+ const raw = node ? (node as any)[sortKey] : undefined;
76
+ if (typeof raw !== "number") {
77
+ return 999999;
78
+ }
79
+ if (!Number.isFinite(raw)) {
80
+ return 999999;
81
+ }
82
+ if (raw < 1) {
83
+ return 999999;
84
+ }
85
+ return raw;
86
+ };
87
+
88
+ const compareNode = (a: T, b: T): number => {
89
+ const aSort = getSortValue(a);
90
+ const bSort = getSortValue(b);
91
+
92
+ if (aSort !== bSort) {
93
+ return aSort - bSort;
94
+ }
95
+
96
+ const aId = a ? (a as any)[idKey] : "";
97
+ const bId = b ? (b as any)[idKey] : "";
98
+
99
+ return collator.compare(typeof aId === "string" ? aId : "", typeof bId === "string" ? bId : "");
100
+ };
101
+
102
+ const sortTreeInPlace = (nodes: Array<T>, seen: WeakSet<object>): void => {
103
+ if (!Array.isArray(nodes)) {
104
+ return;
105
+ }
106
+
107
+ if (nodes.length > 1) {
108
+ nodes.sort(compareNode);
109
+ }
110
+
111
+ for (const node of nodes) {
112
+ if (typeof node !== "object" || node === null) {
113
+ continue;
114
+ }
115
+
116
+ if (seen.has(node)) {
117
+ continue;
118
+ }
119
+ seen.add(node);
120
+
121
+ const childNodes = (node as any)[childrenKey];
122
+ if (Array.isArray(childNodes) && childNodes.length > 0) {
123
+ sortTreeInPlace(childNodes, seen);
124
+ }
125
+ }
126
+ };
127
+
128
+ sortTreeInPlace(tree, new WeakSet<object>());
129
+
130
+ return {
131
+ flat: flat,
132
+ tree: tree,
133
+ map: map
134
+ };
135
+ }
@@ -0,0 +1,127 @@
1
+ export type BuildTreeByParentPathOptions = {
2
+ pathKey?: string;
3
+ parentPathKey?: string;
4
+ childrenKey?: string;
5
+ normalize?: (value: unknown) => string;
6
+ };
7
+
8
+ export type BuildTreeByParentPathResult<T extends Record<string, any>> = {
9
+ flat: Array<T>;
10
+ tree: Array<T>;
11
+ map: Map<string, T>;
12
+ };
13
+
14
+ /**
15
+ * 将一维数组按 { path, parentPath } 组装为树形结构(纯函数 / 无副作用)。
16
+ *
17
+ * - 默认字段:path / parentPath / children
18
+ * - parentPath 为空字符串或父节点不存在时,视为根节点
19
+ * - 内部会 clone 一份节点对象,并写入 children: []
20
+ * - 默认自带递归排序:按 sort 升序;sort 缺省/非法或 < 1 视为 999999;sort 相同按 path 自然序
21
+ */
22
+ export function buildTreeByParentPath<T extends Record<string, any>>(items: T[], options?: BuildTreeByParentPathOptions): BuildTreeByParentPathResult<T> {
23
+ const pathKey = typeof options?.pathKey === "string" && options.pathKey.length > 0 ? options.pathKey : "path";
24
+ const parentPathKey = typeof options?.parentPathKey === "string" && options.parentPathKey.length > 0 ? options.parentPathKey : "parentPath";
25
+ const childrenKey = typeof options?.childrenKey === "string" && options.childrenKey.length > 0 ? options.childrenKey : "children";
26
+
27
+ const normalize =
28
+ typeof options?.normalize === "function"
29
+ ? options.normalize
30
+ : (value: unknown) => {
31
+ return typeof value === "string" ? value : "";
32
+ };
33
+
34
+ const map = new Map<string, T>();
35
+ const flat: T[] = [];
36
+
37
+ const safeItems = Array.isArray(items) ? items : [];
38
+
39
+ for (const item of safeItems) {
40
+ const rawPath = item ? item[pathKey] : undefined;
41
+ const rawParentPath = item ? item[parentPathKey] : undefined;
42
+
43
+ const normalizedPath = normalize(rawPath);
44
+ const normalizedParentPath = normalize(rawParentPath);
45
+
46
+ const nextNode = Object.assign({}, item) as T;
47
+ (nextNode as any)[pathKey] = normalizedPath;
48
+ (nextNode as any)[parentPathKey] = normalizedParentPath;
49
+ (nextNode as any)[childrenKey] = [];
50
+
51
+ flat.push(nextNode);
52
+
53
+ if (typeof normalizedPath === "string" && normalizedPath.length > 0) {
54
+ map.set(normalizedPath, nextNode);
55
+ }
56
+ }
57
+
58
+ const tree: T[] = [];
59
+ for (const node of flat) {
60
+ const parentPath = (node as any)[parentPathKey];
61
+
62
+ if (typeof parentPath === "string" && parentPath.length > 0) {
63
+ const parent = map.get(parentPath);
64
+ if (parent && Array.isArray((parent as any)[childrenKey])) {
65
+ (parent as any)[childrenKey].push(node);
66
+ continue;
67
+ }
68
+ }
69
+
70
+ tree.push(node);
71
+ }
72
+
73
+ const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
74
+
75
+ const getSortValue = (node: T): number => {
76
+ const raw = node ? (node as any).sort : undefined;
77
+ if (typeof raw !== "number") {
78
+ return 999999;
79
+ }
80
+
81
+ if (!Number.isFinite(raw)) {
82
+ return 999999;
83
+ }
84
+
85
+ if (raw < 1) {
86
+ return 999999;
87
+ }
88
+
89
+ return raw;
90
+ };
91
+
92
+ const compareNode = (a: T, b: T): number => {
93
+ const aSort = getSortValue(a);
94
+ const bSort = getSortValue(b);
95
+
96
+ if (aSort !== bSort) {
97
+ return aSort - bSort;
98
+ }
99
+
100
+ const aPath = a ? (a as any)[pathKey] : "";
101
+ const bPath = b ? (b as any)[pathKey] : "";
102
+ return collator.compare(typeof aPath === "string" ? aPath : "", typeof bPath === "string" ? bPath : "");
103
+ };
104
+
105
+ const sortTreeInPlace = (nodes: Array<T>): void => {
106
+ if (!Array.isArray(nodes) || nodes.length <= 1) {
107
+ return;
108
+ }
109
+
110
+ nodes.sort(compareNode);
111
+
112
+ for (const node of nodes) {
113
+ const children = node ? (node as any)[childrenKey] : undefined;
114
+ if (Array.isArray(children) && children.length > 0) {
115
+ sortTreeInPlace(children);
116
+ }
117
+ }
118
+ };
119
+
120
+ sortTreeInPlace(tree);
121
+
122
+ return {
123
+ flat: flat,
124
+ tree: tree,
125
+ map: map
126
+ };
127
+ }
@@ -0,0 +1,148 @@
1
+ export type ViewDirMeta = {
2
+ title: string;
3
+ order?: number;
4
+ };
5
+
6
+ type MenuNodeLike<T> = {
7
+ name?: string;
8
+ path?: string;
9
+ sort?: number;
10
+ children?: T[];
11
+ };
12
+
13
+ /**
14
+ * 清理目录名中的数字后缀
15
+ * 如:login_1 → login, index_2 → index
16
+ */
17
+ export function cleanDirName(name: string): string {
18
+ return name.replace(/_\d+$/, "");
19
+ }
20
+
21
+ /**
22
+ * 约束:统一 path 形态,避免隐藏菜单匹配、DB 同步出现重复
23
+ * - 必须以 / 开头
24
+ * - 折叠多个 /
25
+ * - 去掉尾随 /(根 / 除外)
26
+ */
27
+ export function normalizeMenuPath(path: string): string {
28
+ let result = path;
29
+
30
+ if (!result) {
31
+ return "/";
32
+ }
33
+
34
+ if (!result.startsWith("/")) {
35
+ result = `/${result}`;
36
+ }
37
+
38
+ result = result.replace(/\/+/g, "/");
39
+
40
+ if (result.length > 1) {
41
+ result = result.replace(/\/+$/, "");
42
+ }
43
+
44
+ return result;
45
+ }
46
+
47
+ /**
48
+ * 递归规范化并按 path 去重(同 path 的 children 合并)
49
+ *
50
+ * 说明:该函数是纯函数,不依赖任何运行时环境;会返回新数组,但会在内部对克隆对象做合并赋值。
51
+ */
52
+ export function normalizeMenuTree<T extends MenuNodeLike<T>>(menus: T[]): T[] {
53
+ const map = new Map<string, T>();
54
+
55
+ for (const menu of menus) {
56
+ const rawPath = menu.path;
57
+ const menuPath = rawPath ? normalizeMenuPath(rawPath) : "";
58
+
59
+ if (!menuPath) {
60
+ continue;
61
+ }
62
+
63
+ // 不使用 structuredClone:
64
+ // - 结构中可能出现函数/类实例等不可 clone 的值
65
+ // - 这里我们只需要“保留额外字段 + 递归 children 生成新数组”
66
+ // 用浅拷贝即可满足需求
67
+ const cloned = Object.assign({}, menu) as T;
68
+ (cloned as any).path = menuPath;
69
+
70
+ const rawChildren = menu.children;
71
+ if (rawChildren && rawChildren.length > 0) {
72
+ (cloned as any).children = normalizeMenuTree(rawChildren);
73
+ }
74
+
75
+ const existing = map.get(menuPath);
76
+ if (existing) {
77
+ const clonedChildren = (cloned as any).children as T[] | undefined;
78
+ if (clonedChildren && clonedChildren.length > 0) {
79
+ let existingChildren = (existing as any).children as T[] | undefined;
80
+ if (!existingChildren) {
81
+ existingChildren = [];
82
+ (existing as any).children = existingChildren;
83
+ }
84
+
85
+ for (const child of clonedChildren) {
86
+ existingChildren.push(child);
87
+ }
88
+
89
+ (existing as any).children = normalizeMenuTree(existingChildren);
90
+ }
91
+
92
+ if (typeof cloned.sort === "number") {
93
+ (existing as any).sort = cloned.sort;
94
+ }
95
+
96
+ if (typeof cloned.name === "string" && cloned.name) {
97
+ (existing as any).name = cloned.name;
98
+ }
99
+ } else {
100
+ map.set(menuPath, cloned);
101
+ }
102
+ }
103
+
104
+ const result = Array.from(map.values());
105
+ result.sort((a, b) => ((a as any).sort ?? 999999) - ((b as any).sort ?? 999999));
106
+ return result;
107
+ }
108
+
109
+ /**
110
+ * 只取第一个 <script ... setup ...> 块
111
+ */
112
+ export function extractScriptSetupBlock(vueContent: string): string | null {
113
+ const openTag = /<script\b[^>]*\bsetup\b[^>]*>/i.exec(vueContent);
114
+ if (!openTag) {
115
+ return null;
116
+ }
117
+
118
+ const start = openTag.index + openTag[0].length;
119
+ const closeIndex = vueContent.indexOf("</script>", start);
120
+ if (closeIndex < 0) {
121
+ return null;
122
+ }
123
+
124
+ return vueContent.slice(start, closeIndex);
125
+ }
126
+
127
+ /**
128
+ * 从 <script setup> 中提取 definePage({ meta })
129
+ *
130
+ * 简化约束:
131
+ * - 每个页面只有一个 definePage
132
+ * - title 是纯字符串字面量
133
+ * - order 是数字字面量(可选)
134
+ * - 不考虑变量/表达式/多段 meta 组合
135
+ */
136
+ export function extractDefinePageMetaFromScriptSetup(scriptSetup: string): ViewDirMeta | null {
137
+ const titleMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?title\s*:\s*(["'`])([^"'`]+)\1/);
138
+ if (!titleMatch) {
139
+ return null;
140
+ }
141
+
142
+ const orderMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?order\s*:\s*(\d+)/);
143
+
144
+ return {
145
+ title: titleMatch[2],
146
+ order: orderMatch ? Number(orderMatch[1]) : undefined
147
+ };
148
+ }