mihawk
Version:
A tiny & simple mock server tool, support json,js,cjs,ts(typescript).
271 lines (270 loc) • 15.9 kB
JavaScript
;
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;