mock-service-cli 3.7.0 → 4.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.
@@ -0,0 +1 @@
1
+ module.exports = require('./runtime').load('staticServer');
package/dist/utils.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./runtime').load('utils');
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "mock-service-cli",
3
- "version": "3.7.0",
3
+ "version": "4.1.0",
4
4
  "description": "🦅 Local Mock/Static/SPA server, Http?s request proxy, API overview page, File explorer",
5
- "main": "./lib/mockServer.js",
5
+ "main": "./dist/mockServer.js",
6
6
  "bin": {
7
7
  "mock-service-cli": "bin/mock-service-cli"
8
8
  },
9
9
  "scripts": {
10
+ "build": "node ./scripts/build.js",
11
+ "prepack": "npm run build",
12
+ "prestart": "npm run build",
10
13
  "start": "node ./bin/mock-service-cli",
14
+ "pretest": "npm run build",
11
15
  "test:unit": "tap --reporter=spec test/*.test.js",
12
- "test": "tap --cov --coverage-report=lcov --reporter=spec test/*.test.js ",
16
+ "test": "tap --cov --coverage-report=lcov --reporter=spec test/*.test.js",
13
17
  "test-watch": "tap --reporter=spec --watch test/*.test.js",
14
18
  "fix": "eslint --ext .js,.vue,.css --fix",
15
19
  "prettier": "prettier -c --write \"**/*.{ts,js,jsx,css,less,scss,json}\"",
@@ -21,12 +25,12 @@
21
25
  "release:major": "standard-version --release-as major && git push --follow-tags origin master"
22
26
  },
23
27
  "files": [
24
- "lib",
25
28
  "bin",
26
- "doc"
29
+ "dist/*.js",
30
+ "dist/*.html"
27
31
  ],
28
32
  "engines": {
29
- "node": ">=12"
33
+ "node": ">=18"
30
34
  },
31
35
  "author": "chendq <deqiaochen@gmail.com>",
32
36
  "keywords": [
@@ -50,27 +54,27 @@
50
54
  "license": "MIT",
51
55
  "homepage": "https://github.com/chandq/mock-service-cli#readme",
52
56
  "dependencies": {
53
- "body-parser": "^1.20.2",
54
- "colors": "^1.4.0",
55
- "express": "^4.17.1",
56
- "fs-extra": "^10.0.0",
57
- "http-proxy-middleware": "^2.0.6",
58
- "lodash": "^4.17.21",
59
- "minimist": "^1.2.6",
60
- "nodemon": "^2.0.19",
61
- "portfinder": "^1.0.28",
62
- "socket.io": "^4.4.0"
57
+ "nodemon": "^3.1.14"
63
58
  },
64
59
  "devDependencies": {
65
60
  "@commitlint/cli": "^8.3.5",
66
61
  "@commitlint/config-conventional": "^8.3.4",
67
62
  "babel-eslint": "^10.1.0",
63
+ "colors": "^1.4.0",
64
+ "esbuild": "^0.28.1",
68
65
  "eslint": "^7.0.0",
69
66
  "eslint-config-populist": "^4.2.0",
70
67
  "eslint-plugin-mocha": "^9.0.0",
68
+ "express": "^4.22.2",
69
+ "fs-extra": "^11.3.6",
70
+ "http-proxy-middleware": "^2.0.10",
71
71
  "husky": "^4.3.8",
72
72
  "lint-staged": "^10.2.2",
73
+ "lodash": "^4.18.1",
74
+ "minimist": "^1.2.6",
73
75
  "mockjs": "^0.1.10",
76
+ "portfinder": "^1.0.38",
77
+ "socket.io": "^4.8.3",
74
78
  "standard-version": "^8.0.0",
75
79
  "tap": "^15.0.10"
76
80
  },
@@ -98,7 +102,7 @@
98
102
  },
99
103
  "directories": {
100
104
  "doc": "docs",
101
- "lib": "lib",
105
+ "lib": "src/lib",
102
106
  "test": "test"
103
107
  }
104
108
  }
