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