rspack-plugin-mock 2.1.0 → 2.2.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.d.ts +113 -114
- package/dist/helper.js +24 -145
- package/dist/index-BjZK24gW.d.ts +1065 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +15 -8
- package/dist/{logger-CzeuHKAL.js → logger-DTZcb53u.js} +126 -599
- package/dist/{options-CI0LRpJZ.js → options-C3NAM6gv.js} +24 -33
- package/dist/rsbuild.d.ts +2 -3
- package/dist/rsbuild.js +4 -4
- package/dist/{server-pC78IYdH.d.ts → server-DurHqymI.d.ts} +24 -40
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/package.json +32 -50
- package/dist/index-BMMGM-eD.d.ts +0 -1066
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { attempt, attemptAsync, deepEqual, hasOwn, isArray, isBoolean, isEmptyObject, isFunction, isPlainObject, isString, kebabCase, objectKeys, partition, random, sleep, sortBy, timestamp, toArray, uniq } from "@pengzhanbo/utils";
|
|
2
|
+
import { attempt, attemptAsync, deepEqual, hasOwn, isArray, isBoolean, isEmptyObject, isFunction, isPlainObject, isString, kebabCase, objectKeys, omit, partition, random, sleep, sortBy, timestamp, toArray, uniq } from "@pengzhanbo/utils";
|
|
3
3
|
import fs, { promises } from "node:fs";
|
|
4
4
|
import ansis from "ansis";
|
|
5
5
|
import picomatch from "picomatch";
|
|
@@ -7,20 +7,18 @@ import { loadPackageJSONSync } from "local-pkg";
|
|
|
7
7
|
import { match, parse, pathToRegexp } from "path-to-regexp";
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import Debug from "debug";
|
|
11
10
|
import { Volume, createFsFromVolume } from "memfs";
|
|
12
11
|
import { parse as parse$1 } from "node:querystring";
|
|
13
|
-
import crypto from "node:crypto";
|
|
14
12
|
import cors from "cors";
|
|
15
13
|
import bodyParser from "co-body";
|
|
16
14
|
import formidable from "formidable";
|
|
17
|
-
import
|
|
15
|
+
import Cookies from "cookies";
|
|
18
16
|
import { Buffer } from "node:buffer";
|
|
19
17
|
import zlib from "node:zlib";
|
|
20
18
|
import HTTP_STATUS from "http-status";
|
|
21
19
|
import * as mime from "mime-types";
|
|
22
20
|
import { WebSocketServer } from "ws";
|
|
23
|
-
|
|
21
|
+
|
|
24
22
|
function createMatcher(include, exclude, defaultIgnore = true) {
|
|
25
23
|
const pattern = [];
|
|
26
24
|
const ignore = [...defaultIgnore ? ["**/node_modules/**"] : [], ...toArray(exclude)];
|
|
@@ -34,12 +32,12 @@ function createMatcher(include, exclude, defaultIgnore = true) {
|
|
|
34
32
|
isMatch: picomatch(pattern, { ignore })
|
|
35
33
|
};
|
|
36
34
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const PATTERN_CACHE =
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const PATTERN_CACHE = new Map();
|
|
40
38
|
function doesProxyContextMatchUrl(context, req) {
|
|
41
39
|
const url = req.url;
|
|
42
|
-
if (typeof context === "function") return context(url, req);
|
|
40
|
+
if (typeof context === "function") return !!context(url, req);
|
|
43
41
|
if (context[0] === "^") {
|
|
44
42
|
let pattern = PATTERN_CACHE.get(context);
|
|
45
43
|
if (!pattern) PATTERN_CACHE.set(context, pattern = new RegExp(context));
|
|
@@ -47,8 +45,8 @@ function doesProxyContextMatchUrl(context, req) {
|
|
|
47
45
|
}
|
|
48
46
|
return url.startsWith(context);
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
|
|
49
|
+
|
|
52
50
|
function getPackageDeps(cwd) {
|
|
53
51
|
const { dependencies, devDependencies, peerDependencies, optionalDependencies } = loadPackageJSONSync(cwd) || {};
|
|
54
52
|
return {
|
|
@@ -59,22 +57,18 @@ function getPackageDeps(cwd) {
|
|
|
59
57
|
};
|
|
60
58
|
}
|
|
61
59
|
function getPackageDepList(cwd) {
|
|
62
|
-
|
|
60
|
+
const deps = getPackageDeps(cwd);
|
|
61
|
+
return uniq(objectKeys(deps));
|
|
63
62
|
}
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
|
|
64
|
+
|
|
66
65
|
function isStream(stream) {
|
|
67
66
|
return stream !== null && typeof stream === "object" && typeof stream.pipe === "function";
|
|
68
67
|
}
|
|
69
68
|
function isReadableStream(stream) {
|
|
70
69
|
return isStream(stream) && stream.readable !== false && typeof stream._read === "function" && typeof stream._readableState === "object";
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
* 判断内容类型是否为文本类型
|
|
74
|
-
*
|
|
75
|
-
* @param contentType 内容类型
|
|
76
|
-
* @returns 是否为文本类型
|
|
77
|
-
*/
|
|
71
|
+
|
|
78
72
|
function isTextContent(contentType) {
|
|
79
73
|
return [
|
|
80
74
|
"text",
|
|
@@ -82,15 +76,9 @@ function isTextContent(contentType) {
|
|
|
82
76
|
"xml"
|
|
83
77
|
].some((type) => contentType.includes(type));
|
|
84
78
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
* Checks if target object is a subset of source object.
|
|
89
|
-
* That is, all properties and their corresponding values in target exist in source.
|
|
90
|
-
*
|
|
91
|
-
* 深度比较两个对象之间,target 是否属于 source 的子集,
|
|
92
|
-
* 即 target 的所有属性和对应的值,都在 source 中,
|
|
93
|
-
*/
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
94
82
|
function isObjectSubset(source, target) {
|
|
95
83
|
if (!target) return true;
|
|
96
84
|
for (const key in target) if (!isIncluded(source[key], target[key])) return false;
|
|
@@ -98,7 +86,7 @@ function isObjectSubset(source, target) {
|
|
|
98
86
|
}
|
|
99
87
|
function isIncluded(source, target) {
|
|
100
88
|
if (isArray(source) && isArray(target)) {
|
|
101
|
-
const seen =
|
|
89
|
+
const seen = new Set();
|
|
102
90
|
return target.every((ti) => source.some((si, i) => {
|
|
103
91
|
if (seen.has(i)) return false;
|
|
104
92
|
const included = isIncluded(si, ti);
|
|
@@ -109,12 +97,10 @@ function isIncluded(source, target) {
|
|
|
109
97
|
if (isPlainObject(source) && isPlainObject(target)) return isObjectSubset(source, target);
|
|
110
98
|
return Object.is(source, target);
|
|
111
99
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const cache =
|
|
115
|
-
|
|
116
|
-
* 判断 path 是否匹配 pattern
|
|
117
|
-
*/
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
const cache = new Map();
|
|
103
|
+
|
|
118
104
|
function isPathMatch(pattern, path) {
|
|
119
105
|
let regexp = cache.get(pattern);
|
|
120
106
|
if (!regexp) {
|
|
@@ -123,8 +109,8 @@ function isPathMatch(pattern, path) {
|
|
|
123
109
|
}
|
|
124
110
|
return regexp.test(path);
|
|
125
111
|
}
|
|
126
|
-
|
|
127
|
-
|
|
112
|
+
|
|
113
|
+
|
|
128
114
|
function matchScene(activeScene, mockScene) {
|
|
129
115
|
if (!mockScene) return true;
|
|
130
116
|
const scenes = toArray(mockScene);
|
|
@@ -137,7 +123,6 @@ const vfs = createFsFromVolume(new Volume());
|
|
|
137
123
|
function getDirname(importMetaUrl) {
|
|
138
124
|
return path.dirname(fileURLToPath(importMetaUrl));
|
|
139
125
|
}
|
|
140
|
-
Debug("vite:mock-dev-server");
|
|
141
126
|
const windowsSlashRE = /\\/g;
|
|
142
127
|
const isWindows = os.platform() === "win32";
|
|
143
128
|
function slash(p) {
|
|
@@ -146,12 +131,9 @@ function slash(p) {
|
|
|
146
131
|
function normalizePath(id) {
|
|
147
132
|
return path.posix.normalize(isWindows ? slash(id) : id);
|
|
148
133
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
* nodejs 从 19.0.0 开始 弃用 url.parse,因此使用 url.parse 来解析 可能会报错,
|
|
153
|
-
* 使用 URL 来解析
|
|
154
|
-
*/
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
155
137
|
function urlParse(input) {
|
|
156
138
|
const url = new URL(input, "http://example.com");
|
|
157
139
|
return {
|
|
@@ -159,29 +141,21 @@ function urlParse(input) {
|
|
|
159
141
|
query: parse$1(url.search.replace(/^\?/, ""))
|
|
160
142
|
};
|
|
161
143
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
function waitingFor(onSuccess, maxRetry = 5) {
|
|
165
|
-
return function wait(getter, retry = 0) {
|
|
166
|
-
const value = getter();
|
|
167
|
-
if (value) onSuccess(value);
|
|
168
|
-
else if (retry < maxRetry) setTimeout(() => wait(getter, retry + 1), 100);
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
//#endregion
|
|
172
|
-
//#region src/compiler/processData.ts
|
|
144
|
+
|
|
145
|
+
|
|
173
146
|
function processRawData(rawData) {
|
|
174
147
|
return rawData.filter((item) => item[0]).map(([raw, __filepath__]) => {
|
|
175
148
|
let mockConfig;
|
|
176
|
-
if (raw.default)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
149
|
+
if (raw.default) {
|
|
150
|
+
if (isArray(raw.default)) mockConfig = raw.default.map((item) => ({
|
|
151
|
+
...item,
|
|
152
|
+
__filepath__
|
|
153
|
+
}));
|
|
154
|
+
else mockConfig = {
|
|
155
|
+
...raw.default,
|
|
156
|
+
__filepath__
|
|
157
|
+
};
|
|
158
|
+
} else if ("url" in raw) mockConfig = {
|
|
185
159
|
...raw,
|
|
186
160
|
__filepath__
|
|
187
161
|
};
|
|
@@ -215,16 +189,18 @@ function processMockData(mockList) {
|
|
|
215
189
|
};
|
|
216
190
|
if (current.ws !== true) {
|
|
217
191
|
const validator = current.validator;
|
|
218
|
-
if (!isEmptyObject(query))
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
192
|
+
if (!isEmptyObject(query)) {
|
|
193
|
+
if (isFunction(validator)) current.validator = function(request) {
|
|
194
|
+
return isObjectSubset(request.query, query) && validator(request);
|
|
195
|
+
};
|
|
196
|
+
else if (validator) {
|
|
197
|
+
current.validator = { ...validator };
|
|
198
|
+
current.validator.query = current.validator.query ? {
|
|
199
|
+
...query,
|
|
200
|
+
...current.validator.query
|
|
201
|
+
} : query;
|
|
202
|
+
} else current.validator = { query };
|
|
203
|
+
}
|
|
228
204
|
}
|
|
229
205
|
list.push(current);
|
|
230
206
|
});
|
|
@@ -246,35 +222,18 @@ function keysCount(obj) {
|
|
|
246
222
|
if (!obj) return 0;
|
|
247
223
|
return objectKeys(obj).length;
|
|
248
224
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
* Create CORS middleware
|
|
253
|
-
*
|
|
254
|
-
* 创建 CORS 中间件
|
|
255
|
-
*
|
|
256
|
-
* @param corsOptions - CORS options / CORS 配置项
|
|
257
|
-
* @returns CORS middleware function or undefined / CORS 中间件函数或未定义
|
|
258
|
-
*/
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
259
228
|
function createCors(corsOptions) {
|
|
260
229
|
const corsMiddleware = corsOptions ? cors(corsOptions) : void 0;
|
|
261
230
|
return corsMiddleware ? (req, res) => new Promise((resolve, reject) => corsMiddleware(req, res, (err) => {
|
|
262
231
|
err ? reject(err) : resolve();
|
|
263
232
|
})) : void 0;
|
|
264
233
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
* Parse request body
|
|
269
|
-
*
|
|
270
|
-
* 解析请求体 request.body
|
|
271
|
-
*
|
|
272
|
-
* @param req - Incoming message object / 入站消息对象
|
|
273
|
-
* @param logger - Logger instance / 日志实例
|
|
274
|
-
* @param formidableOptions - Formidable options for multipart form data / 用于 multipart 表单数据的 Formidable 配置项
|
|
275
|
-
* @param bodyParserOptions - Body parser options / 请求体解析配置项
|
|
276
|
-
* @returns Parsed request body / 解析后的请求体
|
|
277
|
-
*/
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
278
237
|
async function parseRequestBody(req, logger, formidableOptions, bodyParserOptions = {}) {
|
|
279
238
|
const method = req.method.toUpperCase();
|
|
280
239
|
if (["HEAD", "OPTIONS"].includes(method)) return void 0;
|
|
@@ -298,26 +257,14 @@ async function parseRequestBody(req, logger, formidableOptions, bodyParserOption
|
|
|
298
257
|
logger.error(e);
|
|
299
258
|
}
|
|
300
259
|
}
|
|
301
|
-
|
|
302
|
-
* Default formidable options
|
|
303
|
-
*
|
|
304
|
-
* 默认的 formidable 配置项
|
|
305
|
-
*/
|
|
260
|
+
|
|
306
261
|
const DEFAULT_FORMIDABLE_OPTIONS = {
|
|
307
262
|
keepExtensions: true,
|
|
308
263
|
filename(name, ext, part) {
|
|
309
264
|
return part?.originalFilename || `${name}.${Date.now()}${ext ? `.${ext}` : ""}`;
|
|
310
265
|
}
|
|
311
266
|
};
|
|
312
|
-
|
|
313
|
-
* Parse request body with multipart form data
|
|
314
|
-
*
|
|
315
|
-
* 解析 request form multipart body
|
|
316
|
-
*
|
|
317
|
-
* @param req - Incoming message object / 入站消息对象
|
|
318
|
-
* @param options - Formidable options / Formidable 配置项
|
|
319
|
-
* @returns Parsed request body / 解析后的请求体
|
|
320
|
-
*/
|
|
267
|
+
|
|
321
268
|
async function parseRequestBodyWithMultipart(req, options) {
|
|
322
269
|
const form = formidable({
|
|
323
270
|
...DEFAULT_FORMIDABLE_OPTIONS,
|
|
@@ -336,21 +283,9 @@ async function parseRequestBodyWithMultipart(req, options) {
|
|
|
336
283
|
});
|
|
337
284
|
});
|
|
338
285
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
* path-to-regexp 匹配函数缓存
|
|
343
|
-
*/
|
|
344
|
-
const matcherCache = /* @__PURE__ */ new Map();
|
|
345
|
-
/**
|
|
346
|
-
* Parse request URL dynamic parameters
|
|
347
|
-
*
|
|
348
|
-
* 解析请求 url 中的动态参数 params
|
|
349
|
-
*
|
|
350
|
-
* @param pattern - URL pattern / URL 模式
|
|
351
|
-
* @param url - Request URL / 请求 URL
|
|
352
|
-
* @returns Parsed parameters / 解析后的参数
|
|
353
|
-
*/
|
|
286
|
+
|
|
287
|
+
const matcherCache = new Map();
|
|
288
|
+
|
|
354
289
|
function parseRequestParams(pattern, url) {
|
|
355
290
|
let matcher = matcherCache.get(pattern);
|
|
356
291
|
if (!matcher) {
|
|
@@ -360,40 +295,15 @@ function parseRequestParams(pattern, url) {
|
|
|
360
295
|
const matched = matcher(url);
|
|
361
296
|
return matched ? matched.params : {};
|
|
362
297
|
}
|
|
363
|
-
|
|
364
|
-
* Validate request against validator
|
|
365
|
-
*
|
|
366
|
-
* 验证请求是否符合 validator
|
|
367
|
-
*
|
|
368
|
-
* @param request - Request object / 请求对象
|
|
369
|
-
* @param validator - Validator object / 验证器对象
|
|
370
|
-
* @returns Whether the request is valid / 请求是否有效
|
|
371
|
-
*/
|
|
298
|
+
|
|
372
299
|
function requestValidate(request, validator) {
|
|
373
300
|
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);
|
|
374
301
|
}
|
|
375
|
-
|
|
376
|
-
* Format log data
|
|
377
|
-
*
|
|
378
|
-
* 格式化日志数据
|
|
379
|
-
*
|
|
380
|
-
* @param prefix - Log prefix / 日志前缀
|
|
381
|
-
* @param data - Data to format / 要格式化的数据
|
|
382
|
-
* @returns Formatted log string / 格式化后的日志字符串
|
|
383
|
-
*/
|
|
302
|
+
|
|
384
303
|
function formatLog(prefix, data) {
|
|
385
304
|
return !data || isEmptyObject(data) ? "" : ` ${ansis.gray(`${prefix}:`)}${JSON.stringify(data)}`;
|
|
386
305
|
}
|
|
387
|
-
|
|
388
|
-
* Generate request log
|
|
389
|
-
*
|
|
390
|
-
* 生成请求日志
|
|
391
|
-
*
|
|
392
|
-
* @param request - Request object / 请求对象
|
|
393
|
-
* @param filepath - Mock file path / Mock 文件路径
|
|
394
|
-
* @param shouldSimulateError - Whether to simulate error / 是否模拟错误
|
|
395
|
-
* @returns Formatted log string / 格式化后的日志字符串
|
|
396
|
-
*/
|
|
306
|
+
|
|
397
307
|
function requestLog(request, filepath, shouldSimulateError) {
|
|
398
308
|
const { url, method, query, params, body } = request;
|
|
399
309
|
let { pathname } = new URL(url, "http://example.com");
|
|
@@ -406,22 +316,9 @@ function requestLog(request, filepath, shouldSimulateError) {
|
|
|
406
316
|
const file = ` ${ansis.dim.underline(`(${filepath})`)}`;
|
|
407
317
|
return `${ms}${es} ${pathname}${qs}${ps}${bs}${file}`;
|
|
408
318
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
* Find matching mock data
|
|
413
|
-
*
|
|
414
|
-
* 查找匹配的 mock data
|
|
415
|
-
*
|
|
416
|
-
* @param mockList - Mock options list / Mock 配置列表
|
|
417
|
-
* @param logger - Logger instance / 日志实例
|
|
418
|
-
* @param options - Find options / 查找选项
|
|
419
|
-
* @param options.pathname - Request pathname / 请求路径
|
|
420
|
-
* @param options.method - HTTP method / HTTP 方法
|
|
421
|
-
* @param options.request - Request object / 请求对象
|
|
422
|
-
* @param options.activeScene - Active scene / 当前场景
|
|
423
|
-
* @returns Matched mock HTTP item or undefined / 匹配的 Mock HTTP 项或未定义
|
|
424
|
-
*/
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
425
322
|
function findMockData(mockList, logger, { pathname, method, request, activeScene }) {
|
|
426
323
|
return mockList.find((mock) => {
|
|
427
324
|
if (!pathname || !mock || !mock.url || mock.ws) return false;
|
|
@@ -450,212 +347,8 @@ function findMockData(mockList, logger, { pathname, method, request, activeScene
|
|
|
450
347
|
return hasMock;
|
|
451
348
|
});
|
|
452
349
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* RegExp to match field-content in RFC 7230 sec 3.2
|
|
457
|
-
*
|
|
458
|
-
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
|
459
|
-
* field-vchar = VCHAR / obs-text
|
|
460
|
-
* obs-text = %x80-FF
|
|
461
|
-
*/
|
|
462
|
-
const fieldContentRegExp = /^[\t\u0020-\u007E\u0080-\u00FF]+$/;
|
|
463
|
-
/**
|
|
464
|
-
* RegExp to match Priority cookie attribute value.
|
|
465
|
-
*/
|
|
466
|
-
const PRIORITY_REGEXP = /^(?:low|medium|high)$/i;
|
|
467
|
-
/**
|
|
468
|
-
* Cache for generated name regular expressions.
|
|
469
|
-
*/
|
|
470
|
-
const REGEXP_CACHE = Object.create(null);
|
|
471
|
-
/**
|
|
472
|
-
* RegExp to match all characters to escape in a RegExp.
|
|
473
|
-
*/
|
|
474
|
-
const REGEXP_ESCAPE_CHARS_REGEXP = /[\^$\\.*+?()[\]{}|]/g;
|
|
475
|
-
/**
|
|
476
|
-
* RegExp to match basic restricted name characters for loose validation.
|
|
477
|
-
*/
|
|
478
|
-
const RESTRICTED_NAME_CHARS_REGEXP = /[;=]/;
|
|
479
|
-
/**
|
|
480
|
-
* RegExp to match basic restricted value characters for loose validation.
|
|
481
|
-
*/
|
|
482
|
-
const RESTRICTED_VALUE_CHARS_REGEXP = /;/;
|
|
483
|
-
/**
|
|
484
|
-
* RegExp to match Same-Site cookie attribute value.
|
|
485
|
-
*/
|
|
486
|
-
const SAME_SITE_REGEXP = /^(?:lax|none|strict)$/i;
|
|
487
|
-
//#endregion
|
|
488
|
-
//#region src/cookies/Cookie.ts
|
|
489
|
-
var Cookie = class {
|
|
490
|
-
name;
|
|
491
|
-
value;
|
|
492
|
-
maxAge;
|
|
493
|
-
expires;
|
|
494
|
-
path = "/";
|
|
495
|
-
domain;
|
|
496
|
-
secure = false;
|
|
497
|
-
httpOnly = true;
|
|
498
|
-
sameSite = false;
|
|
499
|
-
overwrite = false;
|
|
500
|
-
priority;
|
|
501
|
-
partitioned;
|
|
502
|
-
constructor(name, value, options = {}) {
|
|
503
|
-
if (!fieldContentRegExp.test(name) || RESTRICTED_NAME_CHARS_REGEXP.test(name)) throw new TypeError("argument name is invalid");
|
|
504
|
-
if (value && (!fieldContentRegExp.test(value) || RESTRICTED_VALUE_CHARS_REGEXP.test(value))) throw new TypeError("argument value is invalid");
|
|
505
|
-
this.name = name;
|
|
506
|
-
this.value = value;
|
|
507
|
-
Object.assign(this, options);
|
|
508
|
-
if (!this.value) {
|
|
509
|
-
this.expires = /* @__PURE__ */ new Date(0);
|
|
510
|
-
this.maxAge = void 0;
|
|
511
|
-
}
|
|
512
|
-
if (this.path && !fieldContentRegExp.test(this.path)) throw new TypeError("[Cookie] option path is invalid");
|
|
513
|
-
if (this.domain && !fieldContentRegExp.test(this.domain)) throw new TypeError("[Cookie] option domain is invalid");
|
|
514
|
-
if (typeof this.maxAge === "number" ? Number.isNaN(this.maxAge) || !Number.isFinite(this.maxAge) : this.maxAge) throw new TypeError("[Cookie] option maxAge is invalid");
|
|
515
|
-
if (this.priority && !PRIORITY_REGEXP.test(this.priority)) throw new TypeError("[Cookie] option priority is invalid");
|
|
516
|
-
if (this.sameSite && this.sameSite !== true && !SAME_SITE_REGEXP.test(this.sameSite)) throw new TypeError("[Cookie] option sameSite is invalid");
|
|
517
|
-
}
|
|
518
|
-
toString() {
|
|
519
|
-
return `${this.name}=${this.value}`;
|
|
520
|
-
}
|
|
521
|
-
toHeader() {
|
|
522
|
-
let header = this.toString();
|
|
523
|
-
if (this.maxAge) this.expires = new Date(Date.now() + this.maxAge);
|
|
524
|
-
if (this.path) header += `; path=${this.path}`;
|
|
525
|
-
if (this.expires) header += `; expires=${this.expires.toUTCString()}`;
|
|
526
|
-
if (this.domain) header += `; domain=${this.domain}`;
|
|
527
|
-
if (this.priority) header += `; priority=${this.priority.toLowerCase()}`;
|
|
528
|
-
if (this.sameSite) header += `; samesite=${this.sameSite === true ? "strict" : this.sameSite.toLowerCase()}`;
|
|
529
|
-
if (this.secure) header += "; secure";
|
|
530
|
-
if (this.httpOnly) header += "; httponly";
|
|
531
|
-
if (this.partitioned) header += "; partitioned";
|
|
532
|
-
return header;
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
//#endregion
|
|
536
|
-
//#region src/cookies/timeSafeCompare.ts
|
|
537
|
-
function bufferEqual(a, b) {
|
|
538
|
-
if (a.length !== b.length) return false;
|
|
539
|
-
if (crypto.timingSafeEqual) return crypto.timingSafeEqual(a, b);
|
|
540
|
-
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
541
|
-
return true;
|
|
542
|
-
}
|
|
543
|
-
function createHmac(key, data) {
|
|
544
|
-
return crypto.createHmac("sha256", key).update(data).digest();
|
|
545
|
-
}
|
|
546
|
-
function timeSafeCompare(a, b) {
|
|
547
|
-
const sa = String(a);
|
|
548
|
-
const sb = String(b);
|
|
549
|
-
const key = crypto.randomBytes(32);
|
|
550
|
-
return bufferEqual(createHmac(key, sa), createHmac(key, sb)) && a === b;
|
|
551
|
-
}
|
|
552
|
-
//#endregion
|
|
553
|
-
//#region src/cookies/Keygrip.ts
|
|
554
|
-
const SLASH_PATTERN = /[/+=]/g;
|
|
555
|
-
const REPLACE_MAP = {
|
|
556
|
-
"/": "_",
|
|
557
|
-
"+": "-",
|
|
558
|
-
"=": ""
|
|
559
|
-
};
|
|
560
|
-
var Keygrip = class {
|
|
561
|
-
algorithm;
|
|
562
|
-
encoding;
|
|
563
|
-
keys = [];
|
|
564
|
-
constructor(keys, algorithm, encoding) {
|
|
565
|
-
this.keys = keys;
|
|
566
|
-
this.algorithm = algorithm || "sha256";
|
|
567
|
-
this.encoding = encoding || "base64";
|
|
568
|
-
}
|
|
569
|
-
sign(data, key = this.keys[0]) {
|
|
570
|
-
return crypto.createHmac(this.algorithm, key).update(data).digest(this.encoding).replace(SLASH_PATTERN, (m) => REPLACE_MAP[m]);
|
|
571
|
-
}
|
|
572
|
-
index(data, digest) {
|
|
573
|
-
for (let i = 0, l = this.keys.length; i < l; i++) if (timeSafeCompare(digest, this.sign(data, this.keys[i]))) return i;
|
|
574
|
-
return -1;
|
|
575
|
-
}
|
|
576
|
-
verify(data, digest) {
|
|
577
|
-
return this.index(data, digest) > -1;
|
|
578
|
-
}
|
|
579
|
-
};
|
|
580
|
-
//#endregion
|
|
581
|
-
//#region src/cookies/Cookies.ts
|
|
582
|
-
var Cookies = class {
|
|
583
|
-
request;
|
|
584
|
-
response;
|
|
585
|
-
secure;
|
|
586
|
-
keys;
|
|
587
|
-
constructor(req, res, options = {}) {
|
|
588
|
-
this.request = req;
|
|
589
|
-
this.response = res;
|
|
590
|
-
this.secure = options.secure;
|
|
591
|
-
if (options.keys instanceof Keygrip) this.keys = options.keys;
|
|
592
|
-
else if (isArray(options.keys)) this.keys = new Keygrip(options.keys);
|
|
593
|
-
}
|
|
594
|
-
set(name, value, options) {
|
|
595
|
-
const req = this.request;
|
|
596
|
-
const res = this.response;
|
|
597
|
-
const headers = toArray(res.getHeader("Set-Cookie"));
|
|
598
|
-
const cookie = new Cookie(name, value, options);
|
|
599
|
-
const signed = options?.signed ?? !!this.keys;
|
|
600
|
-
const secure = this.secure === void 0 ? req.protocol === "https" || isRequestEncrypted(req) : Boolean(this.secure);
|
|
601
|
-
if (!secure && options?.secure) throw new Error("Cannot send secure cookie over unencrypted connection");
|
|
602
|
-
cookie.secure = options?.secure ?? secure;
|
|
603
|
-
pushCookie(headers, cookie);
|
|
604
|
-
if (signed && options) {
|
|
605
|
-
if (!this.keys) throw new Error(".keys required for signed cookies");
|
|
606
|
-
cookie.value = this.keys.sign(cookie.toString());
|
|
607
|
-
cookie.name += ".sig";
|
|
608
|
-
pushCookie(headers, cookie);
|
|
609
|
-
}
|
|
610
|
-
(res.set ? http.OutgoingMessage.prototype.setHeader : res.setHeader).call(res, "Set-Cookie", headers);
|
|
611
|
-
return this;
|
|
612
|
-
}
|
|
613
|
-
get(name, options) {
|
|
614
|
-
const signName = `${name}.sig`;
|
|
615
|
-
const signed = options?.signed ?? !!this.keys;
|
|
616
|
-
const header = this.request.headers.cookie;
|
|
617
|
-
if (!header) return;
|
|
618
|
-
const match = header.match(getPattern(name));
|
|
619
|
-
if (!match) return;
|
|
620
|
-
let value = match[1];
|
|
621
|
-
if (value[0] === "\"") value = value.slice(1, -1);
|
|
622
|
-
if (!options || !signed) return value;
|
|
623
|
-
const remote = this.get(signName);
|
|
624
|
-
if (!remote) return;
|
|
625
|
-
const data = `${name}=${value}`;
|
|
626
|
-
if (!this.keys) throw new Error(".keys required for signed cookies");
|
|
627
|
-
const index = this.keys.index(data, remote);
|
|
628
|
-
if (index < 0) this.set(signName, null, {
|
|
629
|
-
path: "/",
|
|
630
|
-
signed: false
|
|
631
|
-
});
|
|
632
|
-
else {
|
|
633
|
-
index && this.set(signName, this.keys.sign(data), { signed: false });
|
|
634
|
-
return value;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
};
|
|
638
|
-
/**
|
|
639
|
-
* Get the pattern to search for a cookie in a string.
|
|
640
|
-
*/
|
|
641
|
-
function getPattern(name) {
|
|
642
|
-
if (!REGEXP_CACHE[name]) REGEXP_CACHE[name] = new RegExp(`(?:^|;) *${name.replace(REGEXP_ESCAPE_CHARS_REGEXP, "\\$&")}=([^;]*)`);
|
|
643
|
-
return REGEXP_CACHE[name];
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* Get the encrypted status for a request.
|
|
647
|
-
*/
|
|
648
|
-
function isRequestEncrypted(req) {
|
|
649
|
-
return Boolean(req.socket ? req.socket.encrypted : req.connection.encrypted);
|
|
650
|
-
}
|
|
651
|
-
function pushCookie(headers, cookie) {
|
|
652
|
-
if (cookie.overwrite) {
|
|
653
|
-
for (let i = headers.length - 1; i >= 0; i--) if (headers[i].indexOf(`${cookie.name}=`) === 0) headers.splice(i, 1);
|
|
654
|
-
}
|
|
655
|
-
headers.push(cookie.toHeader());
|
|
656
|
-
}
|
|
657
|
-
//#endregion
|
|
658
|
-
//#region src/recorder/constants.ts
|
|
350
|
+
|
|
351
|
+
|
|
659
352
|
const FILTERED_RESPONSE_HEADERS = [
|
|
660
353
|
"date",
|
|
661
354
|
"expires",
|
|
@@ -696,17 +389,9 @@ const FILTERED_RESPONSE_HEADERS = [
|
|
|
696
389
|
"server-timing",
|
|
697
390
|
"x-dns-prefetch-control"
|
|
698
391
|
];
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
* Decode response body according to encoding
|
|
703
|
-
*
|
|
704
|
-
* 根据编码解码响应体
|
|
705
|
-
*
|
|
706
|
-
* @param rawBody 原始响应体
|
|
707
|
-
* @param encoding 编码
|
|
708
|
-
* @returns 解码后的响应体
|
|
709
|
-
*/
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
710
395
|
async function decompressBody(rawBody, encoding) {
|
|
711
396
|
try {
|
|
712
397
|
switch (encoding.toLowerCase()) {
|
|
@@ -736,16 +421,7 @@ async function decompressBody(rawBody, encoding) {
|
|
|
736
421
|
};
|
|
737
422
|
}
|
|
738
423
|
let zstdStreaming = null;
|
|
739
|
-
|
|
740
|
-
* Decompress zstd compressed data
|
|
741
|
-
*
|
|
742
|
-
* 解压缩 zstd 压缩数据
|
|
743
|
-
*
|
|
744
|
-
* zlib.zstdDecompress 从 v22.15.0 开始支持,对于旧版本 Node.js 可以使用 zstd-codec 库
|
|
745
|
-
*
|
|
746
|
-
* @param rawBody 压缩数据
|
|
747
|
-
* @returns 解压缩后的数据
|
|
748
|
-
*/
|
|
424
|
+
|
|
749
425
|
async function zstd(rawBody) {
|
|
750
426
|
if (zlib.zstdDecompress) return new Promise((resolve, reject) => {
|
|
751
427
|
zlib.zstdDecompress(rawBody, (err, data) => {
|
|
@@ -762,14 +438,7 @@ async function zstd(rawBody) {
|
|
|
762
438
|
}
|
|
763
439
|
return zstdStreaming.decompress(rawBody, rawBody.length);
|
|
764
440
|
}
|
|
765
|
-
|
|
766
|
-
* Decompress brotli compressed data
|
|
767
|
-
*
|
|
768
|
-
* 解压缩 brotli 压缩数据
|
|
769
|
-
*
|
|
770
|
-
* @param rawBody 压缩数据
|
|
771
|
-
* @returns 解压缩后的数据
|
|
772
|
-
*/
|
|
441
|
+
|
|
773
442
|
async function brotli(rawBody) {
|
|
774
443
|
return new Promise((resolve, reject) => {
|
|
775
444
|
zlib.brotliDecompress(rawBody, (err, data) => {
|
|
@@ -791,8 +460,8 @@ async function deflate(rawBody) {
|
|
|
791
460
|
});
|
|
792
461
|
});
|
|
793
462
|
}
|
|
794
|
-
|
|
795
|
-
|
|
463
|
+
|
|
464
|
+
|
|
796
465
|
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
797
466
|
year: "numeric",
|
|
798
467
|
month: "numeric",
|
|
@@ -802,14 +471,7 @@ const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
|
802
471
|
second: "numeric",
|
|
803
472
|
hour12: false
|
|
804
473
|
});
|
|
805
|
-
|
|
806
|
-
* 处理记录的请求
|
|
807
|
-
*
|
|
808
|
-
* @param req 原始请求对象
|
|
809
|
-
* @param pathname 请求路径
|
|
810
|
-
* @param body 请求体
|
|
811
|
-
* @returns 处理后的请求记录
|
|
812
|
-
*/
|
|
474
|
+
|
|
813
475
|
function processRecordReq(req, pathname, body) {
|
|
814
476
|
const { query } = urlParse(req.url);
|
|
815
477
|
const method = req.method.toUpperCase();
|
|
@@ -833,13 +495,7 @@ function processRecordReq(req, pathname, body) {
|
|
|
833
495
|
body
|
|
834
496
|
};
|
|
835
497
|
}
|
|
836
|
-
|
|
837
|
-
* 处理记录的响应
|
|
838
|
-
*
|
|
839
|
-
* @param res 原始响应对象
|
|
840
|
-
* @param body 响应体
|
|
841
|
-
* @returns 处理后的响应记录
|
|
842
|
-
*/
|
|
498
|
+
|
|
843
499
|
async function processRecordRes(res, body) {
|
|
844
500
|
const status = res.statusCode || 200;
|
|
845
501
|
const statusText = res.statusMessage || "OK";
|
|
@@ -861,13 +517,7 @@ async function processRecordRes(res, body) {
|
|
|
861
517
|
body: body.toString(isText ? "utf-8" : "base64")
|
|
862
518
|
};
|
|
863
519
|
}
|
|
864
|
-
|
|
865
|
-
* 判断两个请求是否是同一个请求
|
|
866
|
-
*
|
|
867
|
-
* @param prev 上一个请求
|
|
868
|
-
* @param current 当前请求
|
|
869
|
-
* @returns 是否是同一个请求
|
|
870
|
-
*/
|
|
520
|
+
|
|
871
521
|
function isSameRecord(prev, current) {
|
|
872
522
|
if (prev.pathname !== current.pathname || prev.method !== current.method) return false;
|
|
873
523
|
if (prev.bodyType !== current.bodyType) return false;
|
|
@@ -880,12 +530,7 @@ function isSameRecord(prev, current) {
|
|
|
880
530
|
if (!deepEqual(prev.body, current.body)) return false;
|
|
881
531
|
return true;
|
|
882
532
|
}
|
|
883
|
-
|
|
884
|
-
* 创建请求记录过滤器
|
|
885
|
-
*
|
|
886
|
-
* @param filter 记录过滤选项
|
|
887
|
-
* @returns 请求匹配函数
|
|
888
|
-
*/
|
|
533
|
+
|
|
889
534
|
function createRecordMatcher(filter) {
|
|
890
535
|
if (isFunction(filter)) return filter;
|
|
891
536
|
const { mode = "glob" } = filter;
|
|
@@ -899,25 +544,14 @@ function createRecordMatcher(filter) {
|
|
|
899
544
|
return include.some((pattern) => isPathMatch(pattern, req.pathname)) && exclude.every((pattern) => !isPathMatch(pattern, req.pathname));
|
|
900
545
|
};
|
|
901
546
|
}
|
|
902
|
-
|
|
903
|
-
* 生成记录文件路径
|
|
904
|
-
*
|
|
905
|
-
* @param pathname 请求路径
|
|
906
|
-
* @param dir 记录目录
|
|
907
|
-
* @returns 记录文件路径
|
|
908
|
-
*/
|
|
547
|
+
|
|
909
548
|
function getFilepath(pathname, dir) {
|
|
910
549
|
return path.join(dir, `${kebabCase(pathname)}.json`);
|
|
911
550
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const storage =
|
|
915
|
-
|
|
916
|
-
* 读取记录文件
|
|
917
|
-
*
|
|
918
|
-
* @param filepath 记录文件路径
|
|
919
|
-
* @returns 记录的请求数组
|
|
920
|
-
*/
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
const storage = new Map();
|
|
554
|
+
|
|
921
555
|
async function readRecordStorage(filepath) {
|
|
922
556
|
if (storage.has(filepath)) return storage.get(filepath);
|
|
923
557
|
try {
|
|
@@ -931,12 +565,7 @@ async function readRecordStorage(filepath) {
|
|
|
931
565
|
return [];
|
|
932
566
|
}
|
|
933
567
|
}
|
|
934
|
-
|
|
935
|
-
* 写入记录文件
|
|
936
|
-
*
|
|
937
|
-
* @param filepath 记录文件路径
|
|
938
|
-
* @param records 记录的请求数组
|
|
939
|
-
*/
|
|
568
|
+
|
|
940
569
|
async function writeRecordStorage(filepath, records) {
|
|
941
570
|
try {
|
|
942
571
|
storage.set(filepath, records);
|
|
@@ -946,16 +575,8 @@ async function writeRecordStorage(filepath, records) {
|
|
|
946
575
|
console.error(`Error writing record file ${filepath}:`, error);
|
|
947
576
|
}
|
|
948
577
|
}
|
|
949
|
-
const originalReqCache =
|
|
950
|
-
|
|
951
|
-
* Record a request with the raw request object.
|
|
952
|
-
*
|
|
953
|
-
* 记录原始请求对象的请求
|
|
954
|
-
*
|
|
955
|
-
* @param req The original request object / 原始请求对象
|
|
956
|
-
* @param pathname The request pathname / 请求路径名
|
|
957
|
-
* @param body The request body / 请求体
|
|
958
|
-
*/
|
|
578
|
+
const originalReqCache = new WeakMap();
|
|
579
|
+
|
|
959
580
|
function recordRequestWithRawReq(req, pathname, body) {
|
|
960
581
|
originalReqCache.set(req, {
|
|
961
582
|
body,
|
|
@@ -963,11 +584,9 @@ function recordRequestWithRawReq(req, pathname, body) {
|
|
|
963
584
|
timestamp: Date.now()
|
|
964
585
|
});
|
|
965
586
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
* 请求记录器
|
|
970
|
-
*/
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
971
590
|
var Recorder = class {
|
|
972
591
|
options;
|
|
973
592
|
filter;
|
|
@@ -1024,19 +643,9 @@ var Recorder = class {
|
|
|
1024
643
|
if (!fs.existsSync(path.join(dirname, ".gitignore"))) await promises.writeFile(path.join(dirname, ".gitignore"), "*\n", "utf-8");
|
|
1025
644
|
}
|
|
1026
645
|
};
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
* Replay a recorded request.
|
|
1031
|
-
*
|
|
1032
|
-
* 重放已记录的请求
|
|
1033
|
-
*
|
|
1034
|
-
* @param rawReq The original request object / 原始请求对象
|
|
1035
|
-
* @param pathname The request pathname / 请求路径名
|
|
1036
|
-
* @param body The request body / 请求体
|
|
1037
|
-
* @param options Record options / 录制配置项
|
|
1038
|
-
* @returns The recorded request object if found, otherwise undefined / 如果找到记录的请求对象,则返回该对象,否则返回 undefined
|
|
1039
|
-
*/
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
|
|
1040
649
|
async function replayRecordedRequest(rawReq, pathname, body, options) {
|
|
1041
650
|
const req = processRecordReq(rawReq, pathname, body);
|
|
1042
651
|
const filepath = path.join(options.cwd, getFilepath(req.pathname, options.dir));
|
|
@@ -1059,8 +668,8 @@ async function replayRecordedRequest(rawReq, pathname, body, options) {
|
|
|
1059
668
|
};
|
|
1060
669
|
}
|
|
1061
670
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
671
|
+
|
|
672
|
+
|
|
1064
673
|
const tokensCache = {};
|
|
1065
674
|
function getTokens(rule) {
|
|
1066
675
|
if (tokensCache[rule]) return tokensCache[rule];
|
|
@@ -1112,16 +721,7 @@ function defaultPriority(rules) {
|
|
|
1112
721
|
const highest = getHighest(rules);
|
|
1113
722
|
return rules.sort((a, b) => computedWeight(a, highest) - computedWeight(b, highest));
|
|
1114
723
|
}
|
|
1115
|
-
|
|
1116
|
-
* Calculate matching weight for mock URLs
|
|
1117
|
-
*
|
|
1118
|
-
* 计算 Mock URL 的匹配权重
|
|
1119
|
-
*
|
|
1120
|
-
* @param rules - Array of URL patterns / URL 模式数组
|
|
1121
|
-
* @param url - Request URL / 请求 URL
|
|
1122
|
-
* @param priority - Priority configuration / 优先级配置
|
|
1123
|
-
* @returns Sorted array of matched rules / 排序后的匹配规则数组
|
|
1124
|
-
*/
|
|
724
|
+
|
|
1125
725
|
function matchingWeight(rules, url, priority) {
|
|
1126
726
|
let matched = defaultPriority(rules.filter((rule) => isPathMatch(rule, url)));
|
|
1127
727
|
const { global = [], special = {} } = priority;
|
|
@@ -1146,16 +746,10 @@ function matchingWeight(rules, url, priority) {
|
|
|
1146
746
|
}
|
|
1147
747
|
return matched;
|
|
1148
748
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
*
|
|
1154
|
-
* 由于 parseReqBody 在解析请求时,会将请求流消费,
|
|
1155
|
-
* 导致当接口不需要被 mock,继而由 vite http-proxy 转发时,请求流无法继续。
|
|
1156
|
-
* 为此,我们在请求流中记录请求数据,当当前请求无法继续时,可以从备份中恢复请求流
|
|
1157
|
-
*/
|
|
1158
|
-
const requestCollectCache = /* @__PURE__ */ new WeakMap();
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
const requestCollectCache = new WeakMap();
|
|
1159
753
|
function collectRequest(req) {
|
|
1160
754
|
const chunks = [];
|
|
1161
755
|
req.on("data", (chunk) => {
|
|
@@ -1173,42 +767,18 @@ function rewriteRequest(proxyReq, req) {
|
|
|
1173
767
|
if (!proxyReq.writableEnded) proxyReq.write(buffer);
|
|
1174
768
|
}
|
|
1175
769
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
* Get HTTP status text by status code
|
|
1180
|
-
*
|
|
1181
|
-
* 根据状态码获取状态文本
|
|
1182
|
-
*
|
|
1183
|
-
* @param status - HTTP status code / HTTP 状态码
|
|
1184
|
-
* @returns HTTP status text / HTTP 状态文本
|
|
1185
|
-
*/
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
|
|
1186
773
|
function getHTTPStatusText(status) {
|
|
1187
774
|
return HTTP_STATUS[status] || "Unknown";
|
|
1188
775
|
}
|
|
1189
|
-
|
|
1190
|
-
* Set response status
|
|
1191
|
-
*
|
|
1192
|
-
* 设置响应状态
|
|
1193
|
-
*
|
|
1194
|
-
* @param response - Response object / 响应对象
|
|
1195
|
-
* @param status - HTTP status code / HTTP 状态码
|
|
1196
|
-
* @param statusText - HTTP status text / HTTP 状态文本
|
|
1197
|
-
*/
|
|
776
|
+
|
|
1198
777
|
function provideResponseStatus(response, status = 200, statusText) {
|
|
1199
778
|
response.statusCode = status;
|
|
1200
779
|
response.statusMessage = statusText || getHTTPStatusText(status);
|
|
1201
780
|
}
|
|
1202
|
-
|
|
1203
|
-
* Set response headers
|
|
1204
|
-
*
|
|
1205
|
-
* 设置响应头
|
|
1206
|
-
*
|
|
1207
|
-
* @param req - Request object / 请求对象
|
|
1208
|
-
* @param res - Response object / 响应对象
|
|
1209
|
-
* @param mock - Mock HTTP item / Mock HTTP 配置项
|
|
1210
|
-
* @param logger - Logger instance / 日志实例
|
|
1211
|
-
*/
|
|
781
|
+
|
|
1212
782
|
async function provideResponseHeaders(req, res, mock, logger) {
|
|
1213
783
|
const { headers, type = "json" } = mock;
|
|
1214
784
|
const filepath = mock.__filepath__;
|
|
@@ -1225,16 +795,7 @@ async function provideResponseHeaders(req, res, mock, logger) {
|
|
|
1225
795
|
}
|
|
1226
796
|
objectKeys(data).forEach((key) => res.setHeader(key, data[key]));
|
|
1227
797
|
}
|
|
1228
|
-
|
|
1229
|
-
* Set response cookies
|
|
1230
|
-
*
|
|
1231
|
-
* 设置响应cookie
|
|
1232
|
-
*
|
|
1233
|
-
* @param req - Request object / 请求对象
|
|
1234
|
-
* @param res - Response object / 响应对象
|
|
1235
|
-
* @param mock - Mock HTTP item / Mock HTTP 配置项
|
|
1236
|
-
* @param logger - Logger instance / 日志实例
|
|
1237
|
-
*/
|
|
798
|
+
|
|
1238
799
|
async function provideResponseCookies(req, res, mock, logger) {
|
|
1239
800
|
const { cookies } = mock;
|
|
1240
801
|
if (!cookies) return;
|
|
@@ -1250,15 +811,7 @@ async function provideResponseCookies(req, res, mock, logger) {
|
|
|
1250
811
|
res.setCookie(key, value, options);
|
|
1251
812
|
});
|
|
1252
813
|
}
|
|
1253
|
-
|
|
1254
|
-
* Send response data
|
|
1255
|
-
*
|
|
1256
|
-
* 设置响应数据
|
|
1257
|
-
*
|
|
1258
|
-
* @param res - Response object / 响应对象
|
|
1259
|
-
* @param raw - Response body data / 响应体数据
|
|
1260
|
-
* @param type - Response data type / 响应数据类型
|
|
1261
|
-
*/
|
|
814
|
+
|
|
1262
815
|
function sendResponseData(res, raw, type) {
|
|
1263
816
|
if (isReadableStream(raw)) raw.pipe(res);
|
|
1264
817
|
else if (Buffer.isBuffer(raw)) res.end(type === "text" || type === "json" ? raw.toString("utf-8") : raw);
|
|
@@ -1267,14 +820,7 @@ function sendResponseData(res, raw, type) {
|
|
|
1267
820
|
res.end(type === "buffer" ? Buffer.from(content) : content);
|
|
1268
821
|
}
|
|
1269
822
|
}
|
|
1270
|
-
|
|
1271
|
-
* Apply real response delay
|
|
1272
|
-
*
|
|
1273
|
-
* 实际响应延迟
|
|
1274
|
-
*
|
|
1275
|
-
* @param startTime - Request start time / 请求开始时间
|
|
1276
|
-
* @param delay - Delay configuration / 延迟配置
|
|
1277
|
-
*/
|
|
823
|
+
|
|
1278
824
|
async function responseRealDelay(startTime, delay) {
|
|
1279
825
|
if (!delay || typeof delay === "number" && delay <= 0 || isArray(delay) && delay.length !== 2) return;
|
|
1280
826
|
let realDelay = 0;
|
|
@@ -1284,28 +830,9 @@ async function responseRealDelay(startTime, delay) {
|
|
|
1284
830
|
} else realDelay = delay - (timestamp() - startTime);
|
|
1285
831
|
if (realDelay > 0) await sleep(realDelay);
|
|
1286
832
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
* Create mock middleware
|
|
1291
|
-
*
|
|
1292
|
-
* 创建 Mock 中间件
|
|
1293
|
-
*
|
|
1294
|
-
* @param compiler - Compiler instance / 编译器实例
|
|
1295
|
-
* @param options - Middleware options / 中间件配置项
|
|
1296
|
-
* @param options.formidableOptions - Formidable options / Formidable 配置项
|
|
1297
|
-
* @param options.bodyParserOptions - Body parser options / 请求体解析配置项
|
|
1298
|
-
* @param options.proxies - Proxy paths / 代理路径
|
|
1299
|
-
* @param options.cookiesOptions - Cookies options / Cookies 配置项
|
|
1300
|
-
* @param options.logger - Logger instance / 日志实例
|
|
1301
|
-
* @param options.priority - Path matching priority / 路径匹配优先级
|
|
1302
|
-
* @param options.cors - CORS options / CORS 配置项
|
|
1303
|
-
* @param options.record - Record options / 录制配置项
|
|
1304
|
-
* @param options.replay - Replay options / 回放配置项
|
|
1305
|
-
* @param options.activeScene - Active scene / 活动场景
|
|
1306
|
-
*
|
|
1307
|
-
* @returns Connect middleware function / Connect 中间件函数
|
|
1308
|
-
*/
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
|
|
1309
836
|
function createMockMiddleware(compiler, { formidableOptions = {}, bodyParserOptions = {}, proxies, cookiesOptions, logger, priority = {}, cors: corsOptions, record, replay, activeScene }) {
|
|
1310
837
|
const cors = createCors(corsOptions);
|
|
1311
838
|
const [globFilter, contextFilter] = partition(proxies, (item) => isString(item) && item.includes("*"));
|
|
@@ -1361,7 +888,7 @@ function createMockMiddleware(compiler, { formidableOptions = {}, bodyParserOpti
|
|
|
1361
888
|
}
|
|
1362
889
|
const request = req;
|
|
1363
890
|
const response = res;
|
|
1364
|
-
Object.assign(request, extraReq);
|
|
891
|
+
Object.assign(request, omit(extraReq, ["headers"]));
|
|
1365
892
|
request.params = parseRequestParams(mock.url, pathname);
|
|
1366
893
|
response.setCookie = cookies.set.bind(cookies);
|
|
1367
894
|
const { delay, type = "json", response: responseFn, log: logLevel, error: errorConfig, __filepath__: filepath } = mock;
|
|
@@ -1405,20 +932,20 @@ function createMockMiddleware(compiler, { formidableOptions = {}, bodyParserOpti
|
|
|
1405
932
|
res.end("");
|
|
1406
933
|
};
|
|
1407
934
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
935
|
+
|
|
936
|
+
|
|
1410
937
|
function mockWebSocket(compiler, httpServer, { wsProxies: proxies, cookiesOptions, logger }) {
|
|
1411
|
-
const hmrMap =
|
|
1412
|
-
const poolMap =
|
|
1413
|
-
const wssContextMap =
|
|
938
|
+
const hmrMap = new Map();
|
|
939
|
+
const poolMap = new Map();
|
|
940
|
+
const wssContextMap = new WeakMap();
|
|
1414
941
|
const getWssMap = (mockUrl) => {
|
|
1415
942
|
let wssMap = poolMap.get(mockUrl);
|
|
1416
|
-
if (!wssMap) poolMap.set(mockUrl, wssMap =
|
|
943
|
+
if (!wssMap) poolMap.set(mockUrl, wssMap = new Map());
|
|
1417
944
|
return wssMap;
|
|
1418
945
|
};
|
|
1419
946
|
const addHmr = (filepath, mockUrl) => {
|
|
1420
947
|
let urlList = hmrMap.get(filepath);
|
|
1421
|
-
if (!urlList) hmrMap.set(filepath, urlList =
|
|
948
|
+
if (!urlList) hmrMap.set(filepath, urlList = new Set());
|
|
1422
949
|
urlList.add(mockUrl);
|
|
1423
950
|
};
|
|
1424
951
|
const setupWss = (wssMap, wss, mock, context, pathname, filepath) => {
|
|
@@ -1524,8 +1051,8 @@ function cleanupRunner(cleanupList) {
|
|
|
1524
1051
|
let cleanup;
|
|
1525
1052
|
while (cleanup = cleanupList.shift()) cleanup?.();
|
|
1526
1053
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1054
|
+
|
|
1055
|
+
|
|
1529
1056
|
const logLevels = {
|
|
1530
1057
|
silent: 0,
|
|
1531
1058
|
error: 1,
|
|
@@ -1540,7 +1067,7 @@ function createLogger(prefix, defaultLevel = "info") {
|
|
|
1540
1067
|
if (logLevels[level] >= logLevels[type]) {
|
|
1541
1068
|
const method = type === "info" || type === "debug" ? "log" : type;
|
|
1542
1069
|
const tag = type === "debug" ? ansis.magenta.bold(prefix) : type === "info" ? ansis.cyan.bold(prefix) : type === "warn" ? ansis.yellow.bold(prefix) : ansis.red.bold(prefix);
|
|
1543
|
-
const format = `${ansis.dim((
|
|
1070
|
+
const format = `${ansis.dim(( new Date()).toLocaleTimeString())} ${tag} ${msg}`;
|
|
1544
1071
|
console[method](format);
|
|
1545
1072
|
}
|
|
1546
1073
|
}
|
|
@@ -1559,5 +1086,5 @@ function createLogger(prefix, defaultLevel = "info") {
|
|
|
1559
1086
|
}
|
|
1560
1087
|
};
|
|
1561
1088
|
}
|
|
1562
|
-
|
|
1563
|
-
export { rewriteRequest as a, processRawData as c,
|
|
1089
|
+
|
|
1090
|
+
export { rewriteRequest as a, processRawData as c, vfs as d, getPackageDepList as f, createMockMiddleware as i, sortByValidator as l, createMatcher as m, logLevels as n, Recorder as o, getPackageDeps as p, mockWebSocket as r, processMockData as s, createLogger as t, normalizePath as u };
|