mock-service-cli 1.2.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +112 -21
- package/bin/mock-service-cli +12 -6
- package/lib/manageMockFiles.js +168 -0
- package/lib/mockServer.js +61 -18
- package/lib/utils.js +27 -7
- package/package.json +9 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
### [2.0.2](https://github.com/chandq/mock-service-cli/compare/v2.0.1...v2.0.2) (2021-12-27)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* issue of convert path ([5276488](https://github.com/chandq/mock-service-cli/commit/527648866f20fd25b5df0dc55b3898d7e7a1ad2c))
|
|
11
|
+
|
|
12
|
+
### [2.0.1](https://github.com/chandq/mock-service-cli/compare/v2.0.0...v2.0.1) (2021-12-27)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* issue of executed failed under windows ([e160753](https://github.com/chandq/mock-service-cli/commit/e16075307e9fec8efcce464004b0881884f68ab8))
|
|
18
|
+
|
|
19
|
+
## [2.0.0](https://github.com/chandq/mock-service-cli/compare/v1.2.1...v2.0.0) (2021-12-25)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* 新增根据api自动生成mock文件、获取mock统计数据的工具函数 ([b22cb6f](https://github.com/chandq/mock-service-cli/commit/b22cb6fa15787a1343ec6d11606d2a59a670171b))
|
|
25
|
+
* add c/s communication base on socket.io ([d0ce542](https://github.com/chandq/mock-service-cli/commit/d0ce5422ece5f7c36d6e74f06620b54096e84f73))
|
|
26
|
+
* add hasMockApi function ([e14231b](https://github.com/chandq/mock-service-cli/commit/e14231b8385835b59f4e0960b37b4379e4853155))
|
|
27
|
+
* add mock-dir-stat mock-file-stat event ([8d8b1fa](https://github.com/chandq/mock-service-cli/commit/8d8b1faf130c84fe35bf205e68ca9634ee8ff767))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* logical judge issue ([1e40b13](https://github.com/chandq/mock-service-cli/commit/1e40b134cfcbfd908346ed701cfd18c39502153b))
|
|
33
|
+
* mock-list.json未及时更新的问题 ([4bb42f5](https://github.com/chandq/mock-service-cli/commit/4bb42f506ca1bab6958554ba6043a721bc296555))
|
|
34
|
+
|
|
35
|
+
### [1.2.1](https://github.com/chandq/mock-service-cli/compare/v1.2.0...v1.2.1) (2021-11-28)
|
|
36
|
+
|
|
37
|
+
## [1.2.0](https://github.com/chandq/mock-service-cli/compare/v1.1.1...v1.2.0) (2021-11-27)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Features
|
|
41
|
+
|
|
42
|
+
* 支持除get请求之外的其他所有常用类型 ([a1bd549](https://github.com/chandq/mock-service-cli/commit/a1bd549e24da958178a3b1244c4b2f472dfcc643))
|
|
43
|
+
|
|
44
|
+
### [1.1.1](https://github.com/chandq/mock-service-cli/compare/v1.1.0...v1.1.1) (2021-10-27)
|
|
45
|
+
|
|
46
|
+
## [1.1.0](https://github.com/chandq/mock-service-cli/compare/v1.0.2...v1.1.0) (2021-10-27)
|
|
47
|
+
|
|
48
|
+
### [1.0.2](https://github.com/chandq/mock-service-cli/compare/v1.0.1...v1.0.2) (2021-10-26)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
### Bug Fixes
|
|
52
|
+
|
|
53
|
+
* recursively through subdirectories ([d11813c](https://github.com/chandq/mock-service-cli/commit/d11813c48175550c6592e52f2fd1abe1c63f8203))
|
|
54
|
+
|
|
55
|
+
### [1.0.1](https://github.com/chandq/mock-service-cli/compare/v1.0.0...v1.0.1) (2021-10-25)
|
|
56
|
+
|
|
57
|
+
## [1.0.0](https://github.com/chandq/mock-service-cli/compare/v0.2.1...v1.0.0) (2021-10-25)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Bug Fixes
|
|
61
|
+
|
|
62
|
+
* default mock directory issue ([064cd90](https://github.com/chandq/mock-service-cli/commit/064cd903cbc891eaa068ac3c38606e37ec529257))
|
|
63
|
+
* make silent argument take effect ([5aa472b](https://github.com/chandq/mock-service-cli/commit/5aa472b34cee4b93683a3cc282bba0b9ad52048e))
|
|
64
|
+
|
|
65
|
+
### [0.2.1](https://github.com/chandq/mock-service-cli/compare/v0.1.1...v0.2.1) (2021-10-22)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### Bug Fixes
|
|
69
|
+
|
|
70
|
+
* 纠正devDep和deps的包分类 ([25cf330](https://github.com/chandq/mock-service-cli/commit/25cf3308337220210a2daccaf9d09444eddb1b24))
|
|
71
|
+
|
|
72
|
+
## [0.2.0](https://github.com/chandq/mock-service-cli/compare/v0.1.1...v0.2.0) (2021-10-22)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
### Bug Fixes
|
|
76
|
+
|
|
77
|
+
* 纠正devDep和deps的包分类 ([25cf330](https://github.com/chandq/mock-service-cli/commit/25cf3308337220210a2daccaf9d09444eddb1b24))
|
|
78
|
+
|
|
79
|
+
### [0.1.1](https://github.com/chandq/mock-service-cli/compare/v0.1.0...v0.1.1) (2021-10-22)
|
|
80
|
+
|
|
81
|
+
## 0.1.0 (2021-10-22)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
### Features
|
|
85
|
+
|
|
86
|
+
* initial commit ([c8c8a1a](https://github.com/chandq/mock-service-cli/commit/c8c8a1a8268eab16411a2d1d6b0ee90e8de4cb35))
|
package/README.md
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
[](https://coveralls.io/github/chandq/mock-service-cli?branch=master)
|
|
3
3
|
[](https://github.com/chandq/mock-service-cli)
|
|
4
4
|
[](https://nodejs.org/download/release/v14.0.0/)
|
|
5
|
-
[](https://nodejs.org/download/release/
|
|
5
|
+
[](https://nodejs.org/download/release/v12.0.0/)
|
|
6
6
|
[](https://github.com/chandq/mock-service-cli/blob/master/LICENSE.md)
|
|
7
7
|
[](https://npmcharts.com/compare/mock-service-cli?minimal=true)
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# 🦅 一个基于 [node](https://nodejs.org/en/) 和 [express](https://www.expressjs.com.cn/) 的 轻量级 Mock 套件 命令行工具
|
|
10
10
|
|
|
11
11
|
### 简介
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**简易轻量**、**B/S 架构**、**0 秒启动**的本地命令行 Mock 服务套件, 支持热更新,对于开发调试 mock 数据很实用,能提高前端开发者的开发效率。支持 `GET`,`POST`,`PUT`,`DELETE`,`PATCH`,`OPTIONS`,`COPY`,`LINK`,`UNLINK`,`PURGE` 等常用请求类型,无需布署后端,可能是本地最好用的 Mock 工具。支持以下常见业务场景:
|
|
14
|
+
|
|
15
|
+
- [x] 无服务端演示项目的数据 Mock
|
|
16
|
+
- [x] 业务开发接口联调前的数据 Mock
|
|
17
|
+
- [x] 保留业务项目的所有或部分接口的响应数据(写入文件)
|
|
18
|
+
- [x] 保障前端开发调试不受后端服务影响(当后端服务挂掉或部分接口响应异常时启用 Mock)
|
|
14
19
|
|
|
15
20
|
## Installation:
|
|
16
21
|
|
|
@@ -36,7 +41,7 @@ This will install `mock-service-cli` globally so that it may be run from the com
|
|
|
36
41
|
|
|
37
42
|
## Usage:
|
|
38
43
|
|
|
39
|
-
mock-service-cli [
|
|
44
|
+
mock-service-cli [options] [path]
|
|
40
45
|
|
|
41
46
|
`[path]` defaults to `./mock` .
|
|
42
47
|
|
|
@@ -48,6 +53,7 @@ This will install `mock-service-cli` globally so that it may be run from the com
|
|
|
48
53
|
| `-s` or `--silent` | Suppress log messages from output | |
|
|
49
54
|
| `-h` or `--help` | Print this list and exit. | |
|
|
50
55
|
| `-v` or `--version` | Print the version and exit. | |
|
|
56
|
+
| `--ss` | Start socket server which used to save api response data for future mock. | false |
|
|
51
57
|
|
|
52
58
|
## Example
|
|
53
59
|
|
|
@@ -63,28 +69,113 @@ This will install `mock-service-cli` globally so that it may be run from the com
|
|
|
63
69
|
|
|
64
70
|
`mock-service-cli -f ./mock/test.js`
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
4. Start socket server for used to save api response data.
|
|
73
|
+
|
|
74
|
+
`mock-service-cli --ss`
|
|
75
|
+
|
|
76
|
+
### 编写 Mock 文件
|
|
67
77
|
|
|
68
|
-
>
|
|
78
|
+
> mock 请求的类型支持`GET`,`POST`,`PUT`,`DELETE`,`PATCH`,`OPTIONS`,`COPY`,`LINK`,`UNLINK`,`PURGE`
|
|
79
|
+
|
|
80
|
+
如果 `./mock/test.js` 的内容如下,
|
|
69
81
|
|
|
70
82
|
```javascript
|
|
71
83
|
module.exports = {
|
|
72
|
-
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'/mock/
|
|
84
|
+
// GET 可忽略
|
|
85
|
+
'/mock/mock/api/test': { aa: 1, bb: '默认GET请求' },
|
|
86
|
+
'GET /mock/api/:id/test': { aa: 1, bb: '使用id占位符' },
|
|
87
|
+
'GET /mock/api/test1': { aa: 1, bb: '指定GET 方法' },
|
|
88
|
+
'POST /mock/api/test': { aa: 1, bb: 'POST 方法' },
|
|
89
|
+
'DELETE /mock/api/test': { aa: 1, bb: 'DELETE 方法' },
|
|
90
|
+
'PUT /mock/api/test': { aa: 1, bb: 'PUT 方法' },
|
|
91
|
+
'PATCH /mock/api/test': { aa: 1, bb: 'PATCH 方法' },
|
|
92
|
+
'OPTIONS /mock/api/test': { aa: 1, bb: 'OPTIONS 方法' },
|
|
93
|
+
'COPY /mock/api/test': { aa: 1, bb: 'COPY 方法' },
|
|
94
|
+
'LINK /mock/api/test': { aa: 1, bb: 'LINK 方法' },
|
|
95
|
+
'UNLINK /mock/api/test': { aa: 1, bb: 'UNLINK 方法' },
|
|
96
|
+
'PURGE /mock/api/test': { aa: 1, bb: 'PURGE 方法' },
|
|
97
|
+
// 支持自定义函数,API 参考 express@4
|
|
98
|
+
'/mock/api/video/test': (req, res) => {
|
|
99
|
+
// 添加特定请求头token
|
|
100
|
+
res.header('token', '5848778333359208');
|
|
84
101
|
res.json({ aa: 1, bb: 'asdf' });
|
|
85
|
-
},
|
|
86
|
-
'/mock/image/test': (req, res) => {
|
|
87
|
-
res.json({ aa: 1, bb: 'yyds' });
|
|
88
102
|
}
|
|
89
103
|
};
|
|
90
104
|
```
|
|
105
|
+
|
|
106
|
+
然后访问 `本地代理地址` + `/mock/api/222/test` (例如`http://127.0.0.1:8090/mock/api/222/test`) 就能得到 { aa: 1, bb: '使用 id 占位符' } 的响应,其他以此类推。
|
|
107
|
+
|
|
108
|
+
### 引入 Mock.js
|
|
109
|
+
|
|
110
|
+
[Mock.js](http://mockjs.com/) 是常用的辅助生成模拟数据的三方库,借助他可以提升我们的 mock 数据能力。
|
|
111
|
+
|
|
112
|
+
比如:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
const mockjs = require('mockjs');
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
// 使用 mockjs 等三方库
|
|
119
|
+
'GET /api/tags': mockjs.mock({
|
|
120
|
+
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }]
|
|
121
|
+
})
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 借助 socket.io 保留接口数据
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
// 连接SocketServer
|
|
129
|
+
import { io } from 'socket.io-client';
|
|
130
|
+
|
|
131
|
+
const socket = io.connect('http://{MockServerIP}:{PORT}/mock-data', {
|
|
132
|
+
transports: ['websocket'],
|
|
133
|
+
path: '/ws/mock-service'
|
|
134
|
+
});
|
|
135
|
+
// 向SocketServer发消息:保存接口数据
|
|
136
|
+
socket.emit('save-data', { url, method, data, dir });
|
|
137
|
+
// 向SocketServer发消息:获取指定目录的Mock统计信息
|
|
138
|
+
socket.emit('mock-dir-stat', dirPath);
|
|
139
|
+
// 监听SocketServer消息:获取指定目录的Mock统计信息
|
|
140
|
+
socket.on('mock-dir-stat', function (mockDirStat) {});
|
|
141
|
+
// 向SocketServer发消息:获取指定文件的Mock统计信息
|
|
142
|
+
socket.emit('mock-file-stat', filePath);
|
|
143
|
+
// 监听SocketServer消息:获取指定文件的Mock统计信息
|
|
144
|
+
socket.on('mock-file-stat', function (mockFileStat) {});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
在 axios.js 文件中完成保存所有接口响应数据、根据 Mock 数据池自动 Mock 的操作, 示例如下:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
import { io } from 'socket.io-client';
|
|
151
|
+
import axios from 'axios';
|
|
152
|
+
|
|
153
|
+
const socket = io.connect('http://192.168.31.54:8090/mock-data', {
|
|
154
|
+
transports: ['websocket'],
|
|
155
|
+
path: '/ws/mock-service',
|
|
156
|
+
query: {
|
|
157
|
+
token: 'yyds'
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
socket.emit('mock-dir-stat', '/home/chen/projects/isc-twin-model-ui/mock');
|
|
162
|
+
socket.on('mock-dir-stat', function (data) {
|
|
163
|
+
console.debug('mock-dir-stat:', data);
|
|
164
|
+
});
|
|
165
|
+
// 保存所有接口响应数据(也可过滤部分接口)
|
|
166
|
+
axios.interceptors.response.use(res => {
|
|
167
|
+
const {
|
|
168
|
+
config: { method, url },
|
|
169
|
+
data
|
|
170
|
+
} = res;
|
|
171
|
+
socket.emit('save-data', { url, method, data, dir: '/home/chen/projects/model-ui/mock' });
|
|
172
|
+
return res;
|
|
173
|
+
});
|
|
174
|
+
// 根据Mock数据池自动Mock(也可根据实际场景决定是否Mock)
|
|
175
|
+
axios.interceptors.request.use(config => {
|
|
176
|
+
if (Object.keys(mockObj).includes(`${config.method} ${config.url}`)) {
|
|
177
|
+
config.baseURL = 'http://localhost:8090';
|
|
178
|
+
}
|
|
179
|
+
return config;
|
|
180
|
+
});
|
|
181
|
+
```
|
package/bin/mock-service-cli
CHANGED
|
@@ -20,6 +20,7 @@ if (argv.h || argv.help) {
|
|
|
20
20
|
' -f --file Specify the input data source, support javascript ',
|
|
21
21
|
' The priority is over the directory watches',
|
|
22
22
|
' -s --silent Suppress log messages from output',
|
|
23
|
+
' --ss Whether start socket server or not, default false.',
|
|
23
24
|
'',
|
|
24
25
|
' -h --help Print this list and exit.',
|
|
25
26
|
' -v --version Print the version and exit.'
|
|
@@ -32,7 +33,8 @@ var port = argv.p || argv.port,
|
|
|
32
33
|
watchDir = argv.d,
|
|
33
34
|
specifiedFile = argv.f || argv.file,
|
|
34
35
|
version = argv.v || argv.version,
|
|
35
|
-
|
|
36
|
+
isStartSocketServer = argv.ss || false,
|
|
37
|
+
log = null;
|
|
36
38
|
|
|
37
39
|
if (!argv.s && !argv.silent) {
|
|
38
40
|
log = logger(false);
|
|
@@ -41,6 +43,10 @@ if (!argv.s && !argv.silent) {
|
|
|
41
43
|
log = logger(true);
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
if (isStartSocketServer) {
|
|
47
|
+
process.env.SOCKETSERVER = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
if (version) {
|
|
45
51
|
log.info('v' + require('../package.json').version);
|
|
46
52
|
process.exit();
|
|
@@ -64,9 +70,9 @@ const watchMockFiles = function (watchDir) {
|
|
|
64
70
|
* ext 检测的文件
|
|
65
71
|
*/
|
|
66
72
|
nodemon({
|
|
67
|
-
script: path.
|
|
73
|
+
script: path.resolve(__dirname, '../lib/mockServer.js'),
|
|
68
74
|
watch: [watchDir],
|
|
69
|
-
ext: 'js'
|
|
75
|
+
ext: 'js,json'
|
|
70
76
|
});
|
|
71
77
|
|
|
72
78
|
nodemon
|
|
@@ -86,14 +92,14 @@ const watchMockFiles = function (watchDir) {
|
|
|
86
92
|
};
|
|
87
93
|
|
|
88
94
|
if (specifiedFile) {
|
|
89
|
-
process.env.SPECIFIED_FILE = path.
|
|
95
|
+
process.env.SPECIFIED_FILE = path.resolve(process.cwd(), specifiedFile);
|
|
90
96
|
watchMockFiles(process.env.SPECIFIED_FILE);
|
|
91
97
|
} else if (watchDir) {
|
|
92
|
-
process.env.SPECIFIED_DIR = path.
|
|
98
|
+
process.env.SPECIFIED_DIR = path.resolve(process.cwd(), watchDir);
|
|
93
99
|
// log.info('SPECIFIED_DIR::', process.env.SPECIFIED_DIR);
|
|
94
100
|
watchMockFiles(process.env.SPECIFIED_DIR);
|
|
95
101
|
} else {
|
|
96
|
-
watchDir = process.env.SPECIFIED_DIR = path.
|
|
102
|
+
watchDir = process.env.SPECIFIED_DIR = path.resolve(process.cwd(), './mock');
|
|
97
103
|
// log.info('SPECIFIED_DIR::', process.env.SPECIFIED_DIR);
|
|
98
104
|
watchMockFiles(watchDir);
|
|
99
105
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @description: 生成Mock文件、获取mock数据统计
|
|
3
|
+
* @Date: 2021-12-22 16:57:08
|
|
4
|
+
* @LastEditors: chendq
|
|
5
|
+
* @LastEditTime: 2021-12-27 10:15:01
|
|
6
|
+
* @Author: chendq
|
|
7
|
+
*/
|
|
8
|
+
const fsa = require('fs-extra'),
|
|
9
|
+
fs = require('fs'),
|
|
10
|
+
path = require('path'),
|
|
11
|
+
_ = require('lodash'),
|
|
12
|
+
colors = require('colors/safe');
|
|
13
|
+
const { getFileLatestContent, SupportMethods, logger } = require('./utils');
|
|
14
|
+
const log = logger(process.env.SILENT);
|
|
15
|
+
|
|
16
|
+
const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
|
|
17
|
+
/**
|
|
18
|
+
* @description: 生成mock文件
|
|
19
|
+
* @param {string} apiUrl 请求url
|
|
20
|
+
* @param {string} method 请求方法
|
|
21
|
+
* @param {object} resJsonData 响应数据
|
|
22
|
+
* @param {string} mockDir mock目录
|
|
23
|
+
* @return {*} void
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
const genMockFiles = function ({ url: apiUrl, method, data: resJsonData, dir: mockDir }) {
|
|
27
|
+
const normalizeUrl = path.normalize(apiUrl);
|
|
28
|
+
const mockListFilePath = path.resolve(process.cwd(), `${mockDir}/mock-list.json`);
|
|
29
|
+
const mockFilePath = path.resolve(process.cwd(), `${mockDir}/${encodeURIComponent(normalizeUrl)}.json`);
|
|
30
|
+
let newMockListContent = {},
|
|
31
|
+
oldMockListContent = null;
|
|
32
|
+
if (fsa.pathExistsSync(mockListFilePath)) {
|
|
33
|
+
oldMockListContent = getFileLatestContent(mockListFilePath);
|
|
34
|
+
newMockListContent = _.cloneDeep(oldMockListContent);
|
|
35
|
+
} else {
|
|
36
|
+
fsa.ensureFileSync(mockListFilePath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// update mock-list.json file
|
|
40
|
+
if (!newMockListContent.hasOwnProperty(normalizeUrl)) {
|
|
41
|
+
newMockListContent[normalizeUrl] = [method];
|
|
42
|
+
} else if (!newMockListContent[normalizeUrl].includes(method)) {
|
|
43
|
+
newMockListContent[normalizeUrl].push(method);
|
|
44
|
+
}
|
|
45
|
+
if (!oldMockListContent || !_.isEqual(oldMockListContent, newMockListContent)) {
|
|
46
|
+
fsa.writeFileSync(mockListFilePath, JSON.stringify(newMockListContent, null, 2));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// mock文件存在
|
|
50
|
+
if (fsa.pathExistsSync(mockFilePath)) {
|
|
51
|
+
const mockFileContent = getFileLatestContent(mockFilePath);
|
|
52
|
+
// 内容一样不作修改
|
|
53
|
+
if (mockFileContent.hasOwnProperty(method) && _.isEqual(mockFileContent[method], resJsonData)) {
|
|
54
|
+
// log.assert(
|
|
55
|
+
// !_.isEqual(mockFileContent[method], resJsonData),
|
|
56
|
+
// colors.bgYellow(`${method} ${apiUrl}, Mock data is same`)
|
|
57
|
+
// );
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
mockFileContent[method] = resJsonData ? resJsonData : {};
|
|
61
|
+
fsa.writeFileSync(mockFilePath, JSON.stringify(mockFileContent, null, 2));
|
|
62
|
+
} else {
|
|
63
|
+
// fsa.ensureFileSync(mockFilePath);
|
|
64
|
+
fsa.writeFileSync(mockFilePath, JSON.stringify({ [method]: resJsonData ? resJsonData : {} }, null, 2));
|
|
65
|
+
}
|
|
66
|
+
// console.debug('file::', `${mockDir}/${normalizeUrl}`, newMockListContent);
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* @description: 从js文件中收集mock数据
|
|
70
|
+
* @param {map} mockDataMap
|
|
71
|
+
* @param {string} filePath
|
|
72
|
+
* @return {*} void
|
|
73
|
+
*/
|
|
74
|
+
const collectMockDataFromJsFile = function (mockDataMap, filePath) {
|
|
75
|
+
// 先删除require之前导入的缓存文件,否则获取到的文件内容还是旧内容
|
|
76
|
+
const fileObject = getFileLatestContent(filePath);
|
|
77
|
+
|
|
78
|
+
Object.keys(fileObject).forEach(item => {
|
|
79
|
+
let reqMethod = 'get',
|
|
80
|
+
reqUrl = item;
|
|
81
|
+
if (methodRegExp.exec(item)) {
|
|
82
|
+
const [, method, url] = methodRegExp.exec(item);
|
|
83
|
+
reqMethod = method.toLowerCase();
|
|
84
|
+
reqUrl = path.normalize(url);
|
|
85
|
+
}
|
|
86
|
+
if (typeof fileObject[item] === 'function') {
|
|
87
|
+
mockDataMap[`${reqMethod} ${reqUrl}`] = String(fileObject[item]);
|
|
88
|
+
} else if (typeof fileObject[item] === 'object') {
|
|
89
|
+
mockDataMap[`${reqMethod} ${reqUrl}`] = fileObject[item];
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @description: 递归收集mock数据
|
|
96
|
+
* @param {map} mockDataMap mock数据集
|
|
97
|
+
* @param {string} specialDir 目录
|
|
98
|
+
* @return {*} void
|
|
99
|
+
*/
|
|
100
|
+
const deepCollectMockData = function (mockDataMap, specialDir = '../mock') {
|
|
101
|
+
const files = fs.readdirSync(path.resolve(process.cwd(), specialDir), { withFileTypes: true });
|
|
102
|
+
const curFilesSize = files.length;
|
|
103
|
+
for (let index = 0; index < curFilesSize; index++) {
|
|
104
|
+
const el = files[index];
|
|
105
|
+
const filePath = path.normalize(`${specialDir}/${el.name}`);
|
|
106
|
+
// 递归目录
|
|
107
|
+
if (!el.isFile()) {
|
|
108
|
+
deepCollectMockData(mockDataMap, filePath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 处理存放mock数据的json文件
|
|
112
|
+
if (el.name.endsWith('.json') && el.name !== 'mock-list.json') {
|
|
113
|
+
const fileObject = getFileLatestContent(filePath);
|
|
114
|
+
Object.keys(fileObject).forEach(method => {
|
|
115
|
+
mockDataMap[`${method.toLocaleLowerCase()} ${decodeURIComponent(el.name).split('.json')[0]}`] =
|
|
116
|
+
fileObject[method];
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 非js文件跳过
|
|
122
|
+
if (!el.name.endsWith('.js')) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
collectMockDataFromJsFile(mockDataMap, filePath);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* @description: 从文件目录中获取mock数据统计
|
|
130
|
+
* @param {string} dirPath
|
|
131
|
+
* @return {map} mock数据统计
|
|
132
|
+
*/
|
|
133
|
+
const getMockStatFromDir = dirPath => {
|
|
134
|
+
const mockDataMap = {};
|
|
135
|
+
deepCollectMockData(mockDataMap, dirPath);
|
|
136
|
+
return mockDataMap;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @description: 从js文件中获取mock数据统计
|
|
141
|
+
* @param {string} filePath
|
|
142
|
+
* @return {map} mock数据统计
|
|
143
|
+
*/
|
|
144
|
+
const getMockStatFromFile = filePath => {
|
|
145
|
+
const mockDataMap = {};
|
|
146
|
+
collectMockDataFromJsFile(mockDataMap, filePath);
|
|
147
|
+
return mockDataMap;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* @description: 判断Mock服务中是否存在某个API
|
|
151
|
+
* @param {object} mockDataMap
|
|
152
|
+
* @param {string} apiUrl
|
|
153
|
+
* @param {string} method
|
|
154
|
+
* @return {boolean}
|
|
155
|
+
*/
|
|
156
|
+
const hasMockApi = function (mockDataMap, apiUrl, method) {
|
|
157
|
+
if (!mockDataMap || !(mockDataMap instanceof Object)) {
|
|
158
|
+
throw new Error(`如参无效,${mockDataMap} 必须是 mock数据的Object对象`);
|
|
159
|
+
}
|
|
160
|
+
return mockDataMap.hasOwnProperty(`${method.toLocaleLowerCase()} ${path.normalize(apiUrl)}`);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
genMockFiles,
|
|
165
|
+
getMockStatFromDir,
|
|
166
|
+
getMockStatFromFile,
|
|
167
|
+
hasMockApi
|
|
168
|
+
};
|
package/lib/mockServer.js
CHANGED
|
@@ -6,12 +6,15 @@ const express = require('express'), // 引入express
|
|
|
6
6
|
portfinder = require('portfinder'),
|
|
7
7
|
chalk = require('chalk');
|
|
8
8
|
const ifaces = os.networkInterfaces();
|
|
9
|
-
const {
|
|
9
|
+
const { Server } = require('socket.io');
|
|
10
|
+
const { dateFormat, logger, SupportMethods, getFileLatestContent } = require('./utils');
|
|
11
|
+
const { genMockFiles, getMockStatFromDir, getMockStatFromFile } = require('./manageMockFiles');
|
|
10
12
|
|
|
11
|
-
const methodRegExp = new RegExp('
|
|
13
|
+
const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
|
|
12
14
|
|
|
13
15
|
const app = express();
|
|
14
16
|
const log = logger(process.env.SILENT);
|
|
17
|
+
let socketServer = null; // Socket server instance
|
|
15
18
|
|
|
16
19
|
let count = 0,
|
|
17
20
|
fileCount = 0; // 记录mock api个数,mock file个数
|
|
@@ -42,7 +45,7 @@ const crossDomain = () => (req, res, next) => {
|
|
|
42
45
|
res.header('Access-Control-Allow-Credentials', true);
|
|
43
46
|
// res.header('Access-Control-Allow-Origin', 'http://localhost:8081');
|
|
44
47
|
res.header('Access-Control-Allow-Origin', '*');
|
|
45
|
-
res.header('Access-Control-Allow-Methods', '
|
|
48
|
+
res.header('Access-Control-Allow-Methods', SupportMethods.join(','));
|
|
46
49
|
// res.header(
|
|
47
50
|
// 'Access-Control-Allow-Headers',
|
|
48
51
|
// 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
|
|
@@ -59,8 +62,7 @@ const crossDomain = () => (req, res, next) => {
|
|
|
59
62
|
*/
|
|
60
63
|
const composeRoute = function (file) {
|
|
61
64
|
// 先删除require之前导入的缓存文件,否则获取到的文件内容还是旧内容
|
|
62
|
-
|
|
63
|
-
const fileObject = require(path.resolve(__dirname, file));
|
|
65
|
+
const fileObject = getFileLatestContent(file);
|
|
64
66
|
|
|
65
67
|
log.info(colors.green(`Mockfile ${++fileCount}: `), file, colors.yellow('all API URL is follows: '));
|
|
66
68
|
Object.keys(fileObject).forEach(item => {
|
|
@@ -73,6 +75,7 @@ const composeRoute = function (file) {
|
|
|
73
75
|
reqUrl = url;
|
|
74
76
|
}
|
|
75
77
|
log.info(colors.yellow(`path ${++count}: `), item, colors.blue(typeof fileObject[item]));
|
|
78
|
+
log.assert(typeof app[reqMethod] === 'function', colors.red(`${file}, ${reqMethod}`));
|
|
76
79
|
if (typeof fileObject[item] === 'function') {
|
|
77
80
|
app[reqMethod](reqUrl, fileObject[item]);
|
|
78
81
|
} else if (typeof fileObject[item] === 'object') {
|
|
@@ -84,26 +87,41 @@ const composeRoute = function (file) {
|
|
|
84
87
|
};
|
|
85
88
|
/**
|
|
86
89
|
* @description: 解析mock目录下的js文件,为构建路由请求做准备
|
|
87
|
-
* @param {
|
|
88
|
-
* @return {
|
|
90
|
+
* @param {string} specialDir 目录路径
|
|
91
|
+
* @return {void}
|
|
89
92
|
*/
|
|
90
|
-
const parseMockFiles = function (specialDir = '../mock'
|
|
91
|
-
const files = fs.readdirSync(path.resolve(
|
|
92
|
-
|
|
93
|
+
const parseMockFiles = function (specialDir = '../mock') {
|
|
94
|
+
const files = fs.readdirSync(path.resolve(process.cwd(), specialDir), { withFileTypes: true });
|
|
95
|
+
const curFilesSize = files.length;
|
|
96
|
+
for (let index = 0; index < curFilesSize; index++) {
|
|
93
97
|
const el = files[index];
|
|
94
|
-
|
|
98
|
+
const filePath = path.normalize(`${specialDir}/${el.name}`);
|
|
99
|
+
// 递归目录
|
|
95
100
|
if (!el.isFile()) {
|
|
96
|
-
parseMockFiles(
|
|
101
|
+
parseMockFiles(filePath);
|
|
97
102
|
}
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
// 处理存放mock数据的json文件
|
|
105
|
+
if (el.name.endsWith('.json') && el.name !== 'mock-list.json') {
|
|
106
|
+
log.info(colors.green(`Mockfile ${++fileCount}: `), filePath, colors.yellow('all API URL is follows: '));
|
|
107
|
+
const fileObject = getFileLatestContent(filePath);
|
|
108
|
+
// eslint-disable-next-line no-loop-func
|
|
109
|
+
Object.keys(fileObject).forEach(method => {
|
|
110
|
+
const apiUrl = decodeURIComponent(el.name.split('.')[0]);
|
|
111
|
+
log.info(colors.yellow(`path ${++count}: `), `${method} ${apiUrl}`, colors.blue(typeof fileObject[method]));
|
|
112
|
+
log.assert(typeof app[method] === 'function', colors.red(`${filePath}, ${method}`));
|
|
113
|
+
app[method](apiUrl, function (req, res) {
|
|
114
|
+
res.json(fileObject[method]);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
100
117
|
continue;
|
|
101
118
|
}
|
|
102
|
-
composeRoute(`${specialDir}/${el.name}`);
|
|
103
|
-
}
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
// 非js文件跳过
|
|
121
|
+
if (!el.name.endsWith('.js')) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
composeRoute(filePath);
|
|
107
125
|
}
|
|
108
126
|
};
|
|
109
127
|
app.use(crossDomain());
|
|
@@ -112,8 +130,33 @@ if (process.env.SPECIFIED_FILE) {
|
|
|
112
130
|
} else {
|
|
113
131
|
parseMockFiles(process.env.SPECIFIED_DIR);
|
|
114
132
|
}
|
|
133
|
+
log.info(chalk.hex('#ce5b34bd')(`${fileCount} mock file are parsed in total.`));
|
|
134
|
+
const http = require('http').createServer(app);
|
|
135
|
+
if (process.env.SOCKETSERVER) {
|
|
136
|
+
socketServer = new Server(http, { path: '/ws/mock-service' });
|
|
137
|
+
log.info(colors.bgBlue(`Socket server has started. path: /ws/mock-service, `));
|
|
138
|
+
socketServer.of('/mock-data').on('connection', function (socket) {
|
|
139
|
+
const { headers, address } = socket.handshake;
|
|
140
|
+
const clientIp = headers.hasOwnProperty('x-forwarded-for') ? headers['x-forwarded-for'] : address;
|
|
141
|
+
log.info(colors.green(`Socket client ${clientIp} has connected.`));
|
|
142
|
+
socket.on('mock-dir-stat', function (dir) {
|
|
143
|
+
socket.emit('mock-dir-stat', getMockStatFromDir(dir));
|
|
144
|
+
});
|
|
145
|
+
socket.on('mock-file-stat', function (filePath) {
|
|
146
|
+
socket.emit('mock-file-stat', getMockStatFromFile(filePath));
|
|
147
|
+
});
|
|
148
|
+
socket.on('save-data', function (data) {
|
|
149
|
+
// console.log('lis save-data:', data);
|
|
150
|
+
genMockFiles(data);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
socket.on('disconnect', function () {
|
|
154
|
+
log.info(colors.gray(`Socket client ${clientIp} has disconnected.`));
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
115
158
|
|
|
116
|
-
|
|
159
|
+
http.listen(Number.parseInt(process.env.PORT, 10), () => {
|
|
117
160
|
console.info(
|
|
118
161
|
[
|
|
119
162
|
colors.yellow(`\n${process.env.RESTARTED ? 'Restarting' : 'Starting'} up mock-server, serving `),
|
package/lib/utils.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @param {*}
|
|
4
4
|
* @return {*}
|
|
5
5
|
*/
|
|
6
|
+
const path = require('path');
|
|
6
7
|
const getLogger = function () {
|
|
7
8
|
const fs = require('fs');
|
|
8
9
|
const output = fs.createWriteStream('./stdout.log');
|
|
@@ -32,6 +33,7 @@ function dateFormat(fmt, date) {
|
|
|
32
33
|
'S+': date.getSeconds().toString() // 秒
|
|
33
34
|
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
|
34
35
|
};
|
|
36
|
+
// eslint-disable-next-line guard-for-in
|
|
35
37
|
for (const k in opt) {
|
|
36
38
|
ret = new RegExp('(' + k + ')').exec(fmt);
|
|
37
39
|
if (ret) {
|
|
@@ -46,17 +48,35 @@ function dateFormat(fmt, date) {
|
|
|
46
48
|
* @return {*}
|
|
47
49
|
*/
|
|
48
50
|
function logger(isSilent = false) {
|
|
49
|
-
let
|
|
51
|
+
let logObj = null;
|
|
50
52
|
if (!isSilent) {
|
|
51
|
-
|
|
52
|
-
info: console.log
|
|
53
|
+
logObj = {
|
|
54
|
+
info: console.log,
|
|
55
|
+
assert: console.assert
|
|
53
56
|
};
|
|
54
57
|
} else {
|
|
55
|
-
|
|
56
|
-
info: function () {}
|
|
58
|
+
logObj = {
|
|
59
|
+
info: function () {},
|
|
60
|
+
assert: function () {}
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
|
-
return
|
|
63
|
+
return logObj;
|
|
60
64
|
}
|
|
65
|
+
// MockServer 支持的请求类型
|
|
66
|
+
const SupportMethods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS', 'COPY', 'LINK', 'UNLINK', 'PURGE'];
|
|
67
|
+
|
|
68
|
+
const isValidMethod = function (m) {
|
|
69
|
+
return SupportMethods.includes(String(m).toLocaleUpperCase());
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @description: 获取文件最新内容
|
|
74
|
+
* @param {string} filePath 文件路径
|
|
75
|
+
* @return {object} 文件内容
|
|
76
|
+
*/
|
|
77
|
+
const getFileLatestContent = function (filePath) {
|
|
78
|
+
delete require.cache[require.resolve(filePath)];
|
|
79
|
+
return require(path.resolve(process.cwd(), filePath));
|
|
80
|
+
};
|
|
61
81
|
|
|
62
|
-
module.exports = { getLogger, dateFormat, logger };
|
|
82
|
+
module.exports = { getLogger, dateFormat, logger, SupportMethods, isValidMethod, getFileLatestContent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mock-service-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "🦅 Local mock server",
|
|
5
5
|
"main": "./lib/mockServer.js",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"doc"
|
|
27
27
|
],
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=12"
|
|
30
30
|
},
|
|
31
31
|
"author": "chendq <deqiaochen@gmail.com>",
|
|
32
32
|
"keywords": [
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"Mock",
|
|
35
35
|
"MockServer",
|
|
36
36
|
"mock-service-cli",
|
|
37
|
-
"CLI"
|
|
37
|
+
"CLI",
|
|
38
|
+
"Socket server",
|
|
39
|
+
"socket.io"
|
|
38
40
|
],
|
|
39
41
|
"repository": {
|
|
40
42
|
"type": "git",
|
|
@@ -46,9 +48,12 @@
|
|
|
46
48
|
"colors": "^1.4.0",
|
|
47
49
|
"deasync": "^0.1.23",
|
|
48
50
|
"express": "^4.17.1",
|
|
51
|
+
"fs-extra": "^10.0.0",
|
|
52
|
+
"lodash": "^4.17.21",
|
|
49
53
|
"minimist": "^1.2.5",
|
|
50
54
|
"nodemon": "^2.0.14",
|
|
51
|
-
"portfinder": "^1.0.28"
|
|
55
|
+
"portfinder": "^1.0.28",
|
|
56
|
+
"socket.io": "^4.4.0"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
59
|
"@commitlint/cli": "^8.3.5",
|