mihawk
Version:
A tiny & simple mock server tool, support json,js,cjs,ts(typescript).
366 lines (365 loc) • 12 kB
TypeScript
/// <reference types="node" />
import * as WS from 'ws';
import type { IncomingMessage } from 'http';
import type { ParsedArgs } from 'minimist';
/**
* koa 类型
*/
import type { Middleware, Context, Next, BaseRequest, Request, Response } from 'koa';
export type { Options as KoaBodyParserOptions } from 'koa-bodyparser';
declare module 'koa' {
interface Context {
/**
* 默认为 false,即:需要打印
*/
disableLogPrint?: boolean;
/**
* 跳过执行默认的 mock 逻辑,默认为 false,即:都是需要处理的
* - default mock 逻辑的中间件 middleware/mock.ts 是最后一个执行的中间件,如果设置 skipDefaultMock 为 true,该部分逻辑将会跳过
*/
skipDefaultMock?: boolean;
/**
* 格式为 Method + Path, 如 'GET /a/b/c'
* - 初始化详见在 middlewares/common.ts 中逻辑代码
*/
routePath?: string;
/**
* mock 文件的相对路径(相对于 data 目录),比如 'GET /a/b' => '/GET/a/b'
* - 初始化详见在 middlewares/common.ts 中逻辑代码
* - 可通过动态设置 `ctx.mockRelPath` 来覆盖,比如 middlewares/route.ts 中就是通过动态设置 `ctx.mockRelPath` 来达到 route 重定向的效果
*/
mockRelPath?: string;
/**
* 是否已经采取默认的 mock 逻辑,默认为 false,即:还未处理过
* - 在执行完 middleware/mock 这个中间件之后,如果走的是默认逻辑,这个值会被设置为 true,详见 middlewares/mock.ts
*/
hitDefaultMock?: boolean;
}
interface Request {
}
interface Response {
}
}
/**
* koa 类型二次导出
*/
export type KoaMiddleware = Middleware;
export type KoaContext = Context;
export type KoaNext = Next;
export type KoaRequest = Request;
export type KoaResponse = Response;
/**
* 将 koa 的 BaseRequest 进行扩展,添加 body 和 rawBody 属性
*/
export type BaseRequestEx = BaseRequest & {
/**
* parsed body, define by koa-bodyparser
*/
body?: any;
/**
* rawBody, string, define by koa-bodyparser
*/
rawBody?: string;
};
/**
* Mock 数据转换器函数中 extra 参数的类型
* - extra 只是个代理对 ctx.request 的对象,包含 ctx.request 上的 url,method,path,query,body 等属性,
* - extra 上所有子属性,以及子属性的子属性,都是 Readonly 只读的
*/
export type MhkCvtrExtra = DeepReadonly<Unfixedify<BaseRequestEx>>;
/**
* Mock 数据转换器
*/
export type MockDataConvertor<T extends Record<string, any> = JSONObject> = (
/**
* json data
*/
originData: T,
/**
* extra 对象,包含 ctx 上的 url,method,path,query,body 等属性
* - 注意,但不等价于 ctx 对象,而是一个只读对象,值从 ctx 上获取而已
*/
extra: MhkCvtrExtra) => Promise<T>;
/**
* https 配置
*/
export interface HttpsConfig {
/**
* key 文件路径
*/
key: string;
/**
* cert 文件路径
*/
cert: string;
/**
* ca 文件路径
*/
ca?: string;
}
/**
* rc 文件中的 logConfig 字段配置
*/
export interface MhkRcLogConfig {
/**
* 指定路由对应日志忽略不打印
*/
ignoreRoutes?: string[];
}
/**
* rc 文件中的 websocket 字段配置
*/
export interface MhkRCWsConfig {
/**
* 是否启用 stomp 协议
*/
stomp?: boolean;
[k: string]: any;
}
/**
* mihawk root-config 配置文件
*/
export interface MihawkRC {
/**
* 监听地址,默认 `0.0.0.0`
*/
host?: string;
/**
* 监听端口, 默认 8888
*/
port?: number;
/**
* 是否开启 https,默认 false
*/
https?: boolean | HttpsConfig;
/**
* 是否开启 cors,默认 true
*/
cors?: boolean;
/**
* 是否开启 watch(mock 文件变化的时候,进行刷新),默认 true
* - watch=true 时,无论是否设置 cache,文件变化必然触发缓存刷新
* - watch=false 时,是否走缓存,则完全看 cache 字段的逻辑
*
* 所以,watch 和 cache 并不冲突( watch 控制文件变化时候的缓存策略,cache 控制常规情况的缓存策略 )
*/
watch?: boolean;
/**
* 对 mock 文件进行缓存,默认 false
* - true: mock 文件的加载,会走缓存,不会每次都重新加载文件(缓存的生成为第一次加载时产生)
* - false: mock 文件的加载,不会走缓存,每次都重新加载文件
*
* 其他:当 watch=true 时
* - 只要 mock 文件变化,缓存必刷新
* - 但 mock 文件不变化时,是否走缓存,就看 cache 字段的设置(即:同上逻辑)
*
* 所以,cache 和 watch 并不冲突( cache 控制常规情况的缓存策略,watch 控制文件变化时候的缓存策略 )
*/
cache?: boolean;
/**
* mock 目录,默认为 `./mocks`
*/
mockDir?: string;
/**
* mock 数据文件类型,默认 json
*/
mockDataFileType?: 'json' | 'json5';
/**
* mock 逻辑文件类型,默认 none
*/
mockLogicFileType?: 'none' | 'js' | 'cjs' | 'javascript' | 'ts' | 'typescript';
/**
* 是否自动创建 mock 文件,默认 true
*/
autoCreateMockLogicFile?: boolean;
/**
* tsconfig.json 的路径
* - 当且仅当 mockLogicFileType 为 ts|typescript 时有效
* - 默认不写则为空字符串,即采用内置的 ts 配置
* - 可以写为 `./mocks/tsconfig.json`(建议和工程中的 tsconfig.json 区分开来,因为 mockTs 并不需要进行打包输出)
*/
tsconfigPath?: string | null;
/**
* 日志打印,配置项
*/
logConfig?: MhkRcLogConfig | null;
/**
* 是否启用 websocket server
*/
socketConfig?: MhkRCWsConfig | null | boolean;
/**
* 从远端接口拉取数据之后,更新本地 json 文件
* - 情况1:当本地mock文件不存在时,从远端代理获取初始化数据
* - 情况2:当本地mock文件存在时, 如果设置了 coverExistedJson=true,会强制更新本地的mock文件,否则直接不会发起请求,直接走本地 mock
* 配置:
* - 配置格式:{ target: string; rewrite?: (path: string) => string }
* - 设置为 null/undefined 时禁用该功能
*/
setJsonByRemote?: {
enable: boolean;
target: string;
timeout?: number;
changeOrigin?: boolean;
rewrite?: (path: string) => string;
coverExistedJson?: boolean;
} | null;
}
/**
* mock 数据文件后缀
*/
export type DataFileExt = 'json' | 'json5';
/**
* mock 逻辑文件后缀
*/
export type LoigicFileExt = 'js' | 'cjs' | 'ts' | '';
/**
* Mihawk 启动参数, 在 MihawkRC 之上封装一些额外的参数,方便后续逻辑处理
* - 和 MihawkRC 区别是,包含一些二次加工过的属性,主要是为了方便程序内部使用
*/
export type MihawkOptions = MihawkRC & {
/**
* @extra
* mock 目录的绝对路径,默认为 `${CWD}/mocks`
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
mockDirPath: string;
/**
* @extra
* mock data 数据文件目录的绝对路径,默认为 `${CWD}/mocks/data`
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
mockDataDirPath: string;
/**
* @extra
* 当 mockLogicFileType 为 ts | typescript 时为 true
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
isTypesctiptMode: boolean;
/**
* @extra
* 是否开启 https,当且仅当 https 属性是一个对象时开启
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
useHttps: boolean;
/**
* @extra
* 当 mockLogicFileType 不是 none 时为 true
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
useLogicFile: boolean;
/**
* @extra
* 当 mockLogicFileType 不是 none 时才会有值,否则是空字符或者 null
* - 值不包含 . 前缀,eg: ts | js | cjs
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
logicFileExt?: LoigicFileExt | null;
/**
* @extra
* 同 mockDataFileType 一致,为 json 或者 json5,这里多定义一个只是为了代码可读一点,与上面 logicFileExt 形成对比
* - 值不包含 . 前缀,eg: json | json5
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
dataFileExt: DataFileExt;
/**
* @extra
* routes.{json|json5|js|cjs|ts} 文件的绝对路径,默认为 `${CWD}/mocks/routes.json`
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
routesFilePath: string;
/**
* @extra
* 自定义中间件的绝对路径,默认为空,推荐为 `${CWD}/mocks/middleware.js`
* - 初始化详见 rc.ts 中的 formatOptionsByConfig 方法
*/
middlewareFilePath: string;
/**
* @extra
* 是否开启 websocket server
*/
useWS?: boolean;
/**
* @extra
* websocket 相关 diy 处理路径
*/
socketFilePath?: string;
/**
* @extra
* 是否启用远端拉取数据能力
*/
useRemoteData?: boolean;
};
/**
* cli 命令行参数
*/
export type CliArgs<T extends Record<string, any> = any> = Loosify<ParsedArgs & T>;
/**
* subCmd 主逻辑对应的回调函数
*/
export type SubCmdCallback<T = any> = (cliArg?: CliArgs<T>) => Promise<void>;
/**
* wss 实例(web-sokcet-server)
* WebSocketServer 实例,指的是 server 实例
* - 【注意】
*/
export type WsWebScketServer = WS.WebSocketServer;
export type SocketServer = WS.WebSocketServer;
/**
* ws 实例(webSocket)
* WebSocket 实例
* - 【注意】这里和 WebsocketServer 的实例,有区别,两者不是一个东西
*/
export type WsWebSocket = WS.WebSocket;
export type Socket = WS.WebSocket;
/**
* SocketResolveFunc 逻辑处理函数的 options 参数
*/
export type SocketReslrFuncOptions = Unfixedify<{
stomp?: boolean;
clientId?: string;
}>;
/**
* socket 逻辑处理函数
*/
export type SocketResolveFunc = (socket: WS.WebSocket, request: IncomingMessage, options?: SocketReslrFuncOptions) => void;
/**
* any function
*/
export type AnyFunc = (...args: any[]) => any;
/**
* JSON 对象属性的 value 常规定义
*/
export type JSONValue = null | boolean | number | string | JSONValue[] | {
[key: string]: JSONValue;
};
/**
* JSON 对象常规定义
*/
export type JSONObject = Record<string | number, JSONValue>;
/**
* 深度递归式让复杂类型的所有子属性都变成可选的。此泛型声明式为了弥补 Partial<T> 的不足
*/
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Record<any, any> ? (T[P] extends AnyFunc ? T[P] : DeepPartial<T[P]>) : T[P];
};
/**
* 深度递归式让复杂类型的所有子属性都变成只读的。此泛型声明式为了弥补 Readonly<T> 的不足
* - 兼容子属性为函数类型
*/
export type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<any, any> ? (T[P] extends AnyFunc ? T[P] : DeepReadonly<T[P]>) : T[P];
};
/**
* 让对象 T 中的子属性变成“松散”(任意未定义的子属性都可能存在)
*/
export type Unfixedify<T> = T & {
[k: string | number | symbol]: any;
};
/**
* 让对象 T 中的子属性变成“宽松”(可选&可自定义)
* - 1.子属性可选的,即 “子属性不一定存在”
* - 2.子属性可自定义,即 “任意未定义的子属性都可能存在” { [k: string | number | symbol]: any }
*
* 可用于“一些对象可能长得像某个类型时,但子属性又不局限于这个类型定义”的场景
*/
export type Loosify<T> = Unfixedify<DeepPartial<T>>;