UNPKG

mockm

Version:

Analog interface server, painless parallel development of front and back ends.

1,754 lines (1,487 loc) 91 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); require("core-js/modules/es.symbol.description.js"); var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign")); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys")); var _url = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/url")); var _reduce = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reduce")); var _flat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flat")); var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each")); var _sort = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/sort")); var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find")); var _splice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/splice")); var _trim = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/trim")); var _some = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/some")); var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); var _entries = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/entries")); var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify")); var _startsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/starts-with")); var _values = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/values")); var _setInterval2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-interval")); var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter")); var _reverse = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reverse")); var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); var _now = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/date/now")); var _findIndex = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find-index")); const fs = require(`fs`); const lib = require(`./lib.js`); const { print } = require(`./log.js`); const tool = require(`./tool.js`); const http = require(`./http.js`); const fileList = require(`../lib/file-list.js`); const jsonServer = require(`@wll8/json-server`); const { lib: { express } } = jsonServer; function business() { // 与业务相关性的函数 function Side(arg) { return new Side.prototype.init(arg); } Side.prototype = { init: function (arg) { const res = (0, _assign.default)(this, arg); return res; } }; Side.prototype.init.prototype = Side.prototype; /** * 运行每个插件的监听函数 * @param {*} key 插件的函数 * @param {...any} arg 函数的参数 */ async function pluginRun(key, ...arg) { const plugin = global.config.plugin; for (let index = 0; index < plugin.length; index++) { const [item] = plugin[index]; item._mainReturn[key] && (await item._mainReturn[key](...arg)); } } /** * 根据一个 app 来创建以及 https 配置来生成 httpServer 并添加端口监听功能 * @param {*} param0 * @returns {httpServer, onlyHttpServer} 当额外配置 https 端口时才会存在 onlyHttpServer */ function getHttpServer({ app, name }) { let httpServer; let onlyHttpServer; const https = global.config.https; const httpProt = global.config[name]; let httpsProt = https[name]; httpsProt = Number(httpsProt) === Number(httpProt) ? undefined : httpsProt; /** * 如果没有配置 https 的端口时, 使用 httpolyglot 实现同一个端口支持 http 和 https */ if (https.key === undefined) { httpServer = require(`http`).createServer(app); } /** * 如果没有配置 https 的端口时, 使用 httpolyglot 实现同一个端口支持 http 和 https */ if (https.key && httpsProt === undefined) { app.use((req, res, next) => { const protocol = tool.httpClient.getProtocol(req); if (protocol === `http` && https.redirect) { const hostName = require(`url`).parse(`ws://${req.headers.host}`).hostname; res.redirect(301, `https://${hostName}:${httpProt}${req.url}`); } else { next(); } }); httpServer = require(`@httptoolkit/httpolyglot`).createServer({ key: fs.readFileSync(https.key), cert: fs.readFileSync(https.cert) }, app); } /** * 如果配置了 https 的端口时, 那么不使用 httpolyglot 而使用原生 http/https, 实现 http => 301 => https */ if (https.key && httpsProt) { onlyHttpServer = require(`http`).createServer(https.redirect ? (req, res) => { const hostName = require(`url`).parse(`ws://${req.headers.host}`).hostname; res.writeHead(301, { Location: `https://${hostName}:${httpsProt}${req.url}` }); res.end(); } : undefined, app).listen(httpProt, `0.0.0.0`, async () => { await pluginRun(`serverCreated`, { onlyHttpServer, httpProt }); }); httpServer = require(`https`).createServer({ key: fs.readFileSync(https.key), cert: fs.readFileSync(https.cert) }, app); } httpServer.listen(httpsProt || httpProt, `0.0.0.0`, async () => { await pluginRun(`serverCreated`, { httpServer, httpsProt, httpProt }); }); const server = { httpServer, onlyHttpServer }; app._server = server; return server; } function midResJson({ res, proxyRes, key, val, cb = body => body }) { const modifyResponse = require(`node-http-proxy-json`); modifyResponse(res, proxyRes, body => { var _context; if ((0, _includes.default)(_context = [`array`, `object`]).call(_context, tool.type.isType(body))) { key && tool.obj.deepSet(body, key, val); } return cb(body); }); } function url() { // url 处理程序 function prepareProxy(proxy = {}) { var _context2; // 解析 proxy 参数, proxy: string, object const pathToRegexp = require(`path-to-regexp`); const isType = tool.type.isType; const proxyType = isType(proxy); let resProxy = []; if (proxyType === `string`) { // 任何路径都转发到 proxy proxy = { '/': proxy }; } function setIndexOfEnd(proxy) { // 需要排序 key:/ 到最后, 否则它生成的拦截器会被其他 key 覆盖 const indexVal = proxy[`/`]; delete proxy[`/`]; proxy[`/`] = indexVal; return proxy; } proxy = setIndexOfEnd(proxy); resProxy = (0, _map.default)(_context2 = (0, _keys.default)(proxy)).call(_context2, context => { let options = proxy[context]; const optionsType = isType(options); if (optionsType === `string`) { // 转换字符串的 value 为对象 const rootOptions = proxy[`/`]; options = { pathRewrite: { [`^${context}`]: options }, // 原样代理 /a 到 /a target: (0, _includes.default)(options).call(options, `://`) // 如果要代理的目录地址已有主域 ? new _url.default(options).origin // 那么就代理到该主域上 : { // 否则就代理到 / 设定的域上 string: rootOptions, object: rootOptions.target // 当主域是对象时则取其 target }[isType(rootOptions)] }; } if (optionsType === `array`) { // 是数组时, 视为设计 res body 的值, 语法为: [k, v] const [item1, item2] = options; const item1Type = isType(item1); const item2Type = isType(item2); const deepMergeObject = tool.obj.deepMergeObject; if (item1Type !== `function` && options.length <= 1) { // 只有0个或一个项, 直接替换 res options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: () => item1 }); } }; } if (item1Type === `function` && options.length <= 1) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => item1({ req, json }) }); } }; } if (item1Type === `function` && item2Type === `function`) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => { return item2({ req, json: item1({ req, json }) }); } }); } }; } if (item1Type === `string` && item2Type === `function`) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => { return item2({ req, json: tool.obj.deepGet(json, item1) }); } }); } }; } if (item1Type === `function` && item2Type === `string`) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => { return tool.obj.deepGet(item1({ req, json }), item2); } }); } }; } if (item1Type === `string` && options.length === 2) { // 以 item1 作为 key, item2 作为 val, 修改原 res options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, key: item1, val: item2 }); } }; } if (item1Type === `object` && options.length === 2) { // 根据 item2 的类型, 合并 item1 options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: body => { return { 'deep': deepMergeObject(body, item1), // 父级【不会】被替换 '...': { ...body, ...item1 } // 父级【会】被替换, 类似于js扩展运行符 }[item2 || `deep`]; } }); } }; } } const re = pathToRegexp(context); return { re, context, options }; }); return resProxy; } function parseProxyTarget(proxy) { // 解析 proxy 为 {pathname, origin} let origin = ``; try { if (typeof proxy === `string`) { origin = proxy; } if (typeof proxy === `object`) { origin = proxy[`/`].target || proxy[`/`]; } const parentUrl = new _url.default(origin); const res = { host: parentUrl.host, port: parentUrl.port || 80, hostname: parentUrl.hostname, pathname: parentUrl.pathname.replace(/\/$/, ``) + `/`, origin: parentUrl.origin, isIp: (parentUrl.host.match(/\./g) || []).length === 3 }; return res; } catch (error) { console.error(`请正确填写 proxy 参数`, error); process.exit(); } } function parseRegPath(rePath, url) { // 使用 path-to-regexp 转换 express 的 router, 并解析参数为对象 // 注: path-to-regexp 1.x 自带 match 方法可处理此方法, 但是当前的 json-server 依赖的 express 的路由语法仅支持 path-to-regexp@0.1.7 // 所以只能手动转换, 参考: https://github.com/ForbesLindesay/express-route-tester/blob/f39c57fa660490e74b387ed67bf8f2b50ee3c27f/index.js#L96 const pathToRegexp = require(`path-to-regexp`); const keys = []; const re = pathToRegexp(rePath, keys); // 去除 url 中的 origin const pathUrl = url.match(/^\//) ? url : url.replace(new _url.default(url).origin, ``); const result = re.exec(pathUrl); const obj = (0, _reduce.default)(keys).call(keys, (acc, cur, index) => { acc[cur.name] = result[index + 1]; return acc; }, {}); return obj; } return { prepareProxy, parseProxyTarget, parseRegPath }; } function middleware() { // express 中间件 const compression = require(`compression`); // 压缩 http 响应 function httpLog() { // 设置 http 请求日志中间件 const morgan = require(`morgan`); const colors = tool.cli.colors; return morgan((tokens, req, res) => { const testApi = res.getHeader(global.config.apiInHeader); // 当保存了历史记录时, 才在控制台的 http log 显示它 if (Boolean(testApi) === false) { return undefined; } const colorTable = { 1: `gray`, 2: `green`, 3: `yellowBright`, 4: `yellow`, 5: `red` }; const statusCode = String(res.statusCode); const len = res.getHeader(`Content-Length`); const str = [tool.time.dateFormat(`YYYY-MM-DD hh:mm:ss`, new Date()), tool.httpClient.getClientIp(req), testApi, `${statusCode} ${res.statusMessage}`, `${tokens[`response-time`](req, res)} ms`, len ? `${len} byte` : ``].join(` `); // 使用原生 nodejs 打印日志 print(colors[colorTable[statusCode[0]]](str)); // 根据状态码的第一位获取颜色函数 return []; // return list.join(' ') }); } function getJsonServerMiddlewares() { var _context3; // 获取 jsonServer 中的中间件 // 利用 jsonServer 已有的中间件, 而不用额外的安装 // 注意: 可能根据 jsonServer 版本的不同, 存在的中间件不同 const jsonServer = require(`@wll8/json-server`); const middlewares = jsonServer.defaults({ bodyParser: global.config._bodyParserMid, logger: false }); // 可以直接使用的所有中间件数组 middlewares.push(httpLog()); const middlewaresObj = (0, _reduce.default)(_context3 = (0, _flat.default)(middlewares).call(middlewares)).call(_context3, (res, item) => { // 使用 jsonServer 里面的中间件, 以保持一致: // compression, corsMiddleware, serveStatic, logger, jsonParser, urlencodedParser return { ...res, [item.name]: item }; }, {}); return { middlewares, middlewaresObj }; } function reWriteRouter({ app, routes = {} }) { var _context4; // 根据 routes 对象, 重写路由 const rewrite = require(`express-urlrewrite`); (0, _forEach.default)(_context4 = (0, _keys.default)(routes)).call(_context4, key => { app.use(rewrite(key, routes[key])); }); } function replayHistoryMiddleware({ id, business } = {}) { const { clientInjection, historyHandle } = business; const { allowCors, setHeader, reSetApiInHeader } = clientInjection(); const { getHistory } = historyHandle(); return (req, res, next) => { // 修改分页参数, 符合项目中的参数 const method = req.method.toLowerCase(); const fullApi = id ? undefined : `${method} ${req.originalUrl}`; const history = getHistory({ id, fullApi, find: list => { const getSize = path => { const bodyPathCwd = require(`path`).join(process.cwd(), path); return require(`fs`).readFileSync(bodyPathCwd).length; }; const getStatus = item => { try { return item.data.res.lineHeaders.line.statusCode; } catch (err) { console.log(`err`, err); } }; (0, _sort.default)(list).call(list, (item, item1) => { try { const size = getSize(item.data.res.bodyPath); const size1 = getSize(item1.data.res.bodyPath); return size1 - size; } catch (error) { console.log(`error`, error); return 0; } }); const getStatusCodeItem = list => (0, _find.default)(list).call(list, item => getStatus(item) === 200); // 查找 http 状态码为 200 的条目 let getItemRes = undefined; if (global.config.replayProxyFind) { // 先使用配置的 replayProxyFind 函数, 如果没有打到则使用普通状态码 try { getItemRes = (0, _find.default)(list).call(list, (...arg) => global.config.replayProxyFind(...arg)); } catch (error) { console.log(error); } } getItemRes = getItemRes || getStatusCodeItem(list) || list[0] || {}; // 如果也没有找到状态码为 200 的, 则直接取第一条 return getItemRes; } }).data; try { const lineHeaders = history.res.lineHeaders; const headers = lineHeaders.headers || require(require(`path`).resolve(lineHeaders.headPath)); setHeader(res, { ...headers, // 还原 headers ...reSetApiInHeader({ headers }) // 更新 testApi }); allowCors({ res, req }); const bodyPath = history.res.bodyPath; if (bodyPath) { const path = require(`path`); const newPath = path.resolve(bodyPath); // 发送 body // 由于 newPath 是由程序生成的, 而不是用户输入的, 所以不用担心路径遍历 res.sendFile(newPath); } else { const { statusCode, statusMessage } = lineHeaders.line; res.statusCode = statusCode; res.statusMessage = statusMessage; res.send(); } } catch (err) { console.log(`err`, err); res.json(global.config.resHandleReplay({ req, res })); } }; } return { reWriteRouter, compression, getJsonServerMiddlewares, replayHistoryMiddleware }; } /** * 保存日志 */ function saveLog({ logStr = ``, logPath, code = `-` }) { var _context5; const fs = require(`fs`); const os = require(`os`); const packageJson = require(`../package.json`); fs.existsSync(logPath) && fs.writeFileSync(logPath, [[tool.time.dateFormat(`YYYY-MM-DD hh:mm:ss`, new Date()), // 当前时间 `mockm:${packageJson.version}`, // mockm 版本号 `node:${process.version}`, // node 版本号 `os:${os.type()} ${os.release()}`, // 操作系统和版本号 `code:${code}`, // 退出码 `arg:${(0, _splice.default)(_context5 = process.argv).call(_context5, 2)}`, // 命令行参数 `lang:${process.env.LANG}` // 终端语言环境 ].join(`, `), // 附件信息 `\n`, (0, _trim.default)(logStr).call(logStr), // 调用栈 `\n\n`, fs.readFileSync(logPath, `utf8`) // 旧 log ].join(``)); } /** * 通过重新保存文件的方式触发 nodemon 的文件监听, 然后让服务重启 */ function reStartServer(filePath) { const fs = require(`fs`); const str = fs.readFileSync(filePath, `utf8`); fs.writeFileSync(filePath, str); } function wrapApiData({ data, code = 200 }) { // 包裹 api 的返回值 code = String(code); return { code, success: Boolean(code.match(/^[2]/)), // 如果状态码以2开头则为 true data }; } /** * 把类似 schema 的列表转换为数据 * @param {array} list 列表 * @param {object} options 规则 */ function listToData(list, options = {}) { var _context6; const Mock = require(`@wll8/better-mock`); const mockMethods = (0, _map.default)(_context6 = (0, _keys.default)(Mock.Random)).call(_context6, key => `@${key}`); function listToDataRef(list) { // 注: 通过此函数转换出的结果并不是可以生成随机值的模板, 而是已生成固定值模板, 因为需要使用值转换为对应的类型 let res = {}; (0, _forEach.default)(list).call(list, item => { var _context8; let example = item.example ? String(item.example) : ``; if (item.type === `eval`) { // 使用代码执行结果 try { const { NodeVM } = require(`vm2`); const vm = new NodeVM({ sandbox: { // 给 vm 使用的变量 Mock } }); example = vm.run(`module.exports = ${example}`, `vm.js`) || ``; } catch (err) { console.log(`err`, err); } // 处理含有 @mock 方法或为正则的 example } else if ((0, _some.default)(mockMethods).call(mockMethods, item => (0, _includes.default)(example).call(example, item)) === false) { // 如果不包含 @mock 方法则进行类型转换 // todo 不应该直接使用 includes 判断, 例如可以是 `@inc` 或 `@inc c` 但不能是 `@incc` // 根据 type 转换 example 值的类型 if (strReMatch(example)) { // 猜测为正则 example = strReMatch(example); } else if (item.type === `number`) { // 数字 example = Number(example); } else if (item.type === `boolean`) { var _context7; // 布尔 example = (0, _includes.default)(_context7 = [`false`, `0`, `假`, `T`, `t`]).call(_context7, example) ? false : Boolean(example); } } // 如果是对象或数里进行递归调用 if ((0, _includes.default)(_context8 = [`object`, `array`]).call(_context8, item.type) && (0, _isArray.default)(item.children)) { switch (item.type) { case `object`: res[item.name] = listToDataRef(item.children); break; case `array`: res[item.name] = res[item.name] || []; res[item.name].push(listToDataRef(item.children)); break; default: console.log(`no type`, item.type); } } else { // 如果不是引用类型, 则应用最后转换后的值 example res[`${item.name}#${item.type || `string`}`] = example; } }); return res; } let res = listToDataRef(list); res = { [`data${options.rule ? `|${options.rule}` : ``}`]: { object: res, array: [res] }[options.type] }; const data = Mock.mock(res); return data; } /** * 如果字符串是正则就返回正则, 否则返回 false * @param {string} str 前后有 / 号的字符串 */ function strReMatch(str) { let reStr = (str.match(/^\/(.*)\/$/) || [])[1]; let re = undefined; if (reStr) { try { re = new RegExp(reStr); } catch (error) { // 正则转换失败 return false; } } else { return false; } return re; } async function customApi() { // 所有 api, 例如 db 解析后的中间件信息 const serverRouterItem = { alias: [], // String[], 路由别名 route: undefined, // String, 路由地址 re: undefined, // RegExp, 由 route 转换的正则 method: undefined, // String, 请求方式, 例如 use get post type: undefined, // Enum, 指定接口来源, 例如 db api description: undefined, // String, 接口描述 disable: undefined, // Boolean, 是否禁用 action: undefined, // Function, 指定接口要运行的方法 occupied: { // Object, 被谁占用, 如果是被占用的状态, 则不会被使用 type: undefined, // Enum route: undefined // String }, info: {} // Object, 根据 type 可能不同结构的附加信息 }; const pathToRegexp = require(`path-to-regexp`); /** * 自定义 api 处理程序, 包括配置中的用户自定义路由(config.api), 以及mock数据生成的路由(config.db) */ async function parseApi() { var _context10, _context12; // 解析自定义 api const { setHeader, allowCors } = clientInjection(); const run = { async curl({ req, res, cmd }) { // cmd: curl/bash const options = await tool.cli.getOptions(cmd); return new _promise.default(async (resolve, reject) => { const request = await tool.generate.initPackge(`request`); request(options, (err, curlRes = {}, body) => { setHeader(res, curlRes.headers); // 复制远程的 header allowCors({ req, res }); // 设置 header 为允许跨域模式 const mergeRes = curlRes; err ? reject(err) : resolve(mergeRes); }); }); }, fetch({ req, res, fetchRes }) { // node-fetch return new _promise.default((resolve, reject) => { fetchRes.then(fetchThenRes => { var _context9; const headers = (0, _reduce.default)(_context9 = [...fetchThenRes.headers]).call(_context9, (acc, cur) => ({ ...acc, [cur[0]]: cur[1] }), {}); const contentEncoding = headers[`content-encoding`]; if (contentEncoding && (0, _includes.default)(contentEncoding).call(contentEncoding, `gzip`)) { // 由于返回的内容其实已经被解码过了, 所以不能再告诉客户端 content-encoding 是压缩的 `gzip`, 否则会导致客户端无法解压缩 // - 例如导致浏览器无法解码: net::ERR_CONTENT_DECODING_FAILED 200 (OK) delete headers[`content-encoding`]; } setHeader(res, headers); allowCors({ req, res }); const mergeRes = fetchThenRes; resolve(mergeRes); }).catch(err => { console.log(`err`, err); reject(err); }); }); } }; const apiUtil = { // 向 config.api 暴露一些工具库 run }; let api = global.config.api(apiUtil); await pluginRun(`apiParsed`, api, apiUtil); const side = {}; // 从 side 函数中扩展 api api = (0, _reduce.default)(_context10 = (0, _entries.default)(api)).call(_context10, (acc, [key, val]) => { if (val instanceof Side) { var _context11; const sideObj = { alias: [] // 路由别名 }; val = { ...sideObj, ...val }; (0, _forEach.default)(_context11 = val.alias).call(_context11, alias => { acc[alias] = val.action; }); side[key] = val; val = val.action; } acc[key] = val; return acc; }, {}); const serverRouterList = []; // server 可使用的路由列表 (0, _forEach.default)(_context12 = (0, _keys.default)(api)).call(_context12, key => { var _context14; let { method, url } = tool.url.fullApi2Obj(key); method = method.toLowerCase(); let val = api[key]; if (method === `use`) { var _context13; // 自定义中间件时不使用自动返回 json 的规则 if ((0, _includes.default)(_context13 = [`function`, `array`, `asyncfunction`]).call(_context13, tool.type.isType(val)) === false) { // use 支持单个和多个(数组)中间件 print(tool.cli.colors.red(`Data other than function|array type is not allowed in the use mode in config.api: ${val}`)); val = (req, res, next) => next(); } } else if ((0, _includes.default)(_context14 = [`string`, `number`, `object`, `array`, `boolean`, `null`]).call(_context14, tool.type.isType(val))) { // 如果配置的值是 json 支持的数据类型, 则直接作为返回值, 注意, 读取一个文件也是对象 const backVal = val; if (method === `ws`) { val = (ws, req) => { const strData = (0, _stringify.default)(backVal); ws.send(strData); ws.on(`message`, msg => ws.send(strData)); }; } else { val = (req, res, next) => res.json(backVal); } } serverRouterList.push({ ...serverRouterItem, ...side[key], route: url, re: pathToRegexp(url), method, type: `api`, action: val, occupied: {} }); }); await pluginRun(`apiListParsed`, serverRouterList); return serverRouterList; } /** * 测试给定的 method 和 pathname 是否匹配当前存在的 route * @param {*} param0 * @returns */ function allRouteTest({ allRoute, upgrade, method, pathname }) { // return true 时不走真实服务器, 而是走自定义 api return (0, _find.default)(allRoute).call(allRoute, item => { if (item.method === `ws` && method === `get`) { // ws 连接时, 实际上得到的 method 是 get, 并且 pathname + .websocket return item.re.exec(pathname) && upgrade.match(/websocket/i) && Boolean(item.disable) === false; } if (item.method === `use`) { // 当为中间件模式时, 匹配其后的任意路径 if (item.type === `db`) { var _context15; // 如果是 db 模式下, 需要匹配 db 下的存在的接口 return (0, _find.default)(_context15 = item.info.apiList).call(_context15, dbItem => dbItem.re.exec(pathname) && dbItem.method === method && Boolean(dbItem.disable) === false); } else { return (0, _startsWith.default)(pathname).call(pathname, item.route) && Boolean(item.disable) === false; } } // 当方法相同时才去匹配 url if (item.method === `all` || item.method === method) { return item.re.exec(pathname) // 如果匹配到自定义的 api 则走自定义 api && Boolean(item.disable) === false // 如果自定义 api 为禁用状态, 则走真实服务器 ; } else { return false; } }); } /** * 展开 db 中的 api */ function unzipDbApi() { const db = JSON.parse(require(`fs`).readFileSync(global.config.dbJsonPath)); const keys = (0, _keys.default)(db); const isType = tool.type.isType; let apiList = []; (0, _forEach.default)(keys).call(keys, key => { const val = db[key]; if (isType(val, `object`)) { var _context16; (0, _forEach.default)(_context16 = `get post put patch`.split(` `)).call(_context16, method => { apiList.push({ method, route: `/${key}` }); }); } if (isType(val, `array`)) { var _context17, _context18; (0, _forEach.default)(_context17 = `get post`.split(` `)).call(_context17, method => { apiList.push({ method, route: `/${key}` }); }); (0, _forEach.default)(_context18 = `get put patch delete`.split(` `)).call(_context18, method => { apiList.push({ method, route: `/${key}/:id` }); }); } }); apiList = (0, _map.default)(apiList).call(apiList, item => { return { route: item.route, re: pathToRegexp(item.route), method: item.method, type: `db`, action: undefined, occupied: {} }; }); return { keys, apiList }; } function parseDbApi() { const db = tool.file.fileStore(global.config.dbJsonPath).get(); if ((0, _keys.default)(db).length === 0) { return []; } const router = jsonServer.router(global.config.dbJsonPath, { _preciseNeste: true, _noRemoveDependents: true, _noDataNext: true, _noDbRoute: true }); global.config._set(`_db`, router.db); router.render = async (req, res) => { // 修改输出的数据, 符合项目格式 // 在 render 方法中, req.query 会被删除 // https://github.com/typicode/json-server/issues/311 // https://github.com/typicode/json-server/issues/314 const querystring = require(`querystring`); if (req._parsedUrl) { const query = querystring.parse(req._parsedUrl.query); req.query = query; } let returnData = res.locals.data; // 前面的数据返回的 data 结构 const xTotalCount = res.get(`X-Total-Count`); if (xTotalCount) { returnData = { count: xTotalCount, results: res.locals.data }; } const data = await res.mm.resHandleJsonApi({ req, res, data: returnData, resHandleJsonApi: global.config.resHandleJsonApi }); res.json(data); }; const { keys, apiList } = unzipDbApi(); const list = [{ route: `/`, re: pathToRegexp(`/`), method: `use`, type: `db`, action: router, info: { keys, apiList }, occupied: {} }]; return list; } function apiWebHandle() { var _context19, _context20; // 处理 webApi 为 api const apiWebStore = tool.file.fileStore(global.config.apiWeb); const paths = apiWebStore.get(`paths`); const disableApiList = apiWebStore.get(`disable`); const pathList = (0, _flat.default)(_context19 = (0, _map.default)(_context20 = (0, _keys.default)(paths)).call(_context20, path => { var _context21; return (0, _map.default)(_context21 = (0, _keys.default)(paths[path])).call(_context21, method => ({ key: `${method} ${path}`, path, method, ...paths[path][method] })); })).call(_context19); const apiList = (0, _reduce.default)(pathList).call(pathList, (acc, cur, curIndex) => { let fn = (req, res, next) => { const { example = {}, table = [] } = cur.responses[`200`]; const { headers = {}, useDataType = `table`, custom, history, rule, type } = example; if (useDataType === `table`) { // 使用表格中的数据 try { const { setHeader } = clientInjection(); setHeader(res, headers); // 设置自定义 header let data; const listToDataRes = listToData(table, { rule, type }); data = listToDataRes.data; // 根据 apiWebWrap 处理数据 if (global.config.apiWebWrap === true) { data = wrapApiData({ data, code: 200 }); } else if (typeof global.config.apiWebWrap === `function`) { data = global.config.apiWebWrap({ data, code: 200 }); } res.json(data); } catch (err) { print(err); res.status(500).json({ msg: String(err) }); } } if (useDataType === `custom`) { // 自定义逻辑 try { var _context22; const { NodeVM } = require(`vm2`); const vm = new NodeVM({ sandbox: { // 给 vm 使用的变量 tool: { libObj: lib, wrapApiData, listToData, cur } } }); const code = vm.run(`module.exports = ${custom}`, `vm.js`) || ``; const codeType = typeof code; if ((0, _includes.default)(_context22 = [`function`]).call(_context22, codeType)) { // 如果是函数则运行函数 code(req, res, next); } else { // 函数之外的类型则当作 json 返回 res.json(code); } } catch (err) { console.log(`err`, err); // 处理客户端代码出现问题, 代码错误或出现不允许的权限 res.status(403).json({ msg: err.message }); } } if (useDataType === `history`) { // 使用历史记录 middleware().replayHistoryMiddleware({ id: history, business: business() })(req, res, next); } }; if (cur.method === `ws`) { // 如果是 websocket 方法则更换函数模版 fn = (ws, req) => { const sendData = data => { const strData = (0, _stringify.default)(data); ws.send(strData); ws.on(`message`, msg => { ws.send(strData); }); }; const { example = {}, table = [] } = cur.responses[`200`]; const { headers = {}, useDataType = `table`, custom, history, rule, type } = example; if (useDataType === `table`) { // 使用表格中的数据 try { let data; const listToDataRes = listToData(table, { rule, type }); data = listToDataRes.data; sendData(data); } catch (err) { print(err); sendData({ msg: String(err) }); } } if (useDataType === `custom`) { // 自定义逻辑 try { var _context23; const { NodeVM } = require(`vm2`); const vm = new NodeVM({ sandbox: { // 给 vm 使用的变量 tool: { libObj: lib, wrapApiData, listToData, cur } } }); const code = vm.run(`module.exports = ${custom}`, `vm.js`) || ``; const codeType = typeof code; if ((0, _includes.default)(_context23 = [`function`]).call(_context23, codeType)) { // 如果是函数则运行函数 code(ws, req); } else { // 函数之外的类型则当作 json 返回 sendData(code); } } catch (err) { console.log(`err`, err); // 处理客户端代码出现问题, 代码错误或出现不允许的权限 sendData({ msg: err.message }); } } }; } const disable = (0, _includes.default)(disableApiList).call(disableApiList, `/`) // 如果含有根结点, 则表示全部禁用 ? true : (0, _includes.default)(disableApiList).call(disableApiList, cur.key); acc.push({ route: cur.path, re: pathToRegexp(cur.path), method: cur.method, type: `apiWeb`, description: cur.description, disable, action: fn, occupied: {} }); return acc; }, []); return apiList; } function prepareProxy(proxy = {}) { var _context24; // 解析 proxy 参数, proxy: string, object const isType = tool.type.isType; const proxyType = isType(proxy); let resProxy = []; if (proxyType === `string`) { // 任何路径都转发到 proxy proxy = { '/': proxy }; } function setIndexOfEnd(proxy) { // 需要排序 key:/ 到最后, 否则它生成的拦截器会被其他 key 覆盖 const indexVal = proxy[`/`]; delete proxy[`/`]; proxy[`/`] = indexVal; return proxy; } proxy = setIndexOfEnd(proxy); resProxy = (0, _map.default)(_context24 = (0, _keys.default)(proxy)).call(_context24, context => { let options = proxy[context]; const optionsType = isType(options); if (optionsType === `string`) { // 转换字符串的 value 为对象 const rootOptions = proxy[`/`]; options = { pathRewrite: { [`^${context}`]: options }, // 原样代理 /a 到 /a target: (0, _includes.default)(options).call(options, `://`) // 如果要代理的目录地址已有主域 ? new _url.default(options).origin // 那么就代理到该主域上 : { // 否则就代理到 / 设定的域上 string: rootOptions, object: rootOptions.target // 当主域是对象时则取其 target }[isType(rootOptions)] }; } if (optionsType === `array`) { // 是数组时, 视为设计 res body 的值, 语法为: [k, v] const [item1, item2] = options; const item1Type = isType(item1); const item2Type = isType(item2); const deepMergeObject = tool.obj.deepMergeObject; if (item1Type !== `function` && options.length <= 1) { // 只有0个或一个项, 直接替换 res options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: () => item1 }); } }; } if (item1Type === `function` && options.length <= 1) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => item1({ req, json }) }); } }; } if (item1Type === `function` && item2Type === `function`) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => { return item2({ req, json: item1({ req, json }) }); } }); } }; } if (item1Type === `string` && item2Type === `function`) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => { return item2({ req, json: tool.obj.deepGet(json, item1) }); } }); } }; } if (item1Type === `function` && item2Type === `string`) { options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: json => { return tool.obj.deepGet(item1({ req, json }), item2); } }); } }; } if (item1Type === `string` && options.length === 2) { // 以 item1 作为 key, item2 作为 val, 修改原 res options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, key: item1, val: item2 }); } }; } if (item1Type === `object` && options.length === 2) { // 根据 item2 的类型, 合并 item1 options = { onProxyRes(proxyRes, req, res) { midResJson({ proxyRes, res, cb: body => { return { 'deep': deepMergeObject(body, item1), // 父级【不会】被替换 '...': { ...body, ...item1 } // 父级【会】被替换, 类似于js扩展运行符 }[item2 || `deep`]; } }); } }; } } return { route: context, re: pathToRegexp(context), method: undefined, type: `proxy`, action: undefined, occupied: {}, info: options }; }); return resProxy; } function staticHandle() { var _context25; // 处理 static 为 api, 实际上是转换为 use 中间件的形式 let list = []; (0, _map.default)(_context25 = global.config.static).call(_context25, item => { list.push({ route: item.path, re: pathToRegexp(item.path), method: `use`, type: `static`, action: [async (req, res, next) => { // 开启列表显示时 if (item.list && item.mode !== `history`) { fileList(item)(req, res, next); } else { next(); } }, async (req, res, next) => { // mode history if (item.mode === `history`) { ; (await tool.generate.initPackge(`connect-history-api-fallback`))(item.option)(req, res, next); } else { next(); } }, express.static(item.fileDir, item.option)], occupied: {} }); }); return list; } function resetUrl() { // 重置 req.url /** 某些中间件例如 json-server 会改变 req.url, 导致后续的 app.use 逻辑不符合预期 @see https://github.com/typicode/json-server/blob/5df482bdd6e864258d6e7180342a30bf7b923cbc/src/server/router/nested.js#L13 */ return [{ route: `/`, re: pathToRegexp(`/`), method: `use`, type: `resetUrl`, action: (req, res, next) => { req.url = req.originalUrl; next(); }, info: {}, occupied: {} }]; } // 处理为统一的列表, 注意列表中的顺序对应 use 注册的顺序 const obj = { api: await parseApi(), db: parseDbApi(), resetUrl: resetUrl(), static: staticHandle(), apiWeb: apiWebHandle(), proxy: prepareProxy(global.config.proxy) }; const allRoute = [...obj.api, ...obj.db, ...obj.resetUrl, ...obj.static, ...obj.apiWeb, ...obj.proxy]; allRoute.obj = obj; return { allRouteTest, allRoute }; } /** * 过时的 API 提示 * @param {*} param0 * @param {*} param0.type api 类型: cli 命令行 option 选项 * @param {*} param0.no 旧API * @param {*} param0.yew 新API * @param {*} param0.v 被删除的版本 */ function oldAPI({ type, no, yes, v }) { type === `cli` && print(tool.cli.colors.red(`API更新: 请将命令行参数 ${no} 替换为 ${yes}, 在 v${v} 版本之后 ${no} 将停止使用!`)); } function initHandle() { // 初始化处理程序 /** * 检查运行环境是否兼容 */ function checkEnv() { return lib.compareVersions.compare(process.version, `10.12.0`, `>=`); } function templateFn({ cliArg, version }) { if (cliArg[`--template`]) { const path = require(`path`); const cwd = process.cwd(); if (tool.file.hasFile(cwd) === false) { require(`fs`).mkdirSync(cwd, { recursive: true }); } const copyPath = path.normalize(`${cwd}/mm/`); tool.file.copyFolderSync(path.normalize(`${__dirname}/../example/template/`), copyPath); { // 创建 package.json 中的 scripts 和 devDependencies const fs = require(`fs`); const jsonPath = `${process.cwd()}/package.json`; const hasJson = tool.file.hasFile(jsonPath); const scripts = `npx mockm --cwd=mm`; const devDependencies = version; if (hasJson && (require(jsonPath).scripts || {}).mm && (require(jsonPath).devDependencies || {}).mockm) {// console.log(`无需修改`) } else { var _context26; hasJson === false && fs.writeFileSync(jsonPath, (0, _trim.default)(_context26 = tool.string.removeLeft(` { "scripts": { "mm": "${scripts}" }, "devDependencies": { "mockm": "${devDependencies}" } } `)).call(_context26)); let packageText = fs.readFileSync(jsonPath, `utf8`); const packageJson = JSON.parse(packageText); packageJson.scripts = packageJson.scripts || {}; packageJson.devDependencies = packageJson.devDependencies || {}; packageJson.scripts.mm = packageJson.scripts.mm || scripts; packageJson.devDependencies.mockm = packageJson.devDependencies.mockm || devDependencies; const split = (packageText.match(/[\t ]+/) || [` `])[0]; // 获取缩进风格 packageText = (0, _stringify.default)(packageJson, null, split); fs.writeFileSync(jsonPath, packageText); } } process.chdir(copyPath); print(`模板 ${copyPath} 已创建成功!`); print(`使用命令 npm run mm`); } } function configFileFn({ cliArg }) { const path = require(`path`); const fs = require(`fs`); const packagePath = `${process.cwd()}/package.json`; const packageType = fs.existsSync(packagePath) && require(packagePath).type || `commonjs`; // 要在当前位置创建什么配置文件 const cwdConfigPath = packageType === `commonjs` ?