mock-service-cli 3.7.0 → 4.0.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/lib/mockServer.js DELETED
@@ -1,497 +0,0 @@
1
- const express = require('express'), // 引入express
2
- bodyParser = require('body-parser'),
3
- { readdirSync, existsSync } = require('fs'),
4
- os = require('os'),
5
- path = require('path'),
6
- colors = require('colors/safe'),
7
- portfinder = require('portfinder'),
8
- { exec } = require('child_process');
9
- const { createProxyMiddleware } = require('http-proxy-middleware');
10
- const ifaces = os.networkInterfaces();
11
- const { Server } = require('socket.io');
12
- const {
13
- dateFormat,
14
- logger,
15
- SupportMethods,
16
- DefaultHeaders,
17
- getFileLatestContent,
18
- filePath2ApiUrl,
19
- isEmptyObj
20
- } = require('./utils');
21
- const { genMockFiles, getMockStatFromDir, getMockStatFromFile } = require('./manageMockFiles');
22
- const registeredApis = [];
23
- const methodRegExp = new RegExp(`((${SupportMethods.join('|')}) +)?([^?]*)`, 'i');
24
-
25
- const app = express();
26
- const log = logger(process.env.SILENT);
27
- const argv = JSON.parse(process.env.ARGV);
28
- let socketServer = null; // Socket server instance
29
- let corsOrigin = [],
30
- corsHeaders = '';
31
-
32
- const httpsRE = /^https:\/\//;
33
- let count = 0,
34
- fileCount = 0, // 记录mock api个数,mock file个数
35
- mockDirStat = {}, // 缓存mock目录的统计信息
36
- mockFileStat = {}; // 缓存mock文件的统计信息
37
-
38
- // whether include web serve or not
39
- const includesWebServe = process.env.STATIC_DIRECTORY || process.env.WEB_ROOT;
40
-
41
- const mockFileOrDir = process.env.SPECIFIED_FILE || process.env.SPECIFIED_DIR;
42
-
43
- if (!process.env.PORT) {
44
- portfinder.basePort = 8090;
45
- portfinder.getPort(function (err, port) {
46
- if (err) {
47
- throw err;
48
- }
49
- process.env.PORT = port;
50
- init();
51
- });
52
- } else {
53
- init();
54
- }
55
-
56
- /**
57
- * Init mock server
58
- */
59
- function init() {
60
- if (process.env.CORS_ORIGIN) {
61
- process.env.CORS_ORIGIN.split(/\s*,\s*/).forEach(function (h) {
62
- corsOrigin.push(h);
63
- }, this);
64
-
65
- corsHeaders = DefaultHeaders;
66
- }
67
- if (process.env.CORS_HEADERS) {
68
- process.env.CORS_HEADERS.split(/\s*,\s*/).forEach(function (h) {
69
- corsHeaders += corsHeaders ? ', ' + h : h;
70
- }, this);
71
- }
72
-
73
- app.use(crossDomain()); // 允许跨域
74
- app.use(bodyParser.json()); // 解析body
75
-
76
- // API 概览页面路由
77
- app.get('/__api-overview', (req, res) => {
78
- const htmlPath = path.resolve(__dirname, './api-overview.html');
79
- if (existsSync(htmlPath)) {
80
- res.sendFile(htmlPath);
81
- } else {
82
- res.status(404).send('API overview page not found');
83
- }
84
- });
85
-
86
- // API 数据路由
87
- app.get('/__api-data', (req, res) => {
88
- // 按目录分组 API
89
- const groupedApis = {};
90
- registeredApis.forEach(api => {
91
- // 从文件路径中提取相对路径作为目录
92
- const dirPath = path.dirname(api.file);
93
- if (!groupedApis[dirPath]) {
94
- groupedApis[dirPath] = [];
95
- }
96
- groupedApis[dirPath].push(api);
97
- });
98
-
99
- res.json({
100
- apis: registeredApis,
101
- groupedApis: groupedApis,
102
- port: process.env.PORT
103
- });
104
- });
105
-
106
- let requireMockServe = true;
107
- if (process.env.SPECIFIED_FILE) {
108
- composeRouteFromJsFile(process.env.SPECIFIED_FILE);
109
- } else if (includesWebServe && !existsSync(path.resolve(process.cwd(), process.env.SPECIFIED_DIR))) {
110
- // 若包含web服务,则允许不启动mock服务
111
- requireMockServe = false;
112
- } else {
113
- parseMockFiles(process.env.SPECIFIED_DIR);
114
- }
115
- requireMockServe && argv.a && log.info(colors.yellow(`${fileCount} mock file are parsed in total.`));
116
- startServer();
117
- }
118
-
119
- /**
120
- * @description: 跨域设置
121
- * @param {*}
122
- * @return {*}
123
- */
124
- function crossDomain() {
125
- return (req, res, next) => {
126
- if (typeof argv.A === 'string') {
127
- const resHeaders = {};
128
- argv.A.split(/\s*,\s*/).forEach(function (h) {
129
- const [key, value] = h.split('=');
130
- resHeaders[key] = value;
131
- }, this);
132
- // 添加自定义响应头
133
- for (const key in resHeaders) {
134
- if (resHeaders.hasOwnProperty(key)) {
135
- res.header(key, resHeaders[key]);
136
- }
137
- }
138
- }
139
-
140
- // 设置withCredentials: true时,需设置以下两项
141
- res.header('Access-Control-Allow-Credentials', true);
142
- // withCredentials, must be specified value instead of *
143
- res.header('Access-Control-Allow-Origin', corsOrigin.includes(req.headers.origin) ? req.headers.origin : '*');
144
- // res.header('Access-Control-Allow-Origin', '*');
145
- res.header('Access-Control-Allow-Methods', SupportMethods.join(','));
146
- res.header('Access-Control-Allow-Headers', corsHeaders ? corsHeaders : '*');
147
- // res.header('Access-Control-Allow-Headers', '*');
148
- if (req.method === 'OPTIONS') res.status(200); // 让OPTIONS快速返回
149
- next();
150
- };
151
- }
152
-
153
- /**
154
- * @description: 构建本地服务的路由请求
155
- * @param {*} file
156
- * @returns {*}
157
- */
158
- function composeRouteFromJsFile(file) {
159
- // 先删除require之前导入的缓存文件,否则获取到的文件内容还是旧内容
160
- const fileObject = getFileLatestContent(file);
161
-
162
- argv.a && log.info(colors.green(`Mockfile ${++fileCount}: `), file, colors.yellow('all API URL is follows: '));
163
- if (fileObject === 'getFileLatestContent_ERROR') {
164
- // console.error(colors.red(`[warning] "${file}" 文件解析失败,请检查并确保文件中的js语法正确`));
165
- return;
166
- }
167
- Object.keys(fileObject).forEach(item => {
168
- let reqMethod = 'get',
169
- reqUrl = item;
170
- // 支持(GET|POST|PUT|DELETE|HEAD|PATCH|OPTIONS|COPY|LINK|UNLINK|PURGE)常用方法, 默认GET请求
171
- const [, , method, url] = methodRegExp.exec(item);
172
- if (method) {
173
- reqMethod = method.toLowerCase();
174
- }
175
- reqUrl = url;
176
- argv.a &&
177
- log.info(
178
- colors.grey(`path ${String(++count).padStart(3, ' ')}: `),
179
- colors.cyan(reqMethod.padEnd(8, ' ')),
180
- colors.cyan(reqUrl),
181
- colors.grey(typeof fileObject[item])
182
- );
183
- log.assert(typeof app[reqMethod] === 'function', colors.red(`${file}, ${reqMethod}`));
184
- if (typeof fileObject[item] === 'function') {
185
- app[reqMethod](reqUrl, fileObject[item]);
186
- } else if (typeof fileObject[item] === 'object') {
187
- app[reqMethod](reqUrl, function (req, res) {
188
- res.json(fileObject[item]);
189
- });
190
- }
191
- // 收集 API 信息
192
- registeredApis.push({
193
- method: reqMethod.toUpperCase(),
194
- url: reqUrl,
195
- file: file,
196
- data: fileObject[item]
197
- });
198
- });
199
- }
200
- /**
201
- * @description: 解析mock目录下的js文件,为构建路由请求做准备
202
- * @param {string} specialDir 目录路径
203
- * @return {void}
204
- */
205
- function parseMockFiles(specialDir = '../mock') {
206
- const files = readdirSync(path.resolve(process.cwd(), specialDir), { withFileTypes: true });
207
- const curFilesSize = files.length;
208
- for (let index = 0; index < curFilesSize; index++) {
209
- const el = files[index];
210
- const filePath = path.normalize(`${specialDir}/${el.name}`);
211
- // 递归目录
212
- if (!el.isFile()) {
213
- parseMockFiles(filePath);
214
- }
215
-
216
- // 处理存放mock数据的json文件
217
- if (el.name.endsWith('.json') && el.name !== 'mock-list.json') {
218
- argv.a &&
219
- log.info(colors.green(`Mockfile ${++fileCount}: `), filePath, colors.yellow('all API URL is follows: '));
220
- const fileObject = getFileLatestContent(filePath);
221
- if (fileObject === 'getFileLatestContent_ERROR') {
222
- // console.error(colors.red(`[warning] "${filePath}" 文件解析失败,请检查并确保文件中的js语法正确`));
223
- continue;
224
- }
225
- // eslint-disable-next-line no-loop-func
226
- Object.keys(fileObject).forEach(method => {
227
- const apiUrl = filePath2ApiUrl(decodeURIComponent(el.name.split('.')[0]));
228
- argv.a &&
229
- log.info(
230
- colors.grey(`path ${String(++count).padStart(3, ' ')}: `),
231
- colors.cyan(method.padEnd(8, ' ')),
232
- colors.cyan(apiUrl),
233
- colors.grey(typeof fileObject[method])
234
- );
235
- log.assert(typeof app[method] === 'function', colors.red(`${filePath}, ${method}`));
236
- app[method](apiUrl, function (req, res) {
237
- res.json(fileObject[method]);
238
- });
239
- // 收集 API 信息
240
- registeredApis.push({
241
- method: method.toUpperCase(),
242
- url: apiUrl,
243
- file: filePath,
244
- data: fileObject[method]
245
- });
246
- });
247
- continue;
248
- }
249
-
250
- // 非js文件跳过
251
- if (!el.name.endsWith('.js') && !el.name.endsWith('.cjs')) {
252
- continue;
253
- }
254
- composeRouteFromJsFile(filePath);
255
- }
256
- }
257
-
258
- /**
259
- * Start web server、socket server
260
- */
261
- function startServer() {
262
- let webApp = null,
263
- webPublicPath = '/',
264
- proxyTable = {};
265
- // Enable Web Server
266
- if (process.env.WEB_ROOT) {
267
- webApp = express();
268
- // Enable web proxy
269
- if (process.env.PROXY_OPTIONS) {
270
- try {
271
- const options = JSON.parse(process.env.PROXY_OPTIONS);
272
- Object.keys(options).forEach(function (prefix) {
273
- proxyTable[prefix] = options[prefix];
274
- const opts = {
275
- target: options[prefix],
276
- changeOrigin: true,
277
- ws: true
278
- };
279
- if (httpsRE.test(options[prefix])) {
280
- // https is require secure=false
281
- opts.secure = false;
282
- }
283
- if (process.env.PREFIX_REWRITE) {
284
- opts.pathRewrite = path => path.replace(new RegExp(`^${prefix}`), '');
285
- }
286
- // 接口代理
287
- // https://github.com/http-party/node-http-proxy#options
288
- webApp.use(prefix, createProxyMiddleware(opts));
289
- }, this);
290
- } catch (error) {
291
- console.warn(colors.yellow('\nEnable web proxy Failed:', error));
292
- }
293
- }
294
- // 添加自定义响应头
295
- if (typeof argv.A === 'string') {
296
- webApp.use((req, res, next) => {
297
- const resHeaders = {};
298
- argv.A.split(/\s*,\s*/).forEach(function (h) {
299
- const [key, value] = h.split('=');
300
- resHeaders[key] = value;
301
- }, this);
302
- // 添加自定义响应头
303
- for (const key in resHeaders) {
304
- if (resHeaders.hasOwnProperty(key)) {
305
- res.header(key, resHeaders[key]);
306
- }
307
- }
308
- next();
309
- });
310
- }
311
- // console.log(process.env.WEB_PUBLIC_PATH, process.env.WEB_ROOT);
312
- if (process.env.WEB_PUBLIC_PATH) {
313
- webPublicPath = process.env.WEB_PUBLIC_PATH;
314
- }
315
- webApp.use(webPublicPath, express.static(process.env.WEB_ROOT)); // 将dist目录下所有文件作为静态文件来管理
316
- const defaultStaticEntry = `${process.env.WEB_ROOT}/index.html`;
317
- if (existsSync(defaultStaticEntry)) {
318
- // Used as static HTTP server for SPA
319
- webApp.get('*', (req, res) => {
320
- res.sendFile(defaultStaticEntry);
321
- });
322
- } else {
323
- // Used as api server for test on browser console or api request tools
324
- webApp.use(webPublicPath, (req, res) => {
325
- res.status(200)
326
- .send(`Hi, welcome to web server entrance ui, you can test http proxy on browser console or api request tools. Examples as follows:<br />
327
- fetch("http://localhost:${process.env.WEB_PORT}/api/permission/auth/login", {
328
- method: 'post',
329
- headers: {
330
- "accept": "*/*",
331
- "Content-type": "application/json; charset=UTF-8",
332
- },
333
- body:JSON.stringify({"loginName":"admin","password":"admin"})
334
- })
335
- `);
336
- });
337
- }
338
- }
339
- const http = require('http').createServer(app);
340
- if (process.env.SOCKET_SERVER) {
341
- const AsyncTaskQueue = require('./asyncTaskQueue');
342
- const saveDataAsyncTask = new AsyncTaskQueue();
343
-
344
- socketServer = new Server(http, { path: '/ws/mock-service' });
345
- log.info(colors.bgBlue(`Socket server has started. path: /ws/mock-service, `));
346
- socketServer.of('/mock-data').on('connection', function (socket) {
347
- const { headers, address } = socket.handshake;
348
- const clientIp = headers.hasOwnProperty('x-forwarded-for') ? headers['x-forwarded-for'] : address;
349
- log.info(
350
- colors.green(
351
- `Socket client ${clientIp} has connected. --- ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`
352
- )
353
- );
354
- socket.on('mock-dir-stat', function (dir) {
355
- socket.emit('mock-dir-stat', isEmptyObj(mockDirStat) ? (mockDirStat = getMockStatFromDir(dir)) : mockDirStat);
356
- });
357
- socket.on('mock-file-stat', function (filePath) {
358
- socket.emit(
359
- 'mock-file-stat',
360
- isEmptyObj(mockFileStat) ? (mockFileStat = getMockStatFromFile(filePath)) : mockFileStat
361
- );
362
- });
363
- const async = args => {
364
- return async next => {
365
- await genMockFiles(args);
366
- next();
367
- };
368
- };
369
- socket.on('save-data', function (data) {
370
- // console.log('lis save-data:', data);
371
- saveDataAsyncTask.add(async(data));
372
-
373
- if (saveDataAsyncTask.list.length === 1) {
374
- saveDataAsyncTask.run();
375
- } else {
376
- saveDataAsyncTask.next();
377
- }
378
- // await genMockFiles(data);
379
- });
380
-
381
- socket.on('disconnect', function () {
382
- log.info(
383
- colors.gray(
384
- `Socket client ${clientIp} has disconnected. --- ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`
385
- )
386
- );
387
- });
388
- });
389
- }
390
-
391
- if (existsSync(mockFileOrDir)) {
392
- http.listen(Number.parseInt(process.env.PORT, 10), () => {
393
- console.info(colors.red('\n Mock File仅支持commonjs规范的js、cjs文件,不支持ES Module'));
394
-
395
- console.info(
396
- [
397
- colors.yellow(`\n${process.env.RESTARTED ? 'Restarting' : 'Starting'} up mock-server, serving `),
398
- colors.cyan(process.env.SPECIFIED_FILE || process.env.SPECIFIED_DIR),
399
- colors.yellow(` ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`)
400
- ].join('')
401
- );
402
- if (!process.env.RESTARTED) {
403
- console.info(
404
- [colors.yellow('\n🌍 mock-server version: '), colors.cyan(require('../package.json').version), '\n'].join('')
405
- );
406
- console.info(colors.yellow(`\n Mock server available on:\n`));
407
- console.info(' http://localhost:' + colors.green(process.env.PORT));
408
- console.info(
409
- ' ' +
410
- colors.blue('API Overview: ') +
411
- 'http://localhost:' +
412
- colors.green(process.env.PORT) +
413
- colors.green('/__api-overview')
414
- );
415
- Object.keys(ifaces).forEach(function (dev) {
416
- ifaces[dev].forEach(function (details) {
417
- if (details.family === 'IPv4') {
418
- console.info(' http://' + details.address + ':' + colors.green(process.env.PORT));
419
- }
420
- });
421
- });
422
- }
423
-
424
- // 自动打开浏览器
425
- if (process.env.OPEN_API_OVERVIEW && !process.env.RESTARTED) {
426
- const apiOverviewUrl = `http://localhost:${process.env.PORT}/__api-overview`;
427
- console.info(colors.yellow(`\nOpening API overview page...`));
428
- let openCommand;
429
- switch (process.platform) {
430
- case 'darwin':
431
- openCommand = `open ${apiOverviewUrl}`;
432
- break;
433
- case 'win32':
434
- openCommand = `start ${apiOverviewUrl}`;
435
- break;
436
- case 'linux':
437
- openCommand = `xdg-open ${apiOverviewUrl}`;
438
- break;
439
- default:
440
- console.warn(colors.yellow(`Could not automatically open browser. Please visit: ${apiOverviewUrl}`));
441
- return;
442
- }
443
- exec(openCommand, error => {
444
- if (error) {
445
- console.warn(colors.yellow(`Could not automatically open browser. Please visit: ${apiOverviewUrl}`));
446
- }
447
- });
448
- }
449
- });
450
- }
451
-
452
- if (webApp && !process.env.RESTARTED) {
453
- webApp.listen(Number.parseInt(process.env.WEB_PORT, 10), () => {
454
- console.info(colors.yellow(`\n Web server available on:\n`));
455
- console.info(' http://localhost:' + colors.green(process.env.WEB_PORT) + webPublicPath);
456
- Object.keys(ifaces).forEach(function (dev) {
457
- ifaces[dev].forEach(function (details) {
458
- if (details.family === 'IPv4') {
459
- console.info(' http://' + details.address + ':' + colors.green(process.env.WEB_PORT) + webPublicPath);
460
- }
461
- });
462
- });
463
- if (!isEmptyObj(proxyTable)) {
464
- console.info(colors.yellow(`\n Enable proxy at web server on:`));
465
- Object.keys(proxyTable).forEach(prefix => {
466
- console.info(` ${prefix} -> ${proxyTable[prefix]}`);
467
- });
468
- }
469
- });
470
- }
471
- }
472
-
473
- if (process.platform === 'win32') {
474
- require('readline')
475
- .createInterface({
476
- input: process.stdin,
477
- output: process.stdout
478
- })
479
- .on('SIGINT', function () {
480
- process.emit('SIGINT');
481
- });
482
- }
483
-
484
- process.on('SIGINT', function () {
485
- log.info(colors.red('mock-server process stopped.'));
486
- process.exit();
487
- });
488
-
489
- process.on('SIGTERM', function () {
490
- log.info(colors.red('mock-server process stopped.'));
491
- process.exit();
492
- });
493
-
494
- module.exports = {
495
- composeRouteFromJsFile,
496
- parseMockFiles
497
- };
@@ -1,104 +0,0 @@
1
- /*
2
- * Static Server
3
- * @Date: 2023-12-03 18:02:24
4
- * @LastEditors: chendq
5
- * @LastEditTime: 2025-07-29 19:20:21
6
- * @Author : chendq
7
- */
8
- const express = require('express'), // 引入express
9
- os = require('os'),
10
- colors = require('colors/safe'),
11
- portfinder = require('portfinder');
12
- const { dateFormat, logger } = require('./utils');
13
- const ifaces = os.networkInterfaces();
14
- const log = logger(process.env.SILENT);
15
- const argv = JSON.parse(process.env.ARGV);
16
-
17
- const resHeaders = {};
18
-
19
- if (!process.env.PORT) {
20
- portfinder.basePort = 8090;
21
- portfinder.getPort(function (err, port) {
22
- if (err) {
23
- throw err;
24
- }
25
- process.env.PORT = port;
26
- startServer();
27
- });
28
- } else {
29
- startServer();
30
- }
31
-
32
- /**
33
- * @description: 添加自定义响应头
34
- * @param {*}
35
- * @return {*}
36
- */
37
- function appendResHeaders() {
38
- return (req, res, next) => {
39
- for (const key in resHeaders) {
40
- if (resHeaders.hasOwnProperty(key)) {
41
- res.header(key, resHeaders[key]);
42
- }
43
- }
44
- next();
45
- };
46
- }
47
-
48
- /**
49
- * Start server
50
- */
51
- function startServer() {
52
- const app = express();
53
-
54
- if (typeof argv.A === 'string') {
55
- argv.A.split(/\s*,\s*/).forEach(function (h) {
56
- const [key, value] = h.split('=');
57
- resHeaders[key] = value;
58
- }, this);
59
- app.use(appendResHeaders()); // 添加响应头
60
- }
61
-
62
- app.use(express.static(process.env.STATIC_DIRECTORY)); // 将dist目录下所有文件作为静态文件来管理
63
-
64
- app.listen(Number.parseInt(process.env.PORT, 10), () => {
65
- console.info(
66
- [
67
- colors.yellow(`\nStarting up Static Server, serving `),
68
- colors.cyan(process.env.STATIC_DIRECTORY),
69
- colors.yellow(` ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`)
70
- ].join('')
71
- );
72
-
73
- console.info(colors.yellow(`\n Static Server available on:\n`));
74
- console.info(' http://localhost:' + colors.green(process.env.PORT));
75
- Object.keys(ifaces).forEach(function (dev) {
76
- ifaces[dev].forEach(function (details) {
77
- if (details.family === 'IPv4') {
78
- console.info(' http://' + details.address + ':' + colors.green(process.env.PORT));
79
- }
80
- });
81
- });
82
- });
83
- }
84
-
85
- if (process.platform === 'win32') {
86
- require('readline')
87
- .createInterface({
88
- input: process.stdin,
89
- output: process.stdout
90
- })
91
- .on('SIGINT', function () {
92
- process.emit('SIGINT');
93
- });
94
- }
95
-
96
- process.on('SIGINT', function () {
97
- log.info(colors.red('static-server process stopped.'));
98
- process.exit();
99
- });
100
-
101
- process.on('SIGTERM', function () {
102
- log.info(colors.red('static-server process stopped.'));
103
- process.exit();
104
- });