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.
@@ -1,79 +1,353 @@
1
- import { isArray, isBoolean, isEmptyObject, isFunction, isPlainObject, isString, random, sleep, sortBy, timestamp, toArray, uniq } from "./dist-CAA1v47s.js";
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 { URL as URL$1 } from "node:url";
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 { match, parse as parse$1, pathToRegexp } from "path-to-regexp";
10
- import { Buffer } from "node:buffer";
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/core/utils.ts
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 debug = Debug("vite:mock-dev-server");
26
- function lookupFile(dir, formats, options) {
27
- for (const format of formats) {
28
- const fullPath = path.join(dir, format);
29
- if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
30
- const result = options?.pathOnly ? fullPath : fs.readFileSync(fullPath, "utf-8");
31
- if (!options?.predicate || options.predicate(result)) return result;
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 parentDir = path.dirname(dir);
35
- if (parentDir !== dir && (!options?.rootDir || parentDir.startsWith(options?.rootDir))) return lookupFile(parentDir, formats, options);
36
- }
37
- function ensureProxies(serverProxy = {}) {
38
- const httpProxies = [];
39
- const wsProxies = [];
40
- Object.keys(serverProxy).forEach((key) => {
41
- const value = serverProxy[key];
42
- if (typeof value === "string" || !value.ws && !value.target?.toString().startsWith("ws:") && !value.target?.toString().startsWith("wss:")) httpProxies.push(key);
43
- else wsProxies.push(key);
44
- });
45
- return {
46
- httpProxies,
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
- function doesProxyContextMatchUrl(context, url) {
51
- return context[0] === "^" && new RegExp(context).test(url) || url.startsWith(context);
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 parseParams(pattern, url) {
54
- const urlMatch = match(pattern, { decode: decodeURIComponent })(url) || { params: {} };
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$1(input, "http://example.com");
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
- const windowsSlashRE = /\\/g;
71
- const isWindows = os.platform() === "win32";
72
- function slash(p) {
73
- return p.replace(windowsSlashRE, "/");
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 normalizePath(id) {
76
- return path.posix.normalize(isWindows ? slash(id) : id);
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 tks = parse$1(rule);
85
- const tokens = [];
86
- for (const tk of tks) if (!isString(tk)) tokens.push(tk);
87
- else {
88
- const hasPrefix = tk[0] === "/";
89
- const subTks = hasPrefix ? tk.slice(1).split("/") : tk.split("/");
90
- tokens.push(`${hasPrefix ? "/" : ""}${subTks[0]}`, ...subTks.slice(1).map((t) => `/${t}`));
91
- }
92
- tokensCache[rule] = tokens;
93
- return tokens;
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 (!isString(token)) w += 10 ** (i + 1);
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) => typeof token !== "string").length;
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) => typeof token !== "string");
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 = !isString(token);
133
- const { pattern = "", modifier, prefix, name } = isDynamic ? token : {};
134
- const isGlob = pattern && pattern.includes(".*");
135
- const isSlash = prefix === "/";
136
- const isNamed = isString(name);
137
- exp += isDynamic && isSlash ? 1 : 0;
138
- if (i === tokens.length - 1 && isGlob) weight += 5 * 10 ** (tokens.length === 1 ? highest + 1 : highest);
139
- else if (isGlob) weight += 3 * 10 ** (highest - 1);
140
- else if (pattern) if (isSlash) weight += (isNamed ? 2 : 1) * 10 ** (exp + 1);
141
- else weight -= 1 * 10 ** exp;
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) => pathToRegexp(rule).test(url))));
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) => typeof token !== "string");
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/validator.ts
283
- function validate(request, validator) {
284
- 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);
515
+ //#region src/core/response.ts
516
+ /**
517
+ * 根据状态码获取状态文本
518
+ */
519
+ function getHTTPStatusText(status) {
520
+ return HTTP_STATUS[status] || "Unknown";
285
521
  }
286
522
  /**
287
- * Checks if target object is a subset of source object.
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 isObjectSubset(source, target) {
294
- if (!target) return true;
295
- for (const key in target) if (!isIncluded(source[key], target[key])) return false;
296
- return true;
525
+ function provideResponseStatus(response, status = 200, statusText) {
526
+ response.statusCode = status;
527
+ response.statusMessage = statusText || getHTTPStatusText(status);
297
528
  }
298
- function isIncluded(source, target) {
299
- if (isArray(source) && isArray(target)) {
300
- const seen = /* @__PURE__ */ new Set();
301
- return target.every((ti) => source.some((si, i) => {
302
- if (seen.has(i)) return false;
303
- const included = isIncluded(si, ti);
304
- if (included) seen.add(i);
305
- return included;
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
- if (isPlainObject(source) && isPlainObject(target)) return isObjectSubset(source, target);
309
- return Object.is(source, target);
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/baseMiddleware.ts
314
- function baseMiddleware(compiler, { formidableOptions = {}, bodyParserOptions = {}, proxies, cookiesOptions, logger, priority = {} }) {
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 parseReqBody(req, formidableOptions, bodyParserOptions);
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 = fineMock(mockData[mockUrl], logger, {
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 ? pc.underline(pc.bold(m)) : pc.dim(m)).join(", ");
349
- logger.warn(`${pc.green(pathname)} matches ${matched} , but mock data is not found.`);
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 = parseParams(mock.url, pathname);
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
- responseStatus(response, status, statusText);
362
- await provideHeaders(request, response, mock, logger);
363
- await provideCookies(request, response, mock, logger);
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(`${pc.magenta("DEBUG")} ${pc.underline(pathname)} matches: [ ${mockUrls.map((m) => m === _mockUrl ? pc.underline(pc.bold(m)) : pc.dim(m)).join(", ")} ]\n`);
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 realDelay(startTime, delay);
370
- sendData(response, content, type);
651
+ await responseRealDelay(startTime, delay);
652
+ sendResponseData(response, content, type);
371
653
  } catch (e) {
372
- logger.error(`${pc.red(`mock error at ${pathname}`)}\n${e}\n at body (${pc.underline(filepath)})`, logLevel);
373
- responseStatus(response, 500);
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 realDelay(startTime, delay);
662
+ await responseRealDelay(startTime, delay);
381
663
  await responseFn(request, response, next);
382
664
  } catch (e) {
383
- logger.error(`${pc.red(`mock error at ${pathname}`)}\n${e}\n at response (${pc.underline(filepath)})`, logLevel);
384
- responseStatus(response, 500);
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(`${pc.red(`WebSocket mock error at ${wss.path}`)}\n${e}\n at setup (${filepath})`, mock.log);
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(`${pc.red(`WebSocket mock error at ${wss.path}`)}\n${e}\n at setup (${filepath})`, mock.log);
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 && pathToRegexp(mock$1.url).test(pathname);
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 = parseParams(mockUrl, pathname);
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(`${pc.magenta(pc.bold("WebSocket"))} ${pc.green(req.url)} connected ${pc.dim(`(${filepath})`)}`, mock.log);
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
- //#region src/core/logger.ts
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 };