UNPKG

mockm

Version:

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

289 lines (241 loc) 9.52 kB
#!/usr/bin/env node /** * 要实现监控 js 文件修改后重新启动, 需要用到 nodemon 这个工具, 之前是通过 packge.scripts 中的命令行方式使用的. * 由于向系统注册 packge.bin 时, 这个文件只能由 node 执行, 即只能为 js. * 所以, 需要一个额外的 js 文件来监听 config.js 修改, 然后重启 server.js * 把 run.js 中收集的命令行参数, 以 base64 方式传入 server.js 中. */ "use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var _bind = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/bind")); var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify")); var _now = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/date/now")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter")); var _trim = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/trim")); var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9; const { logHelper, print } = require(`${__dirname}/util/log.js`); process.argv = require(`./lib/cross-argv@1.0.1.js`)(); (0, _includes.default)(_context = process.argv).call(_context, `--log-line`) && logHelper(); const fs = require(`fs`); const path = require(`path`); const util = require('util'); const { tool, business } = require(`${__dirname}/util/index.js`); const lib = require(`${__dirname}/util/lib.js`); const packageJson = require(`${__dirname}/package.json`); const cli = tool.cli; const cliArg = cli.parseArgv(); const serverPath = path.normalize(`${__dirname}/server.js`); // 转换为跨平台的路径 const pm2 = require('pm2'); // 使用 util.promisify 封装 PM2 的回调方法 const pm2Async = { connect: util.promisify((0, _bind.default)(_context2 = pm2.connect).call(_context2, pm2)), start: util.promisify((0, _bind.default)(_context3 = pm2.start).call(_context3, pm2)), restart: util.promisify((0, _bind.default)(_context4 = pm2.restart).call(_context4, pm2)), stop: util.promisify((0, _bind.default)(_context5 = pm2.stop).call(_context5, pm2)), delete: util.promisify((0, _bind.default)(_context6 = pm2.delete).call(_context6, pm2)), list: util.promisify((0, _bind.default)(_context7 = pm2.list).call(_context7, pm2)), sendDataToProcessId: util.promisify((0, _bind.default)(_context8 = pm2.sendDataToProcessId).call(_context8, pm2)), launchBus: util.promisify((0, _bind.default)(_context9 = pm2.launchBus).call(_context9, pm2)), disconnect: () => pm2.disconnect() // 这个方法不需要 promisify }; { // 尽早的, 无依赖的修改 cwd, 避免其他读取到旧值 const cwd = tool.cli.handlePathArg(typeof cliArg[`--cwd`] === `string` ? cliArg[`--cwd`] : process.cwd()); process.chdir(cwd); } { // 仅查看版本号 cliArg[`--version`] && (print(packageJson.version) || process.exit()); } const { initHandle, plugin, saveLog, build } = business; let shareConfig = {}; const { templateFn, configFileFn, checkEnv } = initHandle(); templateFn({ cliArg, version: packageJson.version }); const configFile = configFileFn({ cliArg }); const base64config = Buffer.from((0, _stringify.default)(cliArg)).toString(`base64`); // 以 base64 方式向 `node server.js` 传送命令行参数 const os = require(`os`); const sharePath = path.normalize(`${os.tmpdir}/publicStore_${(0, _now.default)()}.json`); // 此文件用于 run.js 与 server.js 共享变量 new _promise.default(async () => { var _context10; // 显示程序信息, 例如版本号, logo const vTag = `version: `; const logText = require(`fs`).readFileSync(`${__dirname}/util/logo.txt`, `utf8`); const versionLogo = logText.replace(new RegExp(`(${vTag})(.*)`), (match, $1, $2) => { const vStr = build.getBuildStr(packageJson); const vLength = vStr.length; const vLine = vLength > $2.length // 如果版本号替换到版本标志后面 ? `${$1}${vStr}` : match.replace(new RegExp(`(${vTag})(.{${vLength}})`), `$1${vStr}`); return vLine; }); (0, _includes.default)(_context10 = process.argv).call(_context10, `--log-line`) === false && print(versionLogo); }); new _promise.default(async () => { // 检查运行环境 if (checkEnv() === false) { print(cli.colors.red(`node 版本应大于 v10.12.0`)); process.exit(); } }); new _promise.default(async () => { // 检查更新 if (Boolean(cliArg[`--no-update`]) === false) { const { name, version } = packageJson; const { local, server } = await tool.npm.checkUpdate(name, { version }).catch(err => print(`Check for update failed: ${err}`)); if (server && lib.compareVersions.compare(local, server, `<`)) { const msg = tool.string.removeLeft(` New version has been released: ${server} Your current version is: ${local} View updated features: https://wll8.github.io/mockm/dev/change_log.html?update=${local},${server} `); print(cli.colors.yellow(msg)); } } }); new _promise.default(async () => { var _context11, _context12; // 启动 server.js let log = ``; let isShuttingDown = false; const nodeArg = typeof cliArg[`--node-options`] === `string` ? cliArg[`--node-options`] : ``; const processName = `mockm-${process.pid}`; // PM2 进程配置 const pm2Config = { name: processName, script: serverPath, args: [...(0, _slice.default)(_context11 = process.argv).call(_context11, 2), `_base64=${base64config}`, `_share=${sharePath}`], nodeArgs: nodeArg ? (0, _filter.default)(_context12 = nodeArg.split(' ')).call(_context12, arg => (0, _trim.default)(arg).call(arg)) : [], cwd: process.cwd(), autorestart: false, // 手动控制重启 watch: false, // 关闭 PM2 自带的文件监听,使用自定义监听 max_memory_restart: '500M', merge_logs: true, kill_timeout: 5000, namespace: process.env.PM2_NAMESPACE, // 不指定日志文件,让 PM2 使用默认位置,然后我们通过流来转发 silent: false }; // 监听进程消息和日志 async function listenToProcessMessages() { const pm2_bus = await pm2Async.launchBus(); // 监听日志输出 pm2_bus.on('log:out', packet => { if (packet.process.name === processName) { process.stdout.write(packet.data); log = String(packet.data); } }); pm2_bus.on('log:err', packet => { if (packet.process.name === processName) { process.stderr.write(packet.data); log = String(packet.data); } }); pm2_bus.on('process:msg', packet => { if (packet.process.name === processName) { const { action, data = {} } = packet.data || {}; if (action === 'err-exit') { if (pm2Config.autorestart) { console.log(`[${processName}] Auto restarting process...`); } else { killProcess(); } } if (action === 'reboot') { pm2Async.restart(processName); } if (action === 'config') { pm2Config.autorestart = data.guard; } } }); } // 优雅关闭函数 function killProcess() { isShuttingDown = true; console.log(`[${processName}] Shutting down...`); pm2Async.delete(processName).then(() => { pm2Async.disconnect(); process.exit(0); }).catch(err => { console.error('Error during shutdown:', err); pm2Async.disconnect(); process.exit(1); }); } // 绑定进程信号 process.on(`SIGTERM`, killProcess); process.on(`SIGINT`, killProcess); process.on(`uncaughtException`, killProcess); process.on(`unhandledRejection`, killProcess); try { // 启动 PM2 管理的进程 await pm2Async.connect(); // 先清理可能存在的同名进程 await pm2Async.delete(processName).catch(() => {}); // 忽略错误,因为进程可能不存在 // 启动新进程 await pm2Async.start(pm2Config); // 监听进程消息 listenToProcessMessages(); } catch (err) { console.error('Failed to start process:', err); pm2Async.disconnect(); process.exit(1); } const { showLocalInfo, remoteServer } = plugin(); tool.control.awaitTrue({ // 等待 sharePath 文件存在, 期望 config 已经存入 condition: () => tool.file.hasFile(sharePath), timeout: 60e3 }).then(() => { const share = tool.file.fileStore(sharePath); shareConfig = share.get(`config`); const store = tool.file.fileStore(shareConfig._store); store.set(`note.remote`, {}); showLocalInfo({ store, shareConfig }); if (shareConfig.remote) { // 如果启用远程则进行相关功能处理 remoteServer({ store, shareConfig }).catch(err => console.log(`err`, err)); } }).catch(err => { console.log(`err`, err); print(`Start timeout, please check whether the environment or configuration is wrong`); process.exit(); }); });