@fairys/mocker-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +36 -0
  2. package/bin/fairys-mocker +22 -0
  3. package/esm/base.d.ts +28 -0
  4. package/esm/base.js +80 -0
  5. package/esm/ci.d.ts +2 -0
  6. package/esm/ci.js +49 -0
  7. package/esm/connect.d.ts +15 -0
  8. package/esm/connect.js +38 -0
  9. package/esm/controller/base.d.ts +2 -0
  10. package/esm/controller/base.js +3 -0
  11. package/esm/controller/index.d.ts +3 -0
  12. package/esm/controller/index.js +3 -0
  13. package/esm/controller/mock.router.d.ts +21 -0
  14. package/esm/controller/mock.router.js +497 -0
  15. package/esm/controller/proxy.router.d.ts +18 -0
  16. package/esm/controller/proxy.router.js +503 -0
  17. package/esm/index.d.ts +5 -0
  18. package/esm/index.js +5 -0
  19. package/esm/main.d.ts +15 -0
  20. package/esm/main.js +38 -0
  21. package/esm/plugins/rsbuild.d.ts +2 -0
  22. package/esm/plugins/rsbuild.js +12 -0
  23. package/esm/router/base.d.ts +12 -0
  24. package/esm/router/base.js +23 -0
  25. package/esm/router/index.d.ts +3 -0
  26. package/esm/router/index.js +3 -0
  27. package/esm/router/mock.d.ts +7 -0
  28. package/esm/router/mock.js +32 -0
  29. package/esm/router/proxy.d.ts +14 -0
  30. package/esm/router/proxy.js +67 -0
  31. package/esm/utils/decorator.d.ts +18 -0
  32. package/esm/utils/decorator.js +55 -0
  33. package/esm/utils/index.d.ts +2 -0
  34. package/esm/utils/index.js +2 -0
  35. package/esm/utils/mcok.proxy.d.ts +36 -0
  36. package/esm/utils/mcok.proxy.js +141 -0
  37. package/esm/utils/utils.d.ts +20 -0
  38. package/esm/utils/utils.js +31 -0
  39. package/package.json +52 -0
  40. package/public/_fairys_mocker/favicon.png +0 -0
  41. package/public/_fairys_mocker/index.html +1 -0
  42. package/public/_fairys_mocker/static/css/index.2ba69ff5.css +1 -0
  43. package/public/_fairys_mocker/static/js/514.950758f1.js +2 -0
  44. package/public/_fairys_mocker/static/js/514.950758f1.js.LICENSE.txt +5 -0
  45. package/public/_fairys_mocker/static/js/index.f70ed1dc.js +1 -0
  46. package/public/_fairys_mocker/static/js/lib-react.2748fa4b.js +2 -0
  47. package/public/_fairys_mocker/static/js/lib-react.2748fa4b.js.LICENSE.txt +49 -0
  48. package/src/base.ts +125 -0
  49. package/src/ci.ts +61 -0
  50. package/src/connect.ts +62 -0
  51. package/src/controller/base.ts +4 -0
  52. package/src/controller/index.ts +3 -0
  53. package/src/controller/mock.router.ts +175 -0
  54. package/src/controller/proxy.router.ts +182 -0
  55. package/src/index.ts +5 -0
  56. package/src/main.ts +59 -0
  57. package/src/plugins/rsbuild.ts +14 -0
  58. package/src/router/base.ts +36 -0
  59. package/src/router/index.ts +3 -0
  60. package/src/router/mock.ts +38 -0
  61. package/src/router/proxy.ts +89 -0
  62. package/src/utils/decorator.ts +90 -0
  63. package/src/utils/index.ts +2 -0
  64. package/src/utils/mcok.proxy.ts +162 -0
  65. package/src/utils/utils.ts +46 -0
