befly 3.12.4 → 3.13.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.
- package/dist/befly.config.js +1 -1
- package/dist/befly.js +735 -882
- package/dist/befly.min.js +15 -18
- package/dist/checks/checkApi.js +14 -17
- package/dist/checks/checkHook.js +15 -18
- package/dist/checks/checkMenu.js +10 -10
- package/dist/checks/checkPlugin.js +15 -18
- package/dist/checks/checkTable.js +29 -28
- package/dist/hooks/auth.d.ts +3 -7
- package/dist/hooks/auth.js +2 -1
- package/dist/hooks/cors.d.ts +3 -7
- package/dist/hooks/cors.js +2 -1
- package/dist/hooks/parser.d.ts +3 -7
- package/dist/hooks/parser.js +2 -1
- package/dist/hooks/permission.d.ts +3 -7
- package/dist/hooks/permission.js +5 -3
- package/dist/hooks/validator.d.ts +3 -7
- package/dist/hooks/validator.js +2 -1
- package/dist/index.js +2 -2
- package/dist/lib/cacheHelper.js +8 -8
- package/dist/lib/connect.js +5 -5
- package/dist/lib/dbHelper.js +6 -5
- package/dist/lib/logger.d.ts +16 -17
- package/dist/lib/logger.js +335 -757
- package/dist/lib/redisHelper.js +27 -26
- package/dist/loader/loadApis.js +1 -1
- package/dist/loader/loadPlugins.js +1 -1
- package/dist/plugins/cache.d.ts +3 -9
- package/dist/plugins/cache.js +2 -1
- package/dist/plugins/cipher.d.ts +3 -8
- package/dist/plugins/cipher.js +2 -1
- package/dist/plugins/config.d.ts +3 -12
- package/dist/plugins/config.js +2 -1
- package/dist/plugins/db.d.ts +3 -9
- package/dist/plugins/db.js +3 -2
- package/dist/plugins/jwt.d.ts +3 -9
- package/dist/plugins/jwt.js +2 -1
- package/dist/plugins/logger.d.ts +3 -25
- package/dist/plugins/logger.js +2 -1
- package/dist/plugins/redis.d.ts +3 -9
- package/dist/plugins/redis.js +3 -2
- package/dist/plugins/tool.d.ts +3 -11
- package/dist/plugins/tool.js +2 -1
- package/dist/router/api.js +3 -2
- package/dist/router/static.js +1 -1
- package/dist/sync/syncApi.js +3 -3
- package/dist/sync/syncMenu.js +3 -2
- package/dist/sync/syncTable.js +2 -2
- package/dist/types/hook.d.ts +13 -0
- package/dist/types/hook.js +13 -0
- package/dist/types/logger.d.ts +20 -6
- package/dist/types/plugin.d.ts +12 -1
- package/dist/types/plugin.js +12 -1
- package/dist/utils/formatYmdHms.d.ts +1 -0
- package/dist/utils/formatYmdHms.js +20 -0
- package/dist/utils/importDefault.js +1 -1
- package/dist/utils/loadMenuConfigs.js +7 -6
- package/dist/utils/loggerUtils.d.ts +18 -0
- package/dist/utils/loggerUtils.js +167 -0
- package/dist/utils/response.js +6 -4
- package/dist/utils/sortModules.js +8 -7
- package/dist/utils/util.d.ts +2 -0
- package/dist/utils/util.js +16 -0
- package/package.json +2 -2
package/dist/types/logger.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface LoggerConfig {
|
|
|
14
14
|
dir?: string;
|
|
15
15
|
/** 是否输出到控制台(0/1) */
|
|
16
16
|
console?: LoggerFlag;
|
|
17
|
-
/**
|
|
17
|
+
/** 单文件最大大小(MB),例如 10 表示 10MB;默认 20MB */
|
|
18
18
|
maxSize?: number;
|
|
19
19
|
/** 日志清洗深度限制(1..10) */
|
|
20
20
|
sanitizeDepth?: number;
|
|
@@ -31,17 +31,31 @@ export interface LoggerConfig {
|
|
|
31
31
|
*/
|
|
32
32
|
excludeFields?: string[];
|
|
33
33
|
}
|
|
34
|
+
export type LogLevelName = "debug" | "info" | "warn" | "error";
|
|
35
|
+
export type LoggerRecord = {
|
|
36
|
+
/** 文本消息(推荐直接放在 record 里) */
|
|
37
|
+
msg?: string;
|
|
38
|
+
/** 错误对象(会在运行时被清洗为可序列化结构) */
|
|
39
|
+
err?: unknown;
|
|
40
|
+
/** 其他自定义字段 */
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
34
43
|
/**
|
|
35
44
|
* Logger 接口(类型层)。
|
|
36
45
|
*
|
|
37
46
|
* 说明:core/runtime 内部使用的是 Bun 环境的自定义 Logger 实现(异步批量写入)。
|
|
38
|
-
* 对外只承诺常用的 `info/warn/error/debug`
|
|
47
|
+
* 对外只承诺常用的 `info/warn/error/debug` 等调用形式(不再兼容 pino 的多签名调用)。
|
|
39
48
|
*/
|
|
40
49
|
export interface Logger {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
/**
|
|
51
|
+
* 只接受一个参数(任意类型):
|
|
52
|
+
* - plain object({})会作为 record 写入
|
|
53
|
+
* - 其他任何类型会被包装成对象后写入(例如 { msg: "..." } 或 { value: ... })
|
|
54
|
+
*/
|
|
55
|
+
info(input: unknown): any;
|
|
56
|
+
warn(input: unknown): any;
|
|
57
|
+
error(input: unknown): any;
|
|
58
|
+
debug(input: unknown): any;
|
|
45
59
|
configure(cfg: LoggerConfig): void;
|
|
46
60
|
setMock(mock: any | null): void;
|
|
47
61
|
/**
|
package/dist/types/plugin.d.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 插件类型定义
|
|
3
3
|
*
|
|
4
|
-
* 说明:当前 core
|
|
4
|
+
* 说明:当前 core 插件推荐的默认导出写法(避免声明文件深推导导致的类型问题):
|
|
5
|
+
*
|
|
6
|
+
* const xxxPlugin: Plugin = {
|
|
7
|
+
* name: "xxx",
|
|
8
|
+
* enable: true,
|
|
9
|
+
* deps: [],
|
|
10
|
+
* handler: (context) => {
|
|
11
|
+
* return any;
|
|
12
|
+
* }
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* export default xxxPlugin;
|
|
5
16
|
*/
|
|
6
17
|
import type { BeflyContext } from "./befly";
|
|
7
18
|
export interface Plugin {
|
package/dist/types/plugin.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 插件类型定义
|
|
3
3
|
*
|
|
4
|
-
* 说明:当前 core
|
|
4
|
+
* 说明:当前 core 插件推荐的默认导出写法(避免声明文件深推导导致的类型问题):
|
|
5
|
+
*
|
|
6
|
+
* const xxxPlugin: Plugin = {
|
|
7
|
+
* name: "xxx",
|
|
8
|
+
* enable: true,
|
|
9
|
+
* deps: [],
|
|
10
|
+
* handler: (context) => {
|
|
11
|
+
* return any;
|
|
12
|
+
* }
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* export default xxxPlugin;
|
|
5
16
|
*/
|
|
6
17
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatYmdHms(date: Date, format?: "date" | "time" | "dateTime"): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function formatYmdHms(date, format = "dateTime") {
|
|
2
|
+
const y = date.getFullYear();
|
|
3
|
+
const m = date.getMonth() + 1;
|
|
4
|
+
const d = date.getDate();
|
|
5
|
+
const h = date.getHours();
|
|
6
|
+
const mi = date.getMinutes();
|
|
7
|
+
const s = date.getSeconds();
|
|
8
|
+
const mm = m < 10 ? `0${m}` : String(m);
|
|
9
|
+
const dd = d < 10 ? `0${d}` : String(d);
|
|
10
|
+
const hh = h < 10 ? `0${h}` : String(h);
|
|
11
|
+
const mii = mi < 10 ? `0${mi}` : String(mi);
|
|
12
|
+
const ss = s < 10 ? `0${s}` : String(s);
|
|
13
|
+
if (format === "date") {
|
|
14
|
+
return `${y}-${mm}-${dd}`;
|
|
15
|
+
}
|
|
16
|
+
if (format === "time") {
|
|
17
|
+
return `${hh}:${mii}:${ss}`;
|
|
18
|
+
}
|
|
19
|
+
return `${y}-${mm}-${dd} ${hh}:${mii}:${ss}`;
|
|
20
|
+
}
|
|
@@ -23,7 +23,7 @@ export async function importDefault(file, defaultValue) {
|
|
|
23
23
|
return value;
|
|
24
24
|
}
|
|
25
25
|
catch (err) {
|
|
26
|
-
Logger.warn({ err: err, file: file
|
|
26
|
+
Logger.warn({ err: err, file: file, msg: "importDefault 导入失败,已回退到默认值" });
|
|
27
27
|
return defaultValue;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -65,17 +65,17 @@ export async function scanViewsDirToMenuConfigs(viewsDir, prefix, parentPath = "
|
|
|
65
65
|
const content = await readFile(indexVuePath, "utf-8");
|
|
66
66
|
const scriptSetup = extractScriptSetupBlock(content);
|
|
67
67
|
if (!scriptSetup) {
|
|
68
|
-
Logger.warn({ path: indexVuePath
|
|
68
|
+
Logger.warn({ path: indexVuePath, msg: "index.vue 缺少 <script setup>,已跳过该目录菜单同步" });
|
|
69
69
|
continue;
|
|
70
70
|
}
|
|
71
71
|
meta = extractDefinePageMetaFromScriptSetup(scriptSetup);
|
|
72
72
|
if (!meta?.title) {
|
|
73
|
-
Logger.warn({ path: indexVuePath
|
|
73
|
+
Logger.warn({ path: indexVuePath, msg: "index.vue 未声明 definePage({ meta: { title, order? } }),已跳过该目录菜单同步" });
|
|
74
74
|
continue;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
catch (error) {
|
|
78
|
-
Logger.warn({ err: error, path: indexVuePath
|
|
78
|
+
Logger.warn({ err: error, path: indexVuePath, msg: "读取 index.vue 失败" });
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
81
|
if (!meta?.title) {
|
|
@@ -136,8 +136,9 @@ export async function loadMenuConfigs(addons) {
|
|
|
136
136
|
err: error,
|
|
137
137
|
addon: addon.name,
|
|
138
138
|
addonSource: addon.source,
|
|
139
|
-
dir: adminViewsDir
|
|
140
|
-
|
|
139
|
+
dir: adminViewsDir,
|
|
140
|
+
msg: "扫描 addon views 目录失败"
|
|
141
|
+
});
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
const menusJsonPath = join(process.cwd(), "menus.json");
|
|
@@ -152,7 +153,7 @@ export async function loadMenuConfigs(addons) {
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
catch (error) {
|
|
155
|
-
Logger.warn({ err: error
|
|
156
|
+
Logger.warn({ err: error, msg: "读取项目 menus.json 失败" });
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
159
|
return allMenus;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type SensitiveKeyMatcher = {
|
|
2
|
+
exactSet: Set<string>;
|
|
3
|
+
contains: string[];
|
|
4
|
+
};
|
|
5
|
+
export type LogSanitizeOptions = {
|
|
6
|
+
maxStringLen: number;
|
|
7
|
+
maxArrayItems: number;
|
|
8
|
+
sanitizeDepth: number;
|
|
9
|
+
sanitizeNodes: number;
|
|
10
|
+
sanitizeObjectKeys: number;
|
|
11
|
+
sensitiveKeyMatcher: SensitiveKeyMatcher;
|
|
12
|
+
};
|
|
13
|
+
export declare function buildSensitiveKeyMatcher(options: {
|
|
14
|
+
builtinPatterns: string[];
|
|
15
|
+
userPatterns: unknown;
|
|
16
|
+
}): SensitiveKeyMatcher;
|
|
17
|
+
export declare function isSensitiveKey(key: string, matcher: SensitiveKeyMatcher): boolean;
|
|
18
|
+
export declare function sanitizeLogObject(obj: Record<string, any>, options: LogSanitizeOptions): Record<string, any>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { isPlainObject } from "./util";
|
|
2
|
+
export function buildSensitiveKeyMatcher(options) {
|
|
3
|
+
const patterns = [];
|
|
4
|
+
for (const item of options.builtinPatterns) {
|
|
5
|
+
const trimmed = String(item).trim();
|
|
6
|
+
if (trimmed.length > 0) {
|
|
7
|
+
patterns.push(trimmed.toLowerCase());
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(options.userPatterns)) {
|
|
11
|
+
for (const item of options.userPatterns) {
|
|
12
|
+
const trimmed = String(item).trim();
|
|
13
|
+
if (trimmed.length > 0) {
|
|
14
|
+
patterns.push(trimmed.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const exactSet = new Set();
|
|
19
|
+
const contains = [];
|
|
20
|
+
for (const pat of patterns) {
|
|
21
|
+
if (!pat.includes("*")) {
|
|
22
|
+
exactSet.add(pat);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const core = pat.replace(/\*+/g, "").trim();
|
|
26
|
+
if (!core) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
contains.push(core);
|
|
30
|
+
}
|
|
31
|
+
return { exactSet: exactSet, contains: contains };
|
|
32
|
+
}
|
|
33
|
+
export function isSensitiveKey(key, matcher) {
|
|
34
|
+
const lower = String(key).toLowerCase();
|
|
35
|
+
if (matcher.exactSet.has(lower))
|
|
36
|
+
return true;
|
|
37
|
+
for (const part of matcher.contains) {
|
|
38
|
+
if (lower.includes(part))
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
function truncateString(value, maxLen) {
|
|
44
|
+
if (value.length <= maxLen)
|
|
45
|
+
return value;
|
|
46
|
+
return value.slice(0, maxLen);
|
|
47
|
+
}
|
|
48
|
+
function sanitizeErrorValue(err, options) {
|
|
49
|
+
const out = {
|
|
50
|
+
name: err.name || "Error",
|
|
51
|
+
message: truncateString(err.message || "", options.maxStringLen)
|
|
52
|
+
};
|
|
53
|
+
if (typeof err.stack === "string") {
|
|
54
|
+
out.stack = truncateString(err.stack, options.maxStringLen);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function safeToStringMasked(value, options, visited) {
|
|
59
|
+
if (typeof value === "string")
|
|
60
|
+
return value;
|
|
61
|
+
if (value instanceof Error) {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.stringify(sanitizeErrorValue(value, options));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return `${value.name || "Error"}: ${value.message || ""}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (value && typeof value === "object") {
|
|
70
|
+
if (visited.has(value)) {
|
|
71
|
+
return "[Circular]";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const localVisited = visited;
|
|
76
|
+
const replacer = (k, v) => {
|
|
77
|
+
if (k && isSensitiveKey(k, options.sensitiveKeyMatcher)) {
|
|
78
|
+
return "[MASKED]";
|
|
79
|
+
}
|
|
80
|
+
if (v && typeof v === "object") {
|
|
81
|
+
if (localVisited.has(v)) {
|
|
82
|
+
return "[Circular]";
|
|
83
|
+
}
|
|
84
|
+
localVisited.add(v);
|
|
85
|
+
}
|
|
86
|
+
return v;
|
|
87
|
+
};
|
|
88
|
+
return JSON.stringify(value, replacer);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
try {
|
|
92
|
+
return String(value);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return "[Unserializable]";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function stringifyPreview(value, options, visited) {
|
|
100
|
+
const s = safeToStringMasked(value, options, visited);
|
|
101
|
+
return truncateString(s, options.maxStringLen);
|
|
102
|
+
}
|
|
103
|
+
function sanitizeAny(value, options, state, depth, visited) {
|
|
104
|
+
if (value === null || value === undefined)
|
|
105
|
+
return value;
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
return truncateString(value, options.maxStringLen);
|
|
108
|
+
}
|
|
109
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
if (value instanceof Error) {
|
|
113
|
+
return sanitizeErrorValue(value, options);
|
|
114
|
+
}
|
|
115
|
+
const isArr = Array.isArray(value);
|
|
116
|
+
const isObj = isPlainObject(value);
|
|
117
|
+
if (!isArr && !isObj) {
|
|
118
|
+
return stringifyPreview(value, options, visited);
|
|
119
|
+
}
|
|
120
|
+
if (visited.has(value)) {
|
|
121
|
+
return "[Circular]";
|
|
122
|
+
}
|
|
123
|
+
if (depth >= options.sanitizeDepth) {
|
|
124
|
+
return stringifyPreview(value, options, visited);
|
|
125
|
+
}
|
|
126
|
+
if (state.nodes >= options.sanitizeNodes) {
|
|
127
|
+
return stringifyPreview(value, options, visited);
|
|
128
|
+
}
|
|
129
|
+
visited.add(value);
|
|
130
|
+
state.nodes = state.nodes + 1;
|
|
131
|
+
if (isArr) {
|
|
132
|
+
const arr = value;
|
|
133
|
+
const out = [];
|
|
134
|
+
const limit = arr.length > options.maxArrayItems ? options.maxArrayItems : arr.length;
|
|
135
|
+
for (let i = 0; i < limit; i++) {
|
|
136
|
+
out[i] = sanitizeAny(arr[i], options, state, depth + 1, visited);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
const obj = value;
|
|
141
|
+
const out = {};
|
|
142
|
+
const entries = Object.entries(obj);
|
|
143
|
+
const limit = entries.length > options.sanitizeObjectKeys ? options.sanitizeObjectKeys : entries.length;
|
|
144
|
+
for (let i = 0; i < limit; i++) {
|
|
145
|
+
const key = entries[i][0];
|
|
146
|
+
const child = entries[i][1];
|
|
147
|
+
if (isSensitiveKey(key, options.sensitiveKeyMatcher)) {
|
|
148
|
+
out[key] = "[MASKED]";
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
out[key] = sanitizeAny(child, options, state, depth + 1, visited);
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
export function sanitizeLogObject(obj, options) {
|
|
156
|
+
const visited = new WeakSet();
|
|
157
|
+
const state = { nodes: 0 };
|
|
158
|
+
const out = {};
|
|
159
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
160
|
+
if (isSensitiveKey(key, options.sensitiveKeyMatcher)) {
|
|
161
|
+
out[key] = "[MASKED]";
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
out[key] = sanitizeAny(val, options, state, 0, visited);
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
package/dist/utils/response.js
CHANGED
|
@@ -19,8 +19,9 @@ export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, re
|
|
|
19
19
|
reason: msg,
|
|
20
20
|
reasonCode: reasonCode,
|
|
21
21
|
code: code,
|
|
22
|
-
detail: detail
|
|
23
|
-
|
|
22
|
+
detail: detail,
|
|
23
|
+
msg: "request blocked"
|
|
24
|
+
});
|
|
24
25
|
}
|
|
25
26
|
return Response.json({
|
|
26
27
|
code: code,
|
|
@@ -42,8 +43,9 @@ export function FinalResponse(ctx) {
|
|
|
42
43
|
if (ctx.api && ctx.requestId) {
|
|
43
44
|
// requestId/route/user/duration 等字段由 ALS 统一注入,避免在 msg 中重复拼接
|
|
44
45
|
Logger.info({
|
|
45
|
-
event: "request_done"
|
|
46
|
-
|
|
46
|
+
event: "request_done",
|
|
47
|
+
msg: "request done"
|
|
48
|
+
});
|
|
47
49
|
}
|
|
48
50
|
// 1. 如果已经有 response,直接返回
|
|
49
51
|
if (ctx.response) {
|
|
@@ -28,7 +28,7 @@ export function sortModules(items, options = {}) {
|
|
|
28
28
|
for (const item of items) {
|
|
29
29
|
const name = getName(item);
|
|
30
30
|
if (typeof name !== "string" || name.trim() === "") {
|
|
31
|
-
Logger.error({ item: item
|
|
31
|
+
Logger.error({ item: item, msg: `${moduleLabel} 名称解析失败(getName 返回空字符串)` });
|
|
32
32
|
isPass = false;
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
@@ -36,8 +36,9 @@ export function sortModules(items, options = {}) {
|
|
|
36
36
|
Logger.error({
|
|
37
37
|
name: name,
|
|
38
38
|
first: nameToItem[name],
|
|
39
|
-
second: item
|
|
40
|
-
|
|
39
|
+
second: item,
|
|
40
|
+
msg: `${moduleLabel} 名称重复,无法根据 deps 唯一定位`
|
|
41
|
+
});
|
|
41
42
|
isPass = false;
|
|
42
43
|
continue;
|
|
43
44
|
}
|
|
@@ -50,18 +51,18 @@ export function sortModules(items, options = {}) {
|
|
|
50
51
|
const name = getName(item);
|
|
51
52
|
const deps = getDeps(item);
|
|
52
53
|
if (!Array.isArray(deps)) {
|
|
53
|
-
Logger.error({ module: name, item: item
|
|
54
|
+
Logger.error({ module: name, item: item, msg: `${moduleLabel} 的 deps 必须是数组` });
|
|
54
55
|
isPass = false;
|
|
55
56
|
continue;
|
|
56
57
|
}
|
|
57
58
|
for (const dep of deps) {
|
|
58
59
|
if (typeof dep !== "string") {
|
|
59
|
-
Logger.error({ module: name, dependency: dep, item: item
|
|
60
|
+
Logger.error({ module: name, dependency: dep, item: item, msg: `${moduleLabel} 的 deps 必须是字符串数组` });
|
|
60
61
|
isPass = false;
|
|
61
62
|
continue;
|
|
62
63
|
}
|
|
63
64
|
if (!nameToItem[dep]) {
|
|
64
|
-
Logger.error({ module: name, dependency: dep
|
|
65
|
+
Logger.error({ module: name, dependency: dep, msg: `${moduleLabel} 依赖未找到` });
|
|
65
66
|
isPass = false;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -73,7 +74,7 @@ export function sortModules(items, options = {}) {
|
|
|
73
74
|
if (visited.has(name))
|
|
74
75
|
return;
|
|
75
76
|
if (visiting.has(name)) {
|
|
76
|
-
Logger.error({ module: name
|
|
77
|
+
Logger.error({ module: name, msg: `${moduleLabel} 循环依赖` });
|
|
77
78
|
isPass = false;
|
|
78
79
|
return;
|
|
79
80
|
}
|
package/dist/utils/util.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* - 仅在 packages/core 内部使用;core 对外不承诺这些路径导出。
|
|
5
5
|
*/
|
|
6
6
|
export declare function isPlainObject(value: unknown): value is Record<string, any>;
|
|
7
|
+
export declare function escapeRegExp(input: string): string;
|
|
8
|
+
export declare function normalizePositiveInt(value: any, fallback: number, min: number, max: number): number;
|
|
7
9
|
/**
|
|
8
10
|
* 激进空值判断(项目约定):
|
|
9
11
|
* - null/undefined => empty
|
package/dist/utils/util.js
CHANGED
|
@@ -10,6 +10,22 @@ export function isPlainObject(value) {
|
|
|
10
10
|
const proto = Object.getPrototypeOf(value);
|
|
11
11
|
return proto === Object.prototype || proto === null;
|
|
12
12
|
}
|
|
13
|
+
const REGEXP_SPECIAL = /[\\^$.*+?()[\]{}|]/g;
|
|
14
|
+
export function escapeRegExp(input) {
|
|
15
|
+
return String(input).replace(REGEXP_SPECIAL, "\\$&");
|
|
16
|
+
}
|
|
17
|
+
export function normalizePositiveInt(value, fallback, min, max) {
|
|
18
|
+
if (typeof value !== "number")
|
|
19
|
+
return fallback;
|
|
20
|
+
if (!Number.isFinite(value))
|
|
21
|
+
return fallback;
|
|
22
|
+
const v = Math.floor(value);
|
|
23
|
+
if (v < min)
|
|
24
|
+
return fallback;
|
|
25
|
+
if (v > max)
|
|
26
|
+
return max;
|
|
27
|
+
return v;
|
|
28
|
+
}
|
|
13
29
|
/**
|
|
14
30
|
* 激进空值判断(项目约定):
|
|
15
31
|
* - null/undefined => empty
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.13.0",
|
|
4
|
+
"gitHead": "340c00e4dd05b62d89d25c01922c27cb35bd99af",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|