mock-service-cli 2.0.2 → 2.1.0

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 CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
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
4
 
5
+ ## [2.1.0](https://github.com/chandq/mock-service-cli/compare/v2.0.5...v2.1.0) (2021-12-30)
6
+
7
+
8
+ ### Features
9
+
10
+ * 根据命令行参数决定是否 打印api统计信息 ([0e838db](https://github.com/chandq/mock-service-cli/commit/0e838db7820fd096163d14eb87abbd9b852b6a0e))
11
+ * add arguments description ([097b373](https://github.com/chandq/mock-service-cli/commit/097b3733a1da0b43dae6b50080d270323b92f0d2))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * issue of comment typo ([85297a5](https://github.com/chandq/mock-service-cli/commit/85297a5895fd7c0446ee8b90a58ebed90a02e9e7))
17
+ * issue of maybe write content to file failed ([8ed910b](https://github.com/chandq/mock-service-cli/commit/8ed910b1e9687cae46ad6bdab641db0c305c83fb))
18
+ * socket connection show datetime ([c6db3b4](https://github.com/chandq/mock-service-cli/commit/c6db3b47a2370dc1c9aba5495dbb6606c20eb0c9))
19
+
20
+ ### [2.0.5](https://github.com/chandq/mock-service-cli/compare/v2.0.4...v2.0.5) (2021-12-28)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * validate func parameter to avoid exception ([32e4d28](https://github.com/chandq/mock-service-cli/commit/32e4d28114a4907352491e16829d37345fbab9d9))
26
+
27
+ ### [2.0.4](https://github.com/chandq/mock-service-cli/compare/v2.0.3...v2.0.4) (2021-12-27)
28
+
29
+
30
+ ### Bug Fixes
31
+
32
+ * get valid api url for route ([8770751](https://github.com/chandq/mock-service-cli/commit/877075107ea60244a52e345f37b7df855752d5f6))
33
+
34
+ ### [2.0.3](https://github.com/chandq/mock-service-cli/compare/v2.0.2...v2.0.3) (2021-12-27)
35
+
36
+
37
+ ### Bug Fixes
38
+
39
+ * convert windows file path to api url ([1f15b33](https://github.com/chandq/mock-service-cli/commit/1f15b334ab5374bc102e5e11fbfaae994569bca5))
40
+
5
41
  ### [2.0.2](https://github.com/chandq/mock-service-cli/compare/v2.0.1...v2.0.2) (2021-12-27)
6
42
 
7
43
 
package/README.md CHANGED
@@ -45,15 +45,16 @@ This will install `mock-service-cli` globally so that it may be run from the com
45
45
 
46
46
  `[path]` defaults to `./mock` .
47
47
 
48
- | Command | Description | Defaults |
49
- | ------------------- | -------------------------------------------------------------------------------------------------------------- | -------- |
50
- | `-p` or `--port` | Port to use. Use `-p 0` to look for an open port, starting at 8090. It will also read from `process.env.PORT`. | 8090 |
51
- | `-d` | Specify mock directory | ./mock |
52
- | `-f` | Specify mock file | |
53
- | `-s` or `--silent` | Suppress log messages from output | |
54
- | `-h` or `--help` | Print this list and exit. | |
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 |
48
+ | Command | Description | Defaults |
49
+ | ------------------------- | -------------------------------------------------------------------------------------------------------------- | -------- |
50
+ | `-p` or `--port` | Port to use. Use `-p 0` to look for an open port, starting at 8090. It will also read from `process.env.PORT`. | 8090 |
51
+ | `-d` | Specify mock directory | ./mock |
52
+ | `-f` | Specify mock file | |
53
+ | `-s` or `--silent` | Suppress log messages from output | |
54
+ | `-h` or `--help` | Print this list and exit. | |
55
+ | `-v` or `--version` | Print the version and exit. | |
56
+ | `-S` or `--socket-server` | Start socket server which used to save api response data for future mock. | false |
57
+ | `-a` or `--api-stat` | Whether print api url and file path info or not, default false. | false |
57
58
 
58
59
  ## Example
59
60
 
@@ -71,7 +72,7 @@ This will install `mock-service-cli` globally so that it may be run from the com
71
72
 
72
73
  4. Start socket server for used to save api response data.
73
74
 
74
- `mock-service-cli --ss`
75
+ `mock-service-cli -S`
75
76
 
76
77
  ### 编写 Mock 文件
77
78
 
@@ -124,6 +125,8 @@ module.exports = {
124
125
 
125
126
  ### 借助 socket.io 保留接口数据
126
127
 
128
+ > `save-data` 和 `mock-dir-stat` 事件会自动生成 `mock-list.json` 文件(维护接口`<url, [method]>`的关系映射),方便查阅接口统计信息
129
+
127
130
  ```js
128
131
  // 连接SocketServer
129
132
  import { io } from 'socket.io-client';
@@ -20,7 +20,8 @@ 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
+ ' -S --socket-server Whether start socket server or not, default false.',
24
+ ' -a --api-stat Whether print api url and file path or not, default false.',
24
25
  '',
25
26
  ' -h --help Print this list and exit.',
26
27
  ' -v --version Print the version and exit.'
@@ -29,11 +30,14 @@ if (argv.h || argv.help) {
29
30
  process.exit();
30
31
  }
31
32
 
33
+ // 保留命令行参数
34
+ process.env.ARGV = JSON.stringify(argv);
35
+
32
36
  var port = argv.p || argv.port,
33
37
  watchDir = argv.d,
34
38
  specifiedFile = argv.f || argv.file,
35
39
  version = argv.v || argv.version,
36
- isStartSocketServer = argv.ss || false,
40
+ isStartSocketServer = argv.S || false,
37
41
  log = null;
38
42
 
39
43
  if (!argv.s && !argv.silent) {
@@ -44,7 +48,7 @@ if (!argv.s && !argv.silent) {
44
48
  }
45
49
 
46
50
  if (isStartSocketServer) {
47
- process.env.SOCKETSERVER = true;
51
+ process.env.SOCKET_SERVER = true;
48
52
  }
49
53
 
50
54
  if (version) {
@@ -2,7 +2,7 @@
2
2
  * @description: 生成Mock文件、获取mock数据统计
3
3
  * @Date: 2021-12-22 16:57:08
4
4
  * @LastEditors: chendq
5
- * @LastEditTime: 2021-12-27 10:15:01
5
+ * @LastEditTime: 2021-12-30 13:29:00
6
6
  * @Author: chendq
7
7
  */
8
8
  const fsa = require('fs-extra'),
@@ -10,12 +10,12 @@ const fsa = require('fs-extra'),
10
10
  path = require('path'),
11
11
  _ = require('lodash'),
12
12
  colors = require('colors/safe');
13
- const { getFileLatestContent, SupportMethods, logger } = require('./utils');
13
+ const { getFileLatestContent, SupportMethods, filePath2ApiUrl, logger, getDataType } = require('./utils');
14
14
  const log = logger(process.env.SILENT);
15
15
 
16
16
  const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
17
17
  /**
18
- * @description: 生成mock文件
18
+ * @description: 生成mock文件(自动生成mock-list.json文件,并维护<url, [method]>的关系映射)
19
19
  * @param {string} apiUrl 请求url
20
20
  * @param {string} method 请求方法
21
21
  * @param {object} resJsonData 响应数据
@@ -24,9 +24,21 @@ const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
24
24
  *
25
25
  */
26
26
  const genMockFiles = function ({ url: apiUrl, method, data: resJsonData, dir: mockDir }) {
27
- const normalizeUrl = path.normalize(apiUrl);
27
+ if (!apiUrl || !method || !resJsonData || !mockDir) {
28
+ log.info(
29
+ colors.red(
30
+ [`参数: apiUrl: ${apiUrl}, method: ${method}, mockDir: ${mockDir}, resJsonData: `, `必须是有效值`].join(',')
31
+ )
32
+ );
33
+ return;
34
+ } else if (!['object', 'array'].includes(getDataType(resJsonData))) {
35
+ log.info(resJsonData, colors.red('参数resJsonData要求是数组或对象'));
36
+ return;
37
+ }
38
+ apiUrl = apiUrl.trim().split('?')[0];
39
+ const normalizeApiUrl = filePath2ApiUrl(path.normalize(apiUrl));
28
40
  const mockListFilePath = path.resolve(process.cwd(), `${mockDir}/mock-list.json`);
29
- const mockFilePath = path.resolve(process.cwd(), `${mockDir}/${encodeURIComponent(normalizeUrl)}.json`);
41
+ const mockFilePath = path.resolve(process.cwd(), `${mockDir}/${encodeURIComponent(path.normalize(apiUrl))}.json`);
30
42
  let newMockListContent = {},
31
43
  oldMockListContent = null;
32
44
  if (fsa.pathExistsSync(mockListFilePath)) {
@@ -37,13 +49,14 @@ const genMockFiles = function ({ url: apiUrl, method, data: resJsonData, dir: mo
37
49
  }
38
50
 
39
51
  // 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);
52
+ if (!newMockListContent.hasOwnProperty(normalizeApiUrl)) {
53
+ newMockListContent[normalizeApiUrl] = [method];
54
+ } else if (!newMockListContent[normalizeApiUrl].includes(method)) {
55
+ newMockListContent[normalizeApiUrl].push(method);
44
56
  }
45
57
  if (!oldMockListContent || !_.isEqual(oldMockListContent, newMockListContent)) {
46
- fsa.writeFileSync(mockListFilePath, JSON.stringify(newMockListContent, null, 2));
58
+ Object.keys(newMockListContent).length > 0 &&
59
+ fs.writeFileSync(mockListFilePath, JSON.stringify(newMockListContent, null, 2));
47
60
  }
48
61
 
49
62
  // mock文件存在
@@ -57,17 +70,19 @@ const genMockFiles = function ({ url: apiUrl, method, data: resJsonData, dir: mo
57
70
  // );
58
71
  return;
59
72
  }
60
- mockFileContent[method] = resJsonData ? resJsonData : {};
61
- fsa.writeFileSync(mockFilePath, JSON.stringify(mockFileContent, null, 2));
73
+ mockFileContent[method] = resJsonData;
74
+ Object.keys(mockFileContent).length > 0 &&
75
+ fsa.writeFileSync(mockFilePath, JSON.stringify(mockFileContent, null, 2));
62
76
  } else {
63
77
  // fsa.ensureFileSync(mockFilePath);
64
- fsa.writeFileSync(mockFilePath, JSON.stringify({ [method]: resJsonData ? resJsonData : {} }, null, 2));
78
+ Object.keys(mockFilePath).length > 0 &&
79
+ fsa.writeFileSync(mockFilePath, JSON.stringify({ [method]: resJsonData }, null, 2));
65
80
  }
66
- // console.debug('file::', `${mockDir}/${normalizeUrl}`, newMockListContent);
81
+ // console.debug('file::', `${mockDir}/${apiUrl}`, newMockListContent);
67
82
  };
68
83
  /**
69
84
  * @description: 从js文件中收集mock数据
70
- * @param {map} mockDataMap
85
+ * @param {object} mockDataMap
71
86
  * @param {string} filePath
72
87
  * @return {*} void
73
88
  */
@@ -93,7 +108,7 @@ const collectMockDataFromJsFile = function (mockDataMap, filePath) {
93
108
 
94
109
  /**
95
110
  * @description: 递归收集mock数据
96
- * @param {map} mockDataMap mock数据集
111
+ * @param {object} mockDataMap mock数据集
97
112
  * @param {string} specialDir 目录
98
113
  * @return {*} void
99
114
  */
@@ -126,22 +141,48 @@ const deepCollectMockData = function (mockDataMap, specialDir = '../mock') {
126
141
  }
127
142
  };
128
143
  /**
129
- * @description: 从文件目录中获取mock数据统计
144
+ * @description: 从文件目录中获取mock数据统计(若mock-list.json文件不存在则自动生成)
130
145
  * @param {string} dirPath
131
- * @return {map} mock数据统计
146
+ * @return {object} mock数据统计
132
147
  */
133
148
  const getMockStatFromDir = dirPath => {
149
+ if (!fsa.pathExistsSync(dirPath)) {
150
+ throw new Error(`入参无效,${dirPath} 目录不存在`);
151
+ }
134
152
  const mockDataMap = {};
135
153
  deepCollectMockData(mockDataMap, dirPath);
154
+
155
+ const mockListFilePath = path.resolve(process.cwd(), `${dirPath}/mock-list.json`);
156
+ // 若不存在mock-list.json文件,则根据现有mock文件重新生成<api, [method]>映射关系
157
+ if (Object.keys(mockDataMap).length > 0 && !fsa.pathExistsSync(mockListFilePath)) {
158
+ fsa.ensureFileSync(mockListFilePath);
159
+ const reverseMockList = {};
160
+ Object.keys(mockDataMap).forEach(it => {
161
+ if (methodRegExp.exec(it)) {
162
+ const [, method, url] = methodRegExp.exec(it);
163
+ if (url.trim() && method.trim()) {
164
+ if (reverseMockList.hasOwnProperty(url)) {
165
+ reverseMockList[url].push(method);
166
+ } else {
167
+ reverseMockList[url] = [method];
168
+ }
169
+ }
170
+ }
171
+ });
172
+ fs.writeFileSync(mockListFilePath, JSON.stringify(reverseMockList, null, 2));
173
+ }
136
174
  return mockDataMap;
137
175
  };
138
176
 
139
177
  /**
140
178
  * @description: 从js文件中获取mock数据统计
141
179
  * @param {string} filePath
142
- * @return {map} mock数据统计
180
+ * @return {object} mock数据统计
143
181
  */
144
182
  const getMockStatFromFile = filePath => {
183
+ if (!fsa.pathExistsSync(filePath)) {
184
+ throw new Error(`入参无效,${filePath} 文件不存在`);
185
+ }
145
186
  const mockDataMap = {};
146
187
  collectMockDataFromJsFile(mockDataMap, filePath);
147
188
  return mockDataMap;
@@ -154,8 +195,8 @@ const getMockStatFromFile = filePath => {
154
195
  * @return {boolean}
155
196
  */
156
197
  const hasMockApi = function (mockDataMap, apiUrl, method) {
157
- if (!mockDataMap || !(mockDataMap instanceof Object)) {
158
- throw new Error(`如参无效,${mockDataMap} 必须是 mock数据的Object对象`);
198
+ if (!apiUrl || !method || !mockDataMap || !(mockDataMap instanceof Object)) {
199
+ throw new Error(`入参无效,${mockDataMap} 必须是 mock数据的Object对象`);
159
200
  }
160
201
  return mockDataMap.hasOwnProperty(`${method.toLocaleLowerCase()} ${path.normalize(apiUrl)}`);
161
202
  };
package/lib/mockServer.js CHANGED
@@ -7,13 +7,14 @@ const express = require('express'), // 引入express
7
7
  chalk = require('chalk');
8
8
  const ifaces = os.networkInterfaces();
9
9
  const { Server } = require('socket.io');
10
- const { dateFormat, logger, SupportMethods, getFileLatestContent } = require('./utils');
10
+ const { dateFormat, logger, SupportMethods, getFileLatestContent, filePath2ApiUrl } = require('./utils');
11
11
  const { genMockFiles, getMockStatFromDir, getMockStatFromFile } = require('./manageMockFiles');
12
12
 
13
13
  const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
14
14
 
15
15
  const app = express();
16
16
  const log = logger(process.env.SILENT);
17
+ const argv = JSON.parse(process.env.ARGV);
17
18
  let socketServer = null; // Socket server instance
18
19
 
19
20
  let count = 0,
@@ -60,11 +61,11 @@ const crossDomain = () => (req, res, next) => {
60
61
  * @param {*} file
61
62
  * @returns {*}
62
63
  */
63
- const composeRoute = function (file) {
64
+ const composeRouteFromJsFile = function (file) {
64
65
  // 先删除require之前导入的缓存文件,否则获取到的文件内容还是旧内容
65
66
  const fileObject = getFileLatestContent(file);
66
67
 
67
- log.info(colors.green(`Mockfile ${++fileCount}: `), file, colors.yellow('all API URL is follows: '));
68
+ argv.a && log.info(colors.green(`Mockfile ${++fileCount}: `), file, colors.yellow('all API URL is follows: '));
68
69
  Object.keys(fileObject).forEach(item => {
69
70
  let reqMethod = 'get',
70
71
  reqUrl = item;
@@ -74,7 +75,7 @@ const composeRoute = function (file) {
74
75
  reqMethod = method.toLowerCase();
75
76
  reqUrl = url;
76
77
  }
77
- log.info(colors.yellow(`path ${++count}: `), item, colors.blue(typeof fileObject[item]));
78
+ argv.a && log.info(colors.yellow(`path ${++count}: `), item, colors.blue(typeof fileObject[item]));
78
79
  log.assert(typeof app[reqMethod] === 'function', colors.red(`${file}, ${reqMethod}`));
79
80
  if (typeof fileObject[item] === 'function') {
80
81
  app[reqMethod](reqUrl, fileObject[item]);
@@ -103,12 +104,14 @@ const parseMockFiles = function (specialDir = '../mock') {
103
104
 
104
105
  // 处理存放mock数据的json文件
105
106
  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
+ argv.a &&
108
+ log.info(colors.green(`Mockfile ${++fileCount}: `), filePath, colors.yellow('all API URL is follows: '));
107
109
  const fileObject = getFileLatestContent(filePath);
108
110
  // eslint-disable-next-line no-loop-func
109
111
  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
+ const apiUrl = filePath2ApiUrl(decodeURIComponent(el.name.split('.')[0]));
113
+ argv.a &&
114
+ log.info(colors.yellow(`path ${++count}: `), `${method} ${apiUrl}`, colors.blue(typeof fileObject[method]));
112
115
  log.assert(typeof app[method] === 'function', colors.red(`${filePath}, ${method}`));
113
116
  app[method](apiUrl, function (req, res) {
114
117
  res.json(fileObject[method]);
@@ -121,24 +124,26 @@ const parseMockFiles = function (specialDir = '../mock') {
121
124
  if (!el.name.endsWith('.js')) {
122
125
  continue;
123
126
  }
124
- composeRoute(filePath);
127
+ composeRouteFromJsFile(filePath);
125
128
  }
126
129
  };
127
130
  app.use(crossDomain());
128
131
  if (process.env.SPECIFIED_FILE) {
129
- composeRoute(process.env.SPECIFIED_FILE);
132
+ composeRouteFromJsFile(process.env.SPECIFIED_FILE);
130
133
  } else {
131
134
  parseMockFiles(process.env.SPECIFIED_DIR);
132
135
  }
133
- log.info(chalk.hex('#ce5b34bd')(`${fileCount} mock file are parsed in total.`));
136
+ argv.a && log.info(chalk.hex('#ce5b34bd')(`${fileCount} mock file are parsed in total.`));
134
137
  const http = require('http').createServer(app);
135
- if (process.env.SOCKETSERVER) {
138
+ if (process.env.SOCKET_SERVER) {
136
139
  socketServer = new Server(http, { path: '/ws/mock-service' });
137
140
  log.info(colors.bgBlue(`Socket server has started. path: /ws/mock-service, `));
138
141
  socketServer.of('/mock-data').on('connection', function (socket) {
139
142
  const { headers, address } = socket.handshake;
140
143
  const clientIp = headers.hasOwnProperty('x-forwarded-for') ? headers['x-forwarded-for'] : address;
141
- log.info(colors.green(`Socket client ${clientIp} has connected.`));
144
+ log.info(
145
+ colors.green(`Socket client ${clientIp} has connected. --- ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`)
146
+ );
142
147
  socket.on('mock-dir-stat', function (dir) {
143
148
  socket.emit('mock-dir-stat', getMockStatFromDir(dir));
144
149
  });
@@ -151,7 +156,11 @@ if (process.env.SOCKETSERVER) {
151
156
  });
152
157
 
153
158
  socket.on('disconnect', function () {
154
- log.info(colors.gray(`Socket client ${clientIp} has disconnected.`));
159
+ log.info(
160
+ colors.gray(
161
+ `Socket client ${clientIp} has disconnected. --- ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`
162
+ )
163
+ );
155
164
  });
156
165
  });
157
166
  }
@@ -201,6 +210,6 @@ process.on('SIGTERM', function () {
201
210
  });
202
211
 
203
212
  module.exports = {
204
- composeRoute,
213
+ composeRouteFromJsFile,
205
214
  parseMockFiles
206
215
  };
package/lib/utils.js CHANGED
@@ -1,3 +1,10 @@
1
+ /*
2
+ * @description:
3
+ * @Date: 2021-12-25 17:52:48
4
+ * @LastEditors: chendq
5
+ * @LastEditTime: 2021-12-30 13:17:12
6
+ * @Author: chendq
7
+ */
1
8
  /**
2
9
  * @description: 输出和错误输出写入不同文件
3
10
  * @param {*}
@@ -78,5 +85,30 @@ const getFileLatestContent = function (filePath) {
78
85
  delete require.cache[require.resolve(filePath)];
79
86
  return require(path.resolve(process.cwd(), filePath));
80
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
+ };
96
+ /**
97
+ * @description: 获取数据类型
98
+ * @param {any} data 数据值
99
+ * @return {string} 数据类型
100
+ */
101
+ const getDataType = function (data) {
102
+ return Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
103
+ };
81
104
 
82
- module.exports = { getLogger, dateFormat, logger, SupportMethods, isValidMethod, getFileLatestContent };
105
+ module.exports = {
106
+ getLogger,
107
+ dateFormat,
108
+ logger,
109
+ SupportMethods,
110
+ isValidMethod,
111
+ getFileLatestContent,
112
+ filePath2ApiUrl,
113
+ getDataType
114
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mock-service-cli",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "🦅 Local mock server",
5
5
  "main": "./lib/mockServer.js",
6
6
  "bin": {