vite-plugin-mock-dev-server 1.9.2 → 2.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/dist/{helper-DhHU-YoO.js → helper-BbR8Si2U.js} +85 -61
- package/dist/helper.d.ts +3 -3
- package/dist/helper.js +1 -2
- package/dist/{helper-r_bW1AY8.d.ts → index-CJc2Oax2.d.ts} +57 -41
- package/dist/index.d.ts +4 -10
- package/dist/index.js +442 -324
- package/dist/{server-C-u7jwot.js → server-D-sv_WW9.js} +460 -391
- package/dist/{server-DgmHgcvl.d.ts → server-tcT2vAaP.d.ts} +33 -38
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -3
- package/dist/{types-BbbTJG0b.d.ts → types-BtCJqeLH.d.ts} +12 -4
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/package.json +15 -21
- package/dist/dist-CAA1v47s.js +0 -204
- package/dist/dist-DrfpZ4UT.cjs +0 -323
- package/dist/helper-CCVedLL0.cjs +0 -159
- package/dist/helper-iVHsUTZ6.d.cts +0 -110
- package/dist/helper.cjs +0 -7
- package/dist/helper.d.cts +0 -3
- package/dist/index.cjs +0 -730
- package/dist/index.d.cts +0 -15
- package/dist/server-B5Ua2cmP.d.cts +0 -93
- package/dist/server-BwOfV_62.cjs +0 -810
- package/dist/server.cjs +0 -10
- package/dist/server.d.cts +0 -3
- package/dist/types-DpbHkRjL.d.cts +0 -573
|
@@ -1,79 +1,353 @@
|
|
|
1
|
-
import { isArray, isBoolean, isEmptyObject, isFunction, isPlainObject,
|
|
2
|
-
import pc from "picocolors";
|
|
3
|
-
import fs from "node:fs";
|
|
1
|
+
import { isArray, isBoolean, isEmptyObject, isFunction, isPlainObject, random, sleep, sortBy, timestamp, toArray, uniq } from "@pengzhanbo/utils";
|
|
4
2
|
import path from "node:path";
|
|
5
|
-
import
|
|
3
|
+
import ansis from "ansis";
|
|
4
|
+
import picomatch from "picomatch";
|
|
5
|
+
import { match, parse, pathToRegexp } from "path-to-regexp";
|
|
6
6
|
import os from "node:os";
|
|
7
|
-
import { parse } from "node:querystring";
|
|
8
7
|
import Debug from "debug";
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
8
|
+
import { parse as parse$1 } from "node:querystring";
|
|
9
|
+
import bodyParser from "co-body";
|
|
10
|
+
import formidable from "formidable";
|
|
11
11
|
import Cookies from "cookies";
|
|
12
|
+
import { Buffer } from "node:buffer";
|
|
12
13
|
import HTTP_STATUS from "http-status";
|
|
13
14
|
import * as mime from "mime-types";
|
|
14
|
-
import bodyParser from "co-body";
|
|
15
|
-
import formidable from "formidable";
|
|
16
15
|
import { WebSocketServer } from "ws";
|
|
17
16
|
|
|
18
|
-
//#region src/
|
|
17
|
+
//#region src/utils/createMatcher.ts
|
|
18
|
+
function createMatcher(include, exclude) {
|
|
19
|
+
const pattern = [];
|
|
20
|
+
const ignore = ["**/node_modules/**", ...toArray(exclude)];
|
|
21
|
+
toArray(include).forEach((item) => {
|
|
22
|
+
if (item[0] === "!") ignore.push(item.slice(1));
|
|
23
|
+
else pattern.push(item);
|
|
24
|
+
});
|
|
25
|
+
const isMatch = picomatch(pattern, { ignore });
|
|
26
|
+
return {
|
|
27
|
+
pattern,
|
|
28
|
+
ignore,
|
|
29
|
+
isMatch
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/utils/doesProxyContextMatchUrl.ts
|
|
35
|
+
function doesProxyContextMatchUrl(context, url) {
|
|
36
|
+
return context[0] === "^" && new RegExp(context).test(url) || url.startsWith(context);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/utils/is.ts
|
|
19
41
|
function isStream(stream) {
|
|
20
42
|
return stream !== null && typeof stream === "object" && typeof stream.pipe === "function";
|
|
21
43
|
}
|
|
22
44
|
function isReadableStream(stream) {
|
|
23
45
|
return isStream(stream) && stream.readable !== false && typeof stream._read === "function" && typeof stream._readableState === "object";
|
|
24
46
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/utils/isObjectSubset.ts
|
|
50
|
+
/**
|
|
51
|
+
* Checks if target object is a subset of source object.
|
|
52
|
+
* That is, all properties and their corresponding values in target exist in source.
|
|
53
|
+
*
|
|
54
|
+
* 深度比较两个对象之间,target 是否属于 source 的子集,
|
|
55
|
+
* 即 target 的所有属性和对应的值,都在 source 中,
|
|
56
|
+
*/
|
|
57
|
+
function isObjectSubset(source, target) {
|
|
58
|
+
if (!target) return true;
|
|
59
|
+
for (const key in target) if (!isIncluded(source[key], target[key])) return false;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
function isIncluded(source, target) {
|
|
63
|
+
if (isArray(source) && isArray(target)) {
|
|
64
|
+
const seen = /* @__PURE__ */ new Set();
|
|
65
|
+
return target.every((ti) => source.some((si, i) => {
|
|
66
|
+
if (seen.has(i)) return false;
|
|
67
|
+
const included = isIncluded(si, ti);
|
|
68
|
+
if (included) seen.add(i);
|
|
69
|
+
return included;
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
if (isPlainObject(source) && isPlainObject(target)) return isObjectSubset(source, target);
|
|
73
|
+
return Object.is(source, target);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/utils/isPathMatch.ts
|
|
78
|
+
const cache$1 = /* @__PURE__ */ new Map();
|
|
79
|
+
/**
|
|
80
|
+
* 判断 path 是否匹配 pattern
|
|
81
|
+
*/
|
|
82
|
+
function isPathMatch(pattern, path$1) {
|
|
83
|
+
let regexp = cache$1.get(pattern);
|
|
84
|
+
if (!regexp) {
|
|
85
|
+
regexp = pathToRegexp(pattern).regexp;
|
|
86
|
+
cache$1.set(pattern, regexp);
|
|
87
|
+
}
|
|
88
|
+
return regexp.test(path$1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/utils/logger.ts
|
|
93
|
+
const logLevels = {
|
|
94
|
+
silent: 0,
|
|
95
|
+
error: 1,
|
|
96
|
+
warn: 2,
|
|
97
|
+
info: 3,
|
|
98
|
+
debug: 4
|
|
99
|
+
};
|
|
100
|
+
function createLogger(prefix, defaultLevel = "info") {
|
|
101
|
+
prefix = `[${prefix}]`;
|
|
102
|
+
function output(type, msg, level) {
|
|
103
|
+
level = isBoolean(level) ? level ? defaultLevel : "error" : level;
|
|
104
|
+
const thresh = logLevels[level];
|
|
105
|
+
if (thresh >= logLevels[type]) {
|
|
106
|
+
const method = type === "info" || type === "debug" ? "log" : type;
|
|
107
|
+
const tag = type === "debug" ? ansis.magenta.bold(prefix) : type === "info" ? ansis.cyan.bold(prefix) : type === "warn" ? ansis.yellow.bold(prefix) : ansis.red.bold(prefix);
|
|
108
|
+
const format = `${ansis.dim((/* @__PURE__ */ new Date()).toLocaleTimeString())} ${tag} ${msg}`;
|
|
109
|
+
console[method](format);
|
|
32
110
|
}
|
|
33
111
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
wsProxies
|
|
112
|
+
const logger = {
|
|
113
|
+
debug(msg, level = defaultLevel) {
|
|
114
|
+
output("debug", msg, level);
|
|
115
|
+
},
|
|
116
|
+
info(msg, level = defaultLevel) {
|
|
117
|
+
output("info", msg, level);
|
|
118
|
+
},
|
|
119
|
+
warn(msg, level = defaultLevel) {
|
|
120
|
+
output("warn", msg, level);
|
|
121
|
+
},
|
|
122
|
+
error(msg, level = defaultLevel) {
|
|
123
|
+
output("error", msg, level);
|
|
124
|
+
}
|
|
48
125
|
};
|
|
126
|
+
return logger;
|
|
49
127
|
}
|
|
50
|
-
|
|
51
|
-
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/utils/shared.ts
|
|
131
|
+
const debug = Debug("vite:mock-dev-server");
|
|
132
|
+
const windowsSlashRE = /\\/g;
|
|
133
|
+
const isWindows = os.platform() === "win32";
|
|
134
|
+
function slash(p) {
|
|
135
|
+
return p.replace(windowsSlashRE, "/");
|
|
52
136
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
return urlMatch.params || {};
|
|
137
|
+
function normalizePath(id) {
|
|
138
|
+
return path.posix.normalize(isWindows ? slash(id) : id);
|
|
56
139
|
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/utils/urlParse.ts
|
|
57
143
|
/**
|
|
58
144
|
* nodejs 从 19.0.0 开始 弃用 url.parse,因此使用 url.parse 来解析 可能会报错,
|
|
59
145
|
* 使用 URL 来解析
|
|
60
146
|
*/
|
|
61
147
|
function urlParse(input) {
|
|
62
|
-
const url = new URL
|
|
148
|
+
const url = new URL(input, "http://example.com");
|
|
63
149
|
const pathname = decodeURIComponent(url.pathname);
|
|
64
|
-
const query = parse(url.search.replace(/^\?/, ""));
|
|
150
|
+
const query = parse$1(url.search.replace(/^\?/, ""));
|
|
65
151
|
return {
|
|
66
152
|
pathname,
|
|
67
153
|
query
|
|
68
154
|
};
|
|
69
155
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/compiler/processData.ts
|
|
159
|
+
function processRawData(raw, __filepath__) {
|
|
160
|
+
let res;
|
|
161
|
+
if (isArray(raw)) res = raw.map((item) => ({
|
|
162
|
+
...item,
|
|
163
|
+
__filepath__
|
|
164
|
+
}));
|
|
165
|
+
else if ("url" in raw) res = {
|
|
166
|
+
...raw,
|
|
167
|
+
__filepath__
|
|
168
|
+
};
|
|
169
|
+
else {
|
|
170
|
+
res = [];
|
|
171
|
+
Object.keys(raw).forEach((key) => {
|
|
172
|
+
const data = raw[key];
|
|
173
|
+
if (isArray(data)) res.push(...data.map((item) => ({
|
|
174
|
+
...item,
|
|
175
|
+
__filepath__
|
|
176
|
+
})));
|
|
177
|
+
else res.push({
|
|
178
|
+
...data,
|
|
179
|
+
__filepath__
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return res;
|
|
74
184
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
185
|
+
function processMockData(mockList) {
|
|
186
|
+
const list = [];
|
|
187
|
+
for (const [, handle] of mockList.entries()) if (handle) list.push(...toArray(handle));
|
|
188
|
+
const mocks = {};
|
|
189
|
+
list.filter((mock) => isPlainObject(mock) && mock.enabled !== false && mock.url).forEach((mock) => {
|
|
190
|
+
const { pathname, query } = urlParse(mock.url);
|
|
191
|
+
const list$1 = mocks[pathname] ??= [];
|
|
192
|
+
const current = {
|
|
193
|
+
...mock,
|
|
194
|
+
url: pathname
|
|
195
|
+
};
|
|
196
|
+
if (current.ws !== true) {
|
|
197
|
+
const validator = current.validator;
|
|
198
|
+
if (!isEmptyObject(query)) if (isFunction(validator)) current.validator = function(request) {
|
|
199
|
+
return isObjectSubset(request.query, query) && validator(request);
|
|
200
|
+
};
|
|
201
|
+
else if (validator) {
|
|
202
|
+
current.validator = { ...validator };
|
|
203
|
+
current.validator.query = current.validator.query ? {
|
|
204
|
+
...query,
|
|
205
|
+
...current.validator.query
|
|
206
|
+
} : query;
|
|
207
|
+
} else current.validator = { query };
|
|
208
|
+
}
|
|
209
|
+
list$1.push(current);
|
|
210
|
+
});
|
|
211
|
+
Object.keys(mocks).forEach((key) => {
|
|
212
|
+
mocks[key] = sortByValidator(mocks[key]);
|
|
213
|
+
});
|
|
214
|
+
return mocks;
|
|
215
|
+
}
|
|
216
|
+
function sortByValidator(mocks) {
|
|
217
|
+
return sortBy(mocks, (item) => {
|
|
218
|
+
if (item.ws === true) return 0;
|
|
219
|
+
const { validator } = item;
|
|
220
|
+
if (!validator || isEmptyObject(validator)) return 2;
|
|
221
|
+
if (isFunction(validator)) return 0;
|
|
222
|
+
const count = Object.keys(validator).reduce((prev, key) => prev + keysCount(validator[key]), 0);
|
|
223
|
+
return 1 / count;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function keysCount(obj) {
|
|
227
|
+
if (!obj) return 0;
|
|
228
|
+
return Object.keys(obj).length;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/core/request.ts
|
|
233
|
+
/**
|
|
234
|
+
* 解析请求体 request.body
|
|
235
|
+
*/
|
|
236
|
+
async function parseRequestBody(req, formidableOptions, bodyParserOptions = {}) {
|
|
237
|
+
const method = req.method.toUpperCase();
|
|
238
|
+
if (["HEAD", "OPTIONS"].includes(method)) return void 0;
|
|
239
|
+
const type = req.headers["content-type"]?.toLocaleLowerCase() || "";
|
|
240
|
+
const { limit, formLimit, jsonLimit, textLimit,...rest } = bodyParserOptions;
|
|
241
|
+
try {
|
|
242
|
+
if (type.startsWith("application/json")) return await bodyParser.json(req, {
|
|
243
|
+
limit: jsonLimit || limit,
|
|
244
|
+
...rest
|
|
245
|
+
});
|
|
246
|
+
if (type.startsWith("application/x-www-form-urlencoded")) return await bodyParser.form(req, {
|
|
247
|
+
limit: formLimit || limit,
|
|
248
|
+
...rest
|
|
249
|
+
});
|
|
250
|
+
if (type.startsWith("text/plain")) return await bodyParser.text(req, {
|
|
251
|
+
limit: textLimit || limit,
|
|
252
|
+
...rest
|
|
253
|
+
});
|
|
254
|
+
if (type.startsWith("multipart/form-data")) return await parseRequestBodyWithMultipart(req, formidableOptions);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.error(e);
|
|
257
|
+
}
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
const DEFAULT_FORMIDABLE_OPTIONS = {
|
|
261
|
+
keepExtensions: true,
|
|
262
|
+
filename(name, ext, part) {
|
|
263
|
+
return part?.originalFilename || `${name}.${Date.now()}${ext ? `.${ext}` : ""}`;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* 解析 request form multipart body
|
|
268
|
+
*/
|
|
269
|
+
async function parseRequestBodyWithMultipart(req, options) {
|
|
270
|
+
const form = formidable({
|
|
271
|
+
...DEFAULT_FORMIDABLE_OPTIONS,
|
|
272
|
+
...options
|
|
273
|
+
});
|
|
274
|
+
return new Promise((resolve, reject) => {
|
|
275
|
+
form.parse(req, (error, fields, files) => {
|
|
276
|
+
if (error) {
|
|
277
|
+
reject(error);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
resolve({
|
|
281
|
+
...fields,
|
|
282
|
+
...files
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const matcherCache = /* @__PURE__ */ new Map();
|
|
288
|
+
/**
|
|
289
|
+
* 解析请求 url 中的动态参数 params
|
|
290
|
+
*/
|
|
291
|
+
function parseRequestParams(pattern, url) {
|
|
292
|
+
let matcher = matcherCache.get(pattern);
|
|
293
|
+
if (!matcher) {
|
|
294
|
+
matcher = match(pattern, { decode: decodeURIComponent });
|
|
295
|
+
matcherCache.set(pattern, matcher);
|
|
296
|
+
}
|
|
297
|
+
const matched = matcher(url);
|
|
298
|
+
return matched ? matched.params : {};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 验证请求是否符合 validator
|
|
302
|
+
*/
|
|
303
|
+
function requestValidate(request, validator) {
|
|
304
|
+
return isObjectSubset(request.headers, validator.headers) && isObjectSubset(request.body, validator.body) && isObjectSubset(request.params, validator.params) && isObjectSubset(request.query, validator.query) && isObjectSubset(request.refererQuery, validator.refererQuery);
|
|
305
|
+
}
|
|
306
|
+
function formatLog(prefix, data) {
|
|
307
|
+
return !data || isEmptyObject(data) ? "" : ` ${ansis.gray(`${prefix}:`)}${JSON.stringify(data)}`;
|
|
308
|
+
}
|
|
309
|
+
function requestLog(request, filepath) {
|
|
310
|
+
const { url, method, query, params, body } = request;
|
|
311
|
+
let { pathname } = new URL(url, "http://example.com");
|
|
312
|
+
pathname = ansis.green(decodeURIComponent(pathname));
|
|
313
|
+
const ms = ansis.magenta.bold(method);
|
|
314
|
+
const qs = formatLog("query", query);
|
|
315
|
+
const ps = formatLog("params", params);
|
|
316
|
+
const bs = formatLog("body", body);
|
|
317
|
+
const file = ` ${ansis.dim.underline(`(${filepath})`)}`;
|
|
318
|
+
return `${ms} ${pathname}${qs}${ps}${bs}${file}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/core/findMockData.ts
|
|
323
|
+
/**
|
|
324
|
+
* 查找匹配的 mock data
|
|
325
|
+
*/
|
|
326
|
+
function fineMockData(mockList, logger, { pathname, method, request }) {
|
|
327
|
+
return mockList.find((mock) => {
|
|
328
|
+
if (!pathname || !mock || !mock.url || mock.ws) return false;
|
|
329
|
+
const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
|
|
330
|
+
if (!methods.includes(method)) return false;
|
|
331
|
+
const hasMock = isPathMatch(mock.url, pathname);
|
|
332
|
+
if (hasMock && mock.validator) {
|
|
333
|
+
const params = parseRequestParams(mock.url, pathname);
|
|
334
|
+
if (isFunction(mock.validator)) return mock.validator({
|
|
335
|
+
params,
|
|
336
|
+
...request
|
|
337
|
+
});
|
|
338
|
+
else try {
|
|
339
|
+
return requestValidate({
|
|
340
|
+
params,
|
|
341
|
+
...request
|
|
342
|
+
}, mock.validator);
|
|
343
|
+
} catch (e) {
|
|
344
|
+
const file = mock.__filepath__;
|
|
345
|
+
logger.error(`${ansis.red(`mock error at ${pathname}`)}\n${e}\n at validator (${ansis.underline(file)})`, mock.log);
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return hasMock;
|
|
350
|
+
});
|
|
77
351
|
}
|
|
78
352
|
|
|
79
353
|
//#endregion
|
|
@@ -81,16 +355,23 @@ function normalizePath(id) {
|
|
|
81
355
|
const tokensCache = {};
|
|
82
356
|
function getTokens(rule) {
|
|
83
357
|
if (tokensCache[rule]) return tokensCache[rule];
|
|
84
|
-
const
|
|
85
|
-
const tokens =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
358
|
+
const res = [];
|
|
359
|
+
const flatten = (tokens, group = false) => {
|
|
360
|
+
for (const token of tokens) if (token.type === "text") {
|
|
361
|
+
const sub = token.value.split("/").filter(Boolean);
|
|
362
|
+
sub.length && res.push(...sub.map((v) => ({
|
|
363
|
+
type: "text",
|
|
364
|
+
value: v
|
|
365
|
+
})));
|
|
366
|
+
} else if (token.type === "group") flatten(token.tokens, true);
|
|
367
|
+
else {
|
|
368
|
+
if (group) token.optional = true;
|
|
369
|
+
res.push(token);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
flatten(parse(rule).tokens);
|
|
373
|
+
tokensCache[rule] = res;
|
|
374
|
+
return res;
|
|
94
375
|
}
|
|
95
376
|
function getHighest(rules) {
|
|
96
377
|
let weights = rules.map((rule) => getTokens(rule).length);
|
|
@@ -102,7 +383,7 @@ function sortFn(rule) {
|
|
|
102
383
|
let w = 0;
|
|
103
384
|
for (let i = 0; i < tokens.length; i++) {
|
|
104
385
|
const token = tokens[i];
|
|
105
|
-
if (
|
|
386
|
+
if (token.type !== "text") w += 10 ** (i + 1);
|
|
106
387
|
w += 10 ** (i + 1);
|
|
107
388
|
}
|
|
108
389
|
return w;
|
|
@@ -112,7 +393,7 @@ function preSort(rules) {
|
|
|
112
393
|
const preMatch = [];
|
|
113
394
|
for (const rule of rules) {
|
|
114
395
|
const tokens = getTokens(rule);
|
|
115
|
-
const len = tokens.filter((token) =>
|
|
396
|
+
const len = tokens.filter((token) => token.type !== "text").length;
|
|
116
397
|
if (!preMatch[len]) preMatch[len] = [];
|
|
117
398
|
preMatch[len].push(rule);
|
|
118
399
|
}
|
|
@@ -123,31 +404,28 @@ function defaultPriority(rules) {
|
|
|
123
404
|
const highest = getHighest(rules);
|
|
124
405
|
return sortBy(rules, (rule) => {
|
|
125
406
|
const tokens = getTokens(rule);
|
|
126
|
-
const dym = tokens.filter((token) =>
|
|
407
|
+
const dym = tokens.filter((token) => token.type !== "text");
|
|
127
408
|
if (dym.length === 0) return 0;
|
|
128
409
|
let weight = dym.length;
|
|
129
410
|
let exp = 0;
|
|
130
411
|
for (let i = 0; i < tokens.length; i++) {
|
|
131
412
|
const token = tokens[i];
|
|
132
|
-
const isDynamic =
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (modifier === "+") weight += 1 * 10 ** (highest - 1);
|
|
143
|
-
if (modifier === "*") weight += 1 * 10 ** (highest - 1) + 1;
|
|
144
|
-
if (modifier === "?") weight += 1 * 10 ** (exp + (isSlash ? 1 : 0));
|
|
413
|
+
const isDynamic = token.type !== "text";
|
|
414
|
+
const isWildcard = token.type === "wildcard";
|
|
415
|
+
const isOptional = !!token.optional;
|
|
416
|
+
exp += isDynamic ? 1 : 0;
|
|
417
|
+
if (i === tokens.length - 1 && isWildcard) weight += (isOptional ? 5 : 4) * 10 ** (tokens.length === 1 ? highest + 1 : highest);
|
|
418
|
+
else {
|
|
419
|
+
if (isWildcard) weight += 3 * 10 ** (highest - 1);
|
|
420
|
+
else weight += 2 * 10 ** exp;
|
|
421
|
+
if (isOptional) weight += 10 ** exp;
|
|
422
|
+
}
|
|
145
423
|
}
|
|
146
424
|
return weight;
|
|
147
425
|
});
|
|
148
426
|
}
|
|
149
427
|
function matchingWeight(rules, url, priority) {
|
|
150
|
-
let matched = defaultPriority(preSort(rules.filter((rule) =>
|
|
428
|
+
let matched = defaultPriority(preSort(rules.filter((rule) => isPathMatch(rule, url))));
|
|
151
429
|
const { global = [], special = {} } = priority;
|
|
152
430
|
if (global.length === 0 && isEmptyObject(special) || matched.length === 0) return matched;
|
|
153
431
|
const [statics, dynamics] = twoPartMatch(matched);
|
|
@@ -166,76 +444,31 @@ function matchingWeight(rules, url, priority) {
|
|
|
166
444
|
when: []
|
|
167
445
|
} : options;
|
|
168
446
|
if (lowerRules.includes(matched[0])) {
|
|
169
|
-
if (when.length === 0 || when.some((path$1) => pathToRegexp(path$1).test(url))) matched = uniq([specialRule, ...matched]);
|
|
447
|
+
if (when.length === 0 || when.some((path$1) => pathToRegexp(path$1).regexp.test(url))) matched = uniq([specialRule, ...matched]);
|
|
170
448
|
}
|
|
171
449
|
return matched;
|
|
172
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* 将规则分为静态和动态两部分
|
|
453
|
+
*/
|
|
173
454
|
function twoPartMatch(rules) {
|
|
174
455
|
const statics = [];
|
|
175
456
|
const dynamics = [];
|
|
176
457
|
for (const rule of rules) {
|
|
177
|
-
const tokens = getTokens(rule);
|
|
178
|
-
const dym = tokens.filter((token) =>
|
|
179
|
-
if (dym.length > 0) dynamics.push(rule);
|
|
180
|
-
else statics.push(rule);
|
|
181
|
-
}
|
|
182
|
-
return [statics, dynamics];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
//#endregion
|
|
186
|
-
//#region src/core/parseReqBody.ts
|
|
187
|
-
const DEFAULT_FORMIDABLE_OPTIONS = {
|
|
188
|
-
keepExtensions: true,
|
|
189
|
-
filename(name, ext, part) {
|
|
190
|
-
return part?.originalFilename || `${name}.${Date.now()}${ext ? `.${ext}` : ""}`;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
async function parseReqBody(req, formidableOptions, bodyParserOptions = {}) {
|
|
194
|
-
const method = req.method.toUpperCase();
|
|
195
|
-
if (["HEAD", "OPTIONS"].includes(method)) return void 0;
|
|
196
|
-
const type = req.headers["content-type"]?.toLocaleLowerCase() || "";
|
|
197
|
-
const { limit, formLimit, jsonLimit, textLimit,...rest } = bodyParserOptions;
|
|
198
|
-
try {
|
|
199
|
-
if (type.startsWith("application/json")) return await bodyParser.json(req, {
|
|
200
|
-
limit: jsonLimit || limit,
|
|
201
|
-
...rest
|
|
202
|
-
});
|
|
203
|
-
if (type.startsWith("application/x-www-form-urlencoded")) return await bodyParser.form(req, {
|
|
204
|
-
limit: formLimit || limit,
|
|
205
|
-
...rest
|
|
206
|
-
});
|
|
207
|
-
if (type.startsWith("text/plain")) return await bodyParser.text(req, {
|
|
208
|
-
limit: textLimit || limit,
|
|
209
|
-
...rest
|
|
210
|
-
});
|
|
211
|
-
if (type.startsWith("multipart/form-data")) return await parseMultipart(req, formidableOptions);
|
|
212
|
-
} catch (e) {
|
|
213
|
-
console.error(e);
|
|
214
|
-
}
|
|
215
|
-
return void 0;
|
|
216
|
-
}
|
|
217
|
-
async function parseMultipart(req, options) {
|
|
218
|
-
const form = formidable({
|
|
219
|
-
...DEFAULT_FORMIDABLE_OPTIONS,
|
|
220
|
-
...options
|
|
221
|
-
});
|
|
222
|
-
return new Promise((resolve, reject) => {
|
|
223
|
-
form.parse(req, (error, fields, files) => {
|
|
224
|
-
if (error) {
|
|
225
|
-
reject(error);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
resolve({
|
|
229
|
-
...fields,
|
|
230
|
-
...files
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
});
|
|
458
|
+
const tokens = getTokens(rule);
|
|
459
|
+
const dym = tokens.filter((token) => token.type !== "text");
|
|
460
|
+
if (dym.length > 0) dynamics.push(rule);
|
|
461
|
+
else statics.push(rule);
|
|
462
|
+
}
|
|
463
|
+
return [statics, dynamics];
|
|
234
464
|
}
|
|
235
465
|
|
|
236
466
|
//#endregion
|
|
237
467
|
//#region src/core/requestRecovery.ts
|
|
238
468
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
469
|
+
/**
|
|
470
|
+
* 备份请求数据
|
|
471
|
+
*/
|
|
239
472
|
function collectRequest(req) {
|
|
240
473
|
const chunks = [];
|
|
241
474
|
req.addListener("data", (chunk) => {
|
|
@@ -279,40 +512,89 @@ function recoverRequest(config) {
|
|
|
279
512
|
}
|
|
280
513
|
|
|
281
514
|
//#endregion
|
|
282
|
-
//#region src/core/
|
|
283
|
-
|
|
284
|
-
|
|
515
|
+
//#region src/core/response.ts
|
|
516
|
+
/**
|
|
517
|
+
* 根据状态码获取状态文本
|
|
518
|
+
*/
|
|
519
|
+
function getHTTPStatusText(status) {
|
|
520
|
+
return HTTP_STATUS[status] || "Unknown";
|
|
285
521
|
}
|
|
286
522
|
/**
|
|
287
|
-
*
|
|
288
|
-
* That is, all properties and their corresponding values in target exist in source.
|
|
289
|
-
*
|
|
290
|
-
* 深度比较两个对象之间,target 是否属于 source 的子集,
|
|
291
|
-
* 即 target 的所有属性和对应的值,都在 source 中,
|
|
523
|
+
* 设置响应状态
|
|
292
524
|
*/
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return true;
|
|
525
|
+
function provideResponseStatus(response, status = 200, statusText) {
|
|
526
|
+
response.statusCode = status;
|
|
527
|
+
response.statusMessage = statusText || getHTTPStatusText(status);
|
|
297
528
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
529
|
+
/**
|
|
530
|
+
* 设置响应头
|
|
531
|
+
*/
|
|
532
|
+
async function provideResponseHeaders(req, res, mock, logger) {
|
|
533
|
+
const { headers, type = "json" } = mock;
|
|
534
|
+
const filepath = mock.__filepath__;
|
|
535
|
+
const contentType = mime.contentType(type) || mime.contentType(mime.lookup(type) || "");
|
|
536
|
+
if (contentType) res.setHeader("Content-Type", contentType);
|
|
537
|
+
res.setHeader("Cache-Control", "no-cache,max-age=0");
|
|
538
|
+
res.setHeader("X-Mock-Power-By", "vite-plugin-mock-dev-server");
|
|
539
|
+
if (filepath) res.setHeader("X-File-Path", filepath);
|
|
540
|
+
if (!headers) return;
|
|
541
|
+
try {
|
|
542
|
+
const raw = isFunction(headers) ? await headers(req) : headers;
|
|
543
|
+
Object.keys(raw).forEach((key) => {
|
|
544
|
+
res.setHeader(key, raw[key]);
|
|
545
|
+
});
|
|
546
|
+
} catch (e) {
|
|
547
|
+
logger.error(`${ansis.red(`mock error at ${req.url.split("?")[0]}`)}\n${e}\n at headers (${ansis.underline(filepath)})`, mock.log);
|
|
307
548
|
}
|
|
308
|
-
|
|
309
|
-
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* 设置响应cookie
|
|
552
|
+
*/
|
|
553
|
+
async function provideResponseCookies(req, res, mock, logger) {
|
|
554
|
+
const { cookies } = mock;
|
|
555
|
+
const filepath = mock.__filepath__;
|
|
556
|
+
if (!cookies) return;
|
|
557
|
+
try {
|
|
558
|
+
const raw = isFunction(cookies) ? await cookies(req) : cookies;
|
|
559
|
+
Object.keys(raw).forEach((key) => {
|
|
560
|
+
const cookie = raw[key];
|
|
561
|
+
if (isArray(cookie)) {
|
|
562
|
+
const [value, options] = cookie;
|
|
563
|
+
res.setCookie(key, value, options);
|
|
564
|
+
} else res.setCookie(key, cookie);
|
|
565
|
+
});
|
|
566
|
+
} catch (e) {
|
|
567
|
+
logger.error(`${ansis.red(`mock error at ${req.url.split("?")[0]}`)}\n${e}\n at cookies (${ansis.underline(filepath)})`, mock.log);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* 设置响应数据
|
|
572
|
+
*/
|
|
573
|
+
function sendResponseData(res, raw, type) {
|
|
574
|
+
if (isReadableStream(raw)) raw.pipe(res);
|
|
575
|
+
else if (Buffer.isBuffer(raw)) res.end(type === "text" || type === "json" ? raw.toString("utf-8") : raw);
|
|
576
|
+
else {
|
|
577
|
+
const content = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
578
|
+
res.end(type === "buffer" ? Buffer.from(content) : content);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* 实际响应延迟
|
|
583
|
+
*/
|
|
584
|
+
async function responseRealDelay(startTime, delay) {
|
|
585
|
+
if (!delay || typeof delay === "number" && delay <= 0 || isArray(delay) && delay.length !== 2) return;
|
|
586
|
+
let realDelay = 0;
|
|
587
|
+
if (isArray(delay)) {
|
|
588
|
+
const [min, max] = delay;
|
|
589
|
+
realDelay = random(min, max);
|
|
590
|
+
} else realDelay = delay - (timestamp() - startTime);
|
|
591
|
+
if (realDelay > 0) await sleep(realDelay);
|
|
310
592
|
}
|
|
311
593
|
|
|
312
594
|
//#endregion
|
|
313
|
-
//#region src/core/
|
|
314
|
-
function
|
|
315
|
-
return async function(req, res, next) {
|
|
595
|
+
//#region src/core/mockMiddleware.ts
|
|
596
|
+
function createMockMiddleware(compiler, { formidableOptions = {}, bodyParserOptions = {}, proxies, cookiesOptions, logger, priority = {} }) {
|
|
597
|
+
return async function mockMiddleware(req, res, next) {
|
|
316
598
|
const startTime = timestamp();
|
|
317
599
|
const { query, pathname } = urlParse(req.url);
|
|
318
600
|
if (!pathname || proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) return next();
|
|
@@ -321,14 +603,14 @@ function baseMiddleware(compiler, { formidableOptions = {}, bodyParserOptions =
|
|
|
321
603
|
if (mockUrls.length === 0) return next();
|
|
322
604
|
collectRequest(req);
|
|
323
605
|
const { query: refererQuery } = urlParse(req.headers.referer || "");
|
|
324
|
-
const reqBody = await
|
|
606
|
+
const reqBody = await parseRequestBody(req, formidableOptions, bodyParserOptions);
|
|
325
607
|
const cookies = new Cookies(req, res, cookiesOptions);
|
|
326
608
|
const getCookie = cookies.get.bind(cookies);
|
|
327
609
|
const method = req.method.toUpperCase();
|
|
328
610
|
let mock;
|
|
329
611
|
let _mockUrl;
|
|
330
612
|
for (const mockUrl of mockUrls) {
|
|
331
|
-
mock =
|
|
613
|
+
mock = fineMockData(mockData[mockUrl], logger, {
|
|
332
614
|
pathname,
|
|
333
615
|
method,
|
|
334
616
|
request: {
|
|
@@ -345,8 +627,8 @@ function baseMiddleware(compiler, { formidableOptions = {}, bodyParserOptions =
|
|
|
345
627
|
}
|
|
346
628
|
}
|
|
347
629
|
if (!mock) {
|
|
348
|
-
const matched = mockUrls.map((m) => m === _mockUrl ?
|
|
349
|
-
logger.warn(`${
|
|
630
|
+
const matched = mockUrls.map((m) => m === _mockUrl ? ansis.underline.bold(m) : ansis.dim(m)).join(", ");
|
|
631
|
+
logger.warn(`${ansis.green(pathname)} matches ${matched} , but mock data is not found.`);
|
|
350
632
|
return next();
|
|
351
633
|
}
|
|
352
634
|
const request = req;
|
|
@@ -354,34 +636,34 @@ function baseMiddleware(compiler, { formidableOptions = {}, bodyParserOptions =
|
|
|
354
636
|
request.body = reqBody;
|
|
355
637
|
request.query = query;
|
|
356
638
|
request.refererQuery = refererQuery;
|
|
357
|
-
request.params =
|
|
639
|
+
request.params = parseRequestParams(mock.url, pathname);
|
|
358
640
|
request.getCookie = getCookie;
|
|
359
641
|
response.setCookie = cookies.set.bind(cookies);
|
|
360
642
|
const { body, delay, type = "json", response: responseFn, status = 200, statusText, log: logLevel, __filepath__: filepath } = mock;
|
|
361
|
-
|
|
362
|
-
await
|
|
363
|
-
await
|
|
643
|
+
provideResponseStatus(response, status, statusText);
|
|
644
|
+
await provideResponseHeaders(request, response, mock, logger);
|
|
645
|
+
await provideResponseCookies(request, response, mock, logger);
|
|
364
646
|
logger.info(requestLog(request, filepath), logLevel);
|
|
365
|
-
logger.debug(`${
|
|
647
|
+
logger.debug(`${ansis.magenta("DEBUG")} ${ansis.underline(pathname)} matches: [ ${mockUrls.map((m) => m === _mockUrl ? ansis.underline.bold(m) : ansis.dim(m)).join(", ")} ]\n`);
|
|
366
648
|
if (body) {
|
|
367
649
|
try {
|
|
368
650
|
const content = isFunction(body) ? await body(request) : body;
|
|
369
|
-
await
|
|
370
|
-
|
|
651
|
+
await responseRealDelay(startTime, delay);
|
|
652
|
+
sendResponseData(response, content, type);
|
|
371
653
|
} catch (e) {
|
|
372
|
-
logger.error(`${
|
|
373
|
-
|
|
654
|
+
logger.error(`${ansis.red(`mock error at ${pathname}`)}\n${e}\n at body (${ansis.underline(filepath)})`, logLevel);
|
|
655
|
+
provideResponseStatus(response, 500);
|
|
374
656
|
res.end("");
|
|
375
657
|
}
|
|
376
658
|
return;
|
|
377
659
|
}
|
|
378
660
|
if (responseFn) {
|
|
379
661
|
try {
|
|
380
|
-
await
|
|
662
|
+
await responseRealDelay(startTime, delay);
|
|
381
663
|
await responseFn(request, response, next);
|
|
382
664
|
} catch (e) {
|
|
383
|
-
logger.error(`${
|
|
384
|
-
|
|
665
|
+
logger.error(`${ansis.red(`mock error at ${pathname}`)}\n${e}\n at response (${ansis.underline(filepath)})`, logLevel);
|
|
666
|
+
provideResponseStatus(response, 500);
|
|
385
667
|
res.end("");
|
|
386
668
|
}
|
|
387
669
|
return;
|
|
@@ -389,179 +671,6 @@ function baseMiddleware(compiler, { formidableOptions = {}, bodyParserOptions =
|
|
|
389
671
|
res.end("");
|
|
390
672
|
};
|
|
391
673
|
}
|
|
392
|
-
function fineMock(mockList, logger, { pathname, method, request }) {
|
|
393
|
-
return mockList.find((mock) => {
|
|
394
|
-
if (!pathname || !mock || !mock.url || mock.ws === true) return false;
|
|
395
|
-
const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
|
|
396
|
-
if (!methods.includes(method)) return false;
|
|
397
|
-
const hasMock = pathToRegexp(mock.url).test(pathname);
|
|
398
|
-
if (hasMock && mock.validator) {
|
|
399
|
-
const params = parseParams(mock.url, pathname);
|
|
400
|
-
if (isFunction(mock.validator)) return mock.validator({
|
|
401
|
-
params,
|
|
402
|
-
...request
|
|
403
|
-
});
|
|
404
|
-
else try {
|
|
405
|
-
return validate({
|
|
406
|
-
params,
|
|
407
|
-
...request
|
|
408
|
-
}, mock.validator);
|
|
409
|
-
} catch (e) {
|
|
410
|
-
const file = mock.__filepath__;
|
|
411
|
-
logger.error(`${pc.red(`mock error at ${pathname}`)}\n${e}\n at validator (${pc.underline(file)})`, mock.log);
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return hasMock;
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
function responseStatus(response, status = 200, statusText) {
|
|
419
|
-
response.statusCode = status;
|
|
420
|
-
response.statusMessage = statusText || getHTTPStatusText(status);
|
|
421
|
-
}
|
|
422
|
-
async function provideHeaders(req, res, mock, logger) {
|
|
423
|
-
const { headers, type = "json" } = mock;
|
|
424
|
-
const filepath = mock.__filepath__;
|
|
425
|
-
const contentType = mime.contentType(type) || mime.contentType(mime.lookup(type) || "");
|
|
426
|
-
if (contentType) res.setHeader("Content-Type", contentType);
|
|
427
|
-
res.setHeader("Cache-Control", "no-cache,max-age=0");
|
|
428
|
-
res.setHeader("X-Mock-Power-By", "vite-plugin-mock-dev-server");
|
|
429
|
-
if (filepath) res.setHeader("X-File-Path", filepath);
|
|
430
|
-
if (!headers) return;
|
|
431
|
-
try {
|
|
432
|
-
const raw = isFunction(headers) ? await headers(req) : headers;
|
|
433
|
-
Object.keys(raw).forEach((key) => {
|
|
434
|
-
res.setHeader(key, raw[key]);
|
|
435
|
-
});
|
|
436
|
-
} catch (e) {
|
|
437
|
-
logger.error(`${pc.red(`mock error at ${req.url.split("?")[0]}`)}\n${e}\n at headers (${pc.underline(filepath)})`, mock.log);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
async function provideCookies(req, res, mock, logger) {
|
|
441
|
-
const { cookies } = mock;
|
|
442
|
-
const filepath = mock.__filepath__;
|
|
443
|
-
if (!cookies) return;
|
|
444
|
-
try {
|
|
445
|
-
const raw = isFunction(cookies) ? await cookies(req) : cookies;
|
|
446
|
-
Object.keys(raw).forEach((key) => {
|
|
447
|
-
const cookie = raw[key];
|
|
448
|
-
if (isArray(cookie)) {
|
|
449
|
-
const [value, options] = cookie;
|
|
450
|
-
res.setCookie(key, value, options);
|
|
451
|
-
} else res.setCookie(key, cookie);
|
|
452
|
-
});
|
|
453
|
-
} catch (e) {
|
|
454
|
-
logger.error(`${pc.red(`mock error at ${req.url.split("?")[0]}`)}\n${e}\n at cookies (${pc.underline(filepath)})`, mock.log);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function sendData(res, raw, type) {
|
|
458
|
-
if (isReadableStream(raw)) raw.pipe(res);
|
|
459
|
-
else if (Buffer.isBuffer(raw)) res.end(type === "text" || type === "json" ? raw.toString("utf-8") : raw);
|
|
460
|
-
else {
|
|
461
|
-
const content = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
462
|
-
res.end(type === "buffer" ? Buffer.from(content) : content);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
async function realDelay(startTime, delay) {
|
|
466
|
-
if (!delay || typeof delay === "number" && delay <= 0 || isArray(delay) && delay.length !== 2) return;
|
|
467
|
-
let realDelay$1 = 0;
|
|
468
|
-
if (isArray(delay)) {
|
|
469
|
-
const [min, max] = delay;
|
|
470
|
-
realDelay$1 = random(min, max);
|
|
471
|
-
} else realDelay$1 = delay - (timestamp() - startTime);
|
|
472
|
-
if (realDelay$1 > 0) await sleep(realDelay$1);
|
|
473
|
-
}
|
|
474
|
-
function getHTTPStatusText(status) {
|
|
475
|
-
return HTTP_STATUS[status] || "Unknown";
|
|
476
|
-
}
|
|
477
|
-
function requestLog(request, filepath) {
|
|
478
|
-
const { url, method, query, params, body } = request;
|
|
479
|
-
let { pathname } = new URL(url, "http://example.com");
|
|
480
|
-
pathname = pc.green(decodeURIComponent(pathname));
|
|
481
|
-
const format = (prefix, data) => {
|
|
482
|
-
return !data || isEmptyObject(data) ? "" : ` ${pc.gray(`${prefix}:`)}${JSON.stringify(data)}`;
|
|
483
|
-
};
|
|
484
|
-
const ms = pc.magenta(pc.bold(method));
|
|
485
|
-
const qs = format("query", query);
|
|
486
|
-
const ps = format("params", params);
|
|
487
|
-
const bs = format("body", body);
|
|
488
|
-
const file = ` ${pc.dim(pc.underline(`(${filepath})`))}`;
|
|
489
|
-
return `${ms} ${pathname}${qs}${ps}${bs}${file}`;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
//#endregion
|
|
493
|
-
//#region src/core/transform.ts
|
|
494
|
-
function transformRawData(raw, __filepath__) {
|
|
495
|
-
let mockConfig;
|
|
496
|
-
if (isArray(raw)) mockConfig = raw.map((item) => ({
|
|
497
|
-
...item,
|
|
498
|
-
__filepath__
|
|
499
|
-
}));
|
|
500
|
-
else if ("url" in raw) mockConfig = {
|
|
501
|
-
...raw,
|
|
502
|
-
__filepath__
|
|
503
|
-
};
|
|
504
|
-
else {
|
|
505
|
-
mockConfig = [];
|
|
506
|
-
Object.keys(raw).forEach((key) => {
|
|
507
|
-
const data = raw[key];
|
|
508
|
-
if (isArray(data)) mockConfig.push(...data.map((item) => ({
|
|
509
|
-
...item,
|
|
510
|
-
__filepath__
|
|
511
|
-
})));
|
|
512
|
-
else mockConfig.push({
|
|
513
|
-
...data,
|
|
514
|
-
__filepath__
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
return mockConfig;
|
|
519
|
-
}
|
|
520
|
-
function transformMockData(mockList) {
|
|
521
|
-
const list = [];
|
|
522
|
-
for (const [, handle] of mockList.entries()) if (handle) list.push(...toArray(handle));
|
|
523
|
-
const mocks = {};
|
|
524
|
-
list.filter((mock) => isPlainObject(mock) && mock.enabled !== false && mock.url).forEach((mock) => {
|
|
525
|
-
const { pathname, query } = urlParse(mock.url);
|
|
526
|
-
const list$1 = mocks[pathname] ??= [];
|
|
527
|
-
const current = {
|
|
528
|
-
...mock,
|
|
529
|
-
url: pathname
|
|
530
|
-
};
|
|
531
|
-
if (current.ws !== true) {
|
|
532
|
-
const validator = current.validator;
|
|
533
|
-
if (!isEmptyObject(query)) if (isFunction(validator)) current.validator = function(request) {
|
|
534
|
-
return isObjectSubset(request.query, query) && validator(request);
|
|
535
|
-
};
|
|
536
|
-
else if (validator) {
|
|
537
|
-
current.validator = { ...validator };
|
|
538
|
-
current.validator.query = current.validator.query ? {
|
|
539
|
-
...query,
|
|
540
|
-
...current.validator.query
|
|
541
|
-
} : query;
|
|
542
|
-
} else current.validator = { query };
|
|
543
|
-
}
|
|
544
|
-
list$1.push(current);
|
|
545
|
-
});
|
|
546
|
-
Object.keys(mocks).forEach((key) => {
|
|
547
|
-
mocks[key] = sortByValidator(mocks[key]);
|
|
548
|
-
});
|
|
549
|
-
return mocks;
|
|
550
|
-
}
|
|
551
|
-
function sortByValidator(mocks) {
|
|
552
|
-
return sortBy(mocks, (item) => {
|
|
553
|
-
if (item.ws === true) return 0;
|
|
554
|
-
const { validator } = item;
|
|
555
|
-
if (!validator || isEmptyObject(validator)) return 2;
|
|
556
|
-
if (isFunction(validator)) return 0;
|
|
557
|
-
const count = Object.keys(validator).reduce((prev, key) => prev + keysCount(validator[key]), 0);
|
|
558
|
-
return 1 / count;
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
function keysCount(obj) {
|
|
562
|
-
if (!obj) return 0;
|
|
563
|
-
return Object.keys(obj).length;
|
|
564
|
-
}
|
|
565
674
|
|
|
566
675
|
//#endregion
|
|
567
676
|
//#region src/core/ws.ts
|
|
@@ -592,10 +701,10 @@ function mockWebSocket(compiler, server, { wsProxies: proxies, cookiesOptions, l
|
|
|
592
701
|
mock.setup?.(wss, context);
|
|
593
702
|
wss.on("close", () => wssMap.delete(pathname));
|
|
594
703
|
wss.on("error", (e) => {
|
|
595
|
-
logger.error(`${
|
|
704
|
+
logger.error(`${ansis.red(`WebSocket mock error at ${wss.path}`)}\n${e}\n at setup (${filepath})`, mock.log);
|
|
596
705
|
});
|
|
597
706
|
} catch (e) {
|
|
598
|
-
logger.error(`${
|
|
707
|
+
logger.error(`${ansis.red(`WebSocket mock error at ${wss.path}`)}\n${e}\n at setup (${filepath})`, mock.log);
|
|
599
708
|
}
|
|
600
709
|
};
|
|
601
710
|
const emitConnection = (wss, ws, req, connectionList) => {
|
|
@@ -627,12 +736,10 @@ function mockWebSocket(compiler, server, { wsProxies: proxies, cookiesOptions, l
|
|
|
627
736
|
const { pathname, query } = urlParse(req.url);
|
|
628
737
|
if (!pathname || proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) return;
|
|
629
738
|
const mockData = compiler.mockData;
|
|
630
|
-
const mockUrl = Object.keys(mockData).find((key) =>
|
|
631
|
-
return pathToRegexp(key).test(pathname);
|
|
632
|
-
});
|
|
739
|
+
const mockUrl = Object.keys(mockData).find((key) => isPathMatch(key, pathname));
|
|
633
740
|
if (!mockUrl) return;
|
|
634
741
|
const mock = mockData[mockUrl].find((mock$1) => {
|
|
635
|
-
return mock$1.url && mock$1.ws &&
|
|
742
|
+
return mock$1.url && mock$1.ws && isPathMatch(mock$1.url, pathname);
|
|
636
743
|
});
|
|
637
744
|
if (!mock) return;
|
|
638
745
|
const filepath = mock.__filepath__;
|
|
@@ -656,10 +763,10 @@ function mockWebSocket(compiler, server, { wsProxies: proxies, cookiesOptions, l
|
|
|
656
763
|
const { query: refererQuery } = urlParse(req.headers.referer || "");
|
|
657
764
|
request.query = query;
|
|
658
765
|
request.refererQuery = refererQuery;
|
|
659
|
-
request.params =
|
|
766
|
+
request.params = parseRequestParams(mockUrl, pathname);
|
|
660
767
|
request.getCookie = cookies.get.bind(cookies);
|
|
661
768
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
662
|
-
logger.info(`${
|
|
769
|
+
logger.info(`${ansis.magenta.bold("WebSocket")} ${ansis.green(req.url)} connected ${ansis.dim(`(${filepath})`)}`, mock.log);
|
|
663
770
|
wssContext.connectionList.push({
|
|
664
771
|
req: request,
|
|
665
772
|
ws
|
|
@@ -686,42 +793,4 @@ function cleanupRunner(cleanupList) {
|
|
|
686
793
|
}
|
|
687
794
|
|
|
688
795
|
//#endregion
|
|
689
|
-
|
|
690
|
-
const logLevels = {
|
|
691
|
-
silent: 0,
|
|
692
|
-
error: 1,
|
|
693
|
-
warn: 2,
|
|
694
|
-
info: 3,
|
|
695
|
-
debug: 4
|
|
696
|
-
};
|
|
697
|
-
function createLogger(prefix, defaultLevel = "info") {
|
|
698
|
-
prefix = `[${prefix}]`;
|
|
699
|
-
function output(type, msg, level) {
|
|
700
|
-
level = isBoolean(level) ? level ? defaultLevel : "error" : level;
|
|
701
|
-
const thresh = logLevels[level];
|
|
702
|
-
if (thresh >= logLevels[type]) {
|
|
703
|
-
const method = type === "info" || type === "debug" ? "log" : type;
|
|
704
|
-
const tag = type === "debug" ? pc.magenta(pc.bold(prefix)) : type === "info" ? pc.cyan(pc.bold(prefix)) : type === "warn" ? pc.yellow(pc.bold(prefix)) : pc.red(pc.bold(prefix));
|
|
705
|
-
const format = `${pc.dim((/* @__PURE__ */ new Date()).toLocaleTimeString())} ${tag} ${msg}`;
|
|
706
|
-
console[method](format);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
const logger = {
|
|
710
|
-
debug(msg, level = defaultLevel) {
|
|
711
|
-
output("debug", msg, level);
|
|
712
|
-
},
|
|
713
|
-
info(msg, level = defaultLevel) {
|
|
714
|
-
output("info", msg, level);
|
|
715
|
-
},
|
|
716
|
-
warn(msg, level = defaultLevel) {
|
|
717
|
-
output("warn", msg, level);
|
|
718
|
-
},
|
|
719
|
-
error(msg, level = defaultLevel) {
|
|
720
|
-
output("error", msg, level);
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
return logger;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
//#endregion
|
|
727
|
-
export { baseMiddleware, createLogger, debug, doesProxyContextMatchUrl, ensureProxies, logLevels, lookupFile, mockWebSocket, normalizePath, recoverRequest, sortByValidator, transformMockData, transformRawData, urlParse };
|
|
796
|
+
export { createLogger, createMatcher, createMockMiddleware, debug, doesProxyContextMatchUrl, isPathMatch, logLevels, mockWebSocket, normalizePath, processMockData, processRawData, recoverRequest, sortByValidator, urlParse };
|