UNPKG

mock-service-cli

Version:

🦅 Local Mock/Static/SPA server, Http?s request proxy

253 lines (242 loc) 8.63 kB
/* * @description: 生成Mock文件、获取mock数据统计 * @Date: 2021-12-22 16:57:08 * @LastEditors: chendq * @LastEditTime: 2025-06-15 22:25:45 * @Author: chendq */ const fsa = require('fs-extra'), fs = require('fs'), path = require('path'), isEqual = require('lodash/isEqual'), colors = require('colors/safe'); const { getFileLatestContent, SupportMethods, filePath2ApiUrl, logger, getDataType, getLogger, writeStream } = require('./utils'); const log = logger(process.env.SILENT); const processArgv = process.env.ARGV ? JSON.parse(process.env.ARGV) : {}; const methodRegExp = new RegExp(`(${SupportMethods.join('|')}) +(.*)`, 'i'); /** * @description: 记录日志文件函数 * @param {boolean} isWriteLogFile 是否写入日志文件 * @param {string} mockDir 存放日志文件的目录 * @return {object} */ const logFile = (isWriteLogFile = false, mockDir) => { if (isWriteLogFile) { return getLogger(mockDir); } return { log: function () {}, error: function () {} }; }; /** * @description: 生成mock文件(自动生成mock-list.json文件,并维护<url, [method]>的关系映射) * @param {string} apiUrl 请求url * @param {string} method 请求方法 * @param {object} resJsonData 响应数据 * @param {string} mockDir mock目录 * @return {*} void * */ const genMockFiles = async function ({ url: apiUrl, method, data: resJsonData, dir: mockDir }) { const type = getDataType(resJsonData); if (!apiUrl || !method || !resJsonData || !mockDir) { log.info( colors.red([`参数: apiUrl: ${apiUrl}, method: ${method}, mockDir: ${mockDir} `, `必须是有效值`].join(',')) ); return; } else if ( !['object', 'array'].includes(type) || (type === 'object' && Object.keys(resJsonData).length === 0) || (type === 'array' && resJsonData.length === 0) ) { log.info( `参数: apiUrl: ${apiUrl}, method: ${method}, resJsonData:`, resJsonData, colors.red(', 参数字段resJsonData必须是非空的数组或对象!') ); return; } apiUrl = apiUrl.trim().split('?')[0]; const LOGGER = logFile(processArgv.t || processArgv.track, mockDir); const normalizeApiUrl = filePath2ApiUrl(path.normalize(apiUrl)); const mockListFilePath = path.resolve(process.cwd(), `${mockDir}/mock-list.json`); const mockFilePath = path.resolve(process.cwd(), `${mockDir}/${encodeURIComponent(path.normalize(apiUrl))}.json`); let newMockListContent = {}; if (fsa.pathExistsSync(mockListFilePath)) { newMockListContent = getFileLatestContent(mockListFilePath); } else { fsa.ensureFileSync(mockListFilePath); } let isChange = false; // update mock-list.json file if (!newMockListContent.hasOwnProperty(normalizeApiUrl)) { newMockListContent[normalizeApiUrl] = [method]; isChange = true; } else if (!newMockListContent[normalizeApiUrl].includes(method)) { newMockListContent[normalizeApiUrl].push(method); isChange = true; } if (isChange) { Object.keys(newMockListContent).length > 0 && fs.writeFileSync(mockListFilePath, JSON.stringify(newMockListContent, null, 2)); } // mock文件存在 if (fsa.pathExistsSync(mockFilePath)) { const mockFileContent = getFileLatestContent(mockFilePath); // 内容一样不作修改 if (mockFileContent.hasOwnProperty(method) && isEqual(mockFileContent[method], resJsonData)) { // log.assert( // !_.isEqual(mockFileContent[method], resJsonData), // colors.bgYellow(`${method} ${apiUrl}, Mock data is same`) // ); return; } mockFileContent[method] = resJsonData; try { await writeStream(mockFilePath, JSON.stringify(mockFileContent, null, 2)); } catch (error) { LOGGER.error(`Update file: ${method} ${apiUrl} ${mockFilePath}`); } LOGGER.log(`Update file: ${method} ${apiUrl} ${mockFilePath}`); } else { try { await writeStream(mockFilePath, JSON.stringify({ [method]: resJsonData }, null, 2)); } catch (error) { LOGGER.error(`New file: ${method} ${apiUrl} ${mockFilePath}`); } LOGGER.log(`New file: ${method} ${apiUrl} ${mockFilePath}`); } // console.debug('file::', `${mockDir}/${apiUrl}`, newMockListContent); }; /** * @description: 从js文件中收集mock数据 * @param {object} mockDataMap * @param {string} filePath * @return {*} void */ const collectMockDataFromJsFile = function (mockDataMap, filePath) { // 先删除require之前导入的缓存文件,否则获取到的文件内容还是旧内容 const fileObject = getFileLatestContent(filePath); Object.keys(fileObject).forEach(item => { let reqMethod = 'get', reqUrl = item; if (methodRegExp.exec(item)) { const [, method, url] = methodRegExp.exec(item); reqMethod = method.toLowerCase(); reqUrl = path.normalize(url); } if (typeof fileObject[item] === 'function') { mockDataMap[`${reqMethod} ${reqUrl}`] = String(fileObject[item]); } else if (typeof fileObject[item] === 'object') { mockDataMap[`${reqMethod} ${reqUrl}`] = fileObject[item]; } }); }; /** * @description: 递归收集mock数据 * @param {object} mockDataMap mock数据集 * @param {string} specialDir 目录 * @return {*} void */ const deepCollectMockData = function (mockDataMap, specialDir = '../mock') { const files = fs.readdirSync(path.resolve(process.cwd(), specialDir), { withFileTypes: true }); const curFilesSize = files.length; for (let index = 0; index < curFilesSize; index++) { const el = files[index]; const filePath = path.normalize(`${specialDir}/${el.name}`); // 递归目录 if (!el.isFile()) { deepCollectMockData(mockDataMap, filePath); } // 处理存放mock数据的json文件 if (el.name.endsWith('.json') && el.name !== 'mock-list.json') { const fileObject = getFileLatestContent(filePath); Object.keys(fileObject).forEach(method => { mockDataMap[`${method.toLocaleLowerCase()} ${decodeURIComponent(el.name).split('.json')[0]}`] = fileObject[method]; }); continue; } // 非js文件跳过 if (!el.name.endsWith('.js')) { continue; } collectMockDataFromJsFile(mockDataMap, filePath); } }; /** * @description: 从文件目录中获取mock数据统计(若mock-list.json文件不存在则自动生成) * @param {string} dirPath * @return {object} mock数据统计 */ const getMockStatFromDir = dirPath => { if (!fsa.pathExistsSync(dirPath)) { getLogger(path.resolve(process.cwd())).error(`入参无效,${dirPath} 目录不存在`); return; } const mockDataMap = {}; deepCollectMockData(mockDataMap, dirPath); const mockListFilePath = path.resolve(process.cwd(), `${dirPath}/mock-list.json`); // 若不存在mock-list.json文件,则根据现有mock文件重新生成<api, [method]>映射关系 if (Object.keys(mockDataMap).length > 0 && !fsa.pathExistsSync(mockListFilePath)) { fsa.ensureFileSync(mockListFilePath); const reverseMockList = {}; Object.keys(mockDataMap).forEach(it => { if (methodRegExp.exec(it)) { const [, method, url] = methodRegExp.exec(it); if (url.trim() && method.trim()) { if (reverseMockList.hasOwnProperty(url)) { reverseMockList[url].push(method); } else { reverseMockList[url] = [method]; } } } }); writeStream(mockListFilePath, JSON.stringify(reverseMockList, null, 2)); } return mockDataMap; }; /** * @description: 从js文件中获取mock数据统计 * @param {string} filePath * @return {object} mock数据统计 */ const getMockStatFromFile = filePath => { if (!fsa.pathExistsSync(filePath)) { getLogger(path.resolve(process.cwd())).error(`入参无效,${filePath} 文件不存在`); return; } const mockDataMap = {}; collectMockDataFromJsFile(mockDataMap, filePath); return mockDataMap; }; /** * @description: 判断Mock服务中是否存在某个API * @param {object} mockDataMap * @param {string} apiUrl * @param {string} method * @return {boolean} */ const hasMockApi = function (mockDataMap, apiUrl, method) { if (!apiUrl || !method || !mockDataMap || !(mockDataMap instanceof Object)) { getLogger(path.resolve(process.cwd())).error(`入参无效,${mockDataMap} 必须是 mock数据的Object对象`); return; } return mockDataMap.hasOwnProperty(`${method.toLocaleLowerCase()} ${path.normalize(apiUrl)}`); }; module.exports = { genMockFiles, getMockStatFromDir, getMockStatFromFile, hasMockApi };