rspack-plugin-mock 0.1.0 → 0.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.
@@ -1,91 +1,3 @@
1
- // src/core/requestRecovery.ts
2
- import { Buffer } from "buffer";
3
- var requestCollectCache = /* @__PURE__ */ new WeakMap();
4
- function collectRequest(req) {
5
- const chunks = [];
6
- req.addListener("data", (chunk) => {
7
- chunks.push(Buffer.from(chunk));
8
- });
9
- req.addListener("end", () => {
10
- if (chunks.length)
11
- requestCollectCache.set(req, Buffer.concat(chunks));
12
- });
13
- }
14
- function rewriteRequest(proxyReq, req) {
15
- const buffer = requestCollectCache.get(req);
16
- if (buffer) {
17
- requestCollectCache.delete(req);
18
- if (!proxyReq.headersSent)
19
- proxyReq.setHeader("Content-Length", buffer.byteLength);
20
- if (!proxyReq.writableEnded)
21
- proxyReq.write(buffer);
22
- }
23
- }
24
-
25
- // src/core/mockMiddleware.ts
26
- import process3 from "process";
27
- import { isBoolean as isBoolean2, toArray as toArray2 } from "@pengzhanbo/utils";
28
- import cors from "cors";
29
- import { pathToRegexp as pathToRegexp3 } from "path-to-regexp";
30
-
31
- // src/core/resolvePluginOptions.ts
32
- import process from "process";
33
- function resolvePluginOptions({
34
- prefix = [],
35
- // wsPrefix = [],
36
- cwd,
37
- include = ["mock/**/*.mock.{js,ts,cjs,mjs,json,json5}"],
38
- exclude = ["**/node_modules/**", "**/.vscode/**", "**/.git/**"],
39
- // reload = false,
40
- log = "info",
41
- cors: cors2 = true,
42
- formidableOptions = {},
43
- // build = false,
44
- cookiesOptions = {},
45
- bodyParserOptions = {},
46
- priority = {}
47
- } = {}, context) {
48
- const pluginOptions = {
49
- prefix,
50
- // wsPrefix,
51
- cwd: cwd || context || process.cwd(),
52
- include,
53
- exclude,
54
- // reload,
55
- cors: cors2,
56
- cookiesOptions,
57
- log,
58
- formidableOptions: {
59
- multiples: true,
60
- ...formidableOptions
61
- },
62
- bodyParserOptions,
63
- priority
64
- // build: build
65
- // ? Object.assign(
66
- // {
67
- // serverPort: 8080,
68
- // dist: 'mockServer',
69
- // log: 'error',
70
- // },
71
- // typeof build === 'object' ? build : {},
72
- // )
73
- // : false,
74
- };
75
- return pluginOptions;
76
- }
77
-
78
- // src/core/mockCompiler.ts
79
- import EventEmitter from "events";
80
- import fs3, { promises as fsp2 } from "fs";
81
- import process2 from "process";
82
- import path4 from "path";
83
- import fastGlob from "fast-glob";
84
- import chokidar from "chokidar";
85
- import { createFilter } from "@rollup/pluginutils";
86
- import * as rspackCore from "@rspack/core";
87
- import { Volume, createFsFromVolume } from "memfs";
88
-
89
1
  // src/core/utils.ts
90
2
  import fs from "fs";
91
3
  import path from "path";
@@ -103,7 +15,7 @@ function isReadableStream(stream) {
103
15
  function getDirname(importMetaUrl) {
104
16
  return path.dirname(fileURLToPath(importMetaUrl));
105
17
  }
106
- var debug = Debug("rspack:mock-server");
18
+ var debug = Debug("rspack:mock");
107
19
  function lookupFile(dir, formats, options) {
108
20
  for (const format of formats) {
109
21
  const fullPath = path.join(dir, format);
@@ -144,946 +56,1192 @@ function slash(p) {
144
56
  function normalizePath(id) {
145
57
  return path.posix.normalize(isWindows ? slash(id) : id);
146
58
  }
147
-
148
- // src/core/loadFromCode.ts
149
- import path2 from "path";
150
- import fs2, { promises as fsp } from "fs";
151
- async function loadFromCode({
152
- filepath,
153
- code,
154
- isESM,
155
- cwd
156
- }) {
157
- filepath = path2.resolve(cwd, filepath);
158
- const fileBase = `${filepath}.timestamp-${Date.now()}`;
159
- const ext = isESM ? ".mjs" : ".cjs";
160
- const fileNameTmp = `${fileBase}${ext}`;
161
- await fsp.writeFile(fileNameTmp, code, "utf8");
162
- try {
163
- const result = await import(fileNameTmp);
164
- return result.default || result;
165
- } finally {
166
- try {
167
- fs2.unlinkSync(fileNameTmp);
168
- } catch {
59
+ function waitingFor(onSuccess, maxRetry = 5) {
60
+ return function wait(getter, retry = 0) {
61
+ const value = getter();
62
+ if (value) {
63
+ onSuccess(value);
64
+ } else if (retry < maxRetry) {
65
+ setTimeout(() => wait(getter, retry + 1), 100);
169
66
  }
67
+ };
68
+ }
69
+
70
+ // src/core/requestRecovery.ts
71
+ import { Buffer } from "buffer";
72
+ var requestCollectCache = /* @__PURE__ */ new WeakMap();
73
+ function collectRequest(req) {
74
+ const chunks = [];
75
+ req.addListener("data", (chunk) => {
76
+ chunks.push(Buffer.from(chunk));
77
+ });
78
+ req.addListener("end", () => {
79
+ if (chunks.length)
80
+ requestCollectCache.set(req, Buffer.concat(chunks));
81
+ });
82
+ }
83
+ function rewriteRequest(proxyReq, req) {
84
+ const buffer = requestCollectCache.get(req);
85
+ if (buffer) {
86
+ requestCollectCache.delete(req);
87
+ if (!proxyReq.headersSent)
88
+ proxyReq.setHeader("Content-Length", buffer.byteLength);
89
+ if (!proxyReq.writableEnded)
90
+ proxyReq.write(buffer);
170
91
  }
171
92
  }
172
93
 
173
- // src/core/transform.ts
94
+ // src/core/mockMiddleware.ts
95
+ import cors from "cors";
96
+ import { pathToRegexp as pathToRegexp3 } from "path-to-regexp";
97
+
98
+ // src/core/baseMiddleware.ts
99
+ import { Buffer as Buffer2 } from "buffer";
174
100
  import {
175
- isEmptyObject,
101
+ isArray as isArray3,
102
+ isEmptyObject as isEmptyObject2,
176
103
  isFunction,
177
- isObject as isObject2,
178
- sortBy,
179
- toArray
104
+ random,
105
+ sleep,
106
+ timestamp
180
107
  } from "@pengzhanbo/utils";
108
+ import Cookies from "cookies";
109
+ import HTTP_STATUS from "http-status";
110
+ import * as mime from "mime-types";
111
+ import { pathToRegexp as pathToRegexp2 } from "path-to-regexp";
112
+ import colors from "picocolors";
181
113
 
182
- // src/core/validator.ts
183
- import { isArray, isObject } from "@pengzhanbo/utils";
184
- function validate(request, validator) {
185
- 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);
114
+ // src/core/matchingWeight.ts
115
+ import {
116
+ isArray,
117
+ isEmptyObject,
118
+ isString,
119
+ sortBy,
120
+ uniq
121
+ } from "@pengzhanbo/utils";
122
+ import { parse, pathToRegexp } from "path-to-regexp";
123
+ var tokensCache = {};
124
+ function getTokens(rule) {
125
+ if (tokensCache[rule])
126
+ return tokensCache[rule];
127
+ const { tokens: tks } = parse(rule);
128
+ const tokens = [];
129
+ for (const tk of tks) {
130
+ if (!isString(tk)) {
131
+ tokens.push(tk);
132
+ } else {
133
+ const hasPrefix = tk[0] === "/";
134
+ const subTks = hasPrefix ? tk.slice(1).split("/") : tk.split("/");
135
+ tokens.push(
136
+ `${hasPrefix ? "/" : ""}${subTks[0]}`,
137
+ ...subTks.slice(1).map((t) => `/${t}`)
138
+ );
139
+ }
140
+ }
141
+ tokensCache[rule] = tokens;
142
+ return tokens;
186
143
  }
187
- function isObjectSubset(source, target) {
188
- if (!target)
189
- return true;
190
- for (const key in target) {
191
- if (!isIncluded(source[key], target[key]))
192
- return false;
144
+ function getHighest(rules) {
145
+ let weights = rules.map((rule) => getTokens(rule).length);
146
+ weights = weights.length === 0 ? [1] : weights;
147
+ return Math.max(...weights) + 2;
148
+ }
149
+ function sortFn(rule) {
150
+ const tokens = getTokens(rule);
151
+ let w = 0;
152
+ for (let i = 0; i < tokens.length; i++) {
153
+ const token = tokens[i];
154
+ if (!isString(token))
155
+ w += 10 ** (i + 1);
156
+ w += 10 ** (i + 1);
193
157
  }
194
- return true;
158
+ return w;
195
159
  }
196
- function isIncluded(source, target) {
197
- if (isArray(source) && isArray(target)) {
198
- const seen = /* @__PURE__ */ new Set();
199
- return target.every(
200
- (ti) => source.some((si, i) => {
201
- if (seen.has(i))
202
- return false;
203
- const included = isIncluded(si, ti);
204
- if (included)
205
- seen.add(i);
206
- return included;
207
- })
208
- );
160
+ function preSort(rules) {
161
+ let matched = [];
162
+ const preMatch = [];
163
+ for (const rule of rules) {
164
+ const tokens = getTokens(rule);
165
+ const len = tokens.filter((token) => typeof token !== "string").length;
166
+ if (!preMatch[len])
167
+ preMatch[len] = [];
168
+ preMatch[len].push(rule);
209
169
  }
210
- if (isObject(source) && isObject(target))
211
- return isObjectSubset(source, target);
212
- return Object.is(source, target);
170
+ for (const match2 of preMatch.filter((v) => v && v.length > 0))
171
+ matched = [...matched, ...sortBy(match2, sortFn).reverse()];
172
+ return matched;
213
173
  }
214
-
215
- // src/core/transform.ts
216
- function transformRawData(rawData) {
217
- return rawData.filter((item) => item[0]).map(([raw, __filepath__]) => {
218
- let mockConfig;
219
- if (raw.default) {
220
- if (Array.isArray(raw.default)) {
221
- mockConfig = raw.default.map((item) => ({ ...item, __filepath__ }));
174
+ function defaultPriority(rules) {
175
+ const highest = getHighest(rules);
176
+ return sortBy(rules, (rule) => {
177
+ const tokens = getTokens(rule);
178
+ const dym = tokens.filter((token) => typeof token !== "string");
179
+ if (dym.length === 0)
180
+ return 0;
181
+ let weight = dym.length;
182
+ let exp = 0;
183
+ for (let i = 0; i < tokens.length; i++) {
184
+ const token = tokens[i];
185
+ const isDynamic = !isString(token);
186
+ const {
187
+ pattern = "",
188
+ modifier,
189
+ prefix,
190
+ name
191
+ } = isDynamic ? token : {};
192
+ const isGlob = pattern && pattern.includes(".*");
193
+ const isSlash = prefix === "/";
194
+ const isNamed = isString(name);
195
+ exp += isDynamic && isSlash ? 1 : 0;
196
+ if (i === tokens.length - 1 && isGlob) {
197
+ weight += 5 * 10 ** (tokens.length === 1 ? highest + 1 : highest);
222
198
  } else {
223
- mockConfig = { ...raw.default, __filepath__ };
199
+ if (isGlob) {
200
+ weight += 3 * 10 ** (highest - 1);
201
+ } else if (pattern) {
202
+ if (isSlash) {
203
+ weight += (isNamed ? 2 : 1) * 10 ** (exp + 1);
204
+ } else {
205
+ weight -= 1 * 10 ** exp;
206
+ }
207
+ }
224
208
  }
225
- } else if ("url" in raw) {
226
- mockConfig = { ...raw, __filepath__ };
227
- } else {
228
- mockConfig = [];
229
- Object.keys(raw || {}).forEach((key) => {
230
- if (Array.isArray(raw[key])) {
231
- mockConfig.push(...raw[key].map((item) => ({ ...item, __filepath__ })));
232
- } else {
233
- mockConfig.push({ ...raw[key], __filepath__ });
234
- }
235
- });
209
+ if (modifier === "+")
210
+ weight += 1 * 10 ** (highest - 1);
211
+ if (modifier === "*")
212
+ weight += 1 * 10 ** (highest - 1) + 1;
213
+ if (modifier === "?")
214
+ weight += 1 * 10 ** (exp + (isSlash ? 1 : 0));
236
215
  }
237
- return mockConfig;
216
+ return weight;
238
217
  });
239
218
  }
240
- function transformMockData(mockList) {
241
- const list = [];
242
- for (const [, handle] of mockList.entries()) {
243
- if (handle)
244
- list.push(...toArray(handle));
219
+ function matchingWeight(rules, url, priority) {
220
+ let matched = defaultPriority(
221
+ preSort(rules.filter((rule) => pathToRegexp(rule).test(url)))
222
+ );
223
+ const { global = [], special = {} } = priority;
224
+ if (global.length === 0 && isEmptyObject(special) || matched.length === 0)
225
+ return matched;
226
+ const [statics, dynamics] = twoPartMatch(matched);
227
+ const globalMatch = global.filter((rule) => dynamics.includes(rule));
228
+ if (globalMatch.length > 0) {
229
+ matched = uniq([...statics, ...globalMatch, ...dynamics]);
245
230
  }
246
- const mocks = {};
247
- list.filter((mock) => isObject2(mock) && mock.enabled !== false && mock.url).forEach((mock) => {
248
- const { pathname, query } = urlParse(mock.url);
249
- const list2 = mocks[pathname] ??= [];
250
- const current = { ...mock, url: pathname };
251
- if (current.ws !== true) {
252
- const validator = current.validator;
253
- if (!isEmptyObject(query)) {
254
- if (isFunction(validator)) {
255
- current.validator = function(request) {
256
- return isObjectSubset(request.query, query) && validator(request);
257
- };
258
- } else if (validator) {
259
- current.validator = { ...validator };
260
- current.validator.query = current.validator.query ? { ...query, ...current.validator.query } : query;
261
- } else {
262
- current.validator = { query };
263
- }
264
- }
231
+ if (isEmptyObject(special))
232
+ return matched;
233
+ const specialRule = Object.keys(special).filter(
234
+ (rule) => matched.includes(rule)
235
+ )[0];
236
+ if (!specialRule)
237
+ return matched;
238
+ const options = special[specialRule];
239
+ const { rules: lowerRules, when } = isArray(options) ? { rules: options, when: [] } : options;
240
+ if (lowerRules.includes(matched[0])) {
241
+ if (when.length === 0 || when.some((path5) => pathToRegexp(path5).test(url))) {
242
+ matched = uniq([specialRule, ...matched]);
265
243
  }
266
- list2.push(current);
267
- });
268
- Object.keys(mocks).forEach((key) => {
269
- mocks[key] = sortByValidator(mocks[key]);
270
- });
271
- return mocks;
272
- }
273
- function sortByValidator(mocks) {
274
- return sortBy(mocks, (item) => {
275
- if (item.ws === true)
276
- return 0;
277
- const { validator } = item;
278
- if (!validator || isEmptyObject(validator))
279
- return 2;
280
- if (isFunction(validator))
281
- return 0;
282
- const count = Object.keys(validator).reduce(
283
- (prev, key) => prev + keysCount(validator[key]),
284
- 0
285
- );
286
- return 1 / count;
287
- });
244
+ }
245
+ return matched;
288
246
  }
289
- function keysCount(obj) {
290
- if (!obj)
291
- return 0;
292
- return Object.keys(obj).length;
247
+ function twoPartMatch(rules) {
248
+ const statics = [];
249
+ const dynamics = [];
250
+ for (const rule of rules) {
251
+ const tokens = getTokens(rule);
252
+ const dym = tokens.filter((token) => typeof token !== "string");
253
+ if (dym.length > 0)
254
+ dynamics.push(rule);
255
+ else statics.push(rule);
256
+ }
257
+ return [statics, dynamics];
293
258
  }
294
259
 
295
- // src/core/resolveRspackOptions.ts
296
- import path3 from "path";
297
- var _dirname = getDirname(import.meta.url);
298
- function resolveRspackOptions({
299
- cwd,
300
- isEsm,
301
- entryFile,
302
- outputFile,
303
- plugins,
304
- alias,
305
- watch = false
306
- }) {
307
- const targets = ["node >= 18.0.0"];
308
- return {
309
- mode: "production",
310
- context: cwd,
311
- entry: entryFile,
312
- watch,
313
- target: "node18.0",
314
- externalsType: isEsm ? "module" : "commonjs2",
315
- externals: /^[^./].*/,
316
- resolve: {
317
- alias,
318
- extensions: [".js", ".ts", ".cjs", ".mjs", ".json5", ".json"]
319
- },
320
- plugins,
321
- output: {
322
- library: { type: !isEsm ? "commonjs2" : "module" },
323
- filename: outputFile,
324
- path: "/"
325
- },
326
- experiments: { outputModule: isEsm },
327
- module: {
328
- rules: [
329
- {
330
- test: /\.json5?$/,
331
- loader: path3.join(_dirname, "json5-loader.cjs"),
332
- type: "javascript/auto"
333
- },
334
- {
335
- test: /\.[cm]?js$/,
336
- use: [
337
- {
338
- loader: "builtin:swc-loader",
339
- options: {
340
- jsc: { parser: { syntax: "ecmascript" } },
341
- env: { targets }
342
- }
343
- }
344
- ]
345
- },
346
- {
347
- test: /\.[cm]?ts$/,
348
- use: [
349
- {
350
- loader: "builtin:swc-loader",
351
- options: {
352
- jsc: { parser: { syntax: "typescript" } },
353
- env: { targets }
354
- }
355
- }
356
- ]
357
- }
358
- ]
260
+ // src/core/parseReqBody.ts
261
+ import bodyParser from "co-body";
262
+ import formidable from "formidable";
263
+ async function parseReqBody(req, formidableOptions, bodyParserOptions = {}) {
264
+ const method = req.method.toUpperCase();
265
+ if (["GET", "DELETE", "HEAD"].includes(method))
266
+ return void 0;
267
+ const type = req.headers["content-type"]?.toLocaleLowerCase() || "";
268
+ const { limit, formLimit, jsonLimit, textLimit, ...rest } = bodyParserOptions;
269
+ try {
270
+ if (type.startsWith("application/json")) {
271
+ return await bodyParser.json(req, {
272
+ limit: jsonLimit || limit,
273
+ ...rest
274
+ });
359
275
  }
360
- };
361
- }
362
-
363
- // src/core/mockCompiler.ts
364
- var vfs = createFsFromVolume(new Volume());
365
- function createMockCompiler(options) {
366
- return new MockCompiler(options);
367
- }
368
- var MockCompiler = class extends EventEmitter {
369
- constructor(options) {
370
- super();
371
- this.options = options;
372
- this.cwd = options.cwd || process2.cwd();
373
- const { include, exclude } = this.options;
374
- this.fileFilter = createFilter(include, exclude, {
375
- resolve: false
376
- });
377
- try {
378
- const pkg = lookupFile(this.cwd, ["package.json"]);
379
- this.moduleType = !!pkg && JSON.parse(pkg).type === "module" ? "esm" : "cjs";
380
- } catch {
276
+ if (type.startsWith("application/x-www-form-urlencoded")) {
277
+ return await bodyParser.form(req, {
278
+ limit: formLimit || limit,
279
+ ...rest
280
+ });
381
281
  }
382
- this.entryFile = path4.resolve(process2.cwd(), "node_modules/.cache/mock-server/mock-server.ts");
383
- this.outputFile = "mock.bundle.js";
384
- }
385
- cwd;
386
- mockWatcher;
387
- moduleType = "cjs";
388
- entryFile;
389
- outputFile;
390
- _mockData = {};
391
- fileFilter;
392
- compiler;
393
- get mockData() {
394
- return this._mockData;
282
+ if (type.startsWith("text/plain")) {
283
+ return await bodyParser.text(req, {
284
+ limit: textLimit || limit,
285
+ ...rest
286
+ });
287
+ }
288
+ if (type.startsWith("multipart/form-data"))
289
+ return await parseMultipart(req, formidableOptions);
290
+ } catch (e) {
291
+ console.error(e);
395
292
  }
396
- async run() {
397
- await this.updateMockEntry();
398
- this.watchMockFiles();
399
- this.createCompiler(async (err, stats) => {
400
- const name = "[rspack:mock]";
401
- if (err) {
402
- const error = stats?.compilation.getLogger(name).error || ((...args) => console.error(name, ...args));
403
- error(err.stack || err);
404
- if ("details" in err) {
405
- error(err.details);
406
- }
407
- return;
408
- }
409
- if (stats?.hasErrors()) {
410
- stats.compilation.getLogger(name).error(stats.toString({ colors: true }));
293
+ return void 0;
294
+ }
295
+ async function parseMultipart(req, options) {
296
+ const form = formidable(options);
297
+ return new Promise((resolve, reject) => {
298
+ form.parse(req, (error, fields, files) => {
299
+ if (error) {
300
+ reject(error);
411
301
  return;
412
302
  }
413
- const content = vfs.readFileSync(`/${this.outputFile}`, "utf-8");
414
- try {
415
- const result = await loadFromCode({
416
- filepath: this.outputFile,
417
- code: content,
418
- isESM: this.moduleType === "esm",
419
- cwd: this.cwd
420
- });
421
- this._mockData = transformMockData(transformRawData(result));
422
- this.emit("update");
423
- } catch (e) {
424
- console.error("[rspack:mock-server]", e);
425
- }
426
- });
427
- }
428
- close() {
429
- this.mockWatcher.close();
430
- this.compiler?.close(() => {
303
+ resolve({ ...fields, ...files });
431
304
  });
432
- this.emit("close");
433
- }
434
- updateAlias(alias) {
435
- this.options.alias = {
436
- ...this.options.alias,
437
- ...alias
438
- };
439
- }
440
- async updateMockEntry() {
441
- const files = await this.getMockFiles();
442
- await this.resolveEntryFile(files);
443
- }
444
- async getMockFiles() {
445
- const { include } = this.options;
446
- const files = await fastGlob(include, { cwd: this.cwd });
447
- return files.filter(this.fileFilter);
448
- }
449
- watchMockFiles() {
450
- const { include } = this.options;
451
- const [firstGlob, ...otherGlob] = include;
452
- const watcher = this.mockWatcher = chokidar.watch(firstGlob, {
453
- ignoreInitial: true,
454
- cwd: this.cwd
455
- });
456
- if (otherGlob.length > 0)
457
- otherGlob.forEach((glob) => watcher.add(glob));
458
- watcher.on("add", () => {
459
- this.updateMockEntry();
460
- });
461
- watcher.on("unlink", async () => {
462
- this.updateMockEntry();
463
- });
464
- }
465
- async resolveEntryFile(fileList) {
466
- const importers = [];
467
- const exporters = [];
468
- for (const [index, filepath] of fileList.entries()) {
469
- const file = normalizePath(path4.join(this.cwd, filepath));
470
- importers.push(`import * as m${index} from '${file}'`);
471
- exporters.push(`[m${index}, '${filepath}']`);
472
- }
473
- const code = `${importers.join("\n")}
305
+ });
306
+ }
474
307
 
475
- export default [
476
- ${exporters.join(",\n ")}
477
- ]`;
478
- const dirname = path4.dirname(this.entryFile);
479
- if (!fs3.existsSync(dirname)) {
480
- await fsp2.mkdir(dirname, { recursive: true });
481
- }
482
- await fsp2.writeFile(this.entryFile, code, "utf8");
308
+ // src/core/validator.ts
309
+ import { isArray as isArray2, isObject } from "@pengzhanbo/utils";
310
+ function validate(request, validator) {
311
+ 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);
312
+ }
313
+ function isObjectSubset(source, target) {
314
+ if (!target)
315
+ return true;
316
+ for (const key in target) {
317
+ if (!isIncluded(source[key], target[key]))
318
+ return false;
483
319
  }
484
- createCompiler(callback) {
485
- const options = resolveRspackOptions({
486
- isEsm: this.moduleType === "esm",
487
- cwd: this.cwd,
488
- plugins: this.options.plugins,
489
- entryFile: this.entryFile,
490
- outputFile: this.outputFile,
491
- alias: this.options.alias,
492
- watch: true
493
- });
494
- this.compiler = rspackCore.rspack(options, callback);
495
- if (this.compiler)
496
- this.compiler.outputFileSystem = vfs;
320
+ return true;
321
+ }
322
+ function isIncluded(source, target) {
323
+ if (isArray2(source) && isArray2(target)) {
324
+ const seen = /* @__PURE__ */ new Set();
325
+ return target.every(
326
+ (ti) => source.some((si, i) => {
327
+ if (seen.has(i))
328
+ return false;
329
+ const included = isIncluded(si, ti);
330
+ if (included)
331
+ seen.add(i);
332
+ return included;
333
+ })
334
+ );
497
335
  }
498
- };
336
+ if (isObject(source) && isObject(target))
337
+ return isObjectSubset(source, target);
338
+ return Object.is(source, target);
339
+ }
499
340
 
500
341
  // src/core/baseMiddleware.ts
501
- import { Buffer as Buffer2 } from "buffer";
502
- import {
503
- isArray as isArray3,
504
- isEmptyObject as isEmptyObject3,
505
- isFunction as isFunction2,
506
- random,
507
- sleep,
508
- timestamp
509
- } from "@pengzhanbo/utils";
510
- import Cookies from "cookies";
511
- import HTTP_STATUS from "http-status";
512
- import * as mime from "mime-types";
513
- import { pathToRegexp as pathToRegexp2 } from "path-to-regexp";
514
- import colors from "picocolors";
515
-
516
- // src/core/matchingWeight.ts
517
- import {
518
- isArray as isArray2,
519
- isEmptyObject as isEmptyObject2,
520
- isString,
521
- sortBy as sortBy2,
522
- uniq
523
- } from "@pengzhanbo/utils";
524
- import { parse, pathToRegexp } from "path-to-regexp";
525
- var tokensCache = {};
526
- function getTokens(rule) {
527
- if (tokensCache[rule])
528
- return tokensCache[rule];
529
- const { tokens: tks } = parse(rule);
530
- const tokens = [];
531
- for (const tk of tks) {
532
- if (!isString(tk)) {
533
- tokens.push(tk);
534
- } else {
535
- const hasPrefix = tk[0] === "/";
536
- const subTks = hasPrefix ? tk.slice(1).split("/") : tk.split("/");
537
- tokens.push(
538
- `${hasPrefix ? "/" : ""}${subTks[0]}`,
539
- ...subTks.slice(1).map((t) => `/${t}`)
342
+ function baseMiddleware(compiler, {
343
+ formidableOptions = {},
344
+ bodyParserOptions = {},
345
+ proxies,
346
+ cookiesOptions,
347
+ logger,
348
+ priority = {}
349
+ }) {
350
+ return async function(req, res, next) {
351
+ const startTime = timestamp();
352
+ const { query, pathname } = urlParse(req.url);
353
+ if (!pathname || proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url, req))) {
354
+ return next();
355
+ }
356
+ const mockData = compiler.mockData;
357
+ const mockUrls = matchingWeight(Object.keys(mockData), pathname, priority);
358
+ if (mockUrls.length === 0) {
359
+ return next();
360
+ }
361
+ collectRequest(req);
362
+ const { query: refererQuery } = urlParse(req.headers.referer || "");
363
+ const reqBody = await parseReqBody(req, formidableOptions, bodyParserOptions);
364
+ const cookies = new Cookies(req, res, cookiesOptions);
365
+ const getCookie = cookies.get.bind(cookies);
366
+ const method = req.method.toUpperCase();
367
+ let mock;
368
+ let _mockUrl;
369
+ for (const mockUrl of mockUrls) {
370
+ mock = fineMock(mockData[mockUrl], logger, {
371
+ pathname,
372
+ method,
373
+ request: {
374
+ query,
375
+ refererQuery,
376
+ body: reqBody,
377
+ headers: req.headers,
378
+ getCookie
379
+ }
380
+ });
381
+ if (mock) {
382
+ _mockUrl = mockUrl;
383
+ break;
384
+ }
385
+ }
386
+ if (!mock) {
387
+ const matched = mockUrls.map(
388
+ (m) => m === _mockUrl ? colors.underline(colors.bold(m)) : colors.dim(m)
389
+ ).join(", ");
390
+ logger.warn(
391
+ `${colors.green(
392
+ pathname
393
+ )} matches ${matched} , but mock data is not found.`
540
394
  );
395
+ return next();
541
396
  }
542
- }
543
- tokensCache[rule] = tokens;
544
- return tokens;
397
+ const request = req;
398
+ const response = res;
399
+ request.body = reqBody;
400
+ request.query = query;
401
+ request.refererQuery = refererQuery;
402
+ request.params = parseParams(mock.url, pathname);
403
+ request.getCookie = getCookie;
404
+ response.setCookie = cookies.set.bind(cookies);
405
+ const {
406
+ body,
407
+ delay,
408
+ type = "json",
409
+ response: responseFn,
410
+ status = 200,
411
+ statusText,
412
+ log: logLevel,
413
+ __filepath__: filepath
414
+ } = mock;
415
+ responseStatus(response, status, statusText);
416
+ await provideHeaders(request, response, mock, logger);
417
+ await provideCookies(request, response, mock, logger);
418
+ logger.info(requestLog(request, filepath), logLevel);
419
+ logger.debug(
420
+ `${colors.magenta("DEBUG")} ${colors.underline(
421
+ pathname
422
+ )} matches: [ ${mockUrls.map(
423
+ (m) => m === _mockUrl ? colors.underline(colors.bold(m)) : colors.dim(m)
424
+ ).join(", ")} ]
425
+ `
426
+ );
427
+ if (body) {
428
+ try {
429
+ const content = isFunction(body) ? await body(request) : body;
430
+ await realDelay(startTime, delay);
431
+ sendData(response, content, type);
432
+ } catch (e) {
433
+ logger.error(
434
+ `${colors.red(
435
+ `mock error at ${pathname}`
436
+ )}
437
+ ${e}
438
+ at body (${colors.underline(filepath)})`,
439
+ logLevel
440
+ );
441
+ responseStatus(response, 500);
442
+ res.end("");
443
+ }
444
+ return;
445
+ }
446
+ if (responseFn) {
447
+ try {
448
+ await realDelay(startTime, delay);
449
+ await responseFn(request, response, next);
450
+ } catch (e) {
451
+ logger.error(
452
+ `${colors.red(
453
+ `mock error at ${pathname}`
454
+ )}
455
+ ${e}
456
+ at response (${colors.underline(filepath)})`,
457
+ logLevel
458
+ );
459
+ responseStatus(response, 500);
460
+ res.end("");
461
+ }
462
+ return;
463
+ }
464
+ res.end("");
465
+ };
545
466
  }
546
- function getHighest(rules) {
547
- let weights = rules.map((rule) => getTokens(rule).length);
548
- weights = weights.length === 0 ? [1] : weights;
549
- return Math.max(...weights) + 2;
467
+ function fineMock(mockList, logger, {
468
+ pathname,
469
+ method,
470
+ request
471
+ }) {
472
+ return mockList.find((mock) => {
473
+ if (!pathname || !mock || !mock.url || mock.ws === true)
474
+ return false;
475
+ const methods = mock.method ? isArray3(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
476
+ if (!methods.includes(method))
477
+ return false;
478
+ const hasMock = pathToRegexp2(mock.url).test(pathname);
479
+ if (hasMock && mock.validator) {
480
+ const params = parseParams(mock.url, pathname);
481
+ if (isFunction(mock.validator)) {
482
+ return mock.validator({ params, ...request });
483
+ } else {
484
+ try {
485
+ return validate({ params, ...request }, mock.validator);
486
+ } catch (e) {
487
+ const file = mock.__filepath__;
488
+ logger.error(
489
+ `${colors.red(
490
+ `mock error at ${pathname}`
491
+ )}
492
+ ${e}
493
+ at validator (${colors.underline(file)})`,
494
+ mock.log
495
+ );
496
+ return false;
497
+ }
498
+ }
499
+ }
500
+ return hasMock;
501
+ });
550
502
  }
551
- function sortFn(rule) {
552
- const tokens = getTokens(rule);
553
- let w = 0;
554
- for (let i = 0; i < tokens.length; i++) {
555
- const token = tokens[i];
556
- if (!isString(token))
557
- w += 10 ** (i + 1);
558
- w += 10 ** (i + 1);
559
- }
560
- return w;
503
+ function responseStatus(response, status = 200, statusText) {
504
+ response.statusCode = status;
505
+ response.statusMessage = statusText || getHTTPStatusText(status);
561
506
  }
562
- function preSort(rules) {
563
- let matched = [];
564
- const preMatch = [];
565
- for (const rule of rules) {
566
- const tokens = getTokens(rule);
567
- const len = tokens.filter((token) => typeof token !== "string").length;
568
- if (!preMatch[len])
569
- preMatch[len] = [];
570
- preMatch[len].push(rule);
507
+ async function provideHeaders(req, res, mock, logger) {
508
+ const { headers, type = "json" } = mock;
509
+ const filepath = mock.__filepath__;
510
+ const contentType2 = mime.contentType(type) || mime.contentType(mime.lookup(type) || "");
511
+ if (contentType2)
512
+ res.setHeader("Content-Type", contentType2);
513
+ res.setHeader("Cache-Control", "no-cache,max-age=0");
514
+ res.setHeader("X-Mock-Power-By", "vite-plugin-mock-dev-server");
515
+ if (filepath)
516
+ res.setHeader("X-File-Path", filepath);
517
+ if (!headers)
518
+ return;
519
+ try {
520
+ const raw = isFunction(headers) ? await headers(req) : headers;
521
+ Object.keys(raw).forEach((key) => {
522
+ res.setHeader(key, raw[key]);
523
+ });
524
+ } catch (e) {
525
+ logger.error(
526
+ `${colors.red(
527
+ `mock error at ${req.url.split("?")[0]}`
528
+ )}
529
+ ${e}
530
+ at headers (${colors.underline(filepath)})`,
531
+ mock.log
532
+ );
571
533
  }
572
- for (const match2 of preMatch.filter((v) => v && v.length > 0))
573
- matched = [...matched, ...sortBy2(match2, sortFn).reverse()];
574
- return matched;
575
534
  }
576
- function defaultPriority(rules) {
577
- const highest = getHighest(rules);
578
- return sortBy2(rules, (rule) => {
579
- const tokens = getTokens(rule);
580
- const dym = tokens.filter((token) => typeof token !== "string");
581
- if (dym.length === 0)
582
- return 0;
583
- let weight = dym.length;
584
- let exp = 0;
585
- for (let i = 0; i < tokens.length; i++) {
586
- const token = tokens[i];
587
- const isDynamic = !isString(token);
588
- const {
589
- pattern = "",
590
- modifier,
591
- prefix,
592
- name
593
- } = isDynamic ? token : {};
594
- const isGlob = pattern && pattern.includes(".*");
595
- const isSlash = prefix === "/";
596
- const isNamed = isString(name);
597
- exp += isDynamic && isSlash ? 1 : 0;
598
- if (i === tokens.length - 1 && isGlob) {
599
- weight += 5 * 10 ** (tokens.length === 1 ? highest + 1 : highest);
535
+ async function provideCookies(req, res, mock, logger) {
536
+ const { cookies } = mock;
537
+ const filepath = mock.__filepath__;
538
+ if (!cookies)
539
+ return;
540
+ try {
541
+ const raw = isFunction(cookies) ? await cookies(req) : cookies;
542
+ Object.keys(raw).forEach((key) => {
543
+ const cookie = raw[key];
544
+ if (isArray3(cookie)) {
545
+ const [value, options] = cookie;
546
+ res.setCookie(key, value, options);
600
547
  } else {
601
- if (isGlob) {
602
- weight += 3 * 10 ** (highest - 1);
603
- } else if (pattern) {
604
- if (isSlash) {
605
- weight += (isNamed ? 2 : 1) * 10 ** (exp + 1);
606
- } else {
607
- weight -= 1 * 10 ** exp;
608
- }
609
- }
548
+ res.setCookie(key, cookie);
610
549
  }
611
- if (modifier === "+")
612
- weight += 1 * 10 ** (highest - 1);
613
- if (modifier === "*")
614
- weight += 1 * 10 ** (highest - 1) + 1;
615
- if (modifier === "?")
616
- weight += 1 * 10 ** (exp + (isSlash ? 1 : 0));
617
- }
618
- return weight;
619
- });
550
+ });
551
+ } catch (e) {
552
+ logger.error(
553
+ `${colors.red(
554
+ `mock error at ${req.url.split("?")[0]}`
555
+ )}
556
+ ${e}
557
+ at cookies (${colors.underline(filepath)})`,
558
+ mock.log
559
+ );
560
+ }
620
561
  }
621
- function matchingWeight(rules, url, priority) {
622
- let matched = defaultPriority(
623
- preSort(rules.filter((rule) => pathToRegexp(rule).test(url)))
624
- );
625
- const { global = [], special = {} } = priority;
626
- if (global.length === 0 && isEmptyObject2(special) || matched.length === 0)
627
- return matched;
628
- const [statics, dynamics] = twoPartMatch(matched);
629
- const globalMatch = global.filter((rule) => dynamics.includes(rule));
630
- if (globalMatch.length > 0) {
631
- matched = uniq([...statics, ...globalMatch, ...dynamics]);
562
+ function sendData(res, raw, type) {
563
+ if (isReadableStream(raw)) {
564
+ raw.pipe(res);
565
+ } else if (Buffer2.isBuffer(raw)) {
566
+ res.end(type === "text" || type === "json" ? raw.toString("utf-8") : raw);
567
+ } else {
568
+ const content = typeof raw === "string" ? raw : JSON.stringify(raw);
569
+ res.end(type === "buffer" ? Buffer2.from(content) : content);
632
570
  }
633
- if (isEmptyObject2(special))
634
- return matched;
635
- const specialRule = Object.keys(special).filter(
636
- (rule) => matched.includes(rule)
637
- )[0];
638
- if (!specialRule)
639
- return matched;
640
- const options = special[specialRule];
641
- const { rules: lowerRules, when } = isArray2(options) ? { rules: options, when: [] } : options;
642
- if (lowerRules.includes(matched[0])) {
643
- if (when.length === 0 || when.some((path5) => pathToRegexp(path5).test(url))) {
644
- matched = uniq([specialRule, ...matched]);
571
+ }
572
+ async function realDelay(startTime, delay) {
573
+ if (!delay || typeof delay === "number" && delay <= 0 || isArray3(delay) && delay.length !== 2) {
574
+ return;
575
+ }
576
+ let realDelay2 = 0;
577
+ if (isArray3(delay)) {
578
+ const [min, max] = delay;
579
+ realDelay2 = random(min, max);
580
+ } else {
581
+ realDelay2 = delay - (timestamp() - startTime);
582
+ }
583
+ if (realDelay2 > 0)
584
+ await sleep(realDelay2);
585
+ }
586
+ function getHTTPStatusText(status) {
587
+ return HTTP_STATUS[status] || "Unknown";
588
+ }
589
+ function requestLog(request, filepath) {
590
+ const { url, method, query, params, body } = request;
591
+ let { pathname } = new URL(url, "http://example.com");
592
+ pathname = colors.green(decodeURIComponent(pathname));
593
+ const format = (prefix, data) => {
594
+ return !data || isEmptyObject2(data) ? "" : ` ${colors.gray(`${prefix}:`)}${JSON.stringify(data)}`;
595
+ };
596
+ const ms = colors.magenta(colors.bold(method));
597
+ const qs = format("query", query);
598
+ const ps = format("params", params);
599
+ const bs = format("body", body);
600
+ const file = ` ${colors.dim(colors.underline(`(${filepath})`))}`;
601
+ return `${ms} ${pathname}${qs}${ps}${bs}${file}`;
602
+ }
603
+
604
+ // src/core/mockMiddleware.ts
605
+ function createMockMiddleware(compiler, options) {
606
+ function mockMiddleware(middlewares, reload) {
607
+ middlewares.unshift(baseMiddleware(compiler, options));
608
+ const corsMiddleware = createCorsMiddleware(compiler, options);
609
+ if (corsMiddleware) {
610
+ middlewares.unshift(corsMiddleware);
645
611
  }
612
+ if (options.reload) {
613
+ compiler.on("update", () => reload?.());
614
+ }
615
+ return middlewares;
646
616
  }
647
- return matched;
617
+ return mockMiddleware;
648
618
  }
649
- function twoPartMatch(rules) {
650
- const statics = [];
651
- const dynamics = [];
652
- for (const rule of rules) {
653
- const tokens = getTokens(rule);
654
- const dym = tokens.filter((token) => typeof token !== "string");
655
- if (dym.length > 0)
656
- dynamics.push(rule);
657
- else statics.push(rule);
619
+ function createCorsMiddleware(compiler, options) {
620
+ let corsOptions = {};
621
+ const enabled = options.cors !== false;
622
+ if (enabled) {
623
+ corsOptions = {
624
+ ...corsOptions,
625
+ ...typeof options.cors === "boolean" ? {} : options.cors
626
+ };
658
627
  }
659
- return [statics, dynamics];
628
+ const proxies = options.proxies;
629
+ return !enabled ? void 0 : function(req, res, next) {
630
+ const { pathname } = urlParse(req.url);
631
+ if (!pathname || proxies.length === 0 || !proxies.some(
632
+ (context) => doesProxyContextMatchUrl(context, req.url, req)
633
+ )) {
634
+ return next();
635
+ }
636
+ const mockData = compiler.mockData;
637
+ const mockUrl = Object.keys(mockData).find(
638
+ (key) => pathToRegexp3(key).test(pathname)
639
+ );
640
+ if (!mockUrl)
641
+ return next();
642
+ cors(corsOptions)(req, res, next);
643
+ };
660
644
  }
661
645
 
662
- // src/core/parseReqBody.ts
663
- import bodyParser from "co-body";
664
- import formidable from "formidable";
665
- async function parseReqBody(req, formidableOptions, bodyParserOptions = {}) {
666
- const method = req.method.toUpperCase();
667
- if (["GET", "DELETE", "HEAD"].includes(method))
668
- return void 0;
669
- const type = req.headers["content-type"]?.toLocaleLowerCase() || "";
670
- const { limit, formLimit, jsonLimit, textLimit, ...rest } = bodyParserOptions;
671
- try {
672
- if (type.startsWith("application/json")) {
673
- return await bodyParser.json(req, {
674
- limit: jsonLimit || limit,
675
- ...rest
676
- });
677
- }
678
- if (type.startsWith("application/x-www-form-urlencoded")) {
679
- return await bodyParser.form(req, {
680
- limit: formLimit || limit,
681
- ...rest
682
- });
683
- }
684
- if (type.startsWith("text/plain")) {
685
- return await bodyParser.text(req, {
686
- limit: textLimit || limit,
687
- ...rest
688
- });
646
+ // src/core/resolvePluginOptions.ts
647
+ import process from "process";
648
+ import { isBoolean as isBoolean2, toArray } from "@pengzhanbo/utils";
649
+
650
+ // src/core/logger.ts
651
+ import { isBoolean } from "@pengzhanbo/utils";
652
+ import colors2 from "picocolors";
653
+ var logLevels = {
654
+ silent: 0,
655
+ error: 1,
656
+ warn: 2,
657
+ info: 3,
658
+ debug: 4
659
+ };
660
+ function createLogger(prefix, defaultLevel = "info") {
661
+ prefix = `[${prefix}]`;
662
+ function output(type, msg, level) {
663
+ level = isBoolean(level) ? level ? defaultLevel : "error" : level;
664
+ const thresh = logLevels[level];
665
+ if (thresh >= logLevels[type]) {
666
+ const method = type === "info" || type === "debug" ? "log" : type;
667
+ const tag = type === "debug" ? colors2.magenta(colors2.bold(prefix)) : type === "info" ? colors2.cyan(colors2.bold(prefix)) : type === "warn" ? colors2.yellow(colors2.bold(prefix)) : colors2.red(colors2.bold(prefix));
668
+ const format = `${colors2.dim(
669
+ (/* @__PURE__ */ new Date()).toLocaleTimeString()
670
+ )} ${tag} ${msg}`;
671
+ console[method](format);
689
672
  }
690
- if (type.startsWith("multipart/form-data"))
691
- return await parseMultipart(req, formidableOptions);
692
- } catch (e) {
693
- console.error(e);
694
673
  }
695
- return void 0;
696
- }
697
- async function parseMultipart(req, options) {
698
- const form = formidable(options);
699
- return new Promise((resolve, reject) => {
700
- form.parse(req, (error, fields, files) => {
701
- if (error) {
702
- reject(error);
703
- return;
704
- }
705
- resolve({ ...fields, ...files });
706
- });
707
- });
674
+ const logger = {
675
+ debug(msg, level = defaultLevel) {
676
+ output("debug", msg, level);
677
+ },
678
+ info(msg, level = defaultLevel) {
679
+ output("info", msg, level);
680
+ },
681
+ warn(msg, level = defaultLevel) {
682
+ output("warn", msg, level);
683
+ },
684
+ error(msg, level = defaultLevel) {
685
+ output("error", msg, level);
686
+ }
687
+ };
688
+ return logger;
708
689
  }
709
690
 
710
- // src/core/baseMiddleware.ts
711
- function baseMiddleware(compiler, {
691
+ // src/core/resolvePluginOptions.ts
692
+ function resolvePluginOptions({
693
+ prefix = [],
694
+ wsPrefix = [],
695
+ cwd,
696
+ include = ["mock/**/*.mock.{js,ts,cjs,mjs,json,json5}"],
697
+ exclude = ["**/node_modules/**", "**/.vscode/**", "**/.git/**"],
698
+ reload = false,
699
+ log = "info",
700
+ cors: cors2 = true,
712
701
  formidableOptions = {},
702
+ build = false,
703
+ cookiesOptions = {},
713
704
  bodyParserOptions = {},
714
- proxies,
715
- cookiesOptions,
716
- logger,
717
705
  priority = {}
706
+ } = {}, { alias, context, plugins, proxies }) {
707
+ const logger = createLogger(
708
+ "rspack:mock",
709
+ isBoolean2(log) ? log ? "info" : "error" : log
710
+ );
711
+ return {
712
+ prefix,
713
+ wsPrefix,
714
+ cwd: cwd || context || process.cwd(),
715
+ include,
716
+ exclude,
717
+ reload,
718
+ cors: cors2,
719
+ cookiesOptions,
720
+ log,
721
+ formidableOptions: {
722
+ multiples: true,
723
+ ...formidableOptions
724
+ },
725
+ bodyParserOptions,
726
+ priority,
727
+ build: build ? Object.assign(
728
+ {
729
+ serverPort: 8080,
730
+ dist: "mockServer",
731
+ log: "error"
732
+ },
733
+ typeof build === "object" ? build : {}
734
+ ) : false,
735
+ alias,
736
+ plugins,
737
+ proxies,
738
+ wsProxies: toArray(wsPrefix),
739
+ logger
740
+ };
741
+ }
742
+
743
+ // src/core/mockCompiler.ts
744
+ import EventEmitter from "events";
745
+ import fs3, { promises as fsp2 } from "fs";
746
+ import process2 from "process";
747
+ import path4 from "path";
748
+ import fastGlob from "fast-glob";
749
+ import chokidar from "chokidar";
750
+ import { createFilter } from "@rollup/pluginutils";
751
+ import * as rspackCore from "@rspack/core";
752
+ import { Volume, createFsFromVolume } from "memfs";
753
+ import { toArray as toArray3 } from "@pengzhanbo/utils";
754
+ import color from "picocolors";
755
+
756
+ // src/core/loadFromCode.ts
757
+ import path2 from "path";
758
+ import fs2, { promises as fsp } from "fs";
759
+ async function loadFromCode({
760
+ filepath,
761
+ code,
762
+ isESM,
763
+ cwd
718
764
  }) {
719
- return async function(req, res, next) {
720
- const startTime = timestamp();
721
- const { query, pathname } = urlParse(req.url);
722
- if (!pathname || proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url, req))) {
723
- return next();
724
- }
725
- const mockData = compiler.mockData;
726
- const mockUrls = matchingWeight(Object.keys(mockData), pathname, priority);
727
- if (mockUrls.length === 0) {
728
- return next();
729
- }
730
- collectRequest(req);
731
- const { query: refererQuery } = urlParse(req.headers.referer || "");
732
- const reqBody = await parseReqBody(req, formidableOptions, bodyParserOptions);
733
- const cookies = new Cookies(req, res, cookiesOptions);
734
- const getCookie = cookies.get.bind(cookies);
735
- const method = req.method.toUpperCase();
736
- let mock;
737
- let _mockUrl;
738
- for (const mockUrl of mockUrls) {
739
- mock = fineMock(mockData[mockUrl], logger, {
740
- pathname,
741
- method,
742
- request: {
743
- query,
744
- refererQuery,
745
- body: reqBody,
746
- headers: req.headers,
747
- getCookie
748
- }
749
- });
750
- if (mock) {
751
- _mockUrl = mockUrl;
752
- break;
753
- }
754
- }
755
- if (!mock) {
756
- const matched = mockUrls.map(
757
- (m) => m === _mockUrl ? colors.underline(colors.bold(m)) : colors.dim(m)
758
- ).join(", ");
759
- logger.warn(
760
- `${colors.green(
761
- pathname
762
- )} matches ${matched} , but mock data is not found.`
763
- );
764
- return next();
765
- }
766
- const request = req;
767
- const response = res;
768
- request.body = reqBody;
769
- request.query = query;
770
- request.refererQuery = refererQuery;
771
- request.params = parseParams(mock.url, pathname);
772
- request.getCookie = getCookie;
773
- response.setCookie = cookies.set.bind(cookies);
774
- const {
775
- body,
776
- delay,
777
- type = "json",
778
- response: responseFn,
779
- status = 200,
780
- statusText,
781
- log: logLevel,
782
- __filepath__: filepath
783
- } = mock;
784
- responseStatus(response, status, statusText);
785
- await provideHeaders(request, response, mock, logger);
786
- await provideCookies(request, response, mock, logger);
787
- logger.info(requestLog(request, filepath), logLevel);
788
- logger.debug(
789
- `${colors.magenta("DEBUG")} ${colors.underline(
790
- pathname
791
- )} matches: [ ${mockUrls.map(
792
- (m) => m === _mockUrl ? colors.underline(colors.bold(m)) : colors.dim(m)
793
- ).join(", ")} ]
794
- `
795
- );
796
- if (body) {
797
- try {
798
- const content = isFunction2(body) ? await body(request) : body;
799
- await realDelay(startTime, delay);
800
- sendData(response, content, type);
801
- } catch (e) {
802
- logger.error(
803
- `${colors.red(
804
- `mock error at ${pathname}`
805
- )}
806
- ${e}
807
- at body (${colors.underline(filepath)})`,
808
- logLevel
809
- );
810
- responseStatus(response, 500);
811
- res.end("");
812
- }
813
- return;
765
+ filepath = path2.resolve(cwd, filepath);
766
+ const fileBase = `${filepath}.timestamp-${Date.now()}`;
767
+ const ext = isESM ? ".mjs" : ".cjs";
768
+ const fileNameTmp = `${fileBase}${ext}`;
769
+ await fsp.writeFile(fileNameTmp, code, "utf8");
770
+ try {
771
+ const result = await import(fileNameTmp);
772
+ return result.default || result;
773
+ } finally {
774
+ try {
775
+ fs2.unlinkSync(fileNameTmp);
776
+ } catch {
814
777
  }
815
- if (responseFn) {
816
- try {
817
- await realDelay(startTime, delay);
818
- await responseFn(request, response, next);
819
- } catch (e) {
820
- logger.error(
821
- `${colors.red(
822
- `mock error at ${pathname}`
823
- )}
824
- ${e}
825
- at response (${colors.underline(filepath)})`,
826
- logLevel
827
- );
828
- responseStatus(response, 500);
829
- res.end("");
778
+ }
779
+ }
780
+
781
+ // src/core/transform.ts
782
+ import {
783
+ isEmptyObject as isEmptyObject3,
784
+ isFunction as isFunction2,
785
+ isObject as isObject2,
786
+ sortBy as sortBy2,
787
+ toArray as toArray2
788
+ } from "@pengzhanbo/utils";
789
+ function transformRawData(rawData) {
790
+ return rawData.filter((item) => item[0]).map(([raw, __filepath__]) => {
791
+ let mockConfig;
792
+ if (raw.default) {
793
+ if (Array.isArray(raw.default)) {
794
+ mockConfig = raw.default.map((item) => ({ ...item, __filepath__ }));
795
+ } else {
796
+ mockConfig = { ...raw.default, __filepath__ };
830
797
  }
831
- return;
798
+ } else if ("url" in raw) {
799
+ mockConfig = { ...raw, __filepath__ };
800
+ } else {
801
+ mockConfig = [];
802
+ Object.keys(raw || {}).forEach((key) => {
803
+ if (Array.isArray(raw[key])) {
804
+ mockConfig.push(...raw[key].map((item) => ({ ...item, __filepath__ })));
805
+ } else {
806
+ mockConfig.push({ ...raw[key], __filepath__ });
807
+ }
808
+ });
832
809
  }
833
- res.end("");
834
- };
810
+ return mockConfig;
811
+ });
835
812
  }
836
- function fineMock(mockList, logger, {
837
- pathname,
838
- method,
839
- request
840
- }) {
841
- return mockList.find((mock) => {
842
- if (!pathname || !mock || !mock.url || mock.ws === true)
843
- return false;
844
- const methods = mock.method ? isArray3(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
845
- if (!methods.includes(method))
846
- return false;
847
- const hasMock = pathToRegexp2(mock.url).test(pathname);
848
- if (hasMock && mock.validator) {
849
- const params = parseParams(mock.url, pathname);
850
- if (isFunction2(mock.validator)) {
851
- return mock.validator({ params, ...request });
852
- } else {
853
- try {
854
- return validate({ params, ...request }, mock.validator);
855
- } catch (e) {
856
- const file = mock.__filepath__;
857
- logger.error(
858
- `${colors.red(
859
- `mock error at ${pathname}`
860
- )}
861
- ${e}
862
- at validator (${colors.underline(file)})`,
863
- mock.log
864
- );
865
- return false;
813
+ function transformMockData(mockList) {
814
+ const list = [];
815
+ for (const [, handle] of mockList.entries()) {
816
+ if (handle)
817
+ list.push(...toArray2(handle));
818
+ }
819
+ const mocks = {};
820
+ list.filter((mock) => isObject2(mock) && mock.enabled !== false && mock.url).forEach((mock) => {
821
+ const { pathname, query } = urlParse(mock.url);
822
+ const list2 = mocks[pathname] ??= [];
823
+ const current = { ...mock, url: pathname };
824
+ if (current.ws !== true) {
825
+ const validator = current.validator;
826
+ if (!isEmptyObject3(query)) {
827
+ if (isFunction2(validator)) {
828
+ current.validator = function(request) {
829
+ return isObjectSubset(request.query, query) && validator(request);
830
+ };
831
+ } else if (validator) {
832
+ current.validator = { ...validator };
833
+ current.validator.query = current.validator.query ? { ...query, ...current.validator.query } : query;
834
+ } else {
835
+ current.validator = { query };
866
836
  }
867
837
  }
868
838
  }
869
- return hasMock;
839
+ list2.push(current);
870
840
  });
841
+ Object.keys(mocks).forEach((key) => {
842
+ mocks[key] = sortByValidator(mocks[key]);
843
+ });
844
+ return mocks;
871
845
  }
872
- function responseStatus(response, status = 200, statusText) {
873
- response.statusCode = status;
874
- response.statusMessage = statusText || getHTTPStatusText(status);
846
+ function sortByValidator(mocks) {
847
+ return sortBy2(mocks, (item) => {
848
+ if (item.ws === true)
849
+ return 0;
850
+ const { validator } = item;
851
+ if (!validator || isEmptyObject3(validator))
852
+ return 2;
853
+ if (isFunction2(validator))
854
+ return 0;
855
+ const count = Object.keys(validator).reduce(
856
+ (prev, key) => prev + keysCount(validator[key]),
857
+ 0
858
+ );
859
+ return 1 / count;
860
+ });
875
861
  }
876
- async function provideHeaders(req, res, mock, logger) {
877
- const { headers, type = "json" } = mock;
878
- const filepath = mock.__filepath__;
879
- const contentType2 = mime.contentType(type) || mime.contentType(mime.lookup(type) || "");
880
- if (contentType2)
881
- res.setHeader("Content-Type", contentType2);
882
- res.setHeader("Cache-Control", "no-cache,max-age=0");
883
- res.setHeader("X-Mock-Power-By", "vite-plugin-mock-dev-server");
884
- if (filepath)
885
- res.setHeader("X-File-Path", filepath);
886
- if (!headers)
887
- return;
888
- try {
889
- const raw = isFunction2(headers) ? await headers(req) : headers;
890
- Object.keys(raw).forEach((key) => {
891
- res.setHeader(key, raw[key]);
862
+ function keysCount(obj) {
863
+ if (!obj)
864
+ return 0;
865
+ return Object.keys(obj).length;
866
+ }
867
+
868
+ // src/core/resolveRspackOptions.ts
869
+ import path3 from "path";
870
+ var _dirname = getDirname(import.meta.url);
871
+ function resolveRspackOptions({
872
+ cwd,
873
+ isEsm,
874
+ entryFile,
875
+ outputFile,
876
+ plugins,
877
+ alias,
878
+ watch = false
879
+ }) {
880
+ const targets = ["node >= 18.0.0"];
881
+ return {
882
+ mode: "production",
883
+ context: cwd,
884
+ entry: entryFile,
885
+ watch,
886
+ target: "node18.0",
887
+ externalsType: isEsm ? "module" : "commonjs2",
888
+ externals: /^[^./].*/,
889
+ resolve: {
890
+ alias,
891
+ extensions: [".js", ".ts", ".cjs", ".mjs", ".json5", ".json"]
892
+ },
893
+ plugins,
894
+ output: {
895
+ library: { type: !isEsm ? "commonjs2" : "module" },
896
+ filename: outputFile,
897
+ path: "/"
898
+ },
899
+ experiments: { outputModule: isEsm },
900
+ module: {
901
+ rules: [
902
+ {
903
+ test: /\.json5?$/,
904
+ loader: path3.join(_dirname, "json5-loader.cjs"),
905
+ type: "javascript/auto"
906
+ },
907
+ {
908
+ test: /\.[cm]?js$/,
909
+ use: [
910
+ {
911
+ loader: "builtin:swc-loader",
912
+ options: {
913
+ jsc: { parser: { syntax: "ecmascript" } },
914
+ env: { targets }
915
+ }
916
+ }
917
+ ]
918
+ },
919
+ {
920
+ test: /\.[cm]?ts$/,
921
+ use: [
922
+ {
923
+ loader: "builtin:swc-loader",
924
+ options: {
925
+ jsc: { parser: { syntax: "typescript" } },
926
+ env: { targets }
927
+ }
928
+ }
929
+ ]
930
+ }
931
+ ]
932
+ }
933
+ };
934
+ }
935
+
936
+ // src/core/mockCompiler.ts
937
+ var vfs = createFsFromVolume(new Volume());
938
+ function createMockCompiler(options) {
939
+ return new MockCompiler(options);
940
+ }
941
+ var MockCompiler = class extends EventEmitter {
942
+ constructor(options) {
943
+ super();
944
+ this.options = options;
945
+ this.cwd = options.cwd || process2.cwd();
946
+ const { include, exclude } = this.options;
947
+ this.fileFilter = createFilter(include, exclude, { resolve: false });
948
+ try {
949
+ const pkg = lookupFile(this.cwd, ["package.json"]);
950
+ this.moduleType = !!pkg && JSON.parse(pkg).type === "module" ? "esm" : "cjs";
951
+ } catch {
952
+ }
953
+ this.entryFile = path4.resolve(process2.cwd(), "node_modules/.cache/mock-server/mock-server.ts");
954
+ this.outputFile = "mock.bundle.js";
955
+ }
956
+ cwd;
957
+ mockWatcher;
958
+ moduleType = "cjs";
959
+ entryFile;
960
+ outputFile;
961
+ _mockData = {};
962
+ fileFilter;
963
+ watchInfo;
964
+ compiler;
965
+ get mockData() {
966
+ return this._mockData;
967
+ }
968
+ async run() {
969
+ await this.updateMockEntry();
970
+ this.watchMockFiles();
971
+ this.createCompiler(async (err, stats) => {
972
+ const name = "[rspack:mock]";
973
+ const logError = stats?.compilation.getLogger(name).error || ((...args) => console.error(color.red(name), ...args));
974
+ if (err) {
975
+ logError(err.stack || err);
976
+ if ("details" in err) {
977
+ logError(err.details);
978
+ }
979
+ return;
980
+ }
981
+ if (stats?.hasErrors()) {
982
+ const info = stats.toJson();
983
+ logError(info.errors);
984
+ return;
985
+ }
986
+ const content = vfs.readFileSync(`/${this.outputFile}`, "utf-8");
987
+ try {
988
+ const result = await loadFromCode({
989
+ filepath: this.outputFile,
990
+ code: content,
991
+ isESM: this.moduleType === "esm",
992
+ cwd: this.cwd
993
+ });
994
+ this._mockData = transformMockData(transformRawData(result));
995
+ this.emit("update", this.watchInfo || {});
996
+ } catch (e) {
997
+ logError(e);
998
+ }
892
999
  });
893
- } catch (e) {
894
- logger.error(
895
- `${colors.red(
896
- `mock error at ${req.url.split("?")[0]}`
897
- )}
898
- ${e}
899
- at headers (${colors.underline(filepath)})`,
900
- mock.log
901
- );
902
1000
  }
903
- }
904
- async function provideCookies(req, res, mock, logger) {
905
- const { cookies } = mock;
906
- const filepath = mock.__filepath__;
907
- if (!cookies)
908
- return;
909
- try {
910
- const raw = isFunction2(cookies) ? await cookies(req) : cookies;
911
- Object.keys(raw).forEach((key) => {
912
- const cookie = raw[key];
913
- if (isArray3(cookie)) {
914
- const [value, options] = cookie;
915
- res.setCookie(key, value, options);
916
- } else {
917
- res.setCookie(key, cookie);
918
- }
1001
+ close() {
1002
+ this.mockWatcher.close();
1003
+ this.compiler?.close(() => {
919
1004
  });
920
- } catch (e) {
921
- logger.error(
922
- `${colors.red(
923
- `mock error at ${req.url.split("?")[0]}`
924
- )}
925
- ${e}
926
- at cookies (${colors.underline(filepath)})`,
927
- mock.log
928
- );
1005
+ this.emit("close");
929
1006
  }
930
- }
931
- function sendData(res, raw, type) {
932
- if (isReadableStream(raw)) {
933
- raw.pipe(res);
934
- } else if (Buffer2.isBuffer(raw)) {
935
- res.end(type === "text" || type === "json" ? raw.toString("utf-8") : raw);
936
- } else {
937
- const content = typeof raw === "string" ? raw : JSON.stringify(raw);
938
- res.end(type === "buffer" ? Buffer2.from(content) : content);
1007
+ updateAlias(alias) {
1008
+ this.options.alias = {
1009
+ ...this.options.alias,
1010
+ ...alias
1011
+ };
939
1012
  }
940
- }
941
- async function realDelay(startTime, delay) {
942
- if (!delay || typeof delay === "number" && delay <= 0 || isArray3(delay) && delay.length !== 2) {
943
- return;
1013
+ async updateMockEntry() {
1014
+ const files = await this.getMockFiles();
1015
+ await this.resolveEntryFile(files);
944
1016
  }
945
- let realDelay2 = 0;
946
- if (isArray3(delay)) {
947
- const [min, max] = delay;
948
- realDelay2 = random(min, max);
949
- } else {
950
- realDelay2 = delay - (timestamp() - startTime);
1017
+ async getMockFiles() {
1018
+ const { include } = this.options;
1019
+ const files = await fastGlob(include, { cwd: this.cwd });
1020
+ return files.filter(this.fileFilter);
951
1021
  }
952
- if (realDelay2 > 0)
953
- await sleep(realDelay2);
954
- }
955
- function getHTTPStatusText(status) {
956
- return HTTP_STATUS[status] || "Unknown";
957
- }
958
- function requestLog(request, filepath) {
959
- const { url, method, query, params, body } = request;
960
- let { pathname } = new URL(url, "http://example.com");
961
- pathname = colors.green(decodeURIComponent(pathname));
962
- const format = (prefix, data) => {
963
- return !data || isEmptyObject3(data) ? "" : ` ${colors.gray(`${prefix}:`)}${JSON.stringify(data)}`;
964
- };
965
- const ms = colors.magenta(colors.bold(method));
966
- const qs = format("query", query);
967
- const ps = format("params", params);
968
- const bs = format("body", body);
969
- const file = ` ${colors.dim(colors.underline(`(${filepath})`))}`;
970
- return `${ms} ${pathname}${qs}${ps}${bs}${file}`;
971
- }
972
-
973
- // src/core/logger.ts
974
- import { isBoolean } from "@pengzhanbo/utils";
975
- import colors2 from "picocolors";
976
- var logLevels = {
977
- silent: 0,
978
- error: 1,
979
- warn: 2,
980
- info: 3,
981
- debug: 4
982
- };
983
- function createLogger(prefix, defaultLevel = "info") {
984
- prefix = `[${prefix}]`;
985
- function output(type, msg, level) {
986
- level = isBoolean(level) ? level ? defaultLevel : "error" : level;
987
- const thresh = logLevels[level];
988
- if (thresh >= logLevels[type]) {
989
- const method = type === "info" || type === "debug" ? "log" : type;
990
- const tag = type === "debug" ? colors2.magenta(colors2.bold(prefix)) : type === "info" ? colors2.cyan(colors2.bold(prefix)) : type === "warn" ? colors2.yellow(colors2.bold(prefix)) : colors2.red(colors2.bold(prefix));
991
- const format = `${colors2.dim(
992
- (/* @__PURE__ */ new Date()).toLocaleTimeString()
993
- )} ${tag} ${msg}`;
994
- console[method](format);
995
- }
1022
+ watchMockFiles() {
1023
+ const { include } = this.options;
1024
+ const [firstGlob, ...otherGlob] = toArray3(include);
1025
+ const watcher = this.mockWatcher = chokidar.watch(firstGlob, {
1026
+ ignoreInitial: true,
1027
+ cwd: this.cwd
1028
+ });
1029
+ if (otherGlob.length > 0)
1030
+ otherGlob.forEach((glob) => watcher.add(glob));
1031
+ watcher.on("add", (filepath) => {
1032
+ if (this.fileFilter(filepath)) {
1033
+ this.watchInfo = { filepath, type: "add" };
1034
+ this.updateMockEntry();
1035
+ }
1036
+ });
1037
+ watcher.on("change", (filepath) => {
1038
+ if (this.fileFilter(filepath)) {
1039
+ this.watchInfo = { filepath, type: "change" };
1040
+ }
1041
+ });
1042
+ watcher.on("unlink", async (filepath) => {
1043
+ this.watchInfo = { filepath, type: "unlink" };
1044
+ this.updateMockEntry();
1045
+ });
996
1046
  }
997
- const logger = {
998
- debug(msg, level = defaultLevel) {
999
- output("debug", msg, level);
1000
- },
1001
- info(msg, level = defaultLevel) {
1002
- output("info", msg, level);
1003
- },
1004
- warn(msg, level = defaultLevel) {
1005
- output("warn", msg, level);
1006
- },
1007
- error(msg, level = defaultLevel) {
1008
- output("error", msg, level);
1047
+ async resolveEntryFile(fileList) {
1048
+ const importers = [];
1049
+ const exporters = [];
1050
+ for (const [index, filepath] of fileList.entries()) {
1051
+ const file = normalizePath(path4.join(this.cwd, filepath));
1052
+ importers.push(`import * as m${index} from '${file}'`);
1053
+ exporters.push(`[m${index}, '${filepath}']`);
1009
1054
  }
1010
- };
1011
- return logger;
1012
- }
1055
+ const code = `${importers.join("\n")}
1013
1056
 
1014
- // src/core/mockMiddleware.ts
1015
- function createManuallyMockMiddleware({ alias, proxies, context = process3.cwd(), plugins }, pluginOptions) {
1016
- const options = resolvePluginOptions(pluginOptions, context);
1017
- const logger = createLogger(
1018
- "rspack:mock",
1019
- isBoolean2(options.log) ? options.log ? "info" : "error" : options.log
1020
- );
1021
- const compiler = createMockCompiler({
1022
- alias,
1023
- plugins,
1024
- cwd: options.cwd,
1025
- include: toArray2(options.include),
1026
- exclude: toArray2(options.exclude)
1027
- });
1028
- function mockMiddleware(middlewares) {
1029
- middlewares.unshift(baseMiddleware(compiler, {
1030
- formidableOptions: options.formidableOptions,
1031
- proxies,
1032
- cookiesOptions: options.cookiesOptions,
1033
- bodyParserOptions: options.bodyParserOptions,
1034
- priority: options.priority,
1035
- logger
1036
- }));
1037
- const corsMiddleware = createCorsMiddleware(compiler, proxies, options);
1038
- if (corsMiddleware) {
1039
- middlewares.unshift(corsMiddleware);
1057
+ export default [
1058
+ ${exporters.join(",\n ")}
1059
+ ]`;
1060
+ const dirname = path4.dirname(this.entryFile);
1061
+ if (!fs3.existsSync(dirname)) {
1062
+ await fsp2.mkdir(dirname, { recursive: true });
1040
1063
  }
1041
- return middlewares;
1064
+ await fsp2.writeFile(this.entryFile, code, "utf8");
1042
1065
  }
1043
- return {
1044
- mockMiddleware,
1045
- run: () => compiler.run(),
1046
- close: () => compiler.close(),
1047
- updateAlias: compiler.updateAlias.bind(compiler)
1048
- };
1049
- }
1050
- function createMockMiddleware(middlewareOptions, pluginOptions) {
1051
- const { mockMiddleware, run, close } = createManuallyMockMiddleware(
1052
- middlewareOptions,
1053
- pluginOptions
1054
- );
1055
- run();
1056
- process3.on("exit", () => close());
1057
- return mockMiddleware;
1058
- }
1059
- function createCorsMiddleware(compiler, proxies, options) {
1060
- let corsOptions = {};
1061
- const enabled = options.cors !== false;
1062
- if (enabled) {
1063
- corsOptions = {
1064
- ...corsOptions,
1065
- ...typeof options.cors === "boolean" ? {} : options.cors
1066
- };
1066
+ createCompiler(callback) {
1067
+ const { plugins, alias } = this.options;
1068
+ const options = resolveRspackOptions({
1069
+ isEsm: this.moduleType === "esm",
1070
+ cwd: this.cwd,
1071
+ plugins,
1072
+ entryFile: this.entryFile,
1073
+ outputFile: this.outputFile,
1074
+ alias,
1075
+ watch: true
1076
+ });
1077
+ this.compiler = rspackCore.rspack(options, callback);
1078
+ if (this.compiler)
1079
+ this.compiler.outputFileSystem = vfs;
1067
1080
  }
1068
- return !enabled ? void 0 : function(req, res, next) {
1069
- const { pathname } = urlParse(req.url);
1070
- if (!pathname || proxies.length === 0 || !proxies.some(
1071
- (context) => doesProxyContextMatchUrl(context, req.url, req)
1072
- )) {
1073
- return next();
1081
+ };
1082
+
1083
+ // src/core/mockWebsocket.ts
1084
+ import Cookies2 from "cookies";
1085
+ import { pathToRegexp as pathToRegexp4 } from "path-to-regexp";
1086
+ import colors3 from "picocolors";
1087
+ import { WebSocketServer } from "ws";
1088
+ function mockWebSocket(compiler, httpServer, {
1089
+ wsProxies: proxies,
1090
+ cookiesOptions,
1091
+ logger
1092
+ }) {
1093
+ const hmrMap = /* @__PURE__ */ new Map();
1094
+ const poolMap = /* @__PURE__ */ new Map();
1095
+ const wssContextMap = /* @__PURE__ */ new WeakMap();
1096
+ const getWssMap = (mockUrl) => {
1097
+ let wssMap = poolMap.get(mockUrl);
1098
+ if (!wssMap)
1099
+ poolMap.set(mockUrl, wssMap = /* @__PURE__ */ new Map());
1100
+ return wssMap;
1101
+ };
1102
+ const getWss = (wssMap, pathname) => {
1103
+ let wss = wssMap.get(pathname);
1104
+ if (!wss)
1105
+ wssMap.set(pathname, wss = new WebSocketServer({ noServer: true }));
1106
+ return wss;
1107
+ };
1108
+ const addHmr = (filepath, mockUrl) => {
1109
+ let urlList = hmrMap.get(filepath);
1110
+ if (!urlList)
1111
+ hmrMap.set(filepath, urlList = /* @__PURE__ */ new Set());
1112
+ urlList.add(mockUrl);
1113
+ };
1114
+ const setupWss = (wssMap, wss, mock, context, pathname, filepath) => {
1115
+ try {
1116
+ mock.setup?.(wss, context);
1117
+ wss.on("close", () => wssMap.delete(pathname));
1118
+ wss.on("error", (e) => {
1119
+ logger.error(
1120
+ `${colors3.red(
1121
+ `WebSocket mock error at ${wss.path}`
1122
+ )}
1123
+ ${e}
1124
+ at setup (${filepath})`,
1125
+ mock.log
1126
+ );
1127
+ });
1128
+ } catch (e) {
1129
+ logger.error(
1130
+ `${colors3.red(
1131
+ `WebSocket mock error at ${wss.path}`
1132
+ )}
1133
+ ${e}
1134
+ at setup (${filepath})`,
1135
+ mock.log
1136
+ );
1074
1137
  }
1075
- const mockData = compiler.mockData;
1076
- const mockUrl = Object.keys(mockData).find(
1077
- (key) => pathToRegexp3(key).test(pathname)
1138
+ };
1139
+ const emitConnection = (wss, ws, req, connectionList) => {
1140
+ wss.emit("connection", ws, req);
1141
+ ws.on("close", () => {
1142
+ const i = connectionList.findIndex((item) => item.ws === ws);
1143
+ if (i !== -1)
1144
+ connectionList.splice(i, 1);
1145
+ });
1146
+ };
1147
+ const restartWss = (wssMap, wss, mock, pathname, filepath) => {
1148
+ const { cleanupList, connectionList, context } = wssContextMap.get(wss);
1149
+ cleanupRunner(cleanupList);
1150
+ connectionList.forEach(({ ws }) => ws.removeAllListeners());
1151
+ wss.removeAllListeners();
1152
+ setupWss(wssMap, wss, mock, context, pathname, filepath);
1153
+ connectionList.forEach(
1154
+ ({ ws, req }) => emitConnection(wss, ws, req, connectionList)
1078
1155
  );
1079
- if (!mockUrl)
1080
- return next();
1081
- cors(corsOptions)(req, res, next);
1082
1156
  };
1157
+ compiler.on("update", ({ filepath }) => {
1158
+ if (!hmrMap.has(filepath))
1159
+ return;
1160
+ const mockUrlList = hmrMap.get(filepath);
1161
+ if (!mockUrlList)
1162
+ return;
1163
+ for (const mockUrl of mockUrlList.values()) {
1164
+ for (const mock of compiler.mockData[mockUrl]) {
1165
+ if (!mock.ws || mock.__filepath__ !== filepath)
1166
+ return;
1167
+ const wssMap = getWssMap(mockUrl);
1168
+ for (const [pathname, wss] of wssMap.entries())
1169
+ restartWss(wssMap, wss, mock, pathname, filepath);
1170
+ }
1171
+ }
1172
+ });
1173
+ httpServer?.on("upgrade", (req, socket, head) => {
1174
+ const { pathname, query } = urlParse(req.url);
1175
+ if (!pathname || proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url, req))) {
1176
+ return;
1177
+ }
1178
+ const mockData = compiler.mockData;
1179
+ const mockUrl = Object.keys(mockData).find((key) => {
1180
+ return pathToRegexp4(key).test(pathname);
1181
+ });
1182
+ if (!mockUrl)
1183
+ return;
1184
+ const mock = mockData[mockUrl].find((mock2) => {
1185
+ return mock2.url && mock2.ws && pathToRegexp4(mock2.url).test(pathname);
1186
+ });
1187
+ if (!mock)
1188
+ return;
1189
+ const filepath = mock.__filepath__;
1190
+ addHmr(filepath, mockUrl);
1191
+ const wssMap = getWssMap(mockUrl);
1192
+ const wss = getWss(wssMap, pathname);
1193
+ let wssContext = wssContextMap.get(wss);
1194
+ if (!wssContext) {
1195
+ const cleanupList = [];
1196
+ const context = {
1197
+ onCleanup: (cleanup) => cleanupList.push(cleanup)
1198
+ };
1199
+ wssContext = { cleanupList, context, connectionList: [] };
1200
+ wssContextMap.set(wss, wssContext);
1201
+ setupWss(wssMap, wss, mock, context, pathname, filepath);
1202
+ }
1203
+ const request = req;
1204
+ const cookies = new Cookies2(req, req, cookiesOptions);
1205
+ const { query: refererQuery } = urlParse(req.headers.referer || "");
1206
+ request.query = query;
1207
+ request.refererQuery = refererQuery;
1208
+ request.params = parseParams(mockUrl, pathname);
1209
+ request.getCookie = cookies.get.bind(cookies);
1210
+ wss.handleUpgrade(request, socket, head, (ws) => {
1211
+ logger.info(
1212
+ `${colors3.magenta(colors3.bold("WebSocket"))} ${colors3.green(
1213
+ req.url
1214
+ )} connected ${colors3.dim(`(${filepath})`)}`,
1215
+ mock.log
1216
+ );
1217
+ wssContext.connectionList.push({ req: request, ws });
1218
+ emitConnection(wss, ws, request, wssContext.connectionList);
1219
+ });
1220
+ });
1221
+ httpServer?.on("close", () => {
1222
+ for (const wssMap of poolMap.values()) {
1223
+ for (const wss of wssMap.values()) {
1224
+ const wssContext = wssContextMap.get(wss);
1225
+ cleanupRunner(wssContext.cleanupList);
1226
+ wss.close();
1227
+ }
1228
+ wssMap.clear();
1229
+ }
1230
+ poolMap.clear();
1231
+ hmrMap.clear();
1232
+ });
1233
+ }
1234
+ function cleanupRunner(cleanupList) {
1235
+ let cleanup;
1236
+ while (cleanup = cleanupList.shift())
1237
+ cleanup?.();
1083
1238
  }
1084
1239
 
1085
1240
  export {
1241
+ waitingFor,
1086
1242
  rewriteRequest,
1087
- createManuallyMockMiddleware,
1088
- createMockMiddleware
1243
+ createMockMiddleware,
1244
+ resolvePluginOptions,
1245
+ createMockCompiler,
1246
+ mockWebSocket
1089
1247
  };