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 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
  [![Coverage Status](https://coveralls.io/repos/github/chandq/mock-service-cli/badge.svg?branch=master)](https://coveralls.io/github/chandq/mock-service-cli?branch=master)
3
3
  [![mock-service-cli](https://img.shields.io/github/package-json/v/chandq/mock-service-cli?style=flat-square)](https://github.com/chandq/mock-service-cli)
4
4
  [![node](https://img.shields.io/badge/node-v14.0.0-blue)](https://nodejs.org/download/release/v14.0.0/)
5
- [![node](https://img.shields.io/badge/language-node-orange.svg)](https://nodejs.org/download/release/v14.0.0/)
5
+ [![node](https://img.shields.io/badge/language-node-orange.svg)](https://nodejs.org/download/release/v12.0.0/)
6
6
  [![license:MIT](https://img.shields.io/npm/l/vue.svg?sanitize=true)](https://github.com/chandq/mock-service-cli/blob/master/LICENSE.md)
7
7
  [![Downloads:?](https://img.shields.io/npm/dm/mock-service-cli.svg?sanitize=true)](https://npmcharts.com/compare/mock-service-cli?minimal=true)
8
8
 
9
- # 🦅 一个基于 [node](https://nodejs.org/en/) 和 [express](https://www.expressjs.com.cn/) 的 本地 Mock Server 命令行工具
9
+ # 🦅 一个基于 [node](https://nodejs.org/en/) 和 [express](https://www.expressjs.com.cn/) 的 轻量级 Mock 套件 命令行工具
10
10
 
11
11
  ### 简介
12
12
 
13
- **使用简单**、**零配置**、**0 秒启动**的本地命令行 Mock 服务器, 支持热更新,对于开发调试 mock 数据很实用,能提高前端开发者的开发效率。支持 `GET`,`POST`,`PUT`,`DELETE`,`PATCH`,`OPTIONS`,`COPY`,`LINK`,`UNLINK`,`PURGE` 等常用请求类型,对工程代码无任何侵入性,可能是本地最好用的 Mock 工具。
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.setHeader('token', '5848778333359208');
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
+ ```
@@ -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
- log;
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.join(__dirname, '../lib/mockServer.js'),
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.join(process.cwd(), specifiedFile);
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.join(process.cwd(), watchDir);
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.join(process.cwd(), './mock');
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 { dateFormat, logger } = require('./utils');
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('(GET|POST|PUT|DELETE|HEAD|PATCH|OPTIONS|COPY|LINK|UNLINK|PURGE) +(.*)', 'i');
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', 'GET,POST,PUT,DELETE,HEAD,PATCH,OPTIONS,COPY,LINK,UNLINK,PURGE');
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 composeRoute = function (file) {
63
+ const composeRouteFromJsFile = function (file) {
61
64
  // 先删除require之前导入的缓存文件,否则获取到的文件内容还是旧内容
62
- delete require.cache[require.resolve(file)];
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', isRootDir = true) {
91
- const files = fs.readdirSync(path.resolve(__dirname, specialDir), { withFileTypes: true });
92
- for (let index = 0; index < files.length; index++) {
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(`${specialDir}/${el.name}`, false);
101
+ parseMockFiles(filePath);
97
102
  }
98
103
 
99
- if (!el.name.endsWith('.js')) {
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
- if (isRootDir) {
106
- log.info(chalk.hex('#ce5b34bd')(`${files.length} mock file are parsed in total.`));
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
- composeRoute(process.env.SPECIFIED_FILE);
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
- app.listen(Number.parseInt(process.env.PORT, 10), () => {
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
- composeRoute,
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 logger = null;
58
+ let logObj = null;
50
59
  if (!isSilent) {
51
- logger = {
52
- info: console.log
60
+ logObj = {
61
+ info: console.log,
62
+ assert: console.assert
53
63
  };
54
64
  } else {
55
- logger = {
56
- info: function () {}
65
+ logObj = {
66
+ info: function () {},
67
+ assert: function () {}
57
68
  };
58
69
  }
59
- return logger;
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 = { getLogger, dateFormat, logger };
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": "1.2.1",
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": ">=14"
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",