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.
@@ -1,79 +1,367 @@
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 { hasOwn, 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
+ 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 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
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
- function doesProxyContextMatchUrl(context, url) {
51
- return context[0] === "^" && new RegExp(context).test(url) || url.startsWith(context);
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 parseParams(pattern, url) {
54
- const urlMatch = match(pattern, { decode: decodeURIComponent })(url) || { params: {} };
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$1(input, "http://example.com");
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
- const windowsSlashRE = /\\/g;
71
- const isWindows = os.platform() === "win32";
72
- function slash(p) {
73
- return p.replace(windowsSlashRE, "/");
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 normalizePath(id) {
76
- return path.posix.normalize(isWindows ? slash(id) : id);
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 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;
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 (!isString(token)) w += 10 ** (i + 1);
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) => typeof token !== "string").length;
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) => typeof token !== "string");
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 = !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));
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) => pathToRegexp(rule).test(url))));
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) => 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
- });
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/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);
529
+ //#region src/core/response.ts
530
+ /**
531
+ * 根据状态码获取状态文本
532
+ */
533
+ function getHTTPStatusText(status) {
534
+ return HTTP_STATUS[status] || "Unknown";
285
535
  }
286
536
  /**
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 中,
537
+ * 设置响应状态
292
538
  */
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;
539
+ function provideResponseStatus(response, status = 200, statusText) {
540
+ response.statusCode = status;
541
+ response.statusMessage = statusText || getHTTPStatusText(status);
297
542
  }
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
- }));
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
- if (isPlainObject(source) && isPlainObject(target)) return isObjectSubset(source, target);
309
- return Object.is(source, target);
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/baseMiddleware.ts
314
- function baseMiddleware(compiler, { formidableOptions = {}, bodyParserOptions = {}, proxies, cookiesOptions, logger, priority = {} }) {
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 parseReqBody(req, formidableOptions, bodyParserOptions);
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 = fineMock(mockData[mockUrl], logger, {
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 ? pc.underline(pc.bold(m)) : pc.dim(m)).join(", ");
349
- logger.warn(`${pc.green(pathname)} matches ${matched} , but mock data is not found.`);
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 = parseParams(mock.url, pathname);
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
- responseStatus(response, status, statusText);
362
- await provideHeaders(request, response, mock, logger);
363
- await provideCookies(request, response, mock, logger);
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(`${pc.magenta("DEBUG")} ${pc.underline(pathname)} matches: [ ${mockUrls.map((m) => m === _mockUrl ? pc.underline(pc.bold(m)) : pc.dim(m)).join(", ")} ]\n`);
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 realDelay(startTime, delay);
370
- sendData(response, content, type);
665
+ await responseRealDelay(startTime, delay);
666
+ sendResponseData(response, content, type);
371
667
  } catch (e) {
372
- logger.error(`${pc.red(`mock error at ${pathname}`)}\n${e}\n at body (${pc.underline(filepath)})`, logLevel);
373
- responseStatus(response, 500);
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 realDelay(startTime, delay);
676
+ await responseRealDelay(startTime, delay);
381
677
  await responseFn(request, response, next);
382
678
  } catch (e) {
383
- logger.error(`${pc.red(`mock error at ${pathname}`)}\n${e}\n at response (${pc.underline(filepath)})`, logLevel);
384
- responseStatus(response, 500);
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(`${pc.red(`WebSocket mock error at ${wss.path}`)}\n${e}\n at setup (${filepath})`, mock.log);
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(`${pc.red(`WebSocket mock error at ${wss.path}`)}\n${e}\n at setup (${filepath})`, mock.log);
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 && pathToRegexp(mock$1.url).test(pathname);
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 = parseParams(mockUrl, pathname);
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(`${pc.magenta(pc.bold("WebSocket"))} ${pc.green(req.url)} connected ${pc.dim(`(${filepath})`)}`, mock.log);
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
- //#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 };
810
+ export { createLogger, createMatcher, createMockMiddleware, debug, doesProxyContextMatchUrl, isPackageExists, isPathMatch, logLevels, mockWebSocket, normalizePath, processMockData, processRawData, recoverRequest, sortByValidator, urlParse };