2026-04-28 20:33:41 +08:00

192 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import process from 'node:process';
import { pathToFileURL } from 'node:url';
import * as chokidar from 'chokidar';
import { isArray, isFunction, isPlainObject } from 'es-toolkit/compat';
export default (api) => {
let mockFlag = false; // mock 开关flag
let mockPrefix = '/'; // mock 过滤前缀
let mockFile = ''; // mock 文件
let loadMock; // mock 对象
api.describe({
key: 'mock',
config: {
schema(joi) {
return joi.alternatives(joi.boolean(), joi.object());
},
},
enableBy: () => process.env.NODE_ENV === 'development',
});
// 对 array、object 遍历处理
function traversalHandler(val, callback) {
if (isArray(val)) {
val.forEach(callback);
}
if (isPlainObject(val)) {
Object.keys(val).forEach((key) => {
callback(val[key], key);
});
}
}
// 根据参数个数获取配置
function getOption(arg) {
const len = arg.length;
// 默认配置
const option = {
headers: {
'Cache-Control': 'no-cache',
},
statusCode: 200,
cookies: [],
timeout: 0,
};
if (len === 0) {
return option;
}
if (len === 1) {
const newOption = arg[0];
if (isPlainObject(newOption)) {
traversalHandler(newOption, (value, key) => {
if (key === 'headers') {
traversalHandler(newOption.headers, (headervalue, headerkey) => {
option.headers[headerkey] = newOption.headers[headerkey];
});
}
else {
option[key] = newOption[key];
}
});
}
}
else {
option.url = arg[0];
option.result = arg[1];
}
return option;
}
// 把基于 cgiMockfile 的相对绝对转成绝对路径
function parsePath(value) {
return resolve(api.cwd, value);
}
const createMock = async () => {
const requestList = [];
const cgiMock = (...arg) => {
const option = getOption(arg);
if (!option.url || !option.result) {
return;
}
requestList.push(option);
};
const utils = {};
utils.file = function (file) {
return readFileSync(parsePath(file));
};
// mock打开情况下配置的过滤前缀
const mockPrefixTemp = api.config.mock.prefix || mockPrefix;
mockPrefix = mockPrefixTemp === mockPrefix ? mockPrefixTemp : `${mockPrefixTemp}/`;
// mock文件处理
mockFile = parsePath('./mock.js');
if (!existsSync(mockFile)) {
api.logger.info('mock.js File does not exist, please check');
return;
}
// require最新的 mock.js 文件
try {
// register babel
const _initFunction = await import(pathToFileURL(mockFile).href);
const initFunction = _initFunction.default || _initFunction;
if (!isFunction(initFunction)) {
api.logger.info('mock.js should export Function');
return;
}
const mockjs = await import('@wll8/better-mock');
initFunction({ cgiMock, mockjs: mockjs.default, utils });
}
catch (err) {
api.logger.error('mock.js run fail!', err);
}
const expressModule = await import('express');
const app = expressModule.default();
app.use((req, res, next) => {
// 如果请求不是以 cgiMock.prefix 开头,直接 next
if (!req.path.startsWith(mockPrefix)) {
return next();
}
// 请求以 cgiMock.prefix 开头,匹配处理
const matchRequet = requestList.find(item => req.path.search(item.url) !== -1);
if (!matchRequet) {
return next();
}
const sendData = () => {
// set header
res.set(matchRequet.headers);
// set Content-Type
matchRequet.type && res.type(matchRequet.type);
// set status code
res.status(matchRequet.statusCode);
// set cookie
traversalHandler(matchRequet.cookies, (item) => {
const name = item.name;
const value = item.value;
delete item.name;
delete item.value;
res.cookie(name, value, item);
});
// do result
if (isFunction(matchRequet.result)) {
matchRequet.result(req, res);
}
else if (isArray(matchRequet.result) || isPlainObject(matchRequet.result)) {
!matchRequet.type && res.type('json');
res.json(matchRequet.result);
}
else {
!matchRequet.type && res.type('text');
res.send(matchRequet.result.toString());
}
};
sendData();
});
return app;
};
api.onStart(async () => {
// 获取mock配置: 是否打开
mockFlag = isPlainObject(api.config.mock) ? true : api.config.mock;
if (!mockFlag) {
return;
}
loadMock = await createMock();
return chokidar
.watch(mockFile, {
ignoreInitial: true,
})
.on('change', async () => {
api.logger.info('mock.js changedreload');
loadMock = await createMock();
});
});
api.addBeforeMiddlewares(() => (req, res, next) => {
if (!mockFlag) {
return next();
}
loadMock(req, res, next);
});
};