mock-service-cli 2.3.1 → 2.4.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 CHANGED
@@ -2,6 +2,34 @@
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.4.2](https://github.com/chandq/mock-service-cli/compare/v2.4.1...v2.4.2) (2022-01-07)
6
+
7
+
8
+ ### Features
9
+
10
+ * 缓存mock统计信息 ([1ee87cd](https://github.com/chandq/mock-service-cli/commit/1ee87cd041e68b9f17b64e9e3cb906681cb855fc))
11
+
12
+ ### [2.4.1](https://github.com/chandq/mock-service-cli/compare/v2.4.0...v2.4.1) (2022-01-06)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * 修复无法同时保留相同url不同请求类型的响应数据 ([90326dc](https://github.com/chandq/mock-service-cli/commit/90326dc556760eb81efadd76b031366452c95d2b))
18
+
19
+ ## [2.4.0](https://github.com/chandq/mock-service-cli/compare/v2.3.2...v2.4.0) (2022-01-05)
20
+
21
+
22
+ ### Features
23
+
24
+ * 增加异步任务队列,移出不必要模块 ([c7f3608](https://github.com/chandq/mock-service-cli/commit/c7f3608e4d0185434154d6f8d84889f281147022))
25
+
26
+ ### [2.3.2](https://github.com/chandq/mock-service-cli/compare/v2.3.1...v2.3.2) (2022-01-02)
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * be sure to write file successfully ([55415e3](https://github.com/chandq/mock-service-cli/commit/55415e387af4d8232db2bede0a51dad1dca474b3))
32
+
5
33
  ### [2.3.1](https://github.com/chandq/mock-service-cli/compare/v2.2.0...v2.3.1) (2022-01-01)
6
34
 
7
35
 
package/README.md CHANGED
@@ -162,7 +162,7 @@ const socket = io.connect('http://192.168.31.54:8090/mock-data', {
162
162
  }
163
163
  });
164
164
 
165
- socket.emit('mock-dir-stat', '/home/chen/projects/isc-twin-model-ui/mock');
165
+ socket.emit('mock-dir-stat', '/home/chen/projects/model-ui/mock');
166
166
  socket.on('mock-dir-stat', function (data) {
167
167
  console.debug('mock-dir-stat:', data);
168
168
  });
@@ -0,0 +1,74 @@
1
+ /*
2
+ * @description: 异步任务队列
3
+ * @Date: 2022-01-04 10:46:20
4
+ * @LastEditors: chendq
5
+ * @LastEditTime: 2022-01-06 13:17:21
6
+ * @Author: chendq
7
+ */
8
+ // const { getDataType } = require('./utils');
9
+ class AsyncTaskQueue {
10
+ constructor(isSaveItem = false) {
11
+ this.list = [];
12
+ this.index = 0;
13
+ this.isStop = false;
14
+ this.isParallel = false;
15
+ this.isSaveItem = isSaveItem; // 执行完成后的数据是否保留
16
+ }
17
+
18
+ next() {
19
+ // 加限制
20
+ if (this.index >= this.list.length - 1 || this.isStop) return;
21
+ if (!this.isSaveItem) {
22
+ this.list.splice(this.index, 1, '');
23
+ }
24
+ const cur = this.list[++this.index];
25
+ cur(this.next.bind(this));
26
+ }
27
+ /**
28
+ * @description: 增加异步任务
29
+ * @param {array} fn
30
+ */
31
+ add(...fn) {
32
+ this.list.push(...fn);
33
+ }
34
+ /**
35
+ * @description: 按序执行异步任务
36
+ */
37
+ run() {
38
+ const cur = this.list[this.index];
39
+ typeof cur === 'function' && cur(this.next.bind(this));
40
+ }
41
+
42
+ /**
43
+ * @description: 并发执行异步任务
44
+ */
45
+ parallelRun() {
46
+ this.isParallel = true;
47
+ for (const fn of this.list) {
48
+ fn(this.next.bind(this));
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @description: 暂停执行异步任务
54
+ */
55
+ stop() {
56
+ this.isStop = true;
57
+ }
58
+
59
+ /**
60
+ * @description: 重试执行异步任务
61
+ */
62
+ retry() {
63
+ this.isStop = false;
64
+ run();
65
+ }
66
+ /**
67
+ * @description: 继续执行下一个异步任务
68
+ */
69
+ goOn() {
70
+ this.isStop = false;
71
+ this.next();
72
+ }
73
+ }
74
+ module.exports = AsyncTaskQueue;
@@ -2,7 +2,7 @@
2
2
  * @description: 生成Mock文件、获取mock数据统计
3
3
  * @Date: 2021-12-22 16:57:08
4
4
  * @LastEditors: chendq
5
- * @LastEditTime: 2022-01-01 10:51:12
5
+ * @LastEditTime: 2022-01-06 13:41:12
6
6
  * @Author: chendq
7
7
  */
8
8
  const fsa = require('fs-extra'),
@@ -21,9 +21,23 @@ const {
21
21
  } = require('./utils');
22
22
  const log = logger(process.env.SILENT);
23
23
  const processArgv = process.env.ARGV ? JSON.parse(process.env.ARGV) : {};
24
- let LOGGER = getLogger();
25
24
 
26
25
  const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
26
+ /**
27
+ * @description: 记录日志文件函数
28
+ * @param {boolean} isWriteLogFile 是否写入日志文件
29
+ * @param {string} mockDir 存放日志文件的目录
30
+ * @return {object}
31
+ */
32
+ const logFile = (isWriteLogFile = false, mockDir) => {
33
+ if (isWriteLogFile) {
34
+ return getLogger(mockDir);
35
+ }
36
+ return {
37
+ log: function () {},
38
+ error: function () {}
39
+ };
40
+ };
27
41
  /**
28
42
  * @description: 生成mock文件(自动生成mock-list.json文件,并维护<url, [method]>的关系映射)
29
43
  * @param {string} apiUrl 请求url
@@ -33,7 +47,7 @@ const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
33
47
  * @return {*} void
34
48
  *
35
49
  */
36
- const genMockFiles = function ({ url: apiUrl, method, data: resJsonData, dir: mockDir }) {
50
+ const genMockFiles = async function ({ url: apiUrl, method, data: resJsonData, dir: mockDir }) {
37
51
  const type = getDataType(resJsonData);
38
52
  if (!apiUrl || !method || !resJsonData || !mockDir) {
39
53
  log.info(
@@ -53,28 +67,29 @@ const genMockFiles = function ({ url: apiUrl, method, data: resJsonData, dir: mo
53
67
  return;
54
68
  }
55
69
  apiUrl = apiUrl.trim().split('?')[0];
56
- LOGGER = getLogger(mockDir);
70
+ const LOGGER = logFile(processArgv.l || processArgv.log, mockDir);
57
71
  const normalizeApiUrl = filePath2ApiUrl(path.normalize(apiUrl));
58
72
  const mockListFilePath = path.resolve(process.cwd(), `${mockDir}/mock-list.json`);
59
73
  const mockFilePath = path.resolve(process.cwd(), `${mockDir}/${encodeURIComponent(path.normalize(apiUrl))}.json`);
60
- let newMockListContent = {},
61
- oldMockListContent = null;
74
+ let newMockListContent = {};
62
75
  if (fsa.pathExistsSync(mockListFilePath)) {
63
- oldMockListContent = getFileLatestContent(mockListFilePath);
64
- newMockListContent = _.cloneDeep(oldMockListContent);
76
+ newMockListContent = getFileLatestContent(mockListFilePath);
65
77
  } else {
66
78
  fsa.ensureFileSync(mockListFilePath);
67
79
  }
68
80
 
81
+ let isChange = false;
69
82
  // update mock-list.json file
70
83
  if (!newMockListContent.hasOwnProperty(normalizeApiUrl)) {
71
84
  newMockListContent[normalizeApiUrl] = [method];
85
+ isChange = true;
72
86
  } else if (!newMockListContent[normalizeApiUrl].includes(method)) {
73
87
  newMockListContent[normalizeApiUrl].push(method);
88
+ isChange = true;
74
89
  }
75
- if (!oldMockListContent || !_.isEqual(oldMockListContent, newMockListContent)) {
90
+ if (isChange) {
76
91
  Object.keys(newMockListContent).length > 0 &&
77
- writeStream(mockListFilePath, JSON.stringify(newMockListContent, null, 2));
92
+ fs.writeFileSync(mockListFilePath, JSON.stringify(newMockListContent, null, 2));
78
93
  }
79
94
 
80
95
  // mock文件存在
@@ -89,17 +104,19 @@ const genMockFiles = function ({ url: apiUrl, method, data: resJsonData, dir: mo
89
104
  return;
90
105
  }
91
106
  mockFileContent[method] = resJsonData;
92
- writeStream(mockFilePath, JSON.stringify(mockFileContent, null, 2));
93
- if (processArgv.l || processArgv.log) {
94
- LOGGER.log(`Update file: ${method} ${apiUrl} ${mockFilePath}`);
107
+ try {
108
+ await writeStream(mockFilePath, JSON.stringify(mockFileContent, null, 2));
109
+ } catch (error) {
110
+ LOGGER.error(`Update file: ${method} ${apiUrl} ${mockFilePath}`);
95
111
  }
112
+ LOGGER.log(`Update file: ${method} ${apiUrl} ${mockFilePath}`);
96
113
  } else {
97
- // fsa.ensureFileSync(mockFilePath);
98
-
99
- writeStream(mockFilePath, JSON.stringify({ [method]: resJsonData }, null, 2));
100
- if (processArgv.l || processArgv.log) {
101
- LOGGER.log(`Write new file: ${method} ${apiUrl} ${mockFilePath}`);
114
+ try {
115
+ await writeStream(mockFilePath, JSON.stringify({ [method]: resJsonData }, null, 2));
116
+ } catch (error) {
117
+ LOGGER.error(`New file: ${method} ${apiUrl} ${mockFilePath}`);
102
118
  }
119
+ LOGGER.log(`New file: ${method} ${apiUrl} ${mockFilePath}`);
103
120
  }
104
121
  // console.debug('file::', `${mockDir}/${apiUrl}`, newMockListContent);
105
122
  };
@@ -170,7 +187,8 @@ const deepCollectMockData = function (mockDataMap, specialDir = '../mock') {
170
187
  */
171
188
  const getMockStatFromDir = dirPath => {
172
189
  if (!fsa.pathExistsSync(dirPath)) {
173
- throw new Error(`入参无效,${dirPath} 目录不存在`);
190
+ getLogger(path.resolve(process.cwd())).error(`入参无效,${dirPath} 目录不存在`);
191
+ return;
174
192
  }
175
193
  const mockDataMap = {};
176
194
  deepCollectMockData(mockDataMap, dirPath);
@@ -204,7 +222,8 @@ const getMockStatFromDir = dirPath => {
204
222
  */
205
223
  const getMockStatFromFile = filePath => {
206
224
  if (!fsa.pathExistsSync(filePath)) {
207
- throw new Error(`入参无效,${filePath} 文件不存在`);
225
+ getLogger(path.resolve(process.cwd())).error(`入参无效,${filePath} 文件不存在`);
226
+ return;
208
227
  }
209
228
  const mockDataMap = {};
210
229
  collectMockDataFromJsFile(mockDataMap, filePath);
@@ -219,7 +238,8 @@ const getMockStatFromFile = filePath => {
219
238
  */
220
239
  const hasMockApi = function (mockDataMap, apiUrl, method) {
221
240
  if (!apiUrl || !method || !mockDataMap || !(mockDataMap instanceof Object)) {
222
- throw new Error(`入参无效,${mockDataMap} 必须是 mock数据的Object对象`);
241
+ getLogger(path.resolve(process.cwd())).error(`入参无效,${mockDataMap} 必须是 mock数据的Object对象`);
242
+ return;
223
243
  }
224
244
  return mockDataMap.hasOwnProperty(`${method.toLocaleLowerCase()} ${path.normalize(apiUrl)}`);
225
245
  };
package/lib/mockServer.js CHANGED
@@ -7,7 +7,7 @@ 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, filePath2ApiUrl } = require('./utils');
10
+ const { dateFormat, logger, SupportMethods, getFileLatestContent, filePath2ApiUrl, isEmptyObj } = require('./utils');
11
11
  const { genMockFiles, getMockStatFromDir, getMockStatFromFile } = require('./manageMockFiles');
12
12
 
13
13
  const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i');
@@ -16,9 +16,13 @@ const app = express();
16
16
  const log = logger(process.env.SILENT);
17
17
  const argv = JSON.parse(process.env.ARGV);
18
18
  let socketServer = null; // Socket server instance
19
+ const AsyncTaskQueue = require('./asyncTaskQueue');
20
+ const saveDataAsyncTask = new AsyncTaskQueue();
19
21
 
20
22
  let count = 0,
21
- fileCount = 0; // 记录mock api个数,mock file个数
23
+ fileCount = 0, // 记录mock api个数,mock file个数
24
+ mockDirStat = {}, // 缓存mock目录的统计信息
25
+ mockFileStat = {}; // 缓存mock文件的统计信息
22
26
  let done = false;
23
27
  if (!process.env.PORT) {
24
28
  portfinder.basePort = 8090;
@@ -145,14 +149,27 @@ if (process.env.SOCKET_SERVER) {
145
149
  colors.green(`Socket client ${clientIp} has connected. --- ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`)
146
150
  );
147
151
  socket.on('mock-dir-stat', function (dir) {
148
- socket.emit('mock-dir-stat', getMockStatFromDir(dir));
152
+ socket.emit('mock-dir-stat', isEmptyObj(mockDirStat) ? mockDirStat = getMockStatFromDir(dir) : mockDirStat);
149
153
  });
150
154
  socket.on('mock-file-stat', function (filePath) {
151
- socket.emit('mock-file-stat', getMockStatFromFile(filePath));
155
+ socket.emit('mock-file-stat', isEmptyObj(mockFileStat) ? mockFileStat = getMockStatFromFile(filePath) : mockFileStat);
152
156
  });
157
+ const async = args => {
158
+ return async next => {
159
+ await genMockFiles(args);
160
+ next();
161
+ };
162
+ };
153
163
  socket.on('save-data', function (data) {
154
164
  // console.log('lis save-data:', data);
155
- genMockFiles(data);
165
+ saveDataAsyncTask.add(async(data));
166
+
167
+ if (saveDataAsyncTask.list.length === 1) {
168
+ saveDataAsyncTask.run();
169
+ } else {
170
+ saveDataAsyncTask.next();
171
+ }
172
+ // await genMockFiles(data);
156
173
  });
157
174
 
158
175
  socket.on('disconnect', function () {
package/lib/utils.js CHANGED
@@ -2,11 +2,12 @@
2
2
  * @description: 工具函数库
3
3
  * @Date: 2021-12-25 17:52:48
4
4
  * @LastEditors: chendq
5
- * @LastEditTime: 2021-12-31 23:10:02
5
+ * @LastEditTime: 2022-01-05 20:21:07
6
6
  * @Author: chendq
7
7
  */
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
+ // const JSONStream = require('JSONStream');
10
11
  /**
11
12
  * @description: 输出和错误输出写入不同文件
12
13
  * @param {string} logFileDirPath
@@ -18,11 +19,11 @@ const getLogger = function (logFileDirPath) {
18
19
  encoding: 'utf8' // utf8编码
19
20
  };
20
21
  const output = fs.createWriteStream(
21
- path.resolve(logFileDirPath ? logFileDirPath : process.cwd(), './stdout.log'),
22
+ path.resolve(logFileDirPath ? logFileDirPath : path.resolve(process.cwd()), './stdout.log'),
22
23
  options
23
24
  );
24
25
  const errorOutput = fs.createWriteStream(
25
- path.resolve(logFileDirPath ? logFileDirPath : process.cwd(), './stderr.log'),
26
+ path.resolve(logFileDirPath ? logFileDirPath : path.resolve(process.cwd()), './stderr.log'),
26
27
  options
27
28
  );
28
29
  // 自定义日志打印
@@ -38,9 +39,9 @@ const getLogger = function (logFileDirPath) {
38
39
  };
39
40
  /**
40
41
  * @description: 格式化时间日期
41
- * @param {*} fmt
42
- * @param {*} date
43
- * @returns {*}
42
+ * @param {string} fmt 时间日期字符串格式化模板
43
+ * @param {date} date 时间日期Date
44
+ * @returns {string}
44
45
  */
45
46
  function dateFormat(fmt, date = new Date()) {
46
47
  let ret;
@@ -65,8 +66,8 @@ function dateFormat(fmt, date = new Date()) {
65
66
  }
66
67
  /**
67
68
  * @description: 控制是否写日志
68
- * @param {*} isSilent
69
- * @return {*}
69
+ * @param {boolean} isSilent 是否静默
70
+ * @return {object}
70
71
  */
71
72
  function logger(isSilent = false) {
72
73
  let logObj = null;
@@ -96,8 +97,12 @@ const isValidMethod = function (m) {
96
97
  * @return {object} 文件内容
97
98
  */
98
99
  const getFileLatestContent = function (filePath) {
99
- delete require.cache[require.resolve(filePath)];
100
- return require(path.resolve(process.cwd(), filePath));
100
+ try {
101
+ delete require.cache[require.resolve(filePath)];
102
+ return require(path.resolve(process.cwd(), filePath));
103
+ } catch (error) {
104
+ getLogger(path.resolve(process.cwd())).error(filePath, require.cache[require.resolve(filePath)], error);
105
+ }
101
106
  };
102
107
  /**
103
108
  * @description: 文件路径转成API的url
@@ -115,24 +120,39 @@ const filePath2ApiUrl = function (filePath) {
115
120
  const getDataType = function (data) {
116
121
  return Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
117
122
  };
123
+ /**
124
+ * @description: 判断对象是否为空
125
+ * @param {object} obj
126
+ * @return {boolean}
127
+ */
128
+ const isEmptyObj = function (obj) {
129
+ return getDataType(obj) === 'object' && Object.keys(obj).length === 0;
130
+ };
118
131
  /**
119
132
  * @description: 流方式写文件(适合写超大文件)
120
133
  * @param {string} filePath
121
134
  * @param {object} data
122
- * @return {*}
135
+ * @return {promise}
123
136
  */
124
137
  const writeStream = function (filePath, data) {
125
138
  // 创建一个可写流 也会创建一个test1.txt文件
126
- const writerStream = fs.createWriteStream(filePath);
127
- // 将数据写入流
128
- writerStream.write(data, 'utf-8');
129
- // 标记文件的结束
130
- writerStream.end();
131
- writerStream.on('finish', () => {
132
- // console.log('写入完成');
133
- });
134
- writerStream.on('error', () => {
135
- console.error(`${filePath} 写入失败`);
139
+ return new Promise((resolve, reject) => {
140
+ const writerStream = fs.createWriteStream(filePath);
141
+ // 将数据写入流
142
+ writerStream.write(data, 'utf-8');
143
+ // 标记文件的结束
144
+ writerStream.end();
145
+ writerStream.on('finish', () => {});
146
+ writerStream.on('close', () => {
147
+ setTimeout(() => {
148
+ resolve(data);
149
+ });
150
+ });
151
+
152
+ writerStream.on('error', err => {
153
+ getLogger(path.resolve(process.cwd())).error(filePath, require.cache[require.resolve(filePath)], err);
154
+ reject(err);
155
+ });
136
156
  });
137
157
  };
138
158
  /**
@@ -140,7 +160,7 @@ const writeStream = function (filePath, data) {
140
160
  * @param {string} filePath
141
161
  * @return {*}
142
162
  */
143
- const readeStream = function (filePath) {
163
+ const readStream = function (filePath) {
144
164
  let data = '';
145
165
  // 创建可读流
146
166
  return new Promise((resovle, reject) => {
@@ -161,6 +181,99 @@ const readeStream = function (filePath) {
161
181
  });
162
182
  };
163
183
 
184
+ /**
185
+ * 防抖函数
186
+ * 当函数被连续调用时,该函数并不执行,只有当其全部停止调用超过一定时间后才执行1次。
187
+ * 例如:上电梯的时候,大家陆陆续续进来,电梯的门不会关上,只有当一段时间都没有人上来,电梯才会关门。
188
+ * @param {F} func
189
+ * @param {number} wait
190
+ * @returns {DebounceFunc<F>}
191
+ */
192
+ const debounce = (func, wait) => {
193
+ let timeout;
194
+ let canceled = false;
195
+ const f = function (...args) {
196
+ if (canceled) return;
197
+ clearTimeout(timeout);
198
+ timeout = setTimeout(() => {
199
+ func.call(this, ...args);
200
+ }, wait);
201
+ };
202
+ f.cancel = () => {
203
+ clearTimeout(timeout);
204
+ canceled = true;
205
+ };
206
+ return f;
207
+ };
208
+ /**
209
+ * 节流函数
210
+ * 节流就是节约流量,将连续触发的事件稀释成预设评率。 比如每间隔1秒执行一次函数,无论这期间触发多少次事件。
211
+ * 这有点像公交车,无论在站点等车的人多不多,公交车只会按时来一班,不会来一个人就来一辆公交车。
212
+ * @param {F} func
213
+ * @param {number} wait
214
+ * @param {boolean} immediate
215
+ * @returns {ThrottleFunc<F>}
216
+ */
217
+ const throttle = (func, wait, immediate) => {
218
+ let timeout;
219
+ let canceled = false;
220
+ let lastCalledTime = 0;
221
+ const f = function (...args) {
222
+ if (canceled) return;
223
+ const now = Date.now();
224
+ const call = () => {
225
+ lastCalledTime = now;
226
+ func.call(this, ...args);
227
+ };
228
+ // 第一次执行
229
+ if (lastCalledTime === 0) {
230
+ if (immediate) {
231
+ return call();
232
+ }
233
+
234
+ lastCalledTime = now;
235
+ return;
236
+ }
237
+ const remain = lastCalledTime + wait - now;
238
+ if (remain > 0) {
239
+ clearTimeout(timeout);
240
+ timeout = setTimeout(() => call(), wait);
241
+ } else {
242
+ call();
243
+ }
244
+ };
245
+ f.cancel = () => {
246
+ clearTimeout(timeout);
247
+ canceled = true;
248
+ };
249
+ return f;
250
+ };
251
+ // /**
252
+ // * @description: 带模糊搜索的分块读取大JSON文件
253
+ // * @param {string} filePath
254
+ // * @return {*}
255
+ // */
256
+ // const readBigJson = function (filePath) {
257
+ // return new Promise((resolve, reject) => {
258
+ // let res = '';
259
+ // const readable = fs.createReadStream(filePath, {
260
+ // encoding: 'utf8',
261
+ // highWaterMark: 10
262
+ // });
263
+ // const parser = JSONStream.parse('*');
264
+ // readable.pipe(parser);
265
+ // parser.on('end', function () {
266
+ // // I know it ends here,
267
+ // console.log('end::', res);
268
+ // resolve(res);
269
+ // });
270
+ // parser.on('data', function (data) {
271
+ // res += data;
272
+ // console.log('yy::', data);
273
+ // });
274
+ // });
275
+ // };
276
+
164
277
  module.exports = {
165
278
  getLogger,
166
279
  dateFormat,
@@ -170,6 +283,9 @@ module.exports = {
170
283
  getFileLatestContent,
171
284
  filePath2ApiUrl,
172
285
  getDataType,
286
+ isEmptyObj,
173
287
  writeStream,
174
- readeStream
288
+ readStream,
289
+ debounce,
290
+ throttle
175
291
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mock-service-cli",
3
- "version": "2.3.1",
3
+ "version": "2.4.2",
4
4
  "description": "🦅 Local mock server",
5
5
  "main": "./lib/mockServer.js",
6
6
  "bin": {