@@ -1,74 +0,0 @@
1
- /*
2
- * @description: 异步任务队列
3
- * @Date: 2022-01-04 10:46:20
4
- * @LastEditors: chendq
5
- * @LastEditTime: 2026-03-15 17:23:52
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
- this.run();
65
- }
66
- /**
67
- * @description: 继续执行下一个异步任务
68
- */
69
- goOn() {
70
- this.isStop = false;
71
- this.next();
72
- }
73
- }
74
- module.exports = AsyncTaskQueue;
@@ -1,330 +0,0 @@
1
- const express = require('express'),
2
- bodyParser = require('body-parser'),
3
- { readdirSync, existsSync, readFileSync, statSync, createReadStream } = 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 ifaces = os.networkInterfaces();
10
- const { dateFormat, logger } = require('./utils');
11
-
12
- const app = express();
13
- const log = logger(process.env.SILENT);
14
- const argv = JSON.parse(process.env.ARGV);
15
-
16
- const explorerRoot = process.env.EXPLORER_DIRECTORY || process.cwd();
17
- const port = argv.p || argv.port;
18
-
19
- if (!process.env.PORT) {
20
- portfinder.basePort = port || 8090;
21
- portfinder.getPort(function (err, foundPort) {
22
- if (err) {
23
- throw err;
24
- }
25
- process.env.PORT = foundPort;
26
- init();
27
- });
28
- } else {
29
- init();
30
- }
31
-
32
- function init() {
33
- app.use(crossDomain());
34
- app.use(bodyParser.json());
35
-
36
- // 文件浏览页面
37
- app.get('/', (req, res) => {
38
- const htmlPath = path.resolve(__dirname, './file-explorer.html');
39
- if (existsSync(htmlPath)) {
40
- res.sendFile(htmlPath);
41
- } else {
42
- res.status(404).send('File explorer page not found');
43
- }
44
- });
45
-
46
- // 获取目录内容 API
47
- app.get('/__api/list', (req, res) => {
48
- let dirPath = req.query.path || '/';
49
- dirPath = decodeURIComponent(dirPath);
50
-
51
- let fullPath;
52
- const resolvedRoot = path.resolve(explorerRoot);
53
-
54
- if (explorerRoot === '/') {
55
- // 根目录,可以直接使用绝对路径
56
- fullPath = dirPath;
57
- } else {
58
- // 非根目录,应该将 dirPath 视为相对于 explorerRoot 的路径
59
- // 如果 dirPath 以 / 开头,去掉前面的 /
60
- const relativePath = dirPath.startsWith('/') ? dirPath.substring(1) : dirPath;
61
- fullPath = path.resolve(explorerRoot, relativePath);
62
- }
63
-
64
- // 防止路径遍历攻击 - 确保路径不会越界
65
- if (explorerRoot !== '/') {
66
- if (!fullPath.startsWith(resolvedRoot)) {
67
- return res.status(403).json({ error: 'Access denied' });
68
- }
69
- }
70
-
71
- if (!existsSync(fullPath)) {
72
- return res.status(404).json({ error: 'Path not found' });
73
- }
74
-
75
- const stats = statSync(fullPath);
76
- if (!stats.isDirectory()) {
77
- return res.status(400).json({ error: 'Not a directory' });
78
- }
79
-
80
- try {
81
- const files = readdirSync(fullPath, { withFileTypes: true });
82
- const result = [];
83
-
84
- files.forEach(file => {
85
- const filePath = path.join(fullPath, file.name);
86
- let fileStats;
87
- let hasError = false;
88
-
89
- try {
90
- fileStats = statSync(filePath);
91
- } catch (statError) {
92
- hasError = true;
93
- }
94
-
95
- const relativePath = path.join(dirPath, file.name);
96
-
97
- // 使用 statSync 的结果,因为 readdirSync 在根目录对某些特殊目录识别不准确
98
- const isDirectory = hasError ? file.isDirectory() : fileStats.isDirectory();
99
-
100
- result.push({
101
- name: file.name,
102
- path: relativePath.replace(/\\/g, '/'),
103
- isDirectory: isDirectory,
104
- size: hasError ? 0 : fileStats.size,
105
- mtime: hasError ? new Date() : fileStats.mtime,
106
- birthtime: hasError ? new Date() : fileStats.birthtime,
107
- isHidden: file.name.startsWith('.'),
108
- error: hasError ? 'Cannot access file' : null
109
- });
110
- });
111
-
112
- // 排序:目录在前,文件在后,然后按名称排序
113
- result.sort((a, b) => {
114
- if (a.isDirectory !== b.isDirectory) {
115
- return a.isDirectory ? -1 : 1;
116
- }
117
- return a.name.localeCompare(b.name);
118
- });
119
-
120
- res.json({
121
- currentPath: dirPath,
122
- parentPath: dirPath === '/' ? null : path.dirname(dirPath).replace(/\\/g, '/'),
123
- files: result
124
- });
125
- } catch (error) {
126
- res.status(500).json({ error: error.message });
127
- }
128
- });
129
-
130
- // 文件预览 API
131
- app.get('/__api/file', (req, res) => {
132
- let filePath = req.query.path || '/';
133
- filePath = decodeURIComponent(filePath);
134
-
135
- let fullPath;
136
- const resolvedRoot = path.resolve(explorerRoot);
137
-
138
- if (explorerRoot === '/') {
139
- // 根目录,可以直接使用绝对路径
140
- fullPath = filePath;
141
- } else {
142
- // 非根目录,应该将 filePath 视为相对于 explorerRoot 的路径
143
- // 如果 filePath 以 / 开头,去掉前面的 /
144
- const relativePath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
145
- fullPath = path.resolve(explorerRoot, relativePath);
146
- }
147
-
148
- // 防止路径遍历攻击 - 确保路径不会越界
149
- if (explorerRoot !== '/') {
150
- if (!fullPath.startsWith(resolvedRoot)) {
151
- return res.status(403).json({ error: 'Access denied' });
152
- }
153
- }
154
-
155
- if (!existsSync(fullPath)) {
156
- return res.status(404).json({ error: 'File not found' });
157
- }
158
-
159
- const stats = statSync(fullPath);
160
- if (stats.isDirectory()) {
161
- return res.status(400).json({ error: 'Is a directory' });
162
- }
163
-
164
- const ext = path.extname(filePath).toLowerCase();
165
- const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico'];
166
- const textExts = ['.txt', '.json', '.js', '.css', '.html', '.xml', '.md', '.csv', '.yaml', '.yml', '.log'];
167
-
168
- if (imageExts.includes(ext)) {
169
- res.sendFile(fullPath);
170
- } else if (textExts.includes(ext) || stats.size < 1024 * 1024) {
171
- // 小于1MB的文件尝试作为文本读取
172
- try {
173
- const content = readFileSync(fullPath, 'utf-8');
174
- res.json({
175
- name: path.basename(filePath),
176
- type: 'text',
177
- content: content,
178
- size: stats.size
179
- });
180
- } catch (error) {
181
- // 如果不能作为文本读取,直接提供下载
182
- res.download(fullPath);
183
- }
184
- } else {
185
- res.download(fullPath);
186
- }
187
- });
188
-
189
- // 在系统文件管理器中打开目录/文件 API
190
- app.post('/__api/open-in-explorer', (req, res) => {
191
- let filePath = req.body.path || '/';
192
- filePath = decodeURIComponent(filePath);
193
-
194
- let fullPath;
195
- const resolvedRoot = path.resolve(explorerRoot);
196
-
197
- if (explorerRoot === '/') {
198
- // 根目录,可以直接使用绝对路径
199
- fullPath = filePath;
200
- } else {
201
- // 非根目录,应该将 filePath 视为相对于 explorerRoot 的路径
202
- // 如果 filePath 以 / 开头,去掉前面的 /
203
- const relativePath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
204
- fullPath = path.resolve(explorerRoot, relativePath);
205
- }
206
-
207
- // 防止路径遍历攻击 - 确保路径不会越界
208
- if (explorerRoot !== '/') {
209
- if (!fullPath.startsWith(resolvedRoot)) {
210
- return res.status(403).json({ error: 'Access denied' });
211
- }
212
- }
213
-
214
- if (!existsSync(fullPath)) {
215
- return res.status(404).json({ error: 'Path not found' });
216
- }
217
-
218
- let openCommand;
219
- switch (process.platform) {
220
- case 'darwin':
221
- openCommand = `open "${fullPath}"`;
222
- break;
223
- case 'win32':
224
- openCommand = `explorer "${fullPath}"`;
225
- break;
226
- case 'linux':
227
- openCommand = `xdg-open "${fullPath}"`;
228
- break;
229
- default:
230
- return res.status(400).json({ error: 'Unsupported platform' });
231
- }
232
-
233
- exec(openCommand, error => {
234
- if (error) {
235
- console.error(colors.red(`Failed to open in explorer: ${error.message}`));
236
- return res.status(500).json({ error: 'Failed to open in explorer' });
237
- }
238
- res.json({ success: true, path: fullPath });
239
- });
240
- });
241
-
242
- startServer();
243
- }
244
-
245
- function crossDomain() {
246
- return (req, res, next) => {
247
- res.header('Access-Control-Allow-Origin', '*');
248
- res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
249
- res.header('Access-Control-Allow-Headers', '*');
250
- if (req.method === 'OPTIONS') res.status(200);
251
- next();
252
- };
253
- }
254
-
255
- function startServer() {
256
- const http = require('http').createServer(app);
257
-
258
- http.listen(Number.parseInt(process.env.PORT, 10), () => {
259
- console.info(
260
- [
261
- colors.yellow(`\nStarting up file-explorer-server, serving `),
262
- colors.cyan(explorerRoot),
263
- colors.yellow(` ${dateFormat('YYYY-mm-dd HH:MM:SS', new Date())}`)
264
- ].join('')
265
- );
266
- console.info(
267
- [
268
- colors.yellow('\n🌍 file-explorer-server version: '),
269
- colors.cyan(require('../package.json').version),
270
- '\n'
271
- ].join('')
272
- );
273
- console.info(colors.yellow(`\n File explorer server available on:\n`));
274
- console.info(' http://localhost:' + colors.green(process.env.PORT));
275
- Object.keys(ifaces).forEach(function (dev) {
276
- ifaces[dev].forEach(function (details) {
277
- if (details.family === 'IPv4') {
278
- console.info(' http://' + details.address + ':' + colors.green(process.env.PORT));
279
- }
280
- });
281
- });
282
-
283
- // 自动打开浏览器
284
- if (process.env.OPEN_API_OVERVIEW && !process.env.RESTARTED) {
285
- const url = `http://localhost:${process.env.PORT}`;
286
- console.info(colors.yellow(`\nOpening file explorer...`));
287
- let openCommand;
288
- switch (process.platform) {
289
- case 'darwin':
290
- openCommand = `open ${url}`;
291
- break;
292
- case 'win32':
293
- openCommand = `start ${url}`;
294
- break;
295
- case 'linux':
296
- openCommand = `xdg-open ${url}`;
297
- break;
298
- default:
299
- console.warn(colors.yellow(`Could not automatically open browser. Please visit: ${url}`));
300
- return;
301
- }
302
- exec(openCommand, error => {
303
- if (error) {
304
- console.warn(colors.yellow(`Could not automatically open browser. Please visit: ${url}`));
305
- }
306
- });
307
- }
308
- });
309
- }
310
-
311
- if (process.platform === 'win32') {
312
- require('readline')
313
- .createInterface({
314
- input: process.stdin,
315
- output: process.stdout
316
- })
317
- .on('SIGINT', function () {
318
- process.emit('SIGINT');
319
- });
320
- }
321
-
322
- process.on('SIGINT', function () {
323
- log.info(colors.red('file-explorer-server process stopped.'));
324
- process.exit();
325
- });
326
-
327
- process.on('SIGTERM', function () {
328
- log.info(colors.red('file-explorer-server process stopped.'));
329
- process.exit();
330
- });