@jayfong/x-utils 3.3.0 → 3.4.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/lib/runner.mjs CHANGED
@@ -1,36 +1,52 @@
1
1
  import { XFs } from "./fs.mjs";
2
- import inquirer from "inquirer";
3
2
  import { ms, wait } from "vtils";
4
3
  // src/runner.ts
5
4
  /**
6
5
  * 带配置持久化与失败重试能力的交互式运行器。
7
6
  *
8
- * 行为概要:
9
- * 1. 启动时读取历史配置,作为本次提问默认值;
10
- * 2. 用户确认后回写配置文件,保证下次可复用;
11
- * 3. 执行业务函数,成功即退出;
12
- * 4. 失败时按 `delay` 等待后重试,直到成功或出现不可恢复错误。
7
+ * 典型用法:CLI 启动时先询问用户配置并落盘复用,随后循环执行业务函数,
8
+ * 失败时自动等待并重试。
9
+ *
10
+ * 运行流程(详见 {@link XRunner.run}):
11
+ * 1. 确保配置文件存在(缺失时自动创建);
12
+ * 2. 读取历史配置,作为本次提问默认值;
13
+ * 3. 用户确认后回写配置文件,保证下次可复用;
14
+ * 4. 执行业务函数,成功即退出;
15
+ * 5. 失败时按 `delay` 等待后重试,直到成功或出现不可恢复错误。
13
16
  */
14
17
  var XRunner = class {
15
18
  options;
19
+ /**
20
+ * 创建运行器实例。
21
+ *
22
+ * @param options 运行配置,各字段语义见 {@link XRunnerOptions}。
23
+ */
16
24
  constructor(options) {
17
25
  this.options = options;
18
26
  }
19
27
  /**
20
28
  * 启动运行器。
29
+ *
30
+ * 依次执行:确保配置文件存在 → 读取历史配置 → 通过 `provideConfig`
31
+ * 收集本次配置 → 写回配置文件 → 循环执行 `run`。
32
+ *
33
+ * 边界与异常说明:
34
+ * - 配置文件缺失时由 `XFs.ensureFile` 自动补齐目录并创建空文件;
35
+ * - 配置内容非法(非 JSON)时 `readJson(..., { throws: false })` 返回
36
+ * `null`,经 `|| {}` 兜底为空对象,不阻断启动;
37
+ * - `provideConfig` 抛错或写回配置失败时,错误直接向外抛出,不进入重试;
38
+ * - `run` 抛错时进入重试分支:`retryCount` 自增后计算等待时长,打印
39
+ * 错误与重试提示,等待后重新执行;
40
+ * - `delay` 为函数时按 `retryCount`(从 `1` 开始)动态计算;其返回值
41
+ * 若无法被 `ms` 解析,`ms` 会抛错并终止重试循环。
42
+ *
43
+ * @returns 业务函数成功后 resolve 为 `undefined`;仅在成功或抛出
44
+ * 不可恢复错误时返回,否则持续重试。
21
45
  */
22
46
  async run() {
23
47
  await XFs.ensureFile(this.options.configFile);
24
48
  const savedConfig = await XFs.readJson(this.options.configFile, { throws: false }) || {};
25
- const questions = this.options.questions.map((item) => {
26
- const defaultValue = savedConfig[item.name];
27
- if (defaultValue === void 0) return item;
28
- return {
29
- ...item,
30
- default: defaultValue
31
- };
32
- });
33
- const config = await inquirer.prompt(questions);
49
+ const config = await this.options.provideConfig(savedConfig);
34
50
  await XFs.writeJson(this.options.configFile, config, { spaces: 2 });
35
51
  let retryCount = 0;
36
52
  while (true) try {
@@ -0,0 +1,82 @@
1
+ export interface XSensitiveWordsOptions {
2
+ /**
3
+ * 敏感词列表。
4
+ *
5
+ * 支持三种写法:
6
+ * - 普通文本,如 `赌博`:做大小写不敏感的子串匹配;
7
+ * - `<手机号>`:匹配 11 位大陆手机号;
8
+ * - 正则表达式,以 `/` 开头且以 `/` 结尾,如 `/[色情]+/`:
9
+ * 会去除首尾的 `/` 后编译为正则,对归一化后的文本执行 `test`。
10
+ *
11
+ * 构造时会将每个词统一转为小写并去重,因此列表中重复或
12
+ * 大小写不同的词不会影响匹配结果。
13
+ */
14
+ words: string[];
15
+ }
16
+ /**
17
+ * `XSensitiveWords.validate` 的返回结果。
18
+ */
19
+ export interface XSensitiveWordsValidateResult {
20
+ /** 是否通过校验,`true` 表示未命中任何敏感词。 */
21
+ pass: boolean;
22
+ /**
23
+ * 命中的敏感词列表。
24
+ *
25
+ * - 默认(`every` 为假)最多包含一个词,命中即停止;
26
+ * - 传入 `every` 为真时,包含命中的全部词;
27
+ * - 未命中时为空数组。
28
+ */
29
+ words: string[];
30
+ }
31
+ /**
32
+ * 敏感词校验工具。
33
+ *
34
+ * 用于校验文本是否包含敏感词,支持普通词、`<手机号>` 与正则三种
35
+ * 敏感词写法(见 {@link XSensitiveWordsOptions.words})。
36
+ *
37
+ * 校验前会先对文本做归一化处理,以提升命中率:
38
+ * 1. 全角转半角(如 `A` → `A`);
39
+ * 2. 移除非单词字符(保留中文、字母、数字、下划线等);
40
+ * 3. 移除空白字符;
41
+ * 4. 转小写;
42
+ * 5. 叠词归一(除字母数字外,连续重复字符折叠为单个),
43
+ * 以支持 `qq`、手机号等场景。
44
+ *
45
+ * @example
46
+ * const sw = new XSensitiveWords({
47
+ * words: ['赌博', '<手机号>', '/[色情]+/'],
48
+ * });
49
+ *
50
+ * await sw.validate('这是一条赌博信息');
51
+ * // { pass: false, words: ['赌博'] }
52
+ */
53
+ export declare class XSensitiveWords {
54
+ private sensitiveWords;
55
+ constructor(options: XSensitiveWordsOptions);
56
+ /**
57
+ * 文本归一化。
58
+ *
59
+ * 依次执行:全角转半角 → 移除非单词字符 → 移除空白字符 → 转小写
60
+ * → 叠词归一。与构造时仅做“转小写 + 去重”的敏感词列表配合,
61
+ * 实现大小写不敏感、忽略格式干扰的匹配。
62
+ */
63
+ private transform;
64
+ /**
65
+ * 验证文本是否包含敏感词。
66
+ *
67
+ * 对文本做归一化后,按顺序遍历敏感词:
68
+ * - `<手机号>`:用手机号正则测试;
69
+ * - `/.../` 形式的正则词:编译后测试;
70
+ * - 其余普通词:做子串包含判断。
71
+ *
72
+ * @param text 待校验的文本。
73
+ * @param every 是否返回命中的全部敏感词;默认为 `false`,命中一个即停止。
74
+ * @returns 校验结果,`pass` 表示是否通过,`words` 为命中的敏感词。
75
+ *
76
+ * @example
77
+ * const sw = new XSensitiveWords({ words: ['赌博', '色情'] });
78
+ * await sw.validate('赌博与色情', true);
79
+ * // { pass: false, words: ['赌博', '色情'] }
80
+ */
81
+ validate(text: string, every?: boolean): Promise<XSensitiveWordsValidateResult>;
82
+ }
@@ -0,0 +1,73 @@
1
+ import { memoize, removeBlankChars, removeNonWordChars, toHalfWidthString, uniq } from "vtils";
2
+ import { phoneNumberRegExpBuilder } from "vtils/regexp";
3
+ // src/sensitive_words.ts
4
+ const phoneNumberRegExp = phoneNumberRegExpBuilder.build();
5
+ const toRegExp = memoize((re) => new RegExp(re.substring(1, re.length - 1)), (re) => re);
6
+ /**
7
+ * 敏感词校验工具。
8
+ *
9
+ * 用于校验文本是否包含敏感词,支持普通词、`<手机号>` 与正则三种
10
+ * 敏感词写法(见 {@link XSensitiveWordsOptions.words})。
11
+ *
12
+ * 校验前会先对文本做归一化处理,以提升命中率:
13
+ * 1. 全角转半角(如 `A` → `A`);
14
+ * 2. 移除非单词字符(保留中文、字母、数字、下划线等);
15
+ * 3. 移除空白字符;
16
+ * 4. 转小写;
17
+ * 5. 叠词归一(除字母数字外,连续重复字符折叠为单个),
18
+ * 以支持 `qq`、手机号等场景。
19
+ *
20
+ * @example
21
+ * const sw = new XSensitiveWords({
22
+ * words: ['赌博', '<手机号>', '/[色情]+/'],
23
+ * });
24
+ *
25
+ * await sw.validate('这是一条赌博信息');
26
+ * // { pass: false, words: ['赌博'] }
27
+ */
28
+ var XSensitiveWords = class {
29
+ sensitiveWords = [];
30
+ constructor(options) {
31
+ this.sensitiveWords = uniq(options.words.map((word) => word.toLowerCase()));
32
+ }
33
+ /**
34
+ * 文本归一化。
35
+ *
36
+ * 依次执行:全角转半角 → 移除非单词字符 → 移除空白字符 → 转小写
37
+ * → 叠词归一。与构造时仅做“转小写 + 去重”的敏感词列表配合,
38
+ * 实现大小写不敏感、忽略格式干扰的匹配。
39
+ */
40
+ transform(text) {
41
+ return removeBlankChars(removeNonWordChars(toHalfWidthString(text))).toLowerCase().replace(/([^a-zA-Z0-9])\1+/g, "$1");
42
+ }
43
+ /**
44
+ * 验证文本是否包含敏感词。
45
+ *
46
+ * 对文本做归一化后,按顺序遍历敏感词:
47
+ * - `<手机号>`:用手机号正则测试;
48
+ * - `/.../` 形式的正则词:编译后测试;
49
+ * - 其余普通词:做子串包含判断。
50
+ *
51
+ * @param text 待校验的文本。
52
+ * @param every 是否返回命中的全部敏感词;默认为 `false`,命中一个即停止。
53
+ * @returns 校验结果,`pass` 表示是否通过,`words` 为命中的敏感词。
54
+ *
55
+ * @example
56
+ * const sw = new XSensitiveWords({ words: ['赌博', '色情'] });
57
+ * await sw.validate('赌博与色情', true);
58
+ * // { pass: false, words: ['赌博', '色情'] }
59
+ */
60
+ async validate(text, every) {
61
+ text = this.transform(text);
62
+ const words = [];
63
+ for (const word of this.sensitiveWords) if (word === "<手机号>" ? phoneNumberRegExp.test(text) : word[0] === "/" && word[word.length - 1] === "/" ? toRegExp(word).test(text) : text.includes(word)) {
64
+ words.push(word);
65
+ if (!every) break;
66
+ }
67
+ return {
68
+ pass: !words.length,
69
+ words
70
+ };
71
+ }
72
+ };
73
+ export { XSensitiveWords };
package/lib/sms.d.mts ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * 短信服务提供商。
3
+ */
4
+ export declare enum SmsServiceProvider {
5
+ /** 腾讯云短信。 */
6
+ TencentCloud = "TencentCloud"
7
+ }
8
+ /**
9
+ * `XSms` 的构造配置。
10
+ */
11
+ export interface XSmsOptions {
12
+ /**
13
+ * 短信服务提供商。
14
+ *
15
+ * 目前仅支持 {@link SmsServiceProvider.TencentCloud}。实际发送时以
16
+ * {@link XSmsSendPayload.provider} 为准进行路由,本字段作为实例级默认
17
+ * 配置保留,供后续扩展多提供商时使用。
18
+ */
19
+ provider: SmsServiceProvider;
20
+ /** 云厂商 API 密钥 SecretId(腾讯云访问管理 CAM 中获取)。 */
21
+ secretId: string;
22
+ /** 云厂商 API 密钥 SecretKey(腾讯云访问管理 CAM 中获取)。 */
23
+ secretKey: string;
24
+ /** 短信应用 ID(腾讯云短信控制台中的应用 SdkAppId)。 */
25
+ appId: string;
26
+ }
27
+ /**
28
+ * `XSms.send` 的发送参数。
29
+ */
30
+ export interface XSmsSendPayload {
31
+ /**
32
+ * 本次发送实际使用的短信服务提供商。
33
+ *
34
+ * `send` 依据该字段路由到对应厂商的实现;目前仅支持
35
+ * {@link SmsServiceProvider.TencentCloud}。
36
+ */
37
+ provider: SmsServiceProvider;
38
+ /** 短信签名,必须是已在对应厂商控制台审核通过的签名内容(非签名 ID)。 */
39
+ signature: string;
40
+ /** 短信模板 ID,必须是已在对应厂商控制台审核通过的模板。 */
41
+ templateId: string;
42
+ /**
43
+ * 模板变量值列表。
44
+ *
45
+ * 按顺序对应模板中的变量,个数需与模板变量个数一致;模板无变量时
46
+ * 传空数组即可。例如模板为「您的验证码为 {1},{2} 分钟内有效」时,
47
+ * 传 `['123456', '5']`。
48
+ */
49
+ templateParamValues: string[];
50
+ /**
51
+ * 接收短信的手机号。
52
+ *
53
+ * 支持两种写法:
54
+ * - 以 `+` 开头:视为 E.164 国际号码,原样使用,如 `+8613800138000`;
55
+ * - 不以 `+` 开头:视为大陆手机号,发送时自动补 `+86` 前缀,
56
+ * 如 `13800138000` → `+8613800138000`。
57
+ */
58
+ phoneNumber: string;
59
+ }
60
+ /**
61
+ * `XSms.send` 的返回结果。
62
+ */
63
+ export interface XSmsSendResult {
64
+ /** 是否发送成功。对应厂商返回的首条发送状态码是否为 `Ok`。 */
65
+ success: boolean;
66
+ /** 厂商返回的状态描述。成功时通常为 `send success`,失败时为失败原因。 */
67
+ message: string;
68
+ /** 厂商返回的短信发送流水号(SerialNo),可用于对账与查询。 */
69
+ smsId: string;
70
+ }
71
+ /**
72
+ * 短信发送工具。
73
+ *
74
+ * 统一封装各云厂商的短信发送接口,将厂商原始返回转换为统一的
75
+ * {@link XSmsSendResult}。当前支持腾讯云短信(
76
+ * {@link SmsServiceProvider.TencentCloud}),后续可在此基础上扩展更多
77
+ * 厂商。
78
+ *
79
+ * 腾讯云实现的几个关键点:
80
+ * 1. 使用 `tencentcloud-sdk-nodejs-sms` 的 `v20210111` 版本客户端;
81
+ * 2. 客户端构造必须显式传入 `region`(否则报错缺少 `Region` 参数),
82
+ * 短信用途下该值不参与实际路由,统一使用 `ap-beijing`;
83
+ * 3. 手机号规范化:以 `+` 开头视为 E.164 号码原样使用,否则自动补
84
+ * `+86` 前缀;
85
+ * 4. 单次发送一个号码,取返回的 `SendStatusSet[0]` 作为发送结果,
86
+ * `Code === 'Ok'` 视为成功;若服务商未返回任何状态则抛出错误。
87
+ *
88
+ * @example
89
+ * const sms = new XSms({
90
+ * provider: SmsServiceProvider.TencentCloud,
91
+ * secretId: 'your-secret-id',
92
+ * secretKey: 'your-secret-key',
93
+ * appId: '1400000000',
94
+ * });
95
+ *
96
+ * const result = await sms.send({
97
+ * provider: SmsServiceProvider.TencentCloud,
98
+ * signature: '我的签名',
99
+ * templateId: '1234567',
100
+ * templateParamValues: ['123456', '5'],
101
+ * phoneNumber: '13800138000',
102
+ * });
103
+ * // result = { success: true, message: 'send success', smsId: '...' }
104
+ */
105
+ export declare class XSms {
106
+ private options;
107
+ constructor(options: XSmsOptions);
108
+ /**
109
+ * 发送短信。
110
+ *
111
+ * 根据 `payload.provider` 路由到对应厂商:
112
+ * - {@link SmsServiceProvider.TencentCloud}:创建腾讯云短信客户端并调用
113
+ * `SendSms`,将返回的首条发送状态映射为 {@link XSmsSendResult};
114
+ * - 其他未知提供商:抛出错误。
115
+ *
116
+ * 手机号规范化:以 `+` 开头视为 E.164 号码原样使用,否则自动补
117
+ * `+86` 前缀后发送(与腾讯云「境内号码默认 +86」的规则一致)。
118
+ *
119
+ * @param payload 发送参数,见 {@link XSmsSendPayload}。
120
+ * @returns 发送结果,见 {@link XSmsSendResult}。
121
+ * @throws 当提供商不受支持,或服务商未返回任何发送状态时抛出错误。
122
+ *
123
+ * @example
124
+ * await sms.send({
125
+ * provider: SmsServiceProvider.TencentCloud,
126
+ * signature: '我的签名',
127
+ * templateId: '1234567',
128
+ * templateParamValues: ['123456'],
129
+ * phoneNumber: '13800138000',
130
+ * });
131
+ */
132
+ send(payload: XSmsSendPayload): Promise<XSmsSendResult>;
133
+ }
package/lib/sms.mjs ADDED
@@ -0,0 +1,100 @@
1
+ import * as TencentCloud from "tencentcloud-sdk-nodejs-sms";
2
+ // src/sms.ts
3
+ /**
4
+ * 短信服务提供商。
5
+ */
6
+ let SmsServiceProvider = /* @__PURE__ */ function(SmsServiceProvider) {
7
+ /** 腾讯云短信。 */
8
+ SmsServiceProvider["TencentCloud"] = "TencentCloud";
9
+ return SmsServiceProvider;
10
+ }({});
11
+ /**
12
+ * 短信发送工具。
13
+ *
14
+ * 统一封装各云厂商的短信发送接口,将厂商原始返回转换为统一的
15
+ * {@link XSmsSendResult}。当前支持腾讯云短信(
16
+ * {@link SmsServiceProvider.TencentCloud}),后续可在此基础上扩展更多
17
+ * 厂商。
18
+ *
19
+ * 腾讯云实现的几个关键点:
20
+ * 1. 使用 `tencentcloud-sdk-nodejs-sms` 的 `v20210111` 版本客户端;
21
+ * 2. 客户端构造必须显式传入 `region`(否则报错缺少 `Region` 参数),
22
+ * 短信用途下该值不参与实际路由,统一使用 `ap-beijing`;
23
+ * 3. 手机号规范化:以 `+` 开头视为 E.164 号码原样使用,否则自动补
24
+ * `+86` 前缀;
25
+ * 4. 单次发送一个号码,取返回的 `SendStatusSet[0]` 作为发送结果,
26
+ * `Code === 'Ok'` 视为成功;若服务商未返回任何状态则抛出错误。
27
+ *
28
+ * @example
29
+ * const sms = new XSms({
30
+ * provider: SmsServiceProvider.TencentCloud,
31
+ * secretId: 'your-secret-id',
32
+ * secretKey: 'your-secret-key',
33
+ * appId: '1400000000',
34
+ * });
35
+ *
36
+ * const result = await sms.send({
37
+ * provider: SmsServiceProvider.TencentCloud,
38
+ * signature: '我的签名',
39
+ * templateId: '1234567',
40
+ * templateParamValues: ['123456', '5'],
41
+ * phoneNumber: '13800138000',
42
+ * });
43
+ * // result = { success: true, message: 'send success', smsId: '...' }
44
+ */
45
+ var XSms = class {
46
+ options;
47
+ constructor(options) {
48
+ this.options = options;
49
+ }
50
+ /**
51
+ * 发送短信。
52
+ *
53
+ * 根据 `payload.provider` 路由到对应厂商:
54
+ * - {@link SmsServiceProvider.TencentCloud}:创建腾讯云短信客户端并调用
55
+ * `SendSms`,将返回的首条发送状态映射为 {@link XSmsSendResult};
56
+ * - 其他未知提供商:抛出错误。
57
+ *
58
+ * 手机号规范化:以 `+` 开头视为 E.164 号码原样使用,否则自动补
59
+ * `+86` 前缀后发送(与腾讯云「境内号码默认 +86」的规则一致)。
60
+ *
61
+ * @param payload 发送参数,见 {@link XSmsSendPayload}。
62
+ * @returns 发送结果,见 {@link XSmsSendResult}。
63
+ * @throws 当提供商不受支持,或服务商未返回任何发送状态时抛出错误。
64
+ *
65
+ * @example
66
+ * await sms.send({
67
+ * provider: SmsServiceProvider.TencentCloud,
68
+ * signature: '我的签名',
69
+ * templateId: '1234567',
70
+ * templateParamValues: ['123456'],
71
+ * phoneNumber: '13800138000',
72
+ * });
73
+ */
74
+ async send(payload) {
75
+ if (payload.provider === "TencentCloud") {
76
+ const SmsClient = TencentCloud.sms.v20210111.Client;
77
+ const sendStatus = (await new SmsClient({
78
+ region: "ap-beijing",
79
+ credential: {
80
+ secretId: this.options.secretId,
81
+ secretKey: this.options.secretKey
82
+ }
83
+ }).SendSms({
84
+ SmsSdkAppId: this.options.appId,
85
+ SignName: payload.signature,
86
+ TemplateId: payload.templateId,
87
+ TemplateParamSet: payload.templateParamValues,
88
+ PhoneNumberSet: [payload.phoneNumber[0] === "+" ? payload.phoneNumber : `+86${payload.phoneNumber}`]
89
+ })).SendStatusSet?.[0];
90
+ if (!sendStatus) throw new Error("腾讯云短信发送失败:服务商未返回发送状态");
91
+ return {
92
+ success: sendStatus.Code === "Ok",
93
+ message: sendStatus.Message,
94
+ smsId: sendStatus.SerialNo
95
+ };
96
+ }
97
+ throw new Error(`no sms provider: ${payload.provider}`);
98
+ }
99
+ };
100
+ export { SmsServiceProvider, XSms };
package/lib/task.d.mts CHANGED
@@ -33,5 +33,5 @@ export declare abstract class XTask {
33
33
  * 2. 执行 `start()`,成功后以退出码 `0` 优雅退出;
34
34
  * 3. 执行失败时记录错误并以退出码 `1` 优雅退出。
35
35
  */
36
- run(): Promise<void>;
36
+ run(): Promise<never>;
37
37
  }
package/lib/task.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { asyncExitHook, gracefulExit } from "exit-hook";
1
+ import { XDispose } from "./dispose.mjs";
2
2
  import { consola } from "consola";
3
3
  // src/task.ts
4
4
  /**
@@ -31,12 +31,12 @@ var XTask = class {
31
31
  * 3. 执行失败时记录错误并以退出码 `1` 优雅退出。
32
32
  */
33
33
  run() {
34
- asyncExitHook(() => this.dispose(), { wait: 3e3 });
34
+ XDispose.exit.add(() => this.dispose());
35
35
  return this.start().then(() => {
36
- gracefulExit(0);
36
+ process.exit(0);
37
37
  }).catch((err) => {
38
38
  consola.error(err);
39
- gracefulExit(1);
39
+ process.exit(1);
40
40
  });
41
41
  }
42
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayfong/x-utils",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "license": "ISC",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -8,24 +8,41 @@
8
8
  "lib"
9
9
  ],
10
10
  "dependencies": {
11
- "@jayfong/x-request": "^3.3.0",
11
+ "@aws-sdk/client-s3": "^3.1166.0",
12
+ "@aws-sdk/s3-request-presigner": "^3.1166.0",
13
+ "@inquirer/prompts": "^8.8.0",
14
+ "@jayfong/x-request": "^3.4.0",
15
+ "@paralleldrive/cuid2": "^3.3.0",
12
16
  "@types/fs-extra": "^11.0.4",
17
+ "alipay-sdk": "^4.14.0",
13
18
  "change-case": "^5.4.4",
14
19
  "cli-table3": "^0.6.5",
15
20
  "clipboardy": "^5.3.2",
16
21
  "consola": "^3.4.2",
17
22
  "csv": "^6.6.4",
18
23
  "exit-hook": "^5.1.0",
24
+ "fast-escape-html": "^1.1.0",
19
25
  "fs-extra": "^11.4.1",
26
+ "globby": "^16.2.4",
20
27
  "html-entities": "^2.6.0",
21
- "inquirer": "^14.3.0",
28
+ "ioredis": "^6.0.0",
29
+ "jsonwebtoken": "^9.0.3",
30
+ "lru-cache": "^11.5.3",
22
31
  "mime": "^4.1.2",
32
+ "mini-svg-data-uri": "^1.4.4",
23
33
  "node-html-parser": "^9.0.7",
34
+ "nodemailer": "^10.5.0",
24
35
  "pinyin-pro": "^3.26.0",
36
+ "randomstring": "^1.3.1",
37
+ "superjson": "^2.2.6",
38
+ "svg-captcha": "^1.4.0",
39
+ "tencentcloud-sdk-nodejs-sms": "^4.0.681",
25
40
  "vtils": "^4.138.0",
26
41
  "yaml": "^2.9.1"
27
42
  },
28
43
  "devDependencies": {
44
+ "@types/jsonwebtoken": "^9.0.10",
45
+ "@types/randomstring": "^1.3.0",
29
46
  "puppeteer": "^25.17.0"
30
47
  },
31
48
  "peerDependencies": {
@@ -40,18 +57,33 @@
40
57
  "access": "public"
41
58
  },
42
59
  "exports": {
60
+ "./ai": "./lib/ai.mjs",
61
+ "./cache": "./lib/cache.mjs",
62
+ "./captcha": "./lib/captcha.mjs",
43
63
  "./case": "./lib/case.mjs",
44
64
  "./chinese": "./lib/chinese.mjs",
45
65
  "./cli": "./lib/cli.mjs",
46
66
  "./clipboard": "./lib/clipboard.mjs",
47
67
  "./cmd": "./lib/cmd.mjs",
48
68
  "./crawler": "./lib/crawler.mjs",
69
+ "./crypto": "./lib/crypto.mjs",
49
70
  "./csv": "./lib/csv.mjs",
50
71
  "./data_set": "./lib/data_set.mjs",
72
+ "./db_value": "./lib/db_value.mjs",
73
+ "./dispose": "./lib/dispose.mjs",
74
+ "./file_store": "./lib/file_store.mjs",
51
75
  "./fs": "./lib/fs.mjs",
76
+ "./html": "./lib/html.mjs",
52
77
  "./json": "./lib/json.mjs",
78
+ "./jwt": "./lib/jwt.mjs",
79
+ "./mail": "./lib/mail.mjs",
80
+ "./pay": "./lib/pay.mjs",
53
81
  "./puppeteer": "./lib/puppeteer.mjs",
82
+ "./random": "./lib/random.mjs",
83
+ "./redis": "./lib/redis.mjs",
54
84
  "./runner": "./lib/runner.mjs",
85
+ "./sensitive_words": "./lib/sensitive_words.mjs",
86
+ "./sms": "./lib/sms.mjs",
55
87
  "./task": "./lib/task.mjs",
56
88
  "./text": "./lib/text.mjs",
57
89
  "./yaml": "./lib/yaml.mjs",