@@ -0,0 +1,36 @@
1
+ import express from 'express';
2
+ import { fairysMockerBase } from "../base.js"
3
+ import chalk from 'chalk';
4
+
5
+
6
+ export class BaseRouter<T> {
7
+ /**mock 路由器实例*/
8
+ router: express.Router | null = null;
9
+ /**服务是否启用*/
10
+ isEnabled = false;
11
+ /**加载*/
12
+ load: (data: T[]) => void = (data) => {
13
+ /**销毁路由器实例*/
14
+ this.destroy();
15
+ };
16
+
17
+ /**销毁路由器实例*/
18
+ destroy: (msg?: string) => void = (msg) => {
19
+ if (this.router) {
20
+ // 清空路由器实例
21
+ this.router.stack = [];
22
+ this.isEnabled = false;
23
+ }
24
+ if (msg) {
25
+ console.log(chalk.red(msg))
26
+ console.log('')
27
+ }
28
+ };
29
+
30
+ useRouter() {
31
+ if (this.router && fairysMockerBase.router) {
32
+ fairysMockerBase.router.use(this.router);
33
+ }
34
+ }
35
+
36
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./base.js"
2
+ export * from "./mock.js"
3
+ export * from "./proxy.js"
@@ -0,0 +1,38 @@
1
+ import express from 'express';
2
+ import { MockerItem, createMockItemData } from "@fairys/create-mock-data"
3
+ import { BaseRouter } from "./base.js"
4
+ import chalk from "chalk"
5
+ /**mock 路由器实例*/
6
+ export class MockRouter extends BaseRouter<MockerItem> {
7
+ /**加载mock路由*/
8
+ load = (mockList: MockerItem[]) => {
9
+ /**销毁路由器实例*/
10
+ this.destroy();
11
+ /**创建路由器实例*/
12
+ // const router = this.router = Router();
13
+ const router = this.router = express.Router();
14
+ this.isEnabled = true;
15
+ /**加载mock路由*/
16
+ for (let index = 0; index < mockList.length; index++) {
17
+ const mockItem = mockList[index];
18
+ /**加载mock路由*/
19
+ const method = mockItem.method.toLowerCase() as "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head" | "options";
20
+ const handler = (req: express.Request, res: express.Response) => {
21
+ console.log(chalk.cyan(` 🦄 mock请求:` + '\t' + chalk.yellow(`${method}`) + "\t" + chalk.bold(`${mockItem.url}`)))
22
+ try {
23
+ const mockData = createMockItemData(mockItem);
24
+ res.status(Number(mockData.status) || 200).json(mockData.body);
25
+ } catch (error) {
26
+ res.status(500).json({
27
+ code: 500,
28
+ message: '加载失败',
29
+ });
30
+ }
31
+ };
32
+ console.log(chalk.green(` 🦄 mock加载:` + '\t' + chalk.yellow(`${method}`) + "\t" + chalk.bold(`${mockItem.url}`)))
33
+ router[method](mockItem.url, handler);
34
+ }
35
+ console.log('')
36
+ this.useRouter();
37
+ }
38
+ }
@@ -0,0 +1,89 @@
1
+ import express from 'express';
2
+ import { ProxyItem } from "@fairys/create-mock-data"
3
+ import { BaseRouter } from "./base.js"
4
+ import { createProxyMiddleware, RequestHandler } from "http-proxy-middleware"
5
+ import chalk from "chalk"
6
+ import { fairysMockerBase } from "../base.js"
7
+
8
+ /**代理 路由器实例*/
9
+ export class ProxyRouter extends BaseRouter<ProxyItem> {
10
+ /**代理 路由器实例*/
11
+ router: express.Router | null = null;
12
+ wsProxyList: RequestHandler[] = []
13
+ /**加载代理路由*/
14
+ load = (proxyList: ProxyItem[]) => {
15
+ const _that = this;
16
+ this.destroy();
17
+ /**创建路由器实例*/
18
+ const router = this.router = express.Router();
19
+ this.isEnabled = true;
20
+
21
+ for (let index = 0; index < proxyList.length; index++) {
22
+ const proxyItem = proxyList[index];
23
+ let protocol = 'http';
24
+ let _target = proxyItem.target
25
+ let _path = new RegExp(proxyItem.path)
26
+
27
+ if (/^(http:|https:|ws:|wss:)/.test(proxyItem.target)) {
28
+ const [_protocol] = proxyItem.target.split(":")
29
+ protocol = _protocol;
30
+ }
31
+ if ((protocol !== 'ws' && protocol !== 'wss') && proxyItem.ws) {
32
+ protocol = 'ws'
33
+ const [_protocol, ...rest] = proxyItem.target.split(":")
34
+ _target = [protocol, ...rest].join(":")
35
+ }
36
+ console.log(chalk.hex('#AF52DE')(chalk.bold(` 🍇 proxy代理启动:\t${chalk.yellow(protocol)}\t${proxyItem.path} ===> ${_target}\t`)))
37
+ // 判断是否 ^ 开头
38
+ if (!proxyItem.path.startsWith('^')) {
39
+ _path = new RegExp('^' + proxyItem.path)
40
+ }
41
+ if (proxyItem.ws) {
42
+ const wsProxy = createProxyMiddleware({
43
+ target: proxyItem.target,
44
+ pathRewrite: proxyItem.pathRewrite,
45
+ ws: proxyItem.ws,
46
+ changeOrigin: true,
47
+ })
48
+ _that.wsProxyList.push(wsProxy)
49
+ router.all(_path, wsProxy)
50
+ // 这个地方有个问题,如果在 rsbuild 中使用,websocket 会有问题
51
+ if (fairysMockerBase.server) {
52
+ // 升级 WebSocket 处理
53
+ fairysMockerBase.server?.on('upgrade', wsProxy.upgrade)
54
+ }
55
+ } else {
56
+ // 这个不生效问题
57
+ router.all(_path, createProxyMiddleware({
58
+ target: proxyItem.target,
59
+ pathRewrite: proxyItem.pathRewrite,
60
+ ws: proxyItem.ws,
61
+ changeOrigin: true,
62
+ }))
63
+ }
64
+ }
65
+ console.log('')
66
+ this.useRouter();
67
+ }
68
+
69
+ /**销毁路由器实例*/
70
+ destroy: (msg?: string) => void = (msg) => {
71
+ if (this.router) {
72
+ // 清空路由器实例
73
+ this.router.stack = [];
74
+ this.isEnabled = false;
75
+ }
76
+ /** http 升级 WebSocket 的 upgrade 销毁*/
77
+ const wsProxyList = this.wsProxyList
78
+ if (Array.isArray(wsProxyList) && wsProxyList.length && fairysMockerBase.server) {
79
+ for (let index = 0; index < wsProxyList.length; index++) {
80
+ const wsProxy = wsProxyList[index];
81
+ fairysMockerBase.server.off('upgrade', wsProxy.upgrade)
82
+ }
83
+ }
84
+ if (msg) {
85
+ console.log(chalk.red(msg))
86
+ }
87
+ console.log('')
88
+ };
89
+ }
@@ -0,0 +1,90 @@
1
+
2
+ import express from 'express';
3
+ import { fairysMockerBase } from "../base.js"
4
+
5
+ export type ClassStruct<TInstanceType extends unknown = unknown> = new (
6
+ ...args: any[]
7
+ ) => TInstanceType;
8
+
9
+ export interface RouteItemType {
10
+ method: 'get' | 'delete' | 'post' | 'put' | 'patch' | 'head' | 'options';
11
+ path: string;
12
+ funName?: string | symbol;
13
+ }
14
+
15
+ export interface ControllerItemType {
16
+ prefix?: string;
17
+ }
18
+
19
+ /**控制器参数*/
20
+ export const controllerMap = new WeakMap<object, ControllerItemType>();
21
+ /**路由方法参数*/
22
+ export const routesMap = new WeakMap<object, RouteItemType>();
23
+
24
+ // 类装饰器:统一前缀
25
+ export function Controller(prefix: string) {
26
+ return (target: any, context: ClassDecoratorContext) => {
27
+ controllerMap.set(target, { prefix })
28
+ };
29
+ }
30
+
31
+ // POST 方法装饰器(新版标准,100% 触发)
32
+ export function Post(path: string) {
33
+ return (target: any, context: ClassMethodDecoratorContext) => {
34
+ if (context.kind === 'method') {
35
+ routesMap.set(target, { method: 'post', path, funName: context.name })
36
+ }
37
+ };
38
+ }
39
+
40
+ // POST 方法装饰器(新版标准,100% 触发)
41
+ export function Get(path: string) {
42
+ return (target: any, context: ClassMethodDecoratorContext) => {
43
+ if (context.kind === 'method') {
44
+ routesMap.set(target, { method: 'get', path, funName: context.name })
45
+ }
46
+ };
47
+ }
48
+
49
+ // Method 方法装饰器
50
+ export function MethodPath(path: string, method: RouteItemType['method'] = 'post') {
51
+ return (target: any, context: ClassMethodDecoratorContext) => {
52
+ if (context.kind === 'method') {
53
+ routesMap.set(target, {
54
+ method: method.toLowerCase() as RouteItemType['method'],
55
+ path, funName: context.name
56
+ })
57
+ }
58
+ };
59
+ }
60
+
61
+ // 注册路由(constructor 调用)
62
+ export function registerRoutes(instance: unknown) {
63
+ const fairysRouter = fairysMockerBase.fairysMockerRouter;
64
+ if (!fairysRouter) {
65
+ console.log('请先初始化内置路由');
66
+ return;
67
+ }
68
+ // 获取原型数据
69
+ const proto = Object.getPrototypeOf(instance);
70
+ // 获取控制器前缀
71
+ const controllerAPIRootPath = controllerMap.get(proto.constructor);
72
+ /**实例中方法名*/
73
+ const controllerMethods = Object.getOwnPropertyNames(proto).filter((i) => i !== "constructor");
74
+ /**遍历实例中方法名*/
75
+ for (let index = 0; index < controllerMethods.length; index++) {
76
+ /**获取方法名*/
77
+ const controllerMethod = controllerMethods[index];
78
+ /**方法*/
79
+ const _requestHandle: (req: express.Request, res: express.Response,) => void = proto[controllerMethod];
80
+ const boundRequestHandle: (req: express.Request, res: express.Response) => void = _requestHandle.bind(instance);
81
+ /**获取当前方法配置*/
82
+ const routeItem = routesMap.get(_requestHandle);
83
+ if (routeItem) {
84
+ /**拼接前缀*/
85
+ const fullPath = (controllerAPIRootPath?.prefix || '') + routeItem.path;
86
+ /**方法*/
87
+ fairysRouter[routeItem.method](fullPath, (req, res) => boundRequestHandle(req, res));
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./utils.js"
2
+ export * from "./decorator.js"
@@ -0,0 +1,162 @@
1
+
2
+ /**保存配置和读取配置*/
3
+ import nodePath from "node:path"
4
+ import fs from 'node:fs';
5
+ import { utils } from "./utils.js";
6
+ import { createProxyData, createMockData, ProxyList, DefineMockList } from "@fairys/create-mock-data";
7
+
8
+ export const getProxyFile = (rootDir?: string, dir?: string, fileName?: string) => {
9
+ const _rootDir = rootDir?.trim() || utils.rootDir;
10
+ const _dir = dir?.trim() || utils.dir;
11
+ const _fileName = fileName?.trim() || utils.proxyFile;
12
+ // 读取 .cache.json 文件
13
+ const proxyDir = nodePath.join(_rootDir, _dir);
14
+ const cacheFilePath = nodePath.join(proxyDir, _fileName + '.cache.json');
15
+ if (fs.existsSync(cacheFilePath)) {
16
+ const cacheFileContent = fs.readFileSync(cacheFilePath, 'utf-8');
17
+ const cacheData = JSON.parse(cacheFileContent);
18
+ const proxyList = cacheData.proxyList || [];
19
+ return {
20
+ proxyList,
21
+ rootDir: _rootDir,
22
+ dir: _dir,
23
+ fileName: _fileName,
24
+ cache: _fileName + '.cache.json',
25
+ }
26
+ }
27
+ return undefined
28
+ }
29
+
30
+ export const createProxyFile = (proxyList: ProxyList, rootDir?: string, dir?: string, fileName?: string) => {
31
+ const _rootDir = rootDir?.trim() || utils.rootDir;
32
+ const _dir = dir?.trim() || utils.dir;
33
+ const _fileName = fileName?.trim() || utils.proxyFile;
34
+ // 读取 .cache.json 文件
35
+ const proxyDir = nodePath.join(_rootDir, _dir);
36
+
37
+ if (!fs.existsSync(proxyDir)) {
38
+ fs.mkdirSync(proxyDir, { recursive: true });
39
+ }
40
+ const proxyConfig = createProxyData(proxyList)
41
+
42
+ const proxyFilePath = nodePath.join(proxyDir, `${_fileName}.ts`);
43
+ const proxyFileContent = `// 代理配置文件
44
+ // 自动生成于 ${new Date().toISOString()}
45
+
46
+ /**
47
+ * 代理配置参数
48
+ */
49
+ export type ProxyItem = Record<string,{
50
+ /**转发地址*/
51
+ target: string,
52
+ /**路径重写*/
53
+ pathRewrite?: Record<string, string>,
54
+ /**是否开启ws*/
55
+ ws?: boolean
56
+ }>
57
+
58
+ export const proxyConfig: ProxyItem = ${JSON.stringify(proxyConfig, null, 2)};
59
+ export default proxyConfig;
60
+ `;
61
+ fs.writeFileSync(proxyFilePath, proxyFileContent);
62
+ // 存储原始的 proxyConfig 到 .cache.json 文件
63
+ const cacheFilePath = nodePath.join(proxyDir, _fileName + '.cache.json');
64
+
65
+ const cacheFileContent = JSON.stringify({
66
+ proxyList,
67
+ rootDir: _rootDir,
68
+ dir: _dir,
69
+ fileName: _fileName,
70
+ cache: _fileName + '.cache.json',
71
+ }, null, 2);
72
+ fs.writeFileSync(cacheFilePath, cacheFileContent);
73
+
74
+ return {
75
+ proxyConfig,
76
+ rootDir: _rootDir,
77
+ dir: _dir,
78
+ fileName: _fileName,
79
+ cache: _fileName + '.cache.json',
80
+ }
81
+ }
82
+
83
+ export const getMcokFile = (rootDir?: string, dir?: string, fileName?: string) => {
84
+ const _rootDir = rootDir?.trim() || utils.rootDir;
85
+ const _dir = dir?.trim() || utils.dir;
86
+ const _fileName = fileName?.trim() || utils.file;
87
+ // 读取 .cache.json 文件
88
+ const mockDir = nodePath.join(_rootDir, _dir);
89
+ const cacheFilePath = nodePath.join(mockDir, _fileName + '.cache.json');
90
+ if (fs.existsSync(cacheFilePath)) {
91
+ const cacheFileContent = fs.readFileSync(cacheFilePath, 'utf-8');
92
+ const cacheData = JSON.parse(cacheFileContent);
93
+ const mockList = cacheData.mockList || [];
94
+ return {
95
+ mockList,
96
+ rootDir: _rootDir,
97
+ dir: _dir,
98
+ fileName: _fileName,
99
+ cache: _fileName + '.cache.json',
100
+ }
101
+ }
102
+ return undefined
103
+ }
104
+
105
+ export const createMockFile = (mockList: DefineMockList, rootDir?: string, dir?: string, fileName?: string) => {
106
+ const _rootDir = rootDir?.trim() || utils.rootDir;
107
+ const _dir = dir?.trim() || utils.dir;
108
+ const _fileName = fileName?.trim() || utils.file;
109
+ // 读取 .cache.json 文件
110
+ const mockDir = nodePath.join(_rootDir, _dir);
111
+
112
+ if (!fs.existsSync(mockDir)) {
113
+ fs.mkdirSync(mockDir, { recursive: true });
114
+ }
115
+ const mockConfig = createMockData(mockList)
116
+
117
+ const mockFilePath = nodePath.join(mockDir, `${_fileName}.ts`);
118
+ const mockFileContent = `// Mock 配置文件
119
+ // 自动生成于 ${new Date().toISOString()}
120
+
121
+ export interface MockerItem {
122
+ /**该接口允许的 请求方法,默认同时支持 GET 和 POST*/
123
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
124
+ /**状态码*/
125
+ status: string;
126
+ //配置响应延迟时间, 如果传入的是一个数组,则代表延迟时间的范围
127
+ delay: number | [number, number];
128
+ /**响应体(可以自定义返回格式)*/
129
+ body: any;
130
+ /**接口地址*/
131
+ url: string;
132
+ /**列表数据条数(仅 list 格式有效)*/
133
+ listCount?: number;
134
+ }
135
+
136
+ /**mock配置 列表*/
137
+ export type DefineMockList = MockerItem[];
138
+
139
+ export const mockList: DefineMockList = ${JSON.stringify(mockConfig, null, 2)};
140
+ export default mockList;
141
+ `;
142
+ fs.writeFileSync(mockFilePath, mockFileContent);
143
+ // 存储原始的 mockConfig 到 .cache.json 文件
144
+ const cacheFilePath = nodePath.join(mockDir, _fileName + '.cache.json');
145
+
146
+ const cacheFileContent = JSON.stringify({
147
+ mockList,
148
+ rootDir: _rootDir,
149
+ dir: _dir,
150
+ fileName: _fileName,
151
+ cache: _fileName + '.cache.json',
152
+ }, null, 2);
153
+ fs.writeFileSync(cacheFilePath, cacheFileContent);
154
+
155
+ return {
156
+ mockConfig,
157
+ rootDir: _rootDir,
158
+ dir: _dir,
159
+ fileName: _fileName,
160
+ cache: _fileName + '.cache.json',
161
+ }
162
+ }
@@ -0,0 +1,46 @@
1
+
2
+ import chalk from 'chalk';
3
+ import fs from 'node:fs';
4
+ import path from "node:path"
5
+
6
+ class Utils {
7
+ /**根目录*/
8
+ public rootDir = process.cwd();
9
+ /**目录名*/
10
+ public dir = 'mock';
11
+ /**文件名*/
12
+ public file = 'index.mock';
13
+ /**代理文件名*/
14
+ public proxyFile = 'proxy';
15
+
16
+ /**设置根目录*/
17
+ setRootDir = (value?: string) => {
18
+ if (value && fs.existsSync(value)) {
19
+ this.rootDir = value
20
+ } else {
21
+ if (value) {
22
+ console.log('')
23
+ console.log(chalk.red(`设置的根目录不存在:${value}`))
24
+ console.log('')
25
+ }
26
+ this.rootDir = process.env.FAIRYS_MOCKER_ROOT_DIR || path.join(process.cwd());
27
+ }
28
+ }
29
+
30
+ /**设置目录名*/
31
+ setDir = (value?: string) => {
32
+ this.dir = value || process.env.FAIRYS_MOCKER_DIR || 'mock';
33
+ }
34
+
35
+ /**设置文件名*/
36
+ setFile = (value?: string) => {
37
+ this.file = value || process.env.FAIRYS_MOCKER_FILE || 'index.mock';
38
+ }
39
+
40
+ /**设置代理文件名*/
41
+ setProxyFile = (value?: string) => {
42
+ this.proxyFile = value || process.env.FAIRYS_MOCKER_PROXY_FILE || 'proxy';
43
+ }
44
+ }
45
+
46
+ export const utils = new Utils()