@zhin.js/core 1.0.38 → 1.0.40
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/CHANGELOG.md +20 -0
- package/lib/ai/index.d.ts +10 -5
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +7 -4
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/providers/anthropic.d.ts.map +1 -1
- package/lib/ai/providers/anthropic.js +2 -0
- package/lib/ai/providers/anthropic.js.map +1 -1
- package/lib/ai/providers/openai.d.ts.map +1 -1
- package/lib/ai/providers/openai.js +8 -0
- package/lib/ai/providers/openai.js.map +1 -1
- package/lib/ai/types.d.ts +1 -0
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/cron.d.ts +2 -43
- package/lib/cron.d.ts.map +1 -1
- package/lib/cron.js +2 -126
- package/lib/cron.js.map +1 -1
- package/lib/errors.d.ts +3 -146
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +3 -279
- package/lib/errors.js.map +1 -1
- package/lib/feature.d.ts +5 -87
- package/lib/feature.d.ts.map +1 -1
- package/lib/feature.js +4 -105
- package/lib/feature.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/scheduler/index.d.ts +3 -7
- package/lib/scheduler/index.d.ts.map +1 -1
- package/lib/scheduler/index.js +2 -9
- package/lib/scheduler/index.js.map +1 -1
- package/lib/types.d.ts +8 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts +7 -52
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +9 -325
- package/lib/utils.js.map +1 -1
- package/package.json +6 -4
- package/src/ai/index.ts +15 -9
- package/src/ai/providers/anthropic.ts +1 -0
- package/src/ai/providers/openai.ts +5 -1
- package/src/ai/types.ts +1 -0
- package/src/cron.ts +2 -140
- package/src/errors.ts +15 -334
- package/src/feature.ts +5 -154
- package/src/index.ts +3 -1
- package/src/scheduler/index.ts +8 -17
- package/src/types.ts +10 -2
- package/src/utils.ts +37 -334
- package/tests/cron.test.ts +4 -299
- package/tests/errors.test.ts +17 -307
- package/tests/utils.test.ts +11 -516
- package/tests/feature.test.ts +0 -145
package/src/scheduler/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Re-export from @zhin.js/kernel for backward compatibility.
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
export {
|
|
5
|
+
Scheduler,
|
|
6
|
+
getScheduler,
|
|
7
|
+
setScheduler,
|
|
8
|
+
} from '@zhin.js/kernel';
|
|
5
9
|
export type {
|
|
6
10
|
Schedule,
|
|
7
11
|
JobPayload,
|
|
@@ -11,18 +15,5 @@ export type {
|
|
|
11
15
|
JobCallback,
|
|
12
16
|
AddJobOptions,
|
|
13
17
|
IScheduler,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type { SchedulerOptions } from './scheduler.js';
|
|
17
|
-
|
|
18
|
-
import type { Scheduler } from './scheduler.js';
|
|
19
|
-
|
|
20
|
-
let schedulerInstance: Scheduler | null = null;
|
|
21
|
-
|
|
22
|
-
export function getScheduler(): Scheduler | null {
|
|
23
|
-
return schedulerInstance;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function setScheduler(s: Scheduler | null): void {
|
|
27
|
-
schedulerInstance = s;
|
|
28
|
-
}
|
|
18
|
+
SchedulerOptions,
|
|
19
|
+
} from '@zhin.js/kernel';
|
package/src/types.ts
CHANGED
|
@@ -221,7 +221,8 @@ export interface ToolParametersSchema<TArgs extends Record<string, any> = Record
|
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
223
|
* 工具执行上下文
|
|
224
|
-
*
|
|
224
|
+
* 包含消息来源、发送者等 IM 信息。
|
|
225
|
+
* 通用(IM 无关)版本请使用 @zhin.js/ai 的 ToolContext。
|
|
225
226
|
*/
|
|
226
227
|
export interface ToolContext {
|
|
227
228
|
/** 来源平台 */
|
|
@@ -462,4 +463,11 @@ export namespace Tool {
|
|
|
462
463
|
// ============================================================================
|
|
463
464
|
|
|
464
465
|
/** @deprecated 使用 Tool 替代 */
|
|
465
|
-
export type AITool = Tool;
|
|
466
|
+
export type AITool = Tool;
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* IMToolContext — ToolContext 的显式 IM 别名。
|
|
470
|
+
* 当同时使用 @zhin.js/ai (通用 ToolContext) 和 @zhin.js/core (IM ToolContext) 时,
|
|
471
|
+
* 用此类型消除歧义。
|
|
472
|
+
*/
|
|
473
|
+
export type IMToolContext = ToolContext;
|
package/src/utils.ts
CHANGED
|
@@ -1,55 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @zhin.js/core utilities.
|
|
3
|
+
*
|
|
4
|
+
* Generic utilities are re-exported from @zhin.js/kernel.
|
|
5
|
+
* IM-specific utilities (compose, segment) remain here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── Re-export generic utils from kernel ──
|
|
9
|
+
export {
|
|
10
|
+
evaluate,
|
|
11
|
+
execute,
|
|
12
|
+
clearEvalCache,
|
|
13
|
+
getEvalCacheStats,
|
|
14
|
+
getValueWithRuntime,
|
|
15
|
+
compiler,
|
|
16
|
+
remove,
|
|
17
|
+
isEmpty,
|
|
18
|
+
Time,
|
|
19
|
+
supportedPluginExtensions,
|
|
20
|
+
resolveEntry,
|
|
21
|
+
sleep,
|
|
22
|
+
} from '@zhin.js/kernel';
|
|
23
|
+
|
|
24
|
+
// ── IM-specific utilities ──
|
|
4
25
|
import {
|
|
5
26
|
AdapterMessage,
|
|
6
|
-
Dict,
|
|
7
27
|
MessageElement,
|
|
8
28
|
MessageMiddleware,
|
|
9
29
|
RegisteredAdapter,
|
|
10
30
|
SendContent,
|
|
11
|
-
} from "./types";
|
|
31
|
+
} from "./types.js";
|
|
12
32
|
import { Message } from "./message.js";
|
|
13
33
|
|
|
14
|
-
export function getValueWithRuntime(template: string, ctx: Dict) {
|
|
15
|
-
return evaluate(template, ctx);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Evaluate a single expression in a sandboxed vm context.
|
|
19
|
-
* Unlike `execute`, does NOT wrap in IIFE — the expression value is returned directly.
|
|
20
|
-
*/
|
|
21
|
-
export const evaluate = <S extends Record<string, unknown>, T = unknown>(exp: string, context: S): T | undefined => {
|
|
22
|
-
const script = getOrCompileScript(exp);
|
|
23
|
-
if (!script) return undefined;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
return script.runInNewContext(buildSandbox(context), { timeout: 200 }) as T;
|
|
27
|
-
} catch {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
34
|
/**
|
|
32
35
|
* 组合中间件,洋葱模型
|
|
33
|
-
* 灵感来源于 zhinjs/next 的 Hooks.compose
|
|
34
|
-
*
|
|
35
|
-
* @param middlewares 中间件列表
|
|
36
|
-
* @returns 中间件处理函数
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```typescript
|
|
40
|
-
* const composed = compose([middleware1, middleware2]);
|
|
41
|
-
* await composed(message);
|
|
42
|
-
* ```
|
|
43
36
|
*/
|
|
44
37
|
export function compose<P extends RegisteredAdapter=RegisteredAdapter>(
|
|
45
38
|
middlewares: MessageMiddleware<P>[]
|
|
46
39
|
) {
|
|
47
|
-
// 性能优化:空数组直接返回空函数
|
|
48
40
|
if (middlewares.length === 0) {
|
|
49
41
|
return () => Promise.resolve();
|
|
50
42
|
}
|
|
51
43
|
|
|
52
|
-
// 性能优化:单个中间件直接返回
|
|
53
44
|
if (middlewares.length === 1) {
|
|
54
45
|
return (message: Message<AdapterMessage<P>>, next: () => Promise<void> = () => Promise.resolve()) => {
|
|
55
46
|
return middlewares[0](message, next);
|
|
@@ -62,7 +53,6 @@ export function compose<P extends RegisteredAdapter=RegisteredAdapter>(
|
|
|
62
53
|
) {
|
|
63
54
|
let index = -1;
|
|
64
55
|
const dispatch = async (i: number = 0): Promise<void> => {
|
|
65
|
-
// 防止 next() 被多次调用
|
|
66
56
|
if (i <= index) {
|
|
67
57
|
return Promise.reject(new Error("next() called multiple times"));
|
|
68
58
|
}
|
|
@@ -73,7 +63,6 @@ export function compose<P extends RegisteredAdapter=RegisteredAdapter>(
|
|
|
73
63
|
try {
|
|
74
64
|
return await fn(message, () => dispatch(i + 1));
|
|
75
65
|
} catch (error) {
|
|
76
|
-
// 中间件异常应该被记录但不中断整个流程
|
|
77
66
|
console.error("Middleware error:", error);
|
|
78
67
|
throw error;
|
|
79
68
|
}
|
|
@@ -81,87 +70,7 @@ export function compose<P extends RegisteredAdapter=RegisteredAdapter>(
|
|
|
81
70
|
return dispatch(0);
|
|
82
71
|
};
|
|
83
72
|
}
|
|
84
|
-
// LRU cache for compiled vm.Script instances
|
|
85
|
-
const MAX_EVAL_CACHE_SIZE = 1000;
|
|
86
|
-
const scriptCache = new Map<string, vm.Script>();
|
|
87
|
-
|
|
88
|
-
function getOrCompileScript(code: string): vm.Script | null {
|
|
89
|
-
let script = scriptCache.get(code);
|
|
90
|
-
if (script) return script;
|
|
91
|
-
try {
|
|
92
|
-
script = new vm.Script(code);
|
|
93
|
-
} catch {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
if (scriptCache.size >= MAX_EVAL_CACHE_SIZE) {
|
|
97
|
-
const oldest = scriptCache.keys().next().value;
|
|
98
|
-
if (oldest !== undefined) scriptCache.delete(oldest);
|
|
99
|
-
}
|
|
100
|
-
scriptCache.set(code, script);
|
|
101
|
-
return script;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function buildSandbox<S extends Record<string, unknown>>(context: S): Record<string, unknown> {
|
|
105
|
-
return {
|
|
106
|
-
...context,
|
|
107
|
-
process: {
|
|
108
|
-
version: process.version,
|
|
109
|
-
versions: process.versions,
|
|
110
|
-
platform: process.platform,
|
|
111
|
-
arch: process.arch,
|
|
112
|
-
release: process.release,
|
|
113
|
-
uptime: process.uptime(),
|
|
114
|
-
memoryUsage: process.memoryUsage(),
|
|
115
|
-
cpuUsage: process.cpuUsage(),
|
|
116
|
-
pid: process.pid,
|
|
117
|
-
ppid: process.ppid,
|
|
118
|
-
},
|
|
119
|
-
global: undefined,
|
|
120
|
-
globalThis: undefined,
|
|
121
|
-
Buffer: undefined,
|
|
122
|
-
crypto: undefined,
|
|
123
|
-
require: undefined,
|
|
124
|
-
import: undefined,
|
|
125
|
-
__dirname: undefined,
|
|
126
|
-
__filename: undefined,
|
|
127
|
-
Bun: undefined,
|
|
128
|
-
Deno: undefined,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Execute a code block in a sandboxed vm context.
|
|
134
|
-
* Supports `return` statements by wrapping in an IIFE.
|
|
135
|
-
* Throws on compilation or runtime errors.
|
|
136
|
-
*/
|
|
137
|
-
export const execute = <S extends Record<string, unknown>, T = unknown>(code: string, context: S): T => {
|
|
138
|
-
const wrapped = `(function(){${code}})()`;
|
|
139
|
-
const script = getOrCompileScript(wrapped);
|
|
140
|
-
if (!script) throw new SyntaxError(`Failed to compile: ${code.slice(0, 80)}`);
|
|
141
|
-
|
|
142
|
-
return script.runInNewContext(buildSandbox(context), { timeout: 200 }) as T;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
export function clearEvalCache(): void {
|
|
146
|
-
scriptCache.clear();
|
|
147
|
-
}
|
|
148
73
|
|
|
149
|
-
export function getEvalCacheStats(): { size: number; maxSize: number } {
|
|
150
|
-
return {
|
|
151
|
-
size: scriptCache.size,
|
|
152
|
-
maxSize: MAX_EVAL_CACHE_SIZE,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
export function compiler(template: string, ctx: Dict) {
|
|
156
|
-
const matched = [...template.matchAll(/\${([^}]*?)}/g)];
|
|
157
|
-
for (const item of matched) {
|
|
158
|
-
const tpl = item[1];
|
|
159
|
-
const raw = getValueWithRuntime(tpl, ctx);
|
|
160
|
-
const value = typeof raw === 'string' ? raw : (raw == null ? 'undefined' : JSON.stringify(raw, null, 2));
|
|
161
|
-
template = template.replace(`\${${item[1]}}`, value);
|
|
162
|
-
}
|
|
163
|
-
return template;
|
|
164
|
-
}
|
|
165
74
|
export function segment<T extends object>(type: string, data: T) {
|
|
166
75
|
return {
|
|
167
76
|
type,
|
|
@@ -197,33 +106,27 @@ export namespace segment {
|
|
|
197
106
|
if (!Array.isArray(content)) content = [content];
|
|
198
107
|
const toString = (template: string | MessageElement) => {
|
|
199
108
|
if (typeof template !== "string") return [template];
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const MAX_TEMPLATE_LENGTH = 100000; // 100KB
|
|
109
|
+
|
|
110
|
+
const MAX_TEMPLATE_LENGTH = 100000;
|
|
203
111
|
if (template.length > MAX_TEMPLATE_LENGTH) {
|
|
204
112
|
throw new Error(`Template too large: ${template.length} > ${MAX_TEMPLATE_LENGTH}`);
|
|
205
113
|
}
|
|
206
|
-
|
|
114
|
+
|
|
207
115
|
template = unescape(template);
|
|
208
116
|
const result: MessageElement[] = [];
|
|
209
|
-
// 修复 ReDoS 漏洞:使用更安全的正则表达式
|
|
210
|
-
// 注意:需要使用捕获组来获取属性字符串,否则无法正确重建原始标签
|
|
211
|
-
// closingReg: 自闭合标签 <type attr="val"/>
|
|
212
117
|
const closingReg = /<(\w+)(\s+[^>]*?)?\/>/;
|
|
213
|
-
// twinningReg: 成对标签 <type attr="val">child</type>
|
|
214
118
|
const twinningReg = /<(\w+)(\s+[^>]*?)?>([^]*?)<\/\1>/;
|
|
215
|
-
|
|
119
|
+
|
|
216
120
|
let iterations = 0;
|
|
217
|
-
const MAX_ITERATIONS = 1000;
|
|
218
|
-
|
|
121
|
+
const MAX_ITERATIONS = 1000;
|
|
122
|
+
|
|
219
123
|
while (template.length && iterations++ < MAX_ITERATIONS) {
|
|
220
124
|
const twinMatch = template.match(twinningReg);
|
|
221
125
|
const closeMatch = template.match(closingReg);
|
|
222
|
-
|
|
223
|
-
// 选择位置更靠前的匹配
|
|
126
|
+
|
|
224
127
|
let match: RegExpMatchArray | null = null;
|
|
225
128
|
let isClosing = false;
|
|
226
|
-
|
|
129
|
+
|
|
227
130
|
if (twinMatch && closeMatch) {
|
|
228
131
|
const twinIndex = template.indexOf(twinMatch[0]);
|
|
229
132
|
const closeIndex = template.indexOf(closeMatch[0]);
|
|
@@ -239,14 +142,14 @@ export namespace segment {
|
|
|
239
142
|
} else if (twinMatch) {
|
|
240
143
|
match = twinMatch;
|
|
241
144
|
}
|
|
242
|
-
|
|
145
|
+
|
|
243
146
|
if (!match) break;
|
|
244
|
-
|
|
245
|
-
const [fullMatch, type, attrStr = "", child = ""] = isClosing
|
|
147
|
+
|
|
148
|
+
const [fullMatch, type, attrStr = "", child = ""] = isClosing
|
|
246
149
|
? [match[0], match[1], match[2] || ""]
|
|
247
150
|
: [match[0], match[1], match[2] || "", match[3] || ""];
|
|
248
151
|
const index = template.indexOf(fullMatch);
|
|
249
|
-
if (index === -1) break;
|
|
152
|
+
if (index === -1) break;
|
|
250
153
|
const prevText = template.slice(0, index);
|
|
251
154
|
if (prevText)
|
|
252
155
|
result.push({
|
|
@@ -256,8 +159,6 @@ export namespace segment {
|
|
|
256
159
|
},
|
|
257
160
|
});
|
|
258
161
|
template = template.slice(index + fullMatch.length);
|
|
259
|
-
// 修复 ReDoS 漏洞:使用更简单的正则表达式
|
|
260
|
-
// 原: /\s([^=]+)(?=(?=="([^"]+)")|(?=='([^']+)'))/g 嵌套前瞻断言
|
|
261
162
|
const attrArr = [
|
|
262
163
|
...attrStr.matchAll(/\s+([^=\s]+)=(?:"([^"]*)"|'([^']*)')/g),
|
|
263
164
|
];
|
|
@@ -324,201 +225,3 @@ export namespace segment {
|
|
|
324
225
|
.join("");
|
|
325
226
|
}
|
|
326
227
|
}
|
|
327
|
-
|
|
328
|
-
export function remove<T>(list: T[], fn: (item: T) => boolean): void;
|
|
329
|
-
export function remove<T>(list: T[], item: T): void;
|
|
330
|
-
export function remove<T>(list: T[], arg: T | ((item: T) => boolean)) {
|
|
331
|
-
const index =
|
|
332
|
-
typeof arg === "function" &&
|
|
333
|
-
!list.every((item) => typeof item === "function")
|
|
334
|
-
? list.findIndex(arg as (item: T) => boolean)
|
|
335
|
-
: list.indexOf(arg as T);
|
|
336
|
-
if (index !== -1) list.splice(index, 1);
|
|
337
|
-
}
|
|
338
|
-
export function isEmpty<T>(item: T) {
|
|
339
|
-
if (Array.isArray(item)) return item.length === 0;
|
|
340
|
-
if (typeof item === "object") {
|
|
341
|
-
if (!item) return true;
|
|
342
|
-
return Reflect.ownKeys(item).length === 0;
|
|
343
|
-
}
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export namespace Time {
|
|
348
|
-
export const millisecond = 1;
|
|
349
|
-
export const second = 1000;
|
|
350
|
-
export const minute = second * 60;
|
|
351
|
-
export const hour = minute * 60;
|
|
352
|
-
export const day = hour * 24;
|
|
353
|
-
export const week = day * 7;
|
|
354
|
-
|
|
355
|
-
let timezoneOffset = new Date().getTimezoneOffset();
|
|
356
|
-
|
|
357
|
-
export function setTimezoneOffset(offset: number) {
|
|
358
|
-
timezoneOffset = offset;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
export function getTimezoneOffset() {
|
|
362
|
-
return timezoneOffset;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export function getDateNumber(
|
|
366
|
-
date: number | Date = new Date(),
|
|
367
|
-
offset?: number
|
|
368
|
-
) {
|
|
369
|
-
if (typeof date === "number") date = new Date(date);
|
|
370
|
-
if (offset === undefined) offset = timezoneOffset;
|
|
371
|
-
return Math.floor((date.valueOf() / minute - offset) / 1440);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
export function fromDateNumber(value: number, offset?: number) {
|
|
375
|
-
const date = new Date(value * day);
|
|
376
|
-
if (offset === undefined) offset = timezoneOffset;
|
|
377
|
-
return new Date(+date + offset * minute);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const numeric = /\d+(?:\.\d+)?/.source;
|
|
381
|
-
const timeRegExp = new RegExp(
|
|
382
|
-
`^${[
|
|
383
|
-
"w(?:eek(?:s)?)?",
|
|
384
|
-
"d(?:ay(?:s)?)?",
|
|
385
|
-
"h(?:our(?:s)?)?",
|
|
386
|
-
"m(?:in(?:ute)?(?:s)?)?",
|
|
387
|
-
"s(?:ec(?:ond)?(?:s)?)?",
|
|
388
|
-
]
|
|
389
|
-
.map((unit) => `(${numeric}${unit})?`)
|
|
390
|
-
.join("")}$`
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
export function parseTime(source: string) {
|
|
394
|
-
const capture = timeRegExp.exec(source);
|
|
395
|
-
if (!capture) return 0;
|
|
396
|
-
return (
|
|
397
|
-
(parseFloat(capture[1]) * week || 0) +
|
|
398
|
-
(parseFloat(capture[2]) * day || 0) +
|
|
399
|
-
(parseFloat(capture[3]) * hour || 0) +
|
|
400
|
-
(parseFloat(capture[4]) * minute || 0) +
|
|
401
|
-
(parseFloat(capture[5]) * second || 0)
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
export function parseDate(date: string) {
|
|
406
|
-
const parsed = parseTime(date);
|
|
407
|
-
let dateInput: string | number = date;
|
|
408
|
-
if (parsed) {
|
|
409
|
-
dateInput = Date.now() + parsed;
|
|
410
|
-
} else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) {
|
|
411
|
-
dateInput = `${new Date().toLocaleDateString()}-${date}`;
|
|
412
|
-
} else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) {
|
|
413
|
-
dateInput = `${new Date().getFullYear()}-${date}`;
|
|
414
|
-
}
|
|
415
|
-
return dateInput ? new Date(dateInput) : new Date();
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
export function formatTimeShort(ms: number) {
|
|
419
|
-
const abs = Math.abs(ms);
|
|
420
|
-
if (abs >= day - hour / 2) {
|
|
421
|
-
return Math.round(ms / day) + "d";
|
|
422
|
-
} else if (abs >= hour - minute / 2) {
|
|
423
|
-
return Math.round(ms / hour) + "h";
|
|
424
|
-
} else if (abs >= minute - second / 2) {
|
|
425
|
-
return Math.round(ms / minute) + "m";
|
|
426
|
-
} else if (abs >= second) {
|
|
427
|
-
return Math.round(ms / second) + "s";
|
|
428
|
-
}
|
|
429
|
-
return ms + "ms";
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export function formatTime(ms: number) {
|
|
433
|
-
let result: string;
|
|
434
|
-
if (ms >= day - hour / 2) {
|
|
435
|
-
ms += hour / 2;
|
|
436
|
-
result = Math.floor(ms / day) + " 天";
|
|
437
|
-
if (ms % day > hour) {
|
|
438
|
-
result += ` ${Math.floor((ms % day) / hour)} 小时`;
|
|
439
|
-
}
|
|
440
|
-
} else if (ms >= hour - minute / 2) {
|
|
441
|
-
ms += minute / 2;
|
|
442
|
-
result = Math.floor(ms / hour) + " 小时";
|
|
443
|
-
if (ms % hour > minute) {
|
|
444
|
-
result += ` ${Math.floor((ms % hour) / minute)} 分钟`;
|
|
445
|
-
}
|
|
446
|
-
} else if (ms >= minute - second / 2) {
|
|
447
|
-
ms += second / 2;
|
|
448
|
-
result = Math.floor(ms / minute) + " 分钟";
|
|
449
|
-
if (ms % minute > second) {
|
|
450
|
-
result += ` ${Math.floor((ms % minute) / second)} 秒`;
|
|
451
|
-
}
|
|
452
|
-
} else {
|
|
453
|
-
result = Math.round(ms / second) + " 秒";
|
|
454
|
-
}
|
|
455
|
-
return result;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const dayMap = ["日", "一", "二", "三", "四", "五", "六"];
|
|
459
|
-
|
|
460
|
-
function toDigits(source: number, length = 2) {
|
|
461
|
-
return source.toString().padStart(length, "0");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
export function template(template: string, time = new Date()) {
|
|
465
|
-
return template
|
|
466
|
-
.replace("yyyy", time.getFullYear().toString())
|
|
467
|
-
.replace("yy", time.getFullYear().toString().slice(2))
|
|
468
|
-
.replace("MM", toDigits(time.getMonth() + 1))
|
|
469
|
-
.replace("dd", toDigits(time.getDate()))
|
|
470
|
-
.replace("hh", toDigits(time.getHours()))
|
|
471
|
-
.replace("mm", toDigits(time.getMinutes()))
|
|
472
|
-
.replace("ss", toDigits(time.getSeconds()))
|
|
473
|
-
.replace("SSS", toDigits(time.getMilliseconds(), 3));
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function toHourMinute(time: Date) {
|
|
477
|
-
return `${toDigits(time.getHours())}:${toDigits(time.getMinutes())}`;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
export function formatTimeInterval(time: Date, interval?: number) {
|
|
481
|
-
if (!interval) {
|
|
482
|
-
return template("yyyy-MM-dd hh:mm:ss", time);
|
|
483
|
-
} else if (interval === day) {
|
|
484
|
-
return `每天 ${toHourMinute(time)}`;
|
|
485
|
-
} else if (interval === week) {
|
|
486
|
-
return `每周${dayMap[time.getDay()]} ${toHourMinute(time)}`;
|
|
487
|
-
} else {
|
|
488
|
-
return `${template("yyyy-MM-dd hh:mm:ss", time)} 起每隔 ${formatTime(
|
|
489
|
-
interval
|
|
490
|
-
)}`;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
export const supportedPluginExtensions = [
|
|
495
|
-
".js",
|
|
496
|
-
".ts",
|
|
497
|
-
".mjs",
|
|
498
|
-
".cjs",
|
|
499
|
-
".jsx",
|
|
500
|
-
".tsx",
|
|
501
|
-
"",
|
|
502
|
-
];
|
|
503
|
-
|
|
504
|
-
export function resolveEntry(entry: string) {
|
|
505
|
-
if (fs.existsSync(entry)) {
|
|
506
|
-
const stat = fs.statSync(entry);
|
|
507
|
-
if (stat.isFile()) return entry;
|
|
508
|
-
if (stat.isSymbolicLink()) return resolveEntry(fs.realpathSync(entry));
|
|
509
|
-
if (stat.isDirectory()) {
|
|
510
|
-
const packageJsonPath = path.resolve(entry, 'package.json');
|
|
511
|
-
if (!fs.existsSync(packageJsonPath)) return resolveEntry(path.join(entry, 'index'));
|
|
512
|
-
const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
513
|
-
return resolveEntry(path.resolve(entry, pkgJson.main || 'index.js'));
|
|
514
|
-
}
|
|
515
|
-
} else {
|
|
516
|
-
for (const ext of supportedPluginExtensions) {
|
|
517
|
-
const fullPath = path.resolve(entry + ext);
|
|
518
|
-
if (fs.existsSync(fullPath)) return resolveEntry(fullPath);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
export function sleep(ms: number) {
|
|
523
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
524
|
-
}
|