UNPKG

mihawk

Version:

A tiny & simple mock server tool, support json,js,cjs,ts(typescript).

271 lines (270 loc) 15.9 kB
'use strict'; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const util_1 = require("util"); const koa_1 = __importDefault(require("koa")); const color_cc_1 = __importDefault(require("color-cc")); const koa_bodyparser_1 = __importDefault(require("koa-bodyparser")); const koa_sslify_1 = __importDefault(require("koa-sslify")); const koa_connect_1 = __importDefault(require("koa-connect")); const fs_extra_1 = require("fs-extra"); const free_dedupe_1 = __importDefault(require("free-dedupe")); const print_1 = require("./utils/print"); const rc_1 = require("./composites/rc"); const loader_1 = require("./composites/loader"); const path_1 = require("./utils/path"); const favicon_1 = __importDefault(require("./middlewares/favicon")); const cert_file_1 = __importDefault(require("./middlewares/cert-file")); const common_1 = __importDefault(require("./middlewares/common")); const error_1 = __importDefault(require("./middlewares/error")); const cors_1 = __importDefault(require("./middlewares/cors")); const cache_1 = __importDefault(require("./middlewares/cache")); const _404_1 = __importDefault(require("./middlewares/404")); const routes_1 = __importDefault(require("./middlewares/routes")); const mock_1 = __importDefault(require("./middlewares/mock")); const net_1 = require("./utils/net"); const server_1 = require("./utils/server"); const is_1 = require("./utils/is"); const scanner_1 = require("./composites/scanner"); const obj_1 = require("./utils/obj"); const websocket_1 = __importDefault(require("./composites/websocket")); const async_1 = require("./utils/async"); const root_1 = require("./root"); const PKG_ROOT_PATH = (0, path_1.getRootAbsPath)(); function mihawk(config, isRestart = false) { return __awaiter(this, void 0, void 0, function* () { delete config._; delete config['--']; delete config.$schema; (0, obj_1.delNillProps)(config); !isRestart && print_1.Printer.log('config:', config); const options = (0, rc_1.formatOptionsByConfig)(config); print_1.Debugger.log('formated options:', options); const { cors, https: httpsConfig, useHttps, host, port, mockDir, mockDataDirPath, dataFileExt, useLogicFile, isTypesctiptMode, tsconfigPath, routesFilePath, middlewareFilePath, useWS, socketConfig, socketFilePath, } = options; const loadLogicFile = isTypesctiptMode ? loader_1.loadTS : loader_1.loadJS; const loadRoutesFile = useLogicFile ? loadLogicFile : loader_1.loadJson; const isPortAlreadyInUse = yield (0, net_1.isPortInUse)(port); if (isPortAlreadyInUse) { print_1.Printer.error(color_cc_1.default.yellow(`Port ${port} is already in use`)); process.exit(1); } (0, fs_extra_1.ensureDirSync)(mockDataDirPath); if (isTypesctiptMode) { let tsconfig = null; if ((0, fs_extra_1.existsSync)(tsconfigPath)) { tsconfig = require(tsconfigPath); } else { !isRestart && print_1.Printer.log(color_cc_1.default.gray(`Skip load "${(0, path_1.unixifyPath)((0, path_1.relPathToCWD)(tsconfigPath))}"(file-not-existed), will use default build-in tsconfig.json`)); } (0, loader_1.enableRequireTsFile)(tsconfig || {}); !isRestart && print_1.Printer.log(color_cc_1.default.success('Enable typescript mode success!'), color_cc_1.default.gray('You can write logic in routes.ts, middleware.ts, data/**/*.ts')); } let routes = {}; if ((0, fs_extra_1.existsSync)(routesFilePath)) { routes = (yield loadRoutesFile(routesFilePath, { noLogPrint: true })); !isRestart && print_1.Printer.log(color_cc_1.default.success('Load routes file success!'), color_cc_1.default.gray((0, path_1.unixifyPath)((0, path_1.relPathToCWD)(routesFilePath)))); } let diyMiddleware = null; if (useLogicFile && (0, fs_extra_1.existsSync)(middlewareFilePath)) { const tmpFunction = yield loadLogicFile(middlewareFilePath, { noLogPrint: true }); const isExpressMiddleware = typeof tmpFunction === 'function' && !!tmpFunction.isExpress; diyMiddleware = isExpressMiddleware ? (0, koa_connect_1.default)(tmpFunction) : tmpFunction; !isRestart && print_1.Printer.log(color_cc_1.default.success('Load custom middleware file success!'), color_cc_1.default.gray((0, path_1.unixifyPath)((0, path_1.relPathToCWD)(middlewareFilePath))), isExpressMiddleware ? color_cc_1.default.yellow('Express-Style-Middleware') : ''); } const app = new koa_1.default(); app.use((0, error_1.default)()); useHttps && app.use((0, koa_sslify_1.default)({ hostname: host, port })); useHttps && app.use((0, cert_file_1.default)()); app.use((0, favicon_1.default)(root_1.ASSET_FAVICON_PATH)); app.use((0, common_1.default)(options)); cors && app.use((0, cors_1.default)()); app.use((0, cache_1.default)()); app.use((0, _404_1.default)()); app.use((0, koa_bodyparser_1.default)({ onerror: (err, ctx) => { const invalidBody = err === null || err === void 0 ? void 0 : err.body; print_1.Printer.error('mdw-body-parser:', color_cc_1.default.yellow('Occurs error with code'), `${color_cc_1.default.yellow.bold(`JSON.parse(${invalidBody})`)}${color_cc_1.default.yellow(', will skip parse it!')}\n`, color_cc_1.default.yellow(`${err.message}\n`), err); ctx.status = 200; ctx.request.body = invalidBody; }, })); app.use((0, routes_1.default)(routes)); typeof diyMiddleware === 'function' && app.use(diyMiddleware); app.use((0, mock_1.default)(options)); const protocol = useHttps ? 'https' : 'http'; const addr1 = `${protocol}://${host}:${port}`; let server = null; if (useHttps) { const httpsOptions = { key: null, cert: null, ca: null }; let key = '', cert = '', ca = ''; if ((0, is_1.isObjStrict)(httpsConfig)) { key = httpsConfig.key; cert = httpsConfig.cert; ca = httpsConfig.ca; } const keyFilePath = (0, path_1.absifyPath)(key); const certFilePath = (0, path_1.absifyPath)(cert); const caFilePath = (0, path_1.absifyPath)(ca); if (!key || !cert || !(0, fs_extra_1.existsSync)(keyFilePath) || !(0, fs_extra_1.existsSync)(certFilePath)) { httpsOptions.key = (0, fs_extra_1.readFileSync)(root_1.ASSET_CERT_LOCAL_KEY_PATH); httpsOptions.cert = (0, fs_extra_1.readFileSync)(root_1.ASSET_CERT_LOCAL_CRT_PATH); httpsOptions.ca = (0, fs_extra_1.readFileSync)(root_1.ASSET_CERT_CA_CRT_PATH); !isRestart && print_1.Printer.log(color_cc_1.default.gray(`Custom https cert files ware not found, use default build-in https cert files`)); } else { httpsOptions.key = (0, fs_extra_1.readFileSync)(keyFilePath); httpsOptions.cert = (0, fs_extra_1.readFileSync)(certFilePath); if (ca && (0, fs_extra_1.existsSync)(caFilePath)) { httpsOptions.ca = (0, fs_extra_1.readFileSync)(caFilePath); } !isRestart && print_1.Printer.log(color_cc_1.default.success('Load https cert files success!'), color_cc_1.default.gray(`key=${key}, cert=${cert}, ca=${ca || ''}`)); } server = https_1.default.createServer(httpsOptions, app.callback()); } else { server = http_1.default.createServer(app.callback()); } server.on('error', function (error) { if (error.syscall !== 'listen') { throw error; } switch (error.code) { case 'EACCES': print_1.Printer.error(color_cc_1.default.error(`MockServer failed! Port ${port} requires elevated privileges!!!\n`)); process.exit(1); break; case 'EADDRINUSE': print_1.Printer.error(color_cc_1.default.error(`MockServer failed! Port ${port} is already in use!!!\n`)); process.exit(1); break; default: print_1.Printer.error(color_cc_1.default.red('Server Error:\n'), error); throw error; } }); server.on('listening', function () { print_1.Printer.log(color_cc_1.default.green(`🚀 ${isRestart ? 'Restart' : 'Start'} mock-server success!`)); !isRestart && print_1.Printer.log('Mock directory: ', color_cc_1.default.gray((0, path_1.unixifyPath)(mockDir))); const existedRoutes = (0, scanner_1.scanExistedRoutes)(mockDataDirPath, dataFileExt) || []; print_1.Debugger.log('Existed routes by scann:', existedRoutes); let existedRoutePaths = existedRoutes.map(({ method, path }) => `${method} ${path}`); existedRoutePaths.push(...Object.keys(routes)); existedRoutePaths = (0, free_dedupe_1.default)(existedRoutePaths); existedRoutePaths.sort(); const existedCount = existedRoutePaths.length; !isRestart && print_1.Printer.log(`Detected-Routes(${color_cc_1.default.green(existedCount)}):`, existedCount ? existedRoutePaths : color_cc_1.default.grey('empty')); !isRestart && print_1.Printer.log(`Mock Server address:`); print_1.Printer.log(`${color_cc_1.default.gray('-')} ${color_cc_1.default.cyan(addr1)}`); if ((0, net_1.supportLocalHost)(host)) { const addr2 = `${protocol}://${(0, net_1.getMyIp)()}:${port}`; print_1.Printer.log(`${color_cc_1.default.gray('-')} ${color_cc_1.default.cyan(addr2)}`); } if (useHttps && !isRestart) { print_1.Printer.log('🗝', color_cc_1.default.gray(`You can download CA file for https dev, from url -> https://${(0, net_1.getMyIp)()}:${port}/.cert/ca.crt`)); } !wsController && console.log(); }); server = (0, server_1.enhanceServer)(server); server.listen(port, host); let wsController = null; if (useWS) { const { stomp } = socketConfig || {}; let resolveFunc = null; if ((0, fs_extra_1.existsSync)(socketFilePath)) { resolveFunc = yield loadLogicFile(socketFilePath, { noLogPrint: true }); !isRestart && print_1.Printer.log(color_cc_1.default.success('Load socket logic file success!'), color_cc_1.default.gray((0, path_1.unixifyPath)((0, path_1.relPathToCWD)(socketFilePath)))); } wsController = new websocket_1.default({ stomp, server, host, port, secure: useHttps, resolve: resolveFunc, }); wsController.start(null, isRestart); } return { destory: () => __awaiter(this, void 0, void 0, function* () { var _a, _b; if (wsController) { print_1.Debugger.log('Detected websocket server existed, will destory it...'); const destoryWsSvr = (_a = wsController === null || wsController === void 0 ? void 0 : wsController.destory) === null || _a === void 0 ? void 0 : _a.bind(wsController); if (typeof destoryWsSvr === 'function') { try { yield destoryWsSvr(); wsController = null; print_1.Printer.log('Websocket server has been destoryed.'); } catch (error) { print_1.Printer.error(`Destory websocket server failed!\n`, error); } } } yield (0, async_1.sleep)(0); if (server) { print_1.Debugger.log('Detected http/https server existed, will destory it...'); const destoryServer = (_b = server === null || server === void 0 ? void 0 : server.destory) === null || _b === void 0 ? void 0 : _b.bind(server); if (typeof destoryServer === 'function') { try { yield destoryServer(); server = null; print_1.Printer.log(color_cc_1.default.success(`Destory mock-server(${color_cc_1.default.gray(addr1)}) success!`)); } catch (error) { print_1.Printer.error(`Destory Server Failed!\n`, error); } } } }), close: () => __awaiter(this, void 0, void 0, function* () { var _c; if (wsController) { print_1.Debugger.log('Detected websocket server existed, will close it...'); const closeWsSvr = (_c = wsController === null || wsController === void 0 ? void 0 : wsController.close) === null || _c === void 0 ? void 0 : _c.bind(wsController); if (typeof closeWsSvr === 'function') { try { yield closeWsSvr(); wsController = null; print_1.Printer.log(color_cc_1.default.success('Close websocket server success!')); } catch (error) { print_1.Printer.error(`Close websocket server failed!\n`, error); } } } yield (0, async_1.sleep)(0); if (server) { print_1.Debugger.log('Detected http/https server existed, will destory it...'); typeof (server === null || server === void 0 ? void 0 : server.closeAllConnections) === 'function' && server.closeAllConnections(); typeof (server === null || server === void 0 ? void 0 : server.closeIdleConnections) === 'function' && server.closeIdleConnections(); const closeServerAsync = (0, util_1.promisify)(server.close).bind(server); try { yield closeServerAsync(); server = null; print_1.Printer.log(color_cc_1.default.success('Close Mock-Server success!')); } catch (error) { print_1.Printer.error(`Close Server Failed!\n`, error); } } }), }; }); } exports.default = mihawk;