UNPKG

t-comm

Version:

专业、稳定、纯粹的工具库

1,787 lines (1,693 loc) 527 kB
/// <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