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 changed,reload'); loadMock = await createMock(); }); }); api.addBeforeMiddlewares(() => (req, res, next) => { if (!mockFlag) { return next(); } loadMock(req, res, next); }); };