t-comm
Version:
专业、稳定、纯粹的工具库
1,787 lines (1,693 loc) • 527 kB
TypeScript
/// <reference types="jest" />
/// <reference types="node" />
import type Chalk from 'chalk';
import type * as Cheerio from 'cheerio';
import type * as childProcess from 'child_process';
import { compile } from './path-to-regexp';
import type COS from 'cos-nodejs-sdk-v5';
import type * as CronParser from 'cron-parser';
import type * as CryptoType from 'crypto';
import type * as Dotenv from 'dotenv';
import type * as DotenvExpand from 'dotenv-expand';
import type { default as FormData_2 } from 'form-data';
import { fs } from 'fs';
import type * as FsExtra from 'fs-extra';
import type * as fsModule from 'fs';
import type * as httpModule from 'http';
import type * as JoseType from 'jose';
import type * as MiniprogramCI from 'miniprogram-ci';
import type * as NetType from 'net';
import type * as osModule from 'os';
import { parse } from './path-to-regexp';
import type * as path from 'path';
import { PlatformPath } from 'path';
import { ScoreInfoType } from '../types';
import { StdioOptions } from 'child_process';
import { tokensToFunction } from './path-to-regexp';
import { tokensToRegExp } from './path-to-regexp';
import type * as UtilType from 'util';
import { WorkBook } from 'xlsx';
import { WorkSheet } from 'xlsx';
import type * as XLSX from 'xlsx';
/**
* 将 ArrayBuffer 转换为字符串
* 支持多种字符编码,优先使用 TextDecoder API
* @param buffer - 要转换的 ArrayBuffer
* @param encoding - 字符编码,默认为 'utf-8'
* @returns 转换后的字符串
* @example
* ```ts
* const buffer = new ArrayBuffer(8);
* const str = ab2str(buffer); // 使用 UTF-8 编码
* const gbkStr = ab2str(buffer, 'gbk'); // 使用 GBK 编码
* ```
*/
export declare function ab2str(buffer: ArrayBuffer, encoding?: string): string;
export declare class AccountService {
private static _ins;
private static _deps;
private static _messages;
/**
* 注入运行所需依赖(应用启动期调用一次即可)
*
* 多次调用会覆盖之前的依赖,便于测试时替换。
*/
static configure(deps: AccountServiceDeps, messages?: AccountServiceMessages): void;
/** 获取当前注入的依赖(未注入时抛错) */
private static getDeps;
/** 单例 */
static get ins(): AccountService;
/** QQ 票据存储 key(默认 `${loginInfoStorageKey}__qq_ticket`) */
private static getQQTicketStorageKey;
/** 防止 wx.onShow 重复绑定 */
private _onShowBound;
/** 防止"切号过程中再次点击"重复触发 */
private _switching;
private constructor();
/** 当前是否为 QQ 账号(基于注入的 isQQAccount) */
isQQ(): boolean;
/** 「切换 QQ 账号 / 切换微信账号」按钮的动态文案 */
getSwitchButtonText(): string;
/**
* onLaunch / Game.bootstrap 阶段调用:
*
* 1) 初始化 qq-wxmini-plugin(QQ 用户访问微信小游戏的插件)
* 2) 若当前是 QQ 环境但本地仍是微信登录态,自动清空 storage 让游戏重走 QQ 登录
* 3) 主动绑定 wx.onShow:QQ 登录小程序返回时提取票据并换登录态
*/
handleAppOnLaunch(): void;
/**
* 「切换QQ账号」点击入口
*
* 根据运行环境自动分流:
* - QQ App 环境(`checkIsQQEnv() === true`)且业务传入了 `code2QQLogin`:
* 调用 `qqPluginLogin()` 直接拿 QQ code → 交 `code2QQLogin` 探后台换登录态。
* 业务后台路径参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 的 `loginMp`(`_ltype=tiploginqqproc`)。
* - 微信宿主环境(或未传 `code2QQLogin`):
* `launchQQMP` → 腾讯 QQ 小程序登录 → `wx.onShow` 提取票据 → `QueryUserInfo` 换登录态。
*/
switchToQQ(): Promise<void>;
/**
* 「切换微信账号」点击(当前账号是 QQ 时)
*
* 根据运行环境自动分流:
* - QQ App 环境(`checkIsQQEnv() === true`)且业务传入了 `code2WxLogin`:
* 调用 `wxLogin()` 拿微信 code → 交 `code2WxLogin` 探后台换登录态。
* 业务后台路径参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 的 `loginMp`(`_ltype=tiploginwxproc`)。
* - 微信宿主环境(或未传 `code2WxLogin`):
* 微信小游戏本身就是微信宿主,"切回微信"等价于:清掉 QQ 登录态(loginInfo + QQ 票据缓存)
* → 让 SDK 重新用 wx.login 走微信登录。
*/
switchToWx(): Promise<void>;
/**
* QQ App 环境下「切换 QQ 账号」的直达路径:插件 login 拿 code → 交业务探后台
*/
private _switchToQQViaPlugin;
/**
* QQ App 环境下「切换微信账号」的直达路径:wx.login 拿 code → 交业务探后台
*/
private _switchToWxViaCode;
/**
* 绑定 wx.onShow(幂等):
* - 启动时由 handleAppOnLaunch 调用一次
* - 业务页面再次需要可重复调用
*/
private _bindWxOnShow;
/** wx.onShow 回调:处理从腾讯 QQ 小程序返回的票据 */
private _onWxShow;
/**
* 用 QQ 票据换取登录态 + 重启玩家服务
*
* 优先级:
* 1. 业务侧注入了 `qqTicket2Login`(推荐,如 cocos `loginMp(_ltype=tiploginqqproc, code=qqAccessToken)`)→ 走它。
* 2. 否则 fallback 到 `queryQQLoginUserInfo`(仅适用于能统一拦截 `Logininfo` 响应头的环境)。
*/
private _consumeQQTicket;
/**
* 统一登录成功收尾:clearPlayer → bootstrapPlayer → toast → onLoginSuccess → 释放 _switching
*
* 说明:
* - `switching` 语义与 UI 层「登录中」loading 是同一件事,因此在 bootstrap 完成前
* 保持为 true;bootstrap 结果 resolve 或 reject 都释放
* - toast 文案根据 platform 选:QQ = switchQQSuccess;WX = switchWxSuccess
* - onLoginSuccess 在 toast 之后调,业务侧派事件/关登录页由回调决定
* - bootstrapPlayer 失败仍会触发 onLoginSuccess?NO:bootstrap 抛错代表玩家数据
* 没拉到,登录不算完整成功,跳过 toast 与 onLoginSuccess,让业务侧看到 catch
*/
private _finishLoginOk;
}
/**
* AccountService 运行所需的所有外部依赖
*
* 全部由业务方在启动期通过 `AccountService.configure(deps)` 一次性注入。
*/
export declare interface AccountServiceDeps {
/** QQ 互联 AppId(业务方常量) */
qqAppId: string | number;
/** 通用 post 方法(用于调 QueryUserInfo 接口) */
post: QQPostFn;
/**
* QueryUserInfo 接口域名
*
* 支持函数形式以便在测试/正式环境间动态切换。
*/
getQQLoginUserInfoHost: string | (() => string);
/**
* storage 适配器
*
* 用于读写 loginInfo / QQ 票据缓存。
* 一般直接桥接到业务方的 oops.storage 即可。
*/
storage: QQStorageLike;
/** loginInfo 在 storage 中的 key(业务方决定) */
loginInfoStorageKey: string;
/**
* QQ 票据在 storage 中的 key(可选)
*
* **不传时默认为 `${loginInfoStorageKey}__qq_ticket`**。
*
* 必须与 `loginInfoStorageKey` 区分开:同一个 key 会导致 QQ 票据覆盖业务方身份的
* loginInfo(如 cocos `loginMp` 写入的 `Logininfo` header 结果)。
*/
qqTicketStorageKey?: string;
/** 当前是否为 QQ 账号(业务方根据自家 loginInfo 结构判断) */
isQQAccount: () => boolean;
/**
* 是否「在 QQ 环境下强制 QQ 登录态」(可选)
*
* 默认 `() => true`,即启动期一旦检测到「QQ 环境 + 微信登录态」就清 storage
* 让游戏重走 QQ 登录。
*
* 若业务期望「QQ App 下也允许手动切到微信账号」,请传入 `() => false`,
* 或基于「用户是否手动切到微信账号」返回动态值。
*/
shouldForceQQ?: () => boolean;
/**
* QQ App 环境下【切换 QQ 账号】的后台探身入口(可选)
*
* 不传则 fallback 到“跳腾讯 QQ 小程序”(launchQQMP)路径。
*
* 实现参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 中的 `loginMp`:
* 拿到 code 后调业务后台的「_ltype=tiploginqqproc」路径换登录态。
*
* @example
* code2QQLogin: code => loginMp({
* url: getApiHost() + '/login',
* appid: WX_APP_ID,
* _ltype: 'tiploginqqproc',
* storage,
* onLoginInfo: (info) => updateLocalLoginInfo(info),
* }),
*
* 注:loginMp 内部会自己调 wx.login 拿 code;AccountService 这里会优先用
* `qqPluginLogin()` 拿到 QQ code 传入 ,以便业务后台拿到的是 QQ 账号的 code。
*/
code2QQLogin?: (code: string) => Promise<unknown> | unknown;
/**
* QQ App 环境下【切换微信账号】的后台探身入口(可选)
*
* 不传则 fallback 到“清 storage + bootstrapPlayer”的原路径(适用于微信宿主)。
*
* 实现参考 [src/cocos/login/login.ts](../cocos/login/login.ts) 中的 `loginMp`:
* 拿到 code 后调业务后台的「_ltype=tiploginwxproc」路径换登录态。
*/
code2WxLogin?: (code: string) => Promise<unknown> | unknown;
/**
* 【微信宿主下】拿到 QQ 票据后的后台探身入口(可选但强烈推荐)
*
* 背景:微信宿主下 QQ 登录路径是
* 1. `launchQQMP` 跳腾讯 QQ 小程序
* 2. QQ 小程序 navigateBack 后、`wx.onShow` 从 referrerInfo 中提取到 QQ 票据
* 3. 依据票据走业务后台换取登录态
*
* 本函数负责第三步。业务侧一般会用
* [src/cocos/login/login.ts](../cocos/login/login.ts) 的 `loginMp({
* _ltype: 'tiploginqqproc',
* code: ticket.qqAccessToken,
* ...
* })` 实现,`loginMp` 会负责读 `Logininfo` 响应头并写入 storage。
*
* **未传时的 fallback**:内部调 `queryQQLoginUserInfo`(快路,仅 H5 等响应头
* 能被项目统一拦截的环境可用)。
*
* @example
* qqTicket2Login: ticket => loginMp({
* url: getApiHost() + '/login',
* appid: WX_APP_ID,
* _ltype: 'tiploginqqproc',
* code: ticket.qqAccessToken,
* storage,
* storageKey: loginInfoStorageKey,
* }),
*/
qqTicket2Login?: (ticket: QQTicketInfo) => Promise<unknown> | unknown;
/**
* 显示 toast 提示
*
* 由业务方决定使用 ToastTip / wx.showToast 等何种实现。
*/
toast: (message: string) => void;
/** 清空玩家本地缓存(如 PlayerService.ins.clear) */
clearPlayer: () => void;
/**
* 强制重新启动玩家服务(如 PlayerService.ins.bootstrap({ force: true }))
*/
bootstrapPlayer: (options: {
force: boolean;
}) => Promise<unknown> | void;
/**
* 登录成功后的业务回调(可选)
*
* 触发时机:`switchToQQ` / `switchToWx` 的所有成功路径(含「已是当前账号」的幂等分支)
* 都会在 `bootstrapPlayer` 拿到玩家数据后调用本函数。
*
* 典型用途:业务方在此派发全局事件、关闭登录页、上报埋点等。
*
* 语义:
* - `platform`:当前登录态平台
* - `reason`:本次触发原因,业务方可用来区分「首次登录 / 切号 / 幂等重登」
*
* @example
* onLoginSuccess: ({ platform, reason }) => {
* oops.message.dispatch(EventName.AuthAccepted, { platform, reason });
* oops.gui.close(UIID.Auth);
* }
*/
onLoginSuccess?: (info: {
platform: 'qq' | 'wx';
reason: LoginSuccessReason;
}) => void;
}
/**
* AccountService 文案配置(可选覆盖默认中文文案)
*/
export declare interface AccountServiceMessages {
alreadyQQ?: string;
alreadyWx?: string;
switchQQFail?: string;
switchWxSuccess?: string;
switchWxFail?: string;
switchQQRetry?: string;
switchQQSuccess?: string;
}
export declare const ACT_ID_MAP: {
GP: string;
};
/**
* 为 Vue 组件添加 emits 属性
* @param {string} filePath 组件地址
* @param {string} [fileContent] 组件内容
* @returns {string} 新的组件内容
*
* @example
* ```ts
* addNameForComponent('xxx.vue');
* ```
*/
export declare function addEmitsForComponent(filePath: string, fileContent?: string): string | undefined;
/**
* 添加 MSDK 原生回调监听器
* 用于监听原生层发送给 Web 层的消息
* @param callback - 回调函数,接收原生层传递的数据
* @example
* ```ts
* addMsdkNativeCallbackListener((data) => {
* console.log('收到原生消息:', data);
* });
* ```
*/
export declare function addMsdkNativeCallbackListener(callback: Function): void;
/**
* 为 Vue 组件添加、修正 name 属性
* @param {string} filePath 组件地址
* @param {string} componentName 组件名称
* @returns {string} 新的组件内容
*
* @example
* ```ts
* addNameForComponent('xxx.vue', 'PressUploader');
* ```
*/
export declare function addNameForComponent(filePath: string, componentName: string): any;
/**
* add num and avoid float number
* @param {number} num1 第1个数字
* @param {number} num2 第2个数字
* @returns {number} 结果
* @example
* ```ts
* addNumber(0.1, 0.2); // 0.3
* ```
*/
export declare function addNumber(num1: number, num2: number): number;
/**
* 添加或更新配置
*
* @param {object} config 配置信息
* @param {object} config.keyValue 配置对象
* @param {string} config.keyValue.key 配置的key
* @param {string} config.keyValue.value 配置的value
* @param {number} config.valueType 配置类型,1: NUMBER, 2: STRING, 3: TEXT, 4: JSON, 5: XML, 18: 日期, 20: yaml
* @param {object} config.secretInfo 密钥信息
* @param {string} config.secretInfo.appId 项目Id
* @param {string} config.secretInfo.userId 用户Id
* @param {string} config.secretInfo.secretKey 密钥
* @param {string} config.secretInfo.envName 配置环境
* @param {string} config.secretInfo.groupName 配置组
* @returns {Promise<object>} 请求Promise
*
* @example
* addOrUpdateRainbowKV({
* keyValue: {
* key: 'theKey',
* value: 'theValue',
* },
* valueType: 2,
* secretInfo: {
* appId: 'xxx',
* userId: 'xxx',
* secretKey: 'xxx',
* envName: 'prod',
* groupName: 'xxx',
* }
* }).then(() => {
*
* })
*/
export declare function addOrUpdateRainbowKV({ keyValue, valueType, secretInfo, }: ModifyConfigParam): Promise<object>;
/**
* 增加配置
*
* @param {object} config 配置信息
* @param {object} config.keyValue 配置对象
* @param {string} config.keyValue.key 配置的key
* @param {string} config.keyValue.value 配置的value
* @param {number} config.valueType 配置类型,1: NUMBER, 2: STRING, 3: TEXT, 4: JSON, 5: XML, 18: 日期, 20: yaml
* @param {object} config.secretInfo 密钥信息
* @param {string} config.secretInfo.appId 项目Id
* @param {string} config.secretInfo.userId 用户Id
* @param {string} config.secretInfo.secretKey 密钥
* @param {string} config.secretInfo.envName 配置环境
* @param {string} config.secretInfo.groupName 配置组
* @returns {Promise<object>} 请求Promise
*
* @example
* addRainbowKV({
* keyValue: {
* key: 'theKey',
* value: 'theValue',
* },
* valueType: 2,
* secretInfo: {
* appId: 'xxx',
* userId: 'xxx',
* secretKey: 'xxx',
* envName: 'prod',
* groupName: 'xxx',
* }
* }).then(() => {
*
* })
*/
export declare function addRainbowKV({ keyValue, valueType, secretInfo, }: ModifyConfigParam): Promise<object>;
/**
* 为图片增加文字
*
* @param {Object} config 配置
* @param {number} config.width 宽度
* @param {number} config.height 高度
* @param {Array<string>} config.textList 文字列表,支持多行
* @param {string} config.imgPath 图片路径
* @returns {string} canvas.toDataURL生成的base64图片
*
* @example
*
* ```ts
* const imgUrl = addTextForImg({
* width: 300,
* height: 300,
* textList: ['第一行', '第二行'],
* imgPath: './test.png',
* })
* ```
*/
export declare function addTextForImg({ width, height, textList, imgPath, }: {
width: number;
height: number;
textList: Array<string>;
imgPath: string;
}): Promise<string>;
export declare function aegisReportErrorV2(mAegisV2: any, options: ReportOptions): void;
export declare function aegisReportEventV2(mAegisV2: any, options: EventOptions): void;
export declare function aegisReportInfoV2(mAegisV2: any, options: ReportOptions, method?: string): void;
export declare class AegisReportInPixui {
static options: InitAegisOptions;
static aegis: any;
static init(options: InitAegisOptions): Promise<any>;
static report(info: Record<string, any>): Promise<void>;
static info(info: Record<string, any>): Promise<void>;
}
export declare class AegisReportInterceptor implements IResponseInterceptor {
private options;
constructor(options: IAegisReportOptions);
interceptor(param: IResponseInterceptorParam): [boolean, IResponseInterceptorParam];
}
export declare function aegisReportV2(mAegisV2: any, options: ReportOptions): void;
/**
* Aegis 上报专用:请求打点拦截器
*
* - 业务:在「请求拦截器链」最前面加本拦截器,把 `Date.now()` 注入到 `param.__aegisStartTime`
* - 配合 `AegisReportInterceptor`(响应拦截器)使用,能算出精确耗时 duration
*
* 设计原因:
* - `NetworkEngine` 用 reduce 串成 Promise 链,**响应拦截器拿不到「请求发起时刻」**
* - 只有显式在请求链里打点,响应拦截器才能 `Date.now() - startTime` 算耗时
* - 字段名带双下划线前缀(`__aegisStartTime`),与 `IRawRequest.__cocosOriginUrl` 一致风格,
* 不会和业务 reqData / header 冲突
*
* 注意:
* - 该拦截器不应该被排在 `CocosHostInterceptor` 之前:domain 拼接后才算真正发起,
* 但 Aegis 关心的是「业务调用时点」,所以其实放在最前更准确;放哪都行,只要 startTime 字段在
* 请求链中能透传到响应拦截器
* - 本拦截器是 **幂等** 的:若上游拦截器已注入过 startTime,就保留上游值不覆盖
* (防重入 / 装饰器场景下开始时间被推迟)
*/
export declare class AegisStartTimeInterceptor implements IRequestInterceptor {
interceptor(param: IRawRequest): [boolean, IRawRequest];
}
/**
* 分析首页Bundle信息
*
* @export
* @param config 配置
* @param {string} config.domain 域名
* @param {string} config.buildPath 打包路径
* @returns {*}
*
* @example
* ```ts
* analyzeIndexBundle({
* domain: '',
* buildPath: '',
* })
* ```
*/
export declare function analyzeIndexBundle({ domain, buildPath }: {
domain: string;
buildPath: string;
}): ({
file: string;
size: number;
time: number;
} | undefined)[];
declare interface AnalyzeItem {
root: string;
simpleRoot: string;
project: string;
git: string;
analyzeDir?: string;
needAnalyzeSubDir?: string[];
}
/**
* 增加需要签名的 CGI 接口
* @param cgiList 需要签名的接口地址列表
*/
export declare function appendSignCGI(cgiList?: Array<string>): void;
/**
* 在源代码中查找 search 并替换为 replace,按 3 级策略匹配
*
* 策略:
* 1. 精确匹配:原样查找 search
* 2. 行级匹配:去除每行首尾空白后逐行对比(应对 AI 输出与源文件缩进不一致)
* 3. 子串匹配:查找首行 trim 后的子串位置,从该位置开始截取与 search 等长的范围替换
*
* @param source 源代码字符串
* @param search 要查找的字符串(可能被 AI 带了行号前缀,函数会自动清理)
* @param replace 替换为的新字符串(同上,自动清理行号前缀)
* @returns 替换后的字符串;查找失败返回 null
*/
export declare function applySearchReplace(fileContent: string, rawSearch: string, rawReplace: string): SearchReplaceResult;
export declare function ApprovalRainbowReleaseTask({ secretInfo, taskId, versionName, status, rejectReason, }: {
secretInfo: ISecretInfo_3;
taskId: string | number;
versionName: string;
status?: number;
rejectReason?: string;
}): Promise<object>;
declare const AREA_MAP: {
readonly MAINLAND: "mainland";
readonly OVERSEAS: "overseas";
};
declare const AREA_MAP_WITH_GLOBAL: {
readonly MAINLAND: "mainland";
readonly OVERSEAS: "overseas";
readonly GLOBAL: "global";
};
/** ArrayBuffer 转十六进制大写字符串 */
export declare function arrayBuffer2Hex(buf: ArrayBuffer | undefined | null): string;
export declare function asyncExportTencentDoc({ accessToken, clientId, openId, fileId, exportType, }: ISecretInfo_2 & {
fileId: string;
exportType: number;
}): Promise<any>;
/**
* 基本请求
* @private
* @param {object} config - 配置信息
* @returns {Promise} 请求Promise
* @example
* ```ts
* baseRequestRainbow({
* url: '/api/some-path',
* data: { foo: 1 },
* secretInfo: {
* appId: 'xxx',
* userId: 'yyy',
* secretKey: 'zzz',
* envName: 'Default',
* groupName: 'group',
* },
* }).then(res => console.log(res));
* ```
*/
export declare function baseRequestRainbow({ url, data: reqData, secretInfo, }: ReqParam): Promise<object>;
/**
* 批量重命名文件和文件夹(同步版本)
* @param {string} dirPath - 要处理的目录路径
* @param {Object} renameConfig - 重命名配置对象 { 旧名称: 新名称 }
* @param {boolean} [recursive=true] - 是否递归处理子目录
* @example
* ```ts
* batchRenameDirEntries('src/components', {
* 'press-icon-plus': 'press-icon',
* 'press-dialog-plus': 'press-dialog',
* });
* // 递归将 src/components 下名为 press-icon-plus、press-dialog-plus 的文件/文件夹重命名
*
* // 只重命名当前层、不递归
* batchRenameDirEntries('src/components', { 'a': 'b' }, false);
* ```
*/
export declare function batchRenameDirEntries(dirPath: string, renameConfig: Record<string, string>, recursive?: boolean): void;
/**
* 批量替换文件内容(性能优化版)
* 将同 dirList 的规则合并,每个文件只读写一次
* @param {Array<{ list: Array, dirList: string|string[] }>} replaceList - 替换规则列表
* @example
* ```ts
* batchReplaceFileContent([
* {
* list: [
* ['<PressIconPlus', '<PressIcon'],
* [/press-icon-plus/g, 'press-icon'],
* ],
* dirList: ['src/**\/*.vue'],
* },
* ]);
* // 同 dirList 的规则会被合并,避免重复读写
* ```
*/
export declare function batchReplaceFileContent(replaceList: Array<{
list: Array<[string | RegExp, string]>;
dirList: string | string[];
}>): void;
/**
* 批量发送企业微信机器人base64图片
* - chatId 支持字符串或字符串数组,传 `'ALL'` 或 `['ALL']` 会发送给所有人
* @param {object} config 配置信息
* @param {string} config.img base64图片
* @param {string | Array<string>} config.chatId 会话Id,支持单个字符串、字符串数组、`'ALL'` 或 `['ALL']`(发送给所有人)
* @param {string} config.webhookUrl webhook地址
* @returns {Promise<object>} 请求Promise
* @example
*
* // 发送给单个会话
* batchSendWxRobotBase64Img({
* img: 'xxx',
* chatId: 'xxx',
* webhookUrl: 'xxx',
* });
*
* // 发送给多个会话
* batchSendWxRobotBase64Img({
* img: 'xxx',
* chatId: ['chatId1', 'chatId2'],
* webhookUrl: 'xxx',
* });
*
* // 发送给所有人
* batchSendWxRobotBase64Img({
* img: 'xxx',
* chatId: 'ALL', // 或 ['ALL']
* webhookUrl: 'xxx',
* });
*
*/
export declare function batchSendWxRobotBase64Img({ img, chatId, webhookUrl, }: {
img: string;
} & ISendReq): Promise<any>;
/**
* 批量发送企业微信机器人Markdown消息(最常用)
* - chatId 支持字符串或字符串数组,传 `'ALL'` 或 `['ALL']` 会发送给所有人
* - 支持 Markdown V2 格式,通过 isV2 参数控制
* @param {object} config 配置信息
* @param {string} config.content Markdown消息内容
* @param {Array<object>} [config.attachments] 附加内容
* @param {string | Array<string>} config.chatId 会话Id,支持单个字符串、字符串数组、`'ALL'` 或 `['ALL']`(发送给所有人)
* @param {string} config.webhookUrl webhook地址
* @param {boolean} [config.isV2=false] 是否使用 Markdown V2 格式
* @returns {Promise<object>} 请求Promise
* @example
*
* // 发送给单个会话
* batchSendWxRobotMarkdown({
* content: '## 标题\n内容',
* chatId: 'xxx',
* webhookUrl: 'xxx',
* });
*
* // 发送给多个会话
* batchSendWxRobotMarkdown({
* content: '## 标题\n内容',
* chatId: ['chatId1', 'chatId2'],
* webhookUrl: 'xxx',
* });
*
* // 发送给所有人
* batchSendWxRobotMarkdown({
* content: '## 标题\n内容',
* chatId: 'ALL', // 或 ['ALL']
* webhookUrl: 'xxx',
* });
*
* // 使用 Markdown V2 格式
* batchSendWxRobotMarkdown({
* content: '## 标题\n内容',
* chatId: 'xxx',
* webhookUrl: 'xxx',
* isV2: true,
* });
*
*/
export declare function batchSendWxRobotMarkdown({ content, attachments, chatId, webhookUrl, isV2, }: {
content: string;
attachments?: Array<object>;
isV2?: boolean;
} & ISendReq): Promise<any>;
/**
* 批量发送企业微信机器人文本消息
* - chatId 支持字符串或字符串数组,传 `'ALL'` 或 `['ALL']` 会发送给所有人
* @param {object} config 配置信息
* @param {string} config.content 消息内容
* @param {string | Array<string>} config.alias 被@的用户别名,支持单个字符串或字符串数组
* @param {string | Array<string>} config.chatId 会话Id,支持单个字符串、字符串数组、`'ALL'` 或 `['ALL']`(发送给所有人)
* @param {string} config.webhookUrl webhook地址
* @returns {Promise<object>} 请求Promise
* @example
*
* // 发送给单个会话
* batchSendWxRobotMsg({
* content: '消息内容',
* alias: 'user1',
* chatId: 'xxx',
* webhookUrl: 'xxx',
* });
*
* // 发送给多个会话并@多个用户
* batchSendWxRobotMsg({
* content: '消息内容',
* alias: ['user1', 'user2'],
* chatId: ['chatId1', 'chatId2'],
* webhookUrl: 'xxx',
* });
*
* // 发送给所有人
* batchSendWxRobotMsg({
* content: '消息内容',
* alias: 'user1',
* chatId: 'ALL', // 或 ['ALL']
* webhookUrl: 'xxx',
* });
*
*/
export declare function batchSendWxRobotMsg({ content, alias, chatId, webhookUrl, }: {
content: string;
alias: string | Array<string>;
} & ISendReq): Promise<any>;
/**
* 批量解除腾讯云 COS 图片封禁
* - 对多张被封禁的图片并行执行解封操作
* - 底层通过 Promise.allSettled 并发调用 unfreezeCosImage
*
* @param {object} config 配置信息
* @param {string} config.secretId 腾讯云 SecretId
* @param {string} config.secretKey 腾讯云 SecretKey
* @param {string} config.bucket COS 存储桶名称(如 'my-bucket-1250000000')
* @param {string} config.region COS 存储桶所在区域(如 'ap-guangzhou')
* @param {Array<string>} config.keys 被封禁的对象键列表
* @returns {Promise<Array<object>>} 每张图片的解封结果数组,包含 key、success、data/error 字段
* @example
* ```ts
* const results = await batchUnfreezeCosImages({
* secretId: 'your-secret-id',
* secretKey: 'your-secret-key',
* bucket: 'my-bucket-1250000000',
* region: 'ap-guangzhou',
* keys: ['images/photo1.jpg', 'images/photo2.jpg'],
* });
*
* results.forEach(r => {
* if (r.success) {
* console.log(`${r.key} 解封成功`);
* } else {
* console.error(`${r.key} 解封失败:`, r.error);
* }
* });
* ```
*/
export declare function batchUnfreezeCosImages({ secretId, secretKey, bucket, region, keys, }: {
secretId: string;
secretKey: string;
bucket: string;
region: string;
keys: Array<string>;
}): Promise<Array<{
key: string;
success: boolean;
data?: any;
error?: any;
}>>;
export declare function batchUpdateTencentSheetV3({ accessToken, clientId, openId, bookId, requests, }: ISecretInfo_2 & {
bookId: string;
requests: Record<string, any>;
}): Promise<any>;
export declare class BluetoothBump {
/** 我方临时 ID(每次启动随机生成,写到广播特征值里) */
myTempId: string;
/** 当前运行平台 */
platform: string;
/** 是否跳过广播(peripheral 初始化失败时降级为只扫描) */
skipAdvertising: boolean;
/** 是否已经成功触发过碰一碰 */
hasBumped: boolean;
private readonly serviceUuid;
private readonly characteristicUuid;
private readonly rssiThreshold;
private readonly cooldownMs;
private readonly lingerBeforeStopMs;
private readonly pollIntervalMs;
private readonly privacyContent;
/** 匹配模式:simple(单向发现即触发)/ mutual(双向互认) */
private readonly mode;
private readonly peerTtlMs;
private readonly advertiseUpdateThrottleMs;
private readonly onBump?;
private readonly onError?;
private readonly onLog?;
private readonly onDeviceFoundCallback?;
private readonly adapter;
private readonly now;
private readonly setTimeoutFn;
private readonly clearTimeoutFn;
private readonly setIntervalFn;
private readonly clearIntervalFn;
private peripheralServer;
private isDiscovering;
private bumpedMap;
private gracefulStopTimer;
private pollTimer;
/** mutual 模式:候选 peer 池,key = peerTempId */
private seenPeers;
/** mutual 模式:当前广播中"点名"的 peerId(为空表示还没看到任何人) */
private currentAdvertisedPeer;
/** mutual 模式:上次更新广播的时间戳(用于节流) */
private lastAdvertiseUpdateAt;
/** mutual 模式:updateAdvertisedValue 连续失败后降级为 simple(仅当前会话内) */
private advertiseUpdateBroken;
private deviceFoundCb;
private adapterStateChangeCb;
constructor(opts?: BluetoothBumpOptions, deps?: BluetoothBumpDeps);
/** 启动碰一碰(必须在用户点击事件回调中调用) */
start(): Promise<void>;
/** 停止:解绑所有监听 + 停止扫描 + 停止广播 + 关闭适配器 */
stop(): void;
/** 启动失败的统一处理(业务方可通过 onError 拿到原始错误) */
private handleStartError;
/** 监听蓝牙适配器状态变化:用户打开蓝牙后自动重试 */
private watchAdapterStateForRetry;
/** 监听设备发现 + 启动 iOS 兜底轮询 */
private bindDeviceFound;
/** 调试用:定期 getBluetoothDevices,避免 iOS 上 allowDuplicatesKey 不上报重复的问题 */
private startPolling;
/** 处理扫到的单个设备 */
private handleDevice;
/**
* mutual 模式(v2,方案2 实现):协议双向 simple
*
* 设计取舍:
* v1 mutual 用"动态更新广播 payload 的 seenPeerId"做双向互认,但 iOS 上
* wx 的 updateBLEAdvertising → stop+addService+start 链路在真机上极不可靠:
* - deviceName 不会真正刷新(CoreBluetooth 缓存)
* - addService 重复注册会失败、走兜底分支后广播就停了
* - 高频 update 还会让 iOS 端事件循环卡死
* 导致弱信号端永远收不到"被点名"广播 → 不弹窗。
*
* v2 直接退一步:
* - 不再动态更新广播 payload;广播一次启动后保持初始 payload 不变
* - 触发条件:扫到的对端必须能解析出 BUMP_ 协议 payload(协议匹配) + 本机 RSSI ≥ 阈值
* - "双向"靠两端都跑这套规则、各自独立达阈值时触发;阈值收紧(≈10cm)就足够防误触
* - iOS↔Android RSSI 不对称的代价:可能两端弹窗时差最大到秒级(取决于谁先稳定过阈值)
* 但相比 v1 的"一端永远不弹",这是更可接受的退化
*/
private handleDeviceMutual;
/** mutual 模式:更新自己广播里的 seenPeerId(带节流 + 失败降级) */
private updateAdvertisedPeer;
/** 触发碰一碰成功:通知业务方 + 优雅停止(保持广播一段时间) */
private triggerBump;
/** 仅停止扫描(保留广播) */
private stopDiscoveryOnly;
private log;
}
/** 主类的额外依赖(用于测试时注入假时钟、假 timer、假 adapter) */
export declare interface BluetoothBumpDeps {
adapter?: WxBluetoothAdapter;
now?: () => number;
setTimeoutFn?: (fn: () => void, ms: number) => any;
clearTimeoutFn?: (handle: any) => void;
setIntervalFn?: (fn: () => void, ms: number) => any;
clearIntervalFn?: (handle: any) => void;
}
/** 启动失败时通过 onError 抛出的错误信息 */
export declare interface BluetoothBumpError {
errCode?: number;
errMsg?: string;
raw?: any;
}
/**
* 匹配模式:
* - 'simple' :默认行为。扫到对方且 RSSI 达标即触发(单向发现,简单快速)。
* - 'mutual' :前端自撮合。双向互认后才触发:
* 我广播里带"我看到的对方 ID",对方广播里也带"它看到的对方 ID";
* 当双方广播里携带的 peerId 都指向对方时,才算配对成功。
*/
export declare type BluetoothBumpMode = 'simple' | 'mutual';
/** BluetoothBump 构造选项 */
export declare interface BluetoothBumpOptions {
/** 服务 UUID,两端必须一致 */
serviceUuid?: string;
/** 特征 UUID(仅外围模式使用) */
characteristicUuid?: string;
/** RSSI 阈值,超过此值视为"碰到了" */
rssiThreshold?: number;
/** 同一设备的去重冷却时间 */
cooldownMs?: number;
/** 成功触发后保持广播的时长(让对方也能扫到自己) */
lingerBeforeStopMs?: number;
/** iOS 兜底轮询间隔 */
pollIntervalMs?: number;
/** 隐私协议弹窗内容(不传用默认文案) */
privacyContent?: string;
/**
* 匹配模式,默认 'simple'(保持向后兼容)。
* 选 'mutual' 开启前端自撮合(双向互认后才触发 onBump)。
*/
mode?: BluetoothBumpMode;
/** mutual 模式:候选 peer 信息的有效期(毫秒),过期则从候选池移除 */
peerTtlMs?: number;
/** mutual 模式:更新自己广播里 seenPeerId 的最小间隔(毫秒),防抖 */
advertiseUpdateThrottleMs?: number;
/**
* 业务回调:碰到对方时触发
* - peerDeviceId:扫描层的设备地址(iOS 是本机视角的 UUID,Android 是 MAC)。
* ⚠️ 这个值是"扫描者本地"视角的,A 拿到的对方 deviceId 与 B 自己的 deviceId 不一定相同,
* 不能用它跟对方的 myTempId 直接对比。
* - peerTempId:对方写在广播 payload 里的 myTempId(6 位字符串)。
* 这个值是"对方应用层产生的",A 看到的 peerTempId 与 B 的 myTempId **一定一致**,
* 适合两台手机做配对核对("我方 ID" vs "对方 ID")。
* 若对方广播解析失败则为空字符串。
*/
onBump?: (peerDeviceId: string, rssi: number, dev: BluetoothDeviceInfo, peerTempId: string) => void;
/** 业务回调:启动失败时触发 */
onError?: (err: BluetoothBumpError) => void;
/** 业务回调:日志/状态变化(用于打日志或 UI 更新) */
onLog?: (msg: string, data?: any) => void;
/** 业务回调:每次扫到设备(不论是否碰到)时触发,用于调试/UI 展示 */
onDeviceFound?: (dev: BluetoothDeviceInfo) => void;
}
/** 广播载荷(mutual 模式):固定长度 13 字节,myTempId|seenPeerId */
export declare interface BluetoothBumpPayload {
/** 发送端自己的临时 ID(6 位大写) */
myTempId: string;
/** 发送端当前"看到的对方 ID",未看到时为 '------' */
seenPeerId: string;
}
/**
* 蓝牙"碰一碰"对外类型定义
*/
/** 扫描到的蓝牙设备(仅声明用得到的字段,避免污染) */
export declare interface BluetoothDeviceInfo {
deviceId: string;
RSSI: number;
name?: string;
localName?: string;
advertisData?: ArrayBuffer;
advertisServiceUUIDs?: string[];
serviceData?: Record<string, ArrayBuffer>;
[key: string]: any;
}
export declare function build({ files, root, bundleName, outputDir, }: {
files?: Array<string>;
root?: string;
bundleName: string;
outputDir?: string;
}): Promise<unknown>;
/**
* 打包并上传到服务器
* @param {object} options 配置
* @param {string} options.hostName 服务器名称
* @param {string} options.hostPwd 服务器密码
* @param {string} [options.root] 项目根目录
* @param {string} [options.bundleName] 打包文件名称
* @param {boolean} [options.wrapHostPwd=true] 是否用双引号包裹密码,默认为 true
* @param {string} [options.outputDir='dist'] 打包输出目录,默认为 'dist'
* @example
*
* await buildAndUpload({
* hostName: '9.9.9.9',
* hostPwd: 'xxxx',
* bundleName: 'cron-job-svr',
* });
*
*/
export declare function buildAndUpload({ root, bundleName, hostName, hostPwd, hostTargetDir, wrapHostPwd, outputDir, }: {
root?: string;
bundleName?: string;
hostName: string;
hostPwd: string;
hostTargetDir: string;
wrapHostPwd?: boolean;
outputDir?: string;
}): Promise<any>;
/**
* 构建审核通知内容数组
*
* 统一拼接审核通知消息,各流水线只需传入标题和差异化字段即可。
*
* @example H5 发布
* ```ts
* const content = buildAuditContent({
* title: '【H5发布审核】',
* projectName: 'pmd-mobile/match/gp',
* creator: 'novlan1',
* auditor: 'junshao',
* buildUrl: 'https://devops.woa.com/xxx',
* extraLines: [
* `子工程:\`gp-hor\``,
* `灰度比例:50%`,
* ],
* });
* ```
*
* @example 回滚审核
* ```ts
* const content = buildAuditContent({
* title: '【`回滚`审核】',
* projectName: 'pmd-mobile/match/gp',
* creator: 'novlan1',
* auditor: 'junshao',
* buildUrl: 'https://devops.woa.com/xxx',
* extraLines: [`子工程:\`gp-hor\``],
* });
* ```
*/
export declare function buildAuditContent(options: IBuildAuditContentOptions): string[];
/**
* 广播设备名前缀(iOS Peripheral 唯一能下发的自定义字段就是 localName)
* 完整格式: `BUMP_<myTempId>` 或 `BUMP_<myTempId>_<seenPeerId>`
*
* 原理:iOS CoreBluetooth 不允许 Peripheral 在广播包里塞 ServiceData,
* 只允许 localName + serviceUUIDs。所以 iOS↔iOS 必须靠 localName 传 myTempId。
* Android 同样支持 localName,所以这套方案跨平台一致。
*/
export declare const BUMP_NAME_PREFIX = "BUMP_";
/**
* API 适配器 —— 由业务方实现,注入到 BumpService
*
* 这是唯一与后端交互的接口,只要后端提供这 4 个接口,
* 任何业务都可以复用 BumpService。
*/
export declare interface BumpApiAdapter {
/** 发起碰一碰,获取 tempId */
bumpStart(req: BumpStartReq): Promise<BumpStartRsp>;
/** 上报撮合 */
bumpReport(req: BumpReportReq): Promise<BumpReportRsp>;
/** 领取奖励 */
bumpReward(req: BumpRewardReq): Promise<BumpRewardRsp>;
/** 通过 tempId 获取对端信息 */
getPeerByTempId(req: GetPeerByTempIdReq): Promise<GetPeerByTempIdRsp>;
}
/** BumpService 派发的事件名 */
export declare const BumpEvent: {
/** 阶段变化 */
readonly PhaseChanged: "bump:phase-changed";
/** 发现附近设备 */
readonly PeerFound: "bump:peer-found";
/** 设备离开 */
readonly PeerLost: "bump:peer-lost";
/** 碰蛋成功(含奖励) */
readonly Matched: "bump:matched";
/** 软失败(可继续选下一只) */
readonly SoftFail: "bump:soft-fail";
/** 硬失败(会话中断) */
readonly Failed: "bump:failed";
/** tempId 已刷新 */
readonly TempIdRefreshed: "bump:tempid-refreshed";
};
/** 事件总线适配器 */
export declare interface BumpEventBus {
emit(event: string, data?: any): void;
on(event: string, handler: (...args: any[]) => void): void;
off(event: string, handler: (...args: any[]) => void): void;
}
/** 日志适配器 */
export declare interface BumpLogger {
info(msg: string, ...args: any[]): void;
warn(msg: string, ...args: any[]): void;
error(msg: string, ...args: any[]): void;
}
/** 后台撮合状态(BumpReport 返回) */
export declare enum BumpMatchStatus {
Unknown = 0,
Waiting = 1,
Matched = 2,
Loser = 3
}
/** bumpPeer 的返回结果 */
export declare interface BumpPeerResult {
success: boolean;
/** 匹配成功时的奖励信息 */
reward?: BumpRewardRsp;
/** 匹配 ID */
matchId?: string;
/** 失败时的错误文案 */
error?: string;
/** 后台错误码 */
ret?: number;
/** 正在等待对方上报 */
waiting?: boolean;
}
/**
* bump-service —— 碰一碰业务编排层类型定义
*
* 与 bluetooth-bump(纯蓝牙层)配合使用:
* bluetooth-bump 负责:蓝牙广播/扫描/RSSI 判定/去重
* bump-service 负责:状态机/API 调用/缓存/重试/事件派发
*/
/** 碰一碰阶段 */
export declare enum BumpPhase {
/** 空闲 */
Idle = "idle",
/** 正在启动(调 BumpStart 拿 tempId) */
Starting = "starting",
/** 扫描中(蓝牙已启动,等待用户碰) */
Scanning = "scanning",
/** 上报中(正在调 BumpReport) */
Reporting = "reporting",
/** 已匹配成功 */
Matched = "matched",
/** 失败(蓝牙/网络硬错误) */
Failed = "failed"
}
/** BumpReport 请求参数 */
export declare interface BumpReportReq {
act_id?: string;
my_temp_id: string;
peer_temp_id: string;
rssi?: number;
ts?: number;
[key: string]: any;
}
/** BumpReport 响应 */
export declare interface BumpReportRsp {
status?: number;
match_id?: string;
matchId?: string;
err_msg?: string;
[key: string]: any;
}
/** BumpReward 请求参数 */
export declare interface BumpRewardReq {
act_id?: string;
match_id: string;
[key: string]: any;
}
/** BumpReward 响应 */
export declare interface BumpRewardRsp {
egg_result_list?: any[];
reward_type?: string;
reward_amount?: number;
[key: string]: any;
}
export declare class BumpService {
/** 当前阶段 */
phase: BumpPhase;
/** 我方 tempId(BumpStart 后获得) */
myTempId: string;
/** tempId 过期时间戳 */
expireAt: number;
/** 活动 ID */
actId: string;
/** 当前匹配 ID(Report 成功后获得) */
matchId: string;
/** 当前正在碰的 peerTempId */
currentPeerTempId: string;
/** 附近设备缓存 */
nearbyPeers: Map<string, NearbyPeer>;
private readonly api;
private readonly eventBus;
private readonly storage;
private readonly logger;
private readonly tempIdRefreshMs;
private readonly peerCacheTtlMs;
private refreshTimer;
private pruneTimer;
/** 正在进行中的 bumpPeer 调用(防重复点击) */
private bumpingSet;
constructor(options: BumpServiceOptions);
/**
* 启动碰一碰会话
* 1. 调用 BumpStart 拿 tempId
* 2. 启动 tempId 自动刷新定时器
* 3. 加载持久化缓存
*
* 返回 tempId 供蓝牙层使用
*/
start(options?: BumpStartOptions): Promise<string>;
/**
* 停止碰一碰会话
*/
stop(): void;
/**
* 碰一碰核心流程:上报 + 领奖
* @param peerTempId 对端的 tempId
* @param rssi 信号强度(可选)
*/
bumpPeer(peerTempId: string, rssi?: number): Promise<BumpPeerResult>;
/**
* 注册附近设备(蓝牙层 onDeviceFound 时调用)
* @param peerTempId 对端 tempId(从 BLE payload 解析)
* @param deviceId BLE 设备 ID
* @param rssi 信号强度
*/
registerNearbyPeer(peerTempId: string, deviceId: string, rssi: number): void;
/**
* 获取对端用户信息(用于 UI 展示昵称/头像)
*/
fetchPeerInfo(peerTempId: string): Promise<GetPeerByTempIdRsp | null>;
/**
* 获取附近设备缓存快照(只返回 TTL 内的)
*/
getNearbyPeerCache(): NearbyPeer[];
/**
* 手动刷新 tempId(也可由定时器自动触发)
*/
refreshTempId(): Promise<string>;
private setPhase;
private fail;
private softFail;
private startRefreshTimer;
private clearRefreshTimer;
private startPruneTimer;
private clearPruneTimer;
private pruneExpiredPeers;
private saveNearbyPeerCache;
private loadNearbyPeerCache;
}
/** BumpService 配置选项 */
export declare interface BumpServiceOptions {
/** API 调用适配器(必传,由业务方注入具体的 HTTP 实现) */
api: BumpApiAdapter;
/** 事件总线适配器(可选,用于派发/订阅事件) */
eventBus?: BumpEventBus;
/** 本地存储适配器(可选,用于缓存持久化) */
storage?: BumpStorage;
/** 日志适配器(可选) */
logger?: BumpLogger;
/** 活动 ID(可选,每次 start 时也可传入) */
actId?: string;
/** tempId 刷新间隔(毫秒),默认 150000(2.5 分钟,3 分钟过期前刷新) */
tempIdRefreshMs?: number;
/** 附近设备缓存 TTL(毫秒),默认 30 分钟 */
peerCacheTtlMs?: number;
}
/** BumpService 启动选项 */
export declare interface BumpStartOptions {
/** 活动 ID(覆盖构造时的 actId) */
actId?: string;
/** 额外传给 BumpStart 接口的字段 */
extra?: Record<string, any>;
}
/** BumpStart 请求参数 */
export declare interface BumpStartReq {
act_id?: string;
[key: string]: any;
}
/** BumpStart 响应 */
export declare interface BumpStartRsp {
temp_id?: string;
tempId?: string;
expire_at?: number;
expireAt?: number;
[key: string]: any;
}
/** 本地存储适配器 */
export declare interface BumpStorage {
get(key: string): string | null;
set(key: string, value: string): void;
remove(key: string): void;
}
/**
* 记忆函数:缓存函数的运算结果
* @param {Function} fn 输入函数
* @returns {any} 函数计算结果
*
* @example
* function test(a) {
* return a + 2
* }
*
* const cachedTest = cached(test)
*
* cachedTest(1)
*
* // => 3
*
* cachedTest(1)
*
* // => 3
*/
export declare function cached<T extends any, R>(fn: (arg: T) => R): (arg: T) => R;
/**
* 添加游戏内浏览器jssdk
* @example
* ```ts
* callJsBrowserAdapter();
* ```
*/
export declare function callJsBrowserAdapter(): Promise<unknown>;
/**
* 设置 MSDK 浏览器退出全屏,需提前加载 sdk
* @example
* ```ts
* callJsReSetFullScreen();
* ```
*/
export declare const callJsReSetFullScreen: () => void;
/**
* 设置 MSDK 浏览器全屏,需提前加载 sdk
* @param isFullScreen 是否全屏
* @example
* ```ts
* callJsSetFullScreen();
* callJsSetFullScreen(false);
* ```
*/
export declare const callJsSetFullScreen: (isFullScreen?: boolean) => void;
/**
* 横线转驼峰命名,如果第一个字符是字母,则不处理。
* @param {string} str 输入字符串
* @param {boolean} handleSnake 是否处理下划线,默认不处理
* @returns {string} 处理后的字符串
* @example
*
* camelize('ab-cd-ef')
*
* // => abCdEf
*
*/
export declare function camelize(str?: string, handleSnake?: boolean): string;
/**
* 字符串首位大写
* @param {string} str 输入字符串
* @returns {string} 处理后的字符串
*
* @example
*
* capitalize('abc')
*
* // => Abc
*/
export declare function capitalize(str: string): string;
/**
* 检查 localStorage 设置,并展示vConsole
* @example
* ```ts
* checkAndShowVConsole()
* ```
*/
export declare function checkAndShowVConsole(): void;
/**
* 统一审核结果检查
*
* 检查审核结果,通过则 resolve,驳回则发送企微通知并 reject。
* 适用于 H5 发布、组件库发布等所有需要审核的流水线。
*
* @example H5 发布
* ```ts
* const { batchSendWxRobotMarkdown, checkAuditResult } = require('t-comm');
*
* await checkAuditResult({
* resultInfo,
* title: '【H5发布】',
* contentLines: [`项目: \`my-project\``, `子工程:\`my-sub\``],
* creator: 'novlan1',
* auditDesc: '需求发布',
* webhookUrl: '0482249e-bf24-4168-b3e2-f72d012840c2',
* sendMarkdown: batchSendWxRobotMarkdown,
* });
* ```
*/
export declare function checkAuditResult(options: ICheckAuditResultOptions & {
/** 发送企微 Markdown 消息的函数,由调用方传入 */
sendMarkdown: (params: {
content: string;
chatId: string[];
webhookUrl: string;
}) => Promise<any>;
}): Promise<void>;
export declare function checkExportTencentDocProgress({ accessToken, clientId, openId, fileId, operationId, }: ISecretInfo_2 & {
fileId: string;
operationId: string;
}): Promise<any>;
export declare function checkFileBaseMinimatch({ file, include, exclude, minimatch, }: {
file: string;
include: string | string[];
exclude: string | string[];
minimatch: Function;
}): boolean;
/**
* 异步并行检查目录下所有 git 仓库的工作区状态
* 相比同步版本,在仓库数量较多时有显著的性能提升
* @param dir - 要检查的父目录路径
* @example
* ```ts
* // 并行检查 ~/Documents/git 下所有子仓库
* await checkGitClean('/Users/foo/Documents/git');
* // 控制台会输出:
* // [not clean] /Users/foo/Documents/git/repoA
* // [not push] /Users/foo/Documents/git/repoB
* ```
*/
export declare function checkGitClean(dir: string): Promise<void>;
/**
* 同步版本,保持向后兼容
* @param dir - 要检查的父目录路径
* @example
* ```ts
* checkGitCleanSync('/Users/foo/Documents/git');
* // 同步依次检查,仓库有变动或未推送会打印警告
* ```
*/
export declare function checkGitCleanSync(dir: string): void;
/**
* 检测当前是否为 QQ 环境(QQ 用户通过 qq-wxmini-plugin 访问微信小程序)
*
* 注意:必须先调用 initQQMiniPlugin(本函数内部会兜底调用)。
*
* @returns true 表示 QQ 环境;非微信小程序环境或插件未安装时返回 false
*/
export declare function checkIsQQEnv(): boolean;
export declare function checkJSFiles(options?: {
whiteDir: string[];
excludeReg: RegExp;
log: boolean;
}): void;
/**
* 执行代码 lint 检查(支持 ESLint 和 StyleLint),并将结果通知到企业微信群、MR 评论等。
*
* 支持增量模式(仅检查 sourceBranch 与 targetBranch 之间的 diff 文件)和全量模式(checkAll=true)。
* 检查完成后可自动执行 --fix 并创建修复 MR(autoFixMR=true)。
*
* @param options - 配置参数
* @param options.privateToken - Git API 私有令牌,用于操作 MR
* @param options.gitApiPrefix - Git API 地址前缀
* @param options.workspace - 项目工作目录绝对路径
* @param options.mrUrl - MR 页面链接,用于消息通知中展示
* @param options.mrId - MR ID,传入后会在 MR 中添加评论和逐行批注
* @param options.buildUrl - 流水线构建链接,用于消息通知中展示
* @param options.repo - 仓库名称(如 group/project)
* @param options.repoUrl - 仓库页面链接
* @param options.sourceBranch - 源分支名(增量模式必填)
* @param options.targetBranch - 目标分支名(增量模式必填)
* @param options.docLink - 说明文档链接,用于消息通知中展示
* @param options.webhookUrl - 企业微信机器人 Webhook 地址
* @param options.chatId - 企业微信群聊 ID 列表,默认 ['ALL']
* @param options.checkAll - 是否全量检查,默认 false(增量模式)
* @param options.mentionList - 需要 @ 的企业微信用户列表
* @param options.lintFiles - 需要检查的文件类型列表,默认检查所有配置的类型
* @param options.throwError - 检查不通过时是否抛出异常,默认 true
* @param options.ignoreSubmodules - 是否在 lint 时忽略 git submodule,默认 true
* @param options.autoFixMR - 是否自动执行 --fix 并创建修复 MR,默认 false
* @param options.mrTitlePrefix - autoFixMR 创建 MR 时的标题前缀,如 '[WIP]' 可防止被自动合入
* @param options.onReport - 上报回调,lint 完成后调用,传入错误数量等关键信息
* @returns 返回 fileMap,包含各文件类型的 lint 结果
* @throws 当 throwError 为 true 且存在 lint 错误时抛出异常
*
* @example
* ```ts
* import { checkLint } from 't-comm';
*
* await checkLint({
* privateToken: 'your-token',
* gitApiPrefix: 'https://git.woa.com/api/v3',
* workspace: '/path/to/project',
* buildUrl: 'https://ci.example.com/build/123',
* repo: 'group/project',
* sourceBranch: 'feature/xxx',
* targetBranch: 'master',
* docLink: 'https://doc.example.com/lint',
* webhookUrl: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx',
* });
* ```
*/
export declare function checkLint({ privateToken, gitApiPrefix, workspace, mrUrl, mrId, buildUrl, repo, repoUrl, sourceBranch, targetBranch, docLink, webhookUrl, chatId, checkAll, mentionList, lintFiles, throwError, ignoreSubmodules, autoFixMR, mrTitlePrefix, onReport, }: {
privateToken: string;
gitApiPrefix?: string;
workspace: string;
mrUrl?: string;
mrId?: string;
buildUrl: string;
repo: string;
repoUrl?: string;
sourceBranch?: string;
targetBranch?: string;
docLink: string;
webhookUrl: string;
chatId?: string[];
checkAll?: boolean;
mentionList?: string[];
lintFiles?: string[];
throwError?: boolean;
ignoreSubmodules?: boolean;
/** 是否自动执行 --fix 并创建修复 MR */
autoFixMR?: boolean;
/** autoFixMR 创建 MR 时的标题前缀,如 '[WIP]' 可防止被自动合入 */
mrTitlePrefix?: string;
/**
* 上报回调。传入后会在 lint 完成、结果解析完毕时调用,
* 将错误数量、是否全量、mrId 等关键信息传给外部。
*/
onReport?: (info: CheckLintReportInfo) => void | Promise<void>;
}): Promise<FileMap>;
/** checkLint 完成后传给 onReport 回调的上报信息 */
export declare type CheckLintReportInfo = {
/** 各文件类型的错误总数 */
totalErrors: number;
/** 各文件类型的错误详情 */
errorDetails: Array<{
fileType: string;
errorCount: number;
fileCount: number;
}>;
/** 是否全量检查 */
checkAll: boolean;
/** MR ID */
mrId?: string;
/** 仓库名 */
repo: string;
/** 源分支 */
sourceBranch?: string;
/** 目标分支 */
targetBranch?: string;
/** 是否通过(无错误) */
passed: boolean;
/** lint 执行耗时(ms) */
duration: number;
/** 检查的文件类型列表 */
lintFiles: string[];
/** 完整的 fileMap 结果 */
fileMap: FileMap;
/** 自动修复创建的 MR 信息列表(autoFixMR 开启时才有,eslint 和 stylelint 分别创建独立 MR) */
fixMRList?: Array<{
/** 修复分支名 */
fixBranch: string;
/** 修复 MR 目标分支 */
fixTargetBranch: string;
/** createMR 返回的原始数据 */
mrData?: any;
}>;
};
/**
* 检查是否是node环境
* @returns {boolean} 是否node环境
* @example
const res = checkNodeEnv();
// false
*/
export declare const checkNodeEnv: () => boolean;
/**
* 检查字符串长度
*
* @export
* @param {string} str 字符串
* @param {number} [num = 30] 长度
* @returns {boolean}
*
* @example
*
* checkStringLength('123', 2) // true
* checkStringLength('123', 3) // true
* checkStringLength('123', 4) // false
*
*
*/
export declare function checkStringLength(str?: string, num?: number): boolean;
export declare function checkTSErrorInMrOrAll({ privateToken, gitApiPrefix, workspace, repo, repoUrl, mrId, mrUrl, sourceBranch, targetBranch, checkAll, buildUrl, docLink, mentionList, postFixList, chatId, webhookUrl, command, }: {
privateToken: string;
gitApiPrefix?: string;
workspace: string;
repo: string;
repoUrl: string;
mrId: string;
mrUrl: string;
sourceBranch: string;
targetBranch: string;
checkAll: boolean;
buildUrl: string;
docLink: string;
mentionList: string[];
postFixList?: string[];
chatId?: string[];
webhookUrl: string;
command?: string;
}): Promise<TsErrorFile[]>;
/**
* 检查是否是ios环境
* @returns {boolean} 是否是ios环境
*
* @example
*
* checkUAIsIOS()
*
* // => true
*
*/
export declare function checkUAIsIOS(): boolean;
declare type ChildProcess = typeof childProcess;
/**
* 将数组分割成指定长度的chunk
*
* @param array 数组
* @param chunkSize 要分的组数
* @returns 结果数组
* @example
* ```js
* chunkArray([1, 2, 3, 4, 5, 6, 7, 8], 3)
*/
export declare function chunkArray<T>(array: T[], chunkSize: number): T[][];
/**
* 清理单个 CHANGELOG 文件
* @param {string} filePath
* @param {boolean} dryRun
* @returns {boolean} 是否成功
* @example
* ```ts
* // 实际写入
* cleanChangelogFile('CHANGELOG.md');
*
* // 预览模式,不修改文件
* cleanChangelogFile('CHANGELOG.md', true);
* ```
*/
export declare function cleanChangelogFile(filePath: string, dryRun?: boolean): boolean;
/**
* 主函数
* @example
* ```bash
* # 处理单个文件
* npx t-comm clean:changelog packages/network-v2/CHANGELOG.md
*
* # 使用 glob 表达式处理多个文件
* npx t-comm clean:changelog 'packages/*\/CHANGELOG.md'
*
* # 预览模式
* npx t-comm clean:changelog 'packages/*\/CHANGELOG.md' --dry-run
* ```
*/
export declare function cleanChangelogScript(args: string[]): Promise<void>;
/**
* 通用「搜索-替换」工具
*
* 适用场景:AI Agent / Code Mod 需要根据"搜索-替换对"对源文件进行原地修改的场景。
*
* 提供两个函数:
* - cleanLineNumberPrefix:清理 AI 输出 search/replace 块时常带的行号前缀(如 "12| " "12 | ")
* - applySearchReplace:在源代码中查找 search 并替换为 replace,按 "精确 → 行级 → 子串" 三级策略匹配
*/
/**
* 清理 AI 输出 search/replace 块时常带的行号前缀
*
* AI 在生成搜索字符串时,有时会无意把行号也带上,导致与源文件无法精确匹配。
* 本函数支持 5 种常见格式:
* - "12| code" → "code"
* - "12 | code" → "code"
* - "12 code" → "code" (多空格分隔,仅当首行匹配且行号递增时才认为是行号)
* - "12: code" → "code"
* - "12. code" → "code"
*
* 为避免误删(如某些 markdown / 注释开头是数字),仅当:
* - 首行能匹配到行号格式
* - 后续行行号严格递增(差值为 1)
* 时才视为带行号前缀,全部清理。
*
* @param text 原始字符串(来自 AI 输出的 search/replace 块)
* @returns 清理后的字符串;不像行号前缀时原样返回
*/
export declare function cleanLineNumberPrefix(text: string): string;
/**
* 清除全部cookie
*
* @param {string} domain 域名
*
* @example
*
* clearAll()
*/
export declare function clearAll(domain?: String): void;
/**
* 清除cookie
* @param {string} key cookie键
*
* @example
*
* clearCookie('name');
*
*/
export declare function clearCookie(name: string): void;
/**
* 持久化存储。清理。传 key 就删除。不传清理所有过期的。
* @param {string} [key]
* @returns {boolean} 是否清楚成功
* @example
* ```ts
* // 清理指定 key
* clearPersist('name