mock-service-cli 1.2.1 → 2.0.3
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 +93 -0
- package/README.md +72 -4
- package/bin/mock-service-cli +12 -6
- package/lib/manageMockFiles.js +168 -0
- package/lib/mockServer.js +64 -21
- package/lib/utils.js +50 -7
- package/package.json +9 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
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.3](https://github.com/chandq/mock-service-cli/compare/v2.0.2...v2.0.3) (2021-12-27)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* convert windows file path to api url ([1f15b33](https://github.com/chandq/mock-service-cli/commit/1f15b334ab5374bc102e5e11fbfaae994569bca5))
|
|
11
|
+
|
|
12
|
+
### [2.0.2](https://github.com/chandq/mock-service-cli/compare/v2.0.1...v2.0.2) (2021-12-27)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* issue of convert path ([5276488](https://github.com/chandq/mock-service-cli/commit/527648866f20fd25b5df0dc55b3898d7e7a1ad2c))
|
|
18
|
+
|
|
19
|
+
### [2.0.1](https://github.com/chandq/mock-service-cli/compare/v2.0.0...v2.0.1) (2021-12-27)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* issue of executed failed under windows ([e160753](https://github.com/chandq/mock-service-cli/commit/e16075307e9fec8efcce464004b0881884f68ab8))
|
|
25
|
+
|
|
26
|
+
## [2.0.0](https://github.com/chandq/mock-service-cli/compare/v1.2.1...v2.0.0) (2021-12-25)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
* 新增根据api自动生成mock文件、获取mock统计数据的工具函数 ([b22cb6f](https://github.com/chandq/mock-service-cli/commit/b22cb6fa15787a1343ec6d11606d2a59a670171b))
|
|
32
|
+
* add c/s communication base on socket.io ([d0ce542](https://github.com/chandq/mock-service-cli/commit/d0ce5422ece5f7c36d6e74f06620b54096e84f73))
|
|
33
|
+
* add hasMockApi function ([e14231b](https://github.com/chandq/mock-service-cli/commit/e14231b8385835b59f4e0960b37b4379e4853155))
|
|
34
|
+
* add mock-dir-stat mock-file-stat event ([8d8b1fa](https://github.com/chandq/mock-service-cli/commit/8d8b1faf130c84fe35bf205e68ca9634ee8ff767))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
* logical judge issue ([1e40b13](https://github.com/chandq/mock-service-cli/commit/1e40b134cfcbfd908346ed701cfd18c39502153b))
|
|
40
|
+
* mock-list.json未及时更新的问题 ([4bb42f5](https://github.com/chandq/mock-service-cli/commit/4bb42f506ca1bab6958554ba6043a721bc296555))
|
|
41
|
+
|
|
42
|
+
### [1.2.1](https://github.com/chandq/mock-service-cli/compare/v1.2.0...v1.2.1) (2021-11-28)
|
|
43
|
+
|
|
44
|
+
## [1.2.0](https://github.com/chandq/mock-service-cli/compare/v1.1.1...v1.2.0) (2021-11-27)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* 支持除get请求之外的其他所有常用类型 ([a1bd549](https://github.com/chandq/mock-service-cli/commit/a1bd549e24da958178a3b1244c4b2f472dfcc643))
|
|
50
|
+
|
|
51
|
+
### [1.1.1](https://github.com/chandq/mock-service-cli/compare/v1.1.0...v1.1.1) (2021-10-27)
|
|
52
|
+
|
|
53
|
+
## [1.1.0](https://github.com/chandq/mock-service-cli/compare/v1.0.2...v1.1.0) (2021-10-27)
|
|
54
|
+
|
|
55
|
+
### [1.0.2](https://github.com/chandq/mock-service-cli/compare/v1.0.1...v1.0.2) (2021-10-26)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Bug Fixes
|
|
59
|
+
|
|
60
|
+
* recursively through subdirectories ([d11813c](https://github.com/chandq/mock-service-cli/commit/d11813c48175550c6592e52f2fd1abe1c63f8203))
|
|
61
|
+
|
|
62
|
+
### [1.0.1](https://github.com/chandq/mock-service-cli/compare/v1.0.0...v1.0.1) (2021-10-25)
|
|
63
|
+
|
|
64
|
+
## [1.0.0](https://github.com/chandq/mock-service-cli/compare/v0.2.1...v1.0.0) (2021-10-25)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Bug Fixes
|
|
68
|
+
|
|
69
|
+
* default mock directory issue ([064cd90](https://github.com/chandq/mock-service-cli/commit/064cd903cbc891eaa068ac3c38606e37ec529257))
|
|
70
|
+
* make silent argument take effect ([5aa472b](https://github.com/chandq/mock-service-cli/commit/5aa472b34cee4b93683a3cc282bba0b9ad52048e))
|
|
71
|
+
|
|
72
|
+
### [0.2.1](https://github.com/chandq/mock-service-cli/compare/v0.1.1...v0.2.1) (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.2.0](https://github.com/chandq/mock-service-cli/compare/v0.1.1...v0.2.0) (2021-10-22)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### Bug Fixes
|
|
83
|
+
|
|
84
|
+
* 纠正devDep和deps的包分类 ([25cf330](https://github.com/chandq/mock-service-cli/commit/25cf3308337220210a2daccaf9d09444eddb1b24))
|
|
85
|
+
|
|
86
|
+
### [0.1.1](https://github.com/chandq/mock-service-cli/compare/v0.1.0...v0.1.1) (2021-10-22)
|
|
87
|
+
|
|
88
|
+
## 0.1.0 (2021-10-22)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
### Features
|
|
92
|
+
|
|
93
|
+
* 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
|
-
# 🦅 一个基于 [node](https://nodejs.org/en/) 和 [express](https://www.expressjs.com.cn/) 的
|
|
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
|
|
|
@@ -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,6 +69,10 @@ 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
|
|
|
72
|
+
4. Start socket server for used to save api response data.
|
|
73
|
+
|
|
74
|
+
`mock-service-cli --ss`
|
|
75
|
+
|
|
66
76
|
### 编写 Mock 文件
|
|
67
77
|
|
|
68
78
|
> mock 请求的类型支持`GET`,`POST`,`PUT`,`DELETE`,`PATCH`,`OPTIONS`,`COPY`,`LINK`,`UNLINK`,`PURGE`
|
|
@@ -87,7 +97,7 @@ module.exports = {
|
|
|
87
97
|
// 支持自定义函数,API 参考 express@4
|
|
88
98
|
'/mock/api/video/test': (req, res) => {
|
|
89
99
|
// 添加特定请求头token
|
|
90
|
-
res.
|
|
100
|
+
res.header('token', '5848778333359208');
|
|
91
101
|
res.json({ aa: 1, bb: 'asdf' });
|
|
92
102
|
}
|
|
93
103
|
};
|
|
@@ -111,3 +121,61 @@ module.exports = {
|
|
|
111
121
|
})
|
|
112
122
|
};
|
|
113
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, filePath2ApiUrl } = 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 normalizeApiUrl = filePath2ApiUrl(path.normalize(apiUrl));
|
|
28
|
+
const mockListFilePath = path.resolve(process.cwd(), `${mockDir}/mock-list.json`);
|
|
29
|
+
const mockFilePath = path.resolve(process.cwd(), `${mockDir}/${encodeURIComponent(path.normalize(apiUrl))}.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(normalizeApiUrl)) {
|
|
41
|
+
newMockListContent[normalizeApiUrl] = [method];
|
|
42
|
+
} else if (!newMockListContent[normalizeApiUrl].includes(method)) {
|
|
43
|
+
newMockListContent[normalizeApiUrl].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}/${apiUrl}`, 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'
|
|
@@ -57,10 +60,9 @@ const crossDomain = () => (req, res, next) => {
|
|
|
57
60
|
* @param {*} file
|
|
58
61
|
* @returns {*}
|
|
59
62
|
*/
|
|
60
|
-
const
|
|
63
|
+
const composeRouteFromJsFile = 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,36 +87,76 @@ 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
|
+
composeRouteFromJsFile(filePath);
|
|
107
125
|
}
|
|
108
126
|
};
|
|
109
127
|
app.use(crossDomain());
|
|
110
128
|
if (process.env.SPECIFIED_FILE) {
|
|
111
|
-
|
|
129
|
+
composeRouteFromJsFile(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 `),
|
|
@@ -158,6 +201,6 @@ process.on('SIGTERM', function () {
|
|
|
158
201
|
});
|
|
159
202
|
|
|
160
203
|
module.exports = {
|
|
161
|
-
|
|
204
|
+
composeRouteFromJsFile,
|
|
162
205
|
parseMockFiles
|
|
163
206
|
};
|
package/lib/utils.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @description:
|
|
3
|
+
* @Date: 2021-12-25 17:52:48
|
|
4
|
+
* @LastEditors: chendq
|
|
5
|
+
* @LastEditTime: 2021-12-27 13:12:16
|
|
6
|
+
* @Author: chendq
|
|
7
|
+
*/
|
|
1
8
|
/**
|
|
2
9
|
* @description: 输出和错误输出写入不同文件
|
|
3
10
|
* @param {*}
|
|
4
11
|
* @return {*}
|
|
5
12
|
*/
|
|
13
|
+
const path = require('path');
|
|
6
14
|
const getLogger = function () {
|
|
7
15
|
const fs = require('fs');
|
|
8
16
|
const output = fs.createWriteStream('./stdout.log');
|
|
@@ -32,6 +40,7 @@ function dateFormat(fmt, date) {
|
|
|
32
40
|
'S+': date.getSeconds().toString() // 秒
|
|
33
41
|
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
|
34
42
|
};
|
|
43
|
+
// eslint-disable-next-line guard-for-in
|
|
35
44
|
for (const k in opt) {
|
|
36
45
|
ret = new RegExp('(' + k + ')').exec(fmt);
|
|
37
46
|
if (ret) {
|
|
@@ -46,17 +55,51 @@ function dateFormat(fmt, date) {
|
|
|
46
55
|
* @return {*}
|
|
47
56
|
*/
|
|
48
57
|
function logger(isSilent = false) {
|
|
49
|
-
let
|
|
58
|
+
let logObj = null;
|
|
50
59
|
if (!isSilent) {
|
|
51
|
-
|
|
52
|
-
info: console.log
|
|
60
|
+
logObj = {
|
|
61
|
+
info: console.log,
|
|
62
|
+
assert: console.assert
|
|
53
63
|
};
|
|
54
64
|
} else {
|
|
55
|
-
|
|
56
|
-
info: function () {}
|
|
65
|
+
logObj = {
|
|
66
|
+
info: function () {},
|
|
67
|
+
assert: function () {}
|
|
57
68
|
};
|
|
58
69
|
}
|
|
59
|
-
return
|
|
70
|
+
return logObj;
|
|
60
71
|
}
|
|
72
|
+
// MockServer 支持的请求类型
|
|
73
|
+
const SupportMethods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS', 'COPY', 'LINK', 'UNLINK', 'PURGE'];
|
|
74
|
+
|
|
75
|
+
const isValidMethod = function (m) {
|
|
76
|
+
return SupportMethods.includes(String(m).toLocaleUpperCase());
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @description: 获取文件最新内容
|
|
81
|
+
* @param {string} filePath 文件路径
|
|
82
|
+
* @return {object} 文件内容
|
|
83
|
+
*/
|
|
84
|
+
const getFileLatestContent = function (filePath) {
|
|
85
|
+
delete require.cache[require.resolve(filePath)];
|
|
86
|
+
return require(path.resolve(process.cwd(), filePath));
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* @description: 文件路径转成API的url
|
|
90
|
+
* @param {string} filePath
|
|
91
|
+
* @return {string} apiUrl
|
|
92
|
+
*/
|
|
93
|
+
const filePath2ApiUrl = function (filePath) {
|
|
94
|
+
return filePath.split(path.sep).join('/');
|
|
95
|
+
};
|
|
61
96
|
|
|
62
|
-
module.exports = {
|
|
97
|
+
module.exports = {
|
|
98
|
+
getLogger,
|
|
99
|
+
dateFormat,
|
|
100
|
+
logger,
|
|
101
|
+
SupportMethods,
|
|
102
|
+
isValidMethod,
|
|
103
|
+
getFileLatestContent,
|
|
104
|
+
filePath2ApiUrl
|
|
105
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mock-service-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.3",
|
|
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",
|