rspack-plugin-mock
Version:
inject api mock server to development server
907 lines (895 loc) • 27.4 kB
JavaScript
// src/core/transform.ts
import {
isEmptyObject,
isFunction,
isPlainObject as isPlainObject2,
sortBy,
toArray
} from "@pengzhanbo/utils";
// src/core/utils.ts
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { parse as queryParse } from "node:querystring";
import { fileURLToPath, URL as URL2 } from "node:url";
import Debug from "debug";
import { createFsFromVolume, Volume } from "memfs";
import { match } from "path-to-regexp";
var packageDir = getDirname(import.meta.url);
var vfs = createFsFromVolume(new Volume());
function isStream(stream) {
return stream !== null && typeof stream === "object" && typeof stream.pipe === "function";
}
function isReadableStream(stream) {
return isStream(stream) && stream.readable !== false && typeof stream._read === "function" && typeof stream._readableState === "object";
}
function getDirname(importMetaUrl) {
return path.dirname(fileURLToPath(importMetaUrl));
}
var debug = Debug("rspack:mock");
function lookupFile(dir, formats, options) {
for (const format of formats) {
const fullPath = path.join(dir, format);
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
const result = options?.pathOnly ? fullPath : fs.readFileSync(fullPath, "utf-8");
if (!options?.predicate || options.predicate(result))
return result;
}
}
const parentDir = path.dirname(dir);
if (parentDir !== dir && (!options?.rootDir || parentDir.startsWith(options?.rootDir))) {
return lookupFile(parentDir, formats, options);
}
}
function doesProxyContextMatchUrl(context, url, req) {
if (typeof context === "function") {
return context(url, req);
}
return context[0] === "^" && new RegExp(context).test(url) || url.startsWith(context);
}
function parseParams(pattern, url) {
const urlMatch = match(pattern, { decode: decodeURIComponent })(url) || {
params: {}
};
return urlMatch.params || {};
}
function urlParse(input) {
const url = new URL2(input, "http://example.com");
const pathname = decodeURIComponent(url.pathname);
const query = queryParse(url.search.replace(/^\?/, ""));
return { pathname, query };
}
var windowsSlashRE = /\\/g;
var isWindows = os.platform() === "win32";
function slash(p) {
return p.replace(windowsSlashRE, "/");
}
function normalizePath(id) {
return path.posix.normalize(isWindows ? slash(id) : id);
}
function waitingFor(onSuccess, maxRetry = 5) {
return function wait(getter, retry = 0) {
const value = getter();
if (value) {
onSuccess(value);
} else if (retry < maxRetry) {
setTimeout(() => wait(getter, retry + 1), 100);
}
};
}
// src/core/validator.ts
import { isArray, isPlainObject } from "@pengzhanbo/utils";
function validate(request, validator) {
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);
}
function isObjectSubset(source, target) {
if (!target)
return true;
for (const key in target) {
if (!isIncluded(source[key], target[key]))
return false;
}
return true;
}
function isIncluded(source, target) {
if (isArray(source) && isArray(target)) {
const seen = /* @__PURE__ */ new Set();
return target.every(
(ti) => source.some((si, i) => {
if (seen.has(i))
return false;
const included = isIncluded(si, ti);
if (included)
seen.add(i);
return included;
})
);
}
if (isPlainObject(source) && isPlainObject(target))
return isObjectSubset(source, target);
return Object.is(source, target);
}
// src/core/transform.ts
function transformRawData(rawData) {
return rawData.filter((item) => item[0]).map(([raw, __filepath__]) => {
let mockConfig;
if (raw.default) {
if (Array.isArray(raw.default)) {
mockConfig = raw.default.map((item) => ({ ...item, __filepath__ }));
} else {
mockConfig = { ...raw.default, __filepath__ };
}
} else if ("url" in raw) {
mockConfig = { ...raw, __filepath__ };
} else {
mockConfig = [];
Object.keys(raw || {}).forEach((key) => {
if (Array.isArray(raw[key])) {
mockConfig.push(...raw[key].map((item) => ({ ...item, __filepath__ })));
} else {
mockConfig.push({ ...raw[key], __filepath__ });
}
});
}
return mockConfig;
});
}
function transformMockData(mockList) {
const list = [];
for (const [, handle] of mockList.entries()) {
if (handle)
list.push(...toArray(handle));
}
const mocks = {};
list.filter((mock) => isPlainObject2(mock) && mock.enabled !== false && mock.url).forEach((mock) => {
const { pathname, query } = urlParse(mock.url);
const list2 = mocks[pathname] ??= [];
const current = { ...mock, url: pathname };
if (current.ws !== true) {
const validator = current.validator;
if (!isEmptyObject(query)) {
if (isFunction(validator)) {
current.validator = function(request) {
return isObjectSubset(request.query, query) && validator(request);
};
} else if (validator) {
current.validator = { ...validator };
current.validator.query = current.validator.query ? { ...query, ...current.validator.query } : query;
} else {
current.validator = { query };
}
}
}
list2.push(current);
});
Object.keys(mocks).forEach((key) => {
mocks[key] = sortByValidator(mocks[key]);
});
return mocks;
}
function sortByValidator(mocks) {
return sortBy(mocks, (item) => {
if (item.ws === true)
return 0;
const { validator } = item;
if (!validator || isEmptyObject(validator))
return 2;
if (isFunction(validator))
return 0;
const count = Object.keys(validator).reduce(
(prev, key) => prev + keysCount(validator[key]),
0
);
return 1 / count;
});
}
function keysCount(obj) {
if (!obj)
return 0;
return Object.keys(obj).length;
}
// src/core/requestRecovery.ts
import { Buffer } from "node:buffer";
var requestCollectCache = /* @__PURE__ */ new WeakMap();
function collectRequest(req) {
const chunks = [];
req.addListener("data", (chunk) => {
chunks.push(Buffer.from(chunk));
});
req.addListener("end", () => {
if (chunks.length)
requestCollectCache.set(req, Buffer.concat(chunks));
});
}
function rewriteRequest(proxyReq, req) {
const buffer = requestCollectCache.get(req);
if (buffer) {
requestCollectCache.delete(req);
if (!proxyReq.headersSent)
proxyReq.setHeader("Content-Length", buffer.byteLength);
if (!proxyReq.writableEnded)
proxyReq.write(buffer);
}
}
// src/core/baseMiddleware.ts
import { Buffer as Buffer2 } from "node:buffer";
import {
isArray as isArray3,
isEmptyObject as isEmptyObject3,
isFunction as isFunction2,
random,
sleep,
timestamp
} from "@pengzhanbo/utils";
import Cookies from "cookies";
import HTTP_STATUS from "http-status";
import * as mime from "mime-types";
import { pathToRegexp as pathToRegexp2 } from "path-to-regexp";
import colors from "picocolors";
// src/core/matchingWeight.ts
import {
isArray as isArray2,
isEmptyObject as isEmptyObject2,
isString,
sortBy as sortBy2,
uniq
} from "@pengzhanbo/utils";
import { parse, pathToRegexp } from "path-to-regexp";
var tokensCache = {};
function getTokens(rule) {
if (tokensCache[rule])
return tokensCache[rule];
const tks = parse(rule);
const tokens = [];
for (const tk of tks) {
if (!isString(tk)) {
tokens.push(tk);
} else {
const hasPrefix = tk[0] === "/";
const subTks = hasPrefix ? tk.slice(1).split("/") : tk.split("/");
tokens.push(
`${hasPrefix ? "/" : ""}${subTks[0]}`,
...subTks.slice(1).map((t) => `/${t}`)
);
}
}
tokensCache[rule] = tokens;
return tokens;
}
function getHighest(rules) {
let weights = rules.map((rule) => getTokens(rule).length);
weights = weights.length === 0 ? [1] : weights;
return Math.max(...weights) + 2;
}
function sortFn(rule) {
const tokens = getTokens(rule);
let w = 0;
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (!isString(token))
w += 10 ** (i + 1);
w += 10 ** (i + 1);
}
return w;
}
function preSort(rules) {
let matched = [];
const preMatch = [];
for (const rule of rules) {
const tokens = getTokens(rule);
const len = tokens.filter((token) => typeof token !== "string").length;
if (!preMatch[len])
preMatch[len] = [];
preMatch[len].push(rule);
}
for (const match2 of preMatch.filter((v) => v && v.length > 0))
matched = [...matched, ...sortBy2(match2, sortFn).reverse()];
return matched;
}
function defaultPriority(rules) {
const highest = getHighest(rules);
return sortBy2(rules, (rule) => {
const tokens = getTokens(rule);
const dym = tokens.filter((token) => typeof token !== "string");
if (dym.length === 0)
return 0;
let weight = dym.length;
let exp = 0;
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
const isDynamic = !isString(token);
const {
pattern = "",
modifier,
prefix,
name
} = isDynamic ? token : {};
const isGlob = pattern && pattern.includes(".*");
const isSlash = prefix === "/";
const isNamed = isString(name);
exp += isDynamic && isSlash ? 1 : 0;
if (i === tokens.length - 1 && isGlob) {
weight += 5 * 10 ** (tokens.length === 1 ? highest + 1 : highest);
} else {
if (isGlob) {
weight += 3 * 10 ** (highest - 1);
} else if (pattern) {
if (isSlash) {
weight += (isNamed ? 2 : 1) * 10 ** (exp + 1);
} else {
weight -= 1 * 10 ** exp;
}
}
}
if (modifier === "+")
weight += 1 * 10 ** (highest - 1);
if (modifier === "*")
weight += 1 * 10 ** (highest - 1) + 1;
if (modifier === "?")
weight += 1 * 10 ** (exp + (isSlash ? 1 : 0));
}
return weight;
});
}
function matchingWeight(rules, url, priority) {
let matched = defaultPriority(
preSort(rules.filter((rule) => pathToRegexp(rule).test(url)))
);
const { global = [], special = {} } = priority;
if (global.length === 0 && isEmptyObject2(special) || matched.length === 0)
return matched;
const [statics, dynamics] = twoPartMatch(matched);
const globalMatch = global.filter((rule) => dynamics.includes(rule));
if (globalMatch.length > 0) {
matched = uniq([...statics, ...globalMatch, ...dynamics]);
}
if (isEmptyObject2(special))
return matched;
const specialRule = Object.keys(special).filter(
(rule) => matched.includes(rule)
)[0];
if (!specialRule)
return matched;
const options = special[specialRule];
const { rules: lowerRules, when } = isArray2(options) ? { rules: options, when: [] } : options;
if (lowerRules.includes(matched[0])) {
if (when.length === 0 || when.some((path2) => pathToRegexp(path2).test(url))) {
matched = uniq([specialRule, ...matched]);
}
}
return matched;
}
function twoPartMatch(rules) {
const statics = [];
const dynamics = [];
for (const rule of rules) {
const tokens = getTokens(rule);
const dym = tokens.filter((token) => typeof token !== "string");
if (dym.length > 0)
dynamics.push(rule);
else statics.push(rule);
}
return [statics, dynamics];
}
// src/core/parseReqBody.ts
import bodyParser from "co-body";
import formidable from "formidable";
async function parseReqBody(req, formidableOptions, bodyParserOptions = {}) {
const method = req.method.toUpperCase();
if (["GET", "DELETE", "HEAD"].includes(method))
return void 0;
const type = req.headers["content-type"]?.toLocaleLowerCase() || "";
const { limit, formLimit, jsonLimit, textLimit, ...rest } = bodyParserOptions;
try {
if (type.startsWith("application/json")) {
return await bodyParser.json(req, {
limit: jsonLimit || limit,
...rest
});
}
if (type.startsWith("application/x-www-form-urlencoded")) {
return await bodyParser.form(req, {
limit: formLimit || limit,
...rest
});
}
if (type.startsWith("text/plain")) {
return await bodyParser.text(req, {
limit: textLimit || limit,
...rest
});
}
if (type.startsWith("multipart/form-data"))
return await parseMultipart(req, formidableOptions);
} catch (e) {
console.error(e);
}
return void 0;
}
async function parseMultipart(req, options) {
const form = formidable(options);
return new Promise((resolve, reject) => {
form.parse(req, (error, fields, files) => {
if (error) {
reject(error);
return;
}
resolve({ ...fields, ...files });
});
});
}
// src/core/baseMiddleware.ts
function baseMiddleware(compiler, {
formidableOptions = {},
bodyParserOptions = {},
proxies,
cookiesOptions,
logger,
priority = {}
}) {
return async function(req, res, next) {
const startTime = timestamp();
const { query, pathname } = urlParse(req.url);
if (!pathname || proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url, req))) {
return next();
}
const mockData = compiler.mockData;
const mockUrls = matchingWeight(Object.keys(mockData), pathname, priority);
if (mockUrls.length === 0) {
return next();
}
collectRequest(req);
const { query: refererQuery } = urlParse(req.headers.referer || "");
const reqBody = await parseReqBody(req, formidableOptions, bodyParserOptions);
const cookies = new Cookies(req, res, cookiesOptions);
const getCookie = cookies.get.bind(cookies);
const method = req.method.toUpperCase();
let mock;
let _mockUrl;
for (const mockUrl of mockUrls) {
mock = fineMock(mockData[mockUrl], logger, {
pathname,
method,
request: {
query,
refererQuery,
body: reqBody,
headers: req.headers,
getCookie
}
});
if (mock) {
_mockUrl = mockUrl;
break;
}
}
if (!mock) {
const matched = mockUrls.map(
(m) => m === _mockUrl ? colors.underline(colors.bold(m)) : colors.dim(m)
).join(", ");
logger.warn(
`${colors.green(
pathname
)} matches ${matched} , but mock data is not found.`
);
return next();
}
const request = req;
const response = res;
request.body = reqBody;
request.query = query;
request.refererQuery = refererQuery;
request.params = parseParams(mock.url, pathname);
request.getCookie = getCookie;
response.setCookie = cookies.set.bind(cookies);
const {
body,
delay,
type = "json",
response: responseFn,
status = 200,
statusText,
log: logLevel,
__filepath__: filepath
} = mock;
responseStatus(response, status, statusText);
await provideHeaders(request, response, mock, logger);
await provideCookies(request, response, mock, logger);
logger.info(requestLog(request, filepath), logLevel);
logger.debug(
`${colors.magenta("DEBUG")} ${colors.underline(
pathname
)} matches: [ ${mockUrls.map(
(m) => m === _mockUrl ? colors.underline(colors.bold(m)) : colors.dim(m)
).join(", ")} ]
`
);
if (body) {
try {
const content = isFunction2(body) ? await body(request) : body;
await realDelay(startTime, delay);
sendData(response, content, type);
} catch (e) {
logger.error(
`${colors.red(
`mock error at ${pathname}`
)}
${e}
at body (${colors.underline(filepath)})`,
logLevel
);
responseStatus(response, 500);
res.end("");
}
return;
}
if (responseFn) {
try {
await realDelay(startTime, delay);
await responseFn(request, response, next);
} catch (e) {
logger.error(
`${colors.red(
`mock error at ${pathname}`
)}
${e}
at response (${colors.underline(filepath)})`,
logLevel
);
responseStatus(response, 500);
res.end("");
}
return;
}
res.end("");
};
}
function fineMock(mockList, logger, {
pathname,
method,
request
}) {
return mockList.find((mock) => {
if (!pathname || !mock || !mock.url || mock.ws === true)
return false;
const methods = mock.method ? isArray3(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
if (!methods.includes(method))
return false;
const hasMock = pathToRegexp2(mock.url).test(pathname);
if (hasMock && mock.validator) {
const params = parseParams(mock.url, pathname);
if (isFunction2(mock.validator)) {
return mock.validator({ params, ...request });
} else {
try {
return validate({ params, ...request }, mock.validator);
} catch (e) {
const file = mock.__filepath__;
logger.error(
`${colors.red(
`mock error at ${pathname}`
)}
${e}
at validator (${colors.underline(file)})`,
mock.log
);
return false;
}
}
}
return hasMock;
});
}
function responseStatus(response, status = 200, statusText) {
response.statusCode = status;
response.statusMessage = statusText || getHTTPStatusText(status);
}
async function provideHeaders(req, res, mock, logger) {
const { headers, type = "json" } = mock;
const filepath = mock.__filepath__;
const contentType2 = mime.contentType(type) || mime.contentType(mime.lookup(type) || "");
if (contentType2)
res.setHeader("Content-Type", contentType2);
res.setHeader("Cache-Control", "no-cache,max-age=0");
res.setHeader("X-Mock-Power-By", "vite-plugin-mock-dev-server");
if (filepath)
res.setHeader("X-File-Path", filepath);
if (!headers)
return;
try {
const raw = isFunction2(headers) ? await headers(req) : headers;
Object.keys(raw).forEach((key) => {
res.setHeader(key, raw[key]);
});
} catch (e) {
logger.error(
`${colors.red(
`mock error at ${req.url.split("?")[0]}`
)}
${e}
at headers (${colors.underline(filepath)})`,
mock.log
);
}
}
async function provideCookies(req, res, mock, logger) {
const { cookies } = mock;
const filepath = mock.__filepath__;
if (!cookies)
return;
try {
const raw = isFunction2(cookies) ? await cookies(req) : cookies;
Object.keys(raw).forEach((key) => {
const cookie = raw[key];
if (isArray3(cookie)) {
const [value, options] = cookie;
res.setCookie(key, value, options);
} else {
res.setCookie(key, cookie);
}
});
} catch (e) {
logger.error(
`${colors.red(
`mock error at ${req.url.split("?")[0]}`
)}
${e}
at cookies (${colors.underline(filepath)})`,
mock.log
);
}
}
function sendData(res, raw, type) {
if (isReadableStream(raw)) {
raw.pipe(res);
} else if (Buffer2.isBuffer(raw)) {
res.end(type === "text" || type === "json" ? raw.toString("utf-8") : raw);
} else {
const content = typeof raw === "string" ? raw : JSON.stringify(raw);
res.end(type === "buffer" ? Buffer2.from(content) : content);
}
}
async function realDelay(startTime, delay) {
if (!delay || typeof delay === "number" && delay <= 0 || isArray3(delay) && delay.length !== 2) {
return;
}
let realDelay2 = 0;
if (isArray3(delay)) {
const [min, max] = delay;
realDelay2 = random(min, max);
} else {
realDelay2 = delay - (timestamp() - startTime);
}
if (realDelay2 > 0)
await sleep(realDelay2);
}
function getHTTPStatusText(status) {
return HTTP_STATUS[status] || "Unknown";
}
function requestLog(request, filepath) {
const { url, method, query, params, body } = request;
let { pathname } = new URL(url, "http://example.com");
pathname = colors.green(decodeURIComponent(pathname));
const format = (prefix, data) => {
return !data || isEmptyObject3(data) ? "" : ` ${colors.gray(`${prefix}:`)}${JSON.stringify(data)}`;
};
const ms = colors.magenta(colors.bold(method));
const qs = format("query", query);
const ps = format("params", params);
const bs = format("body", body);
const file = ` ${colors.dim(colors.underline(`(${filepath})`))}`;
return `${ms} ${pathname}${qs}${ps}${bs}${file}`;
}
// src/core/mockWebsocket.ts
import Cookies2 from "cookies";
import { pathToRegexp as pathToRegexp3 } from "path-to-regexp";
import colors2 from "picocolors";
import { WebSocketServer } from "ws";
function mockWebSocket(compiler, httpServer, {
wsProxies: proxies,
cookiesOptions,
logger
}) {
const hmrMap = /* @__PURE__ */ new Map();
const poolMap = /* @__PURE__ */ new Map();
const wssContextMap = /* @__PURE__ */ new WeakMap();
const getWssMap = (mockUrl) => {
let wssMap = poolMap.get(mockUrl);
if (!wssMap)
poolMap.set(mockUrl, wssMap = /* @__PURE__ */ new Map());
return wssMap;
};
const addHmr = (filepath, mockUrl) => {
let urlList = hmrMap.get(filepath);
if (!urlList)
hmrMap.set(filepath, urlList = /* @__PURE__ */ new Set());
urlList.add(mockUrl);
};
const setupWss = (wssMap, wss, mock, context, pathname, filepath) => {
try {
mock.setup?.(wss, context);
wss.on("close", () => wssMap.delete(pathname));
wss.on("error", (e) => {
logger.error(
`${colors2.red(
`WebSocket mock error at ${wss.path}`
)}
${e}
at setup (${filepath})`,
mock.log
);
});
} catch (e) {
logger.error(
`${colors2.red(
`WebSocket mock error at ${wss.path}`
)}
${e}
at setup (${filepath})`,
mock.log
);
}
};
const restartWss = (wssMap, wss, mock, pathname, filepath) => {
const { cleanupList, connectionList, context } = wssContextMap.get(wss);
cleanupRunner(cleanupList);
connectionList.forEach(({ ws }) => ws.removeAllListeners());
wss.removeAllListeners();
setupWss(wssMap, wss, mock, context, pathname, filepath);
connectionList.forEach(
({ ws, req }) => emitConnection(wss, ws, req, connectionList)
);
};
compiler.on("update", ({ filepath }) => {
if (!hmrMap.has(filepath))
return;
const mockUrlList = hmrMap.get(filepath);
if (!mockUrlList)
return;
for (const mockUrl of mockUrlList.values()) {
for (const mock of compiler.mockData[mockUrl]) {
if (!mock.ws || mock.__filepath__ !== filepath)
return;
const wssMap = getWssMap(mockUrl);
for (const [pathname, wss] of wssMap.entries())
restartWss(wssMap, wss, mock, pathname, filepath);
}
}
});
httpServer?.on("upgrade", (req, socket, head) => {
const { pathname, query } = urlParse(req.url);
if (!pathname || proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url, req))) {
return;
}
const mockData = compiler.mockData;
const mockUrl = Object.keys(mockData).find((key) => {
return pathToRegexp3(key).test(pathname);
});
if (!mockUrl)
return;
const mock = mockData[mockUrl].find((mock2) => {
return mock2.url && mock2.ws && pathToRegexp3(mock2.url).test(pathname);
});
if (!mock)
return;
const filepath = mock.__filepath__;
addHmr(filepath, mockUrl);
const wssMap = getWssMap(mockUrl);
const wss = getWss(wssMap, pathname);
let wssContext = wssContextMap.get(wss);
if (!wssContext) {
const cleanupList = [];
const context = {
onCleanup: (cleanup) => cleanupList.push(cleanup)
};
wssContext = { cleanupList, context, connectionList: [] };
wssContextMap.set(wss, wssContext);
setupWss(wssMap, wss, mock, context, pathname, filepath);
}
const request = req;
const cookies = new Cookies2(req, req, cookiesOptions);
const { query: refererQuery } = urlParse(req.headers.referer || "");
request.query = query;
request.refererQuery = refererQuery;
request.params = parseParams(mockUrl, pathname);
request.getCookie = cookies.get.bind(cookies);
wss.handleUpgrade(request, socket, head, (ws) => {
logger.info(
`${colors2.magenta(colors2.bold("WebSocket"))} ${colors2.green(
req.url
)} connected ${colors2.dim(`(${filepath})`)}`,
mock.log
);
wssContext.connectionList.push({ req: request, ws });
emitConnection(wss, ws, request, wssContext.connectionList);
});
});
httpServer?.on("close", () => {
for (const wssMap of poolMap.values()) {
for (const wss of wssMap.values()) {
const wssContext = wssContextMap.get(wss);
cleanupRunner(wssContext.cleanupList);
wss.close();
}
wssMap.clear();
}
poolMap.clear();
hmrMap.clear();
});
}
function getWss(wssMap, pathname) {
let wss = wssMap.get(pathname);
if (!wss)
wssMap.set(pathname, wss = new WebSocketServer({ noServer: true }));
return wss;
}
function emitConnection(wss, ws, req, connectionList) {
wss.emit("connection", ws, req);
ws.on("close", () => {
const i = connectionList.findIndex((item) => item.ws === ws);
if (i !== -1)
connectionList.splice(i, 1);
});
}
function cleanupRunner(cleanupList) {
let cleanup;
while (cleanup = cleanupList.shift())
cleanup?.();
}
// src/core/logger.ts
import { isBoolean } from "@pengzhanbo/utils";
import colors3 from "picocolors";
var logLevels = {
silent: 0,
error: 1,
warn: 2,
info: 3,
debug: 4
};
function createLogger(prefix, defaultLevel = "info") {
prefix = `[${prefix}]`;
function output(type, msg, level) {
level = isBoolean(level) ? level ? defaultLevel : "error" : level;
const thresh = logLevels[level];
if (thresh >= logLevels[type]) {
const method = type === "info" || type === "debug" ? "log" : type;
const tag = type === "debug" ? colors3.magenta(colors3.bold(prefix)) : type === "info" ? colors3.cyan(colors3.bold(prefix)) : type === "warn" ? colors3.yellow(colors3.bold(prefix)) : colors3.red(colors3.bold(prefix));
const format = `${colors3.dim(
(/* @__PURE__ */ new Date()).toLocaleTimeString()
)} ${tag} ${msg}`;
console[method](format);
}
}
const logger = {
debug(msg, level = defaultLevel) {
output("debug", msg, level);
},
info(msg, level = defaultLevel) {
output("info", msg, level);
},
warn(msg, level = defaultLevel) {
output("warn", msg, level);
},
error(msg, level = defaultLevel) {
output("error", msg, level);
}
};
return logger;
}
export {
packageDir,
vfs,
lookupFile,
doesProxyContextMatchUrl,
urlParse,
normalizePath,
waitingFor,
transformRawData,
transformMockData,
sortByValidator,
rewriteRequest,
baseMiddleware,
mockWebSocket,
logLevels,
createLogger
};