#!/usr/bin/env node import * as path from 'path'; import * as fs from 'fs'; import * as readline from 'readline'; import { spawn } from 'child_process'; import * as define from './util/define'; import * as msgCoder from './components/msgCoder'; import { TcpClient } from './components/tcpClient'; const version = require('../package.json').version; const DEFAULT_MASTER_HOST = '127.0.0.1'; const DEFAULT_MASTER_PORT = 3005; const FILEREAD_ERROR = 'Fail to read the file, please check if the application is started legally.'; let waitInterval: NodeJS.Timeout = null as any; // #region some class class ClientProxy { reqId: number = 1; reqs = new Map(); socket: TcpClient; token: string; connect_cb: Function; private needAbort = true; private heartbeatTimeout: NodeJS.Timeout = null as any; constructor(host: string, port: number, token: string, cb: Function) { this.token = token; this.connect_cb = cb; this.socket = new TcpClient(port, host, define.some_config.SocketBufferMaxLen, true, this.connectCb.bind(this)); this.socket.on('data', (buf: Buffer) => { const data = JSON.parse(buf.toString()); const reqId = data.reqId; const req = this.reqs.get(reqId); if (!req) { return; } this.reqs.delete(reqId); clearTimeout(req.timeOut); req.cb(null, data.msg); }); this.socket.on('close', (err: any) => { clearTimeout(this.heartbeatTimeout); if (this.needAbort) { abort(err); } }); } private connectCb() { // register const loginInfo = { T: define.Cli_To_Master.register, cliToken: this.token }; const loginInfo_buf = msgCoder.encodeInnerData(loginInfo); this.socket.send(loginInfo_buf); this.heartbeat(); clearWait(); this.connect_cb(this); } private heartbeat() { const self = this; this.heartbeatTimeout = setTimeout(function () { const heartBeatMsg = { T: define.Cli_To_Master.heartbeat }; const heartBeatMsg_buf = msgCoder.encodeInnerData(heartBeatMsg); self.socket.send(heartBeatMsg_buf); self.heartbeat(); }, define.some_config.Time.Monitor_Heart_Beat_Time * 1000); } request(msg: any, timeout: number, cb: (err: string, ...args: any[]) => void) { const reqId = this.reqId++; const data = { T: define.Cli_To_Master.cliMsg, reqId, msg }; const buf = msgCoder.encodeInnerData(data); this.socket.send(buf); const self = this; this.reqs.set(reqId, { cb, timeOut: setTimeout(function () { self.reqs.delete(reqId); // eslint-disable-next-line n/no-callback-literal cb('time out'); }, timeout * 1000) }); } close(needAbort = true) { this.needAbort = needAbort; this.socket.close(); } } class Commond { private baseName: string = ''; private ver: string = ''; private readonly cmdArr: I_commond[] = []; setNameVersion(baseName: string, ver: string) { this.baseName = baseName; this.ver = ver; } addCommond(cmd: I_commond) { for (const one of this.cmdArr) { if (one.name === cmd.name) { console.log(`\n Error: [Cmd already exists] cmd -> ${cmd.name}\n`); process.exit(); } } for (let i = 0; i < cmd.options.length; i++) { const one = cmd.options[i]; for (let j = i + 1; j < cmd.options.length; j++) { const two = cmd.options[j]; if (two.opt === one.opt) { console.log(`\n Error: [Option.opt already exists] cmd -> ${cmd.name}, opt -> ${one.opt}\n`); process.exit(); } if (two.name === one.name) { console.log(`\n Error: [Option.name already exists] cmd -> ${cmd.name}, name -> ${one.name}\n`); process.exit(); } } } this.cmdArr.push(cmd); } parse() { const argvArr = [...process.argv]; argvArr.splice(0, 2); if (!argvArr.length) { this.print_help(); return; } const cmdName = argvArr[0]; if (['-h', '-H', '--help'].includes(cmdName)) { this.print_help(); return; } if (['-v', '-V', '--version'].includes(cmdName)) { console.log(`\n Version: ${this.ver}\n`); return; } if (cmdName === 'des') { this.print_des(argvArr[1]); return; } this.parseCmd(cmdName, argvArr); } private parseCmd(cmdName: string, argvArr: string[]) { let cmd: I_commond = null as any; for (const one of this.cmdArr) { if (one.name === cmdName) { cmd = one; break; } } if (!cmd) { console.log(`\n Error: [Cmd not exists] ${cmdName}\n`); return; } if (argvArr.includes('--help')) { this.print_des(cmdName); return; } const keyDic: Record = {}; const otherArr: string[] = []; for (let i = 1; i < argvArr.length;) { const str = argvArr[i]; if (!str.startsWith('-')) { otherArr.push(str); i += 1; continue; } let option: I_option = null as any; for (const one of cmd.options) { if (one.opt === str) { option = one; break; } } if (!option) { console.log(`\n Error: [No such option] ${str}\n`); process.exit(); } if (option.type === 'bool') { keyDic[option.name] = true; i += 1; continue; } if (option.type === 'string') { const str2 = argvArr[i + 1]; if (!str2 || str2.startsWith('-')) { console.log(`\n Error: [Wrong option input] ${str} ${str2 || ''}\n`); process.exit(); } keyDic[option.name] = str2; i += 2; continue; } if (option.type === 'number') { const str2 = argvArr[i + 1]; if (!str2 || str2.startsWith('-')) { console.log(`\n Error: [Wrong option input] ${str} ${str2 || ''}\n`); process.exit(); } const numVal = Number(str2); if (isNaN(numVal)) { console.log(`\n Error: [Wrong option input] ${str} ${str2 || ''}\n`); process.exit(); } keyDic[option.name] = numVal; i += 2; continue; } } for (const one of cmd.options) { if (keyDic[one.name] !== undefined) { continue; } if (one.type === 'bool') { continue; } if (one.mustNeed) { console.log(`\n Error: [Need option] ${one.opt}\n`); process.exit(); } else if (one.default !== undefined) { keyDic[one.name] = one.default; } } cmd.cb(keyDic, otherArr); } private print_help() { console.log(''); console.log(` Version: ${this.ver}`); console.log(' Usage:'); const defaultArr: string[][] = []; defaultArr.push(['-v', 'show the version']); defaultArr.push(['-h', 'list the commonds']); defaultArr.push(['des [command]', 'describe the command']); this.printArr(defaultArr); console.log('\n Commands:'); const arr: string[][] = []; for (const one of this.cmdArr) { arr.push([one.name + (one.options.length ? ' [options]' : ''), one.des]); } this.printArr(arr); console.log(''); } private print_des(cmdName: string) { let cmd: I_commond = null as any; for (const one of this.cmdArr) { if (one.name === cmdName) { cmd = one; break; } } if (!cmd) { console.log(`\n Error: [Cmd not exists] cmd -> ${cmdName}\n`); return; } console.log(''); console.log(` Cmd: ${this.baseName} ${cmd.name}`); console.log(` Des: ${cmd.des}`); if (cmd.usage) { console.log(` Usage: ${cmd.usage}`); } console.log(' Options:'); const arr: string[][] = []; for (const one of cmd.options) { let tmpDes = one.des; if (!one.mustNeed && one.type !== 'bool' && one.default !== undefined) { if (one.type === 'string') { tmpDes += ` (default: "${one.default}")`; } else { tmpDes += ` (default: ${one.default})`; } } arr.push([one.opt, one.type === 'bool' ? '' : `${one.name} [${one.type}]`, tmpDes, one.mustNeed ? '√' : '']); } this.printArr(arr); console.log(''); } private printArr(arr: string[][]) { const widthArr: number[][] = []; const maxWidthArr: number[] = []; for (let i = 0; i < arr.length; i++) { const one = arr[i]; const tmpArr: number[] = []; for (let j = 0; j < one.length; j++) { const len = this.getDisplayLength(one[j]); tmpArr.push(len); if (len >= (maxWidthArr[j] || 0)) { maxWidthArr[j] = len; } } widthArr.push(tmpArr); } for (let i = 0; i < maxWidthArr.length; i++) { maxWidthArr[i] += 5; } for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr[i].length; j++) { arr[i][j] += ' '.repeat(maxWidthArr[j] - widthArr[i][j]); } console.log(' ', arr[i].join('')); } } private getDisplayLength(str: string) { let realLength = 0; const len = str.length; let charCode = -1; for (let i = 0; i < len; i++) { charCode = str.charCodeAt(i); if (charCode >= 0 && charCode <= 128) { realLength += 1; } else { realLength += 2; } } return realLength; } } interface I_commond { /** 名称 */ 'name': string; /** 描述 */ 'des': string; /** 选项 */ 'options': I_option[]; /** 使用示例 */ 'usage': string; /** 回调 */ 'cb': (opts: any, argv: string[]) => void; } interface I_option { /** 关键值(必须以"-"开头) */ 'opt': string; /** 名字 */ 'name': string; /** 描述 */ 'des': string; /** 是否是必选项 */ 'mustNeed': boolean; /** 类型 */ 'type': 'bool' | 'string' | 'number'; /** 默认值 */ 'default'?: number | string; } // #endregion // #region add commond const commond = new Commond(); commond.setNameVersion('omelot', version); commond.addCommond({ name: 'init', des: 'create a new application', options: [], usage: '', cb: () => { cli_init(); } }); commond.addCommond({ name: 'start', des: 'start the application', options: [ { opt: '-e', name: 'env', des: 'the used environment', mustNeed: false, type: 'string', default: 'development' }, { opt: '-d', name: 'daemon', des: 'enable the daemon start', mustNeed: false, type: 'bool' } ], usage: 'omelot start -e env [serverId-1 ...]', cb: (opts: { 'env': string; 'daemon': boolean; 'serverIds': string[] }, argv) => { opts.serverIds = argv; cli_start(opts); } }); commond.addCommond({ name: 'list', des: 'list the servers', options: [ { opt: '-h', name: 'host', des: 'master server host', mustNeed: false, type: 'string', default: DEFAULT_MASTER_HOST }, { opt: '-p', name: 'port', des: 'master server port', mustNeed: false, type: 'number', default: DEFAULT_MASTER_PORT }, { opt: '-t', name: 'token', des: 'cli token', mustNeed: false, type: 'string', default: define.some_config.Cli_Token }, { opt: '-i', name: 'interval', des: 'request interval', mustNeed: false, type: 'number', default: 5 } ], usage: '', cb: (opts: { 'host': string; 'port': number; 'token': string; 'interval': number }) => { cli_list(opts); } }); commond.addCommond({ name: 'stop', des: 'stop the servers', options: [ { opt: '-h', name: 'host', des: 'master server host', mustNeed: false, type: 'string', default: DEFAULT_MASTER_HOST }, { opt: '-p', name: 'port', des: 'master server port', mustNeed: false, type: 'number', default: DEFAULT_MASTER_PORT }, { opt: '-t', name: 'token', des: 'cli token', mustNeed: false, type: 'string', default: define.some_config.Cli_Token } ], usage: '', cb: (opts: { 'host': string; 'port': number; 'token': string }) => { cli_stop(opts); } }); commond.addCommond({ name: 'remove', des: 'remove some servers', options: [ { opt: '-h', name: 'host', des: 'master server host', mustNeed: false, type: 'string', default: DEFAULT_MASTER_HOST }, { opt: '-p', name: 'port', des: 'master server port', mustNeed: false, type: 'number', default: DEFAULT_MASTER_PORT }, { opt: '-t', name: 'token', des: 'cli token', mustNeed: false, type: 'string', default: define.some_config.Cli_Token } ], usage: 'omelot remove serverId-1 [serverId-2 ...]', cb: (opts: { 'host': string; 'port': number; 'token': string; 'serverIds': string[] }, argv) => { opts.serverIds = argv; cli_remove(opts); } }); commond.addCommond({ name: 'removeT', des: 'remove some serverTypes', options: [ { opt: '-h', name: 'host', des: 'master server host', mustNeed: false, type: 'string', default: DEFAULT_MASTER_HOST }, { opt: '-p', name: 'port', des: 'master server port', mustNeed: false, type: 'number', default: DEFAULT_MASTER_PORT }, { opt: '-t', name: 'token', des: 'cli token', mustNeed: false, type: 'string', default: define.some_config.Cli_Token } ], usage: 'omelot removeT serverType-1 [serverType-2 ...]', cb: (opts: { 'host': string; 'port': number; 'token': string; 'serverTypes': string[] }, argv) => { opts.serverTypes = argv; cli_removeT(opts); } }); commond.addCommond({ name: 'cmd', des: 'build cmd file', options: [], usage: 'omelot cmd [ts cs ...]', cb: (opts, argv) => { cli_cmd(argv); } }); commond.addCommond({ name: 'send', des: 'send msg to omelot', options: [ { opt: '-h', name: 'host', des: 'master server host', mustNeed: false, type: 'string', default: DEFAULT_MASTER_HOST }, { opt: '-p', name: 'port', des: 'master server port', mustNeed: false, type: 'number', default: DEFAULT_MASTER_PORT }, { opt: '-t', name: 'token', des: 'cli token', mustNeed: false, type: 'string', default: define.some_config.Cli_Token }, { opt: '-id', name: 'serverId', des: 'serverId will get msg', mustNeed: false, type: 'string' }, { opt: '-svrT', name: 'serverType', des: 'serverType will get msg', mustNeed: false, type: 'string' } ], usage: 'omelot send [-id id1,id2] [-svrT svrT1,svrT2] [argv0 argv1...]', cb: (opts: { 'host': string; 'port': number; 'token': string; 'serverId': string; 'serverType': string }, argv) => { cli_send(opts, argv); } }); commond.parse(); // #endregion // #region some func function cli_init() { const pathStr = process.cwd(); emptyDirectory(pathStr, function (empty) { if (empty) { createApplicationAt(pathStr); } else { confirm('Destination is not empty, continue? (y/n) [no] ', function (force) { if (force) { createApplicationAt(pathStr); } else { abort('[ canceled ]'); } }); } }); function createApplicationAt(ph: string) { confirmFrontend('Select frontend engine, cocos or unity? (cocos/unity) ', function (engine: string) { copy(path.join(__dirname, `../template/frontend_${engine}`), `${ph}/frontend`); copy(path.join(__dirname, `../template/backend`), `${ph}/backend`); }); function copy(origin: string, target: string) { if (!fs.existsSync(origin)) { abort(origin + 'does not exist.'); } if (!fs.existsSync(target)) { fs.mkdirSync(target); console.log(' create : ' + target); } fs.readdir(origin, function (err, datalist) { if (err) { abort(FILEREAD_ERROR); } for (let i = 0; i < datalist.length; i++) { const oCurrent = path.resolve(origin, datalist[i]); const tCurrent = path.resolve(target, datalist[i]); if (fs.statSync(oCurrent).isFile()) { fs.writeFileSync(tCurrent, fs.readFileSync(oCurrent)); console.log(' create : ' + tCurrent); } else if (fs.statSync(oCurrent).isDirectory()) { copy(oCurrent, tCurrent); } } }); } } function emptyDirectory(path: string, fn: (isEmpth: boolean) => void) { fs.readdir(path, function (err, files) { if (err && err.code !== 'ENOENT') { abort(FILEREAD_ERROR); } // eslint-disable-next-line @typescript-eslint/prefer-optional-chain fn(!files || !files.length); }); } } function cli_start(opts: { env: string; daemon: boolean; serverIds: string[] }) { let absScript = path.resolve(process.cwd(), 'app.js'); if (!fs.existsSync(absScript)) { absScript = path.resolve(process.cwd(), '../dist/app.js'); if (!fs.existsSync(absScript)) { abort(' -> Not find the script: ' + absScript); } } opts.env = opts.env || 'development'; opts.daemon = !!opts.daemon; if (opts.serverIds.length === 0) { // eslint-disable-next-line @typescript-eslint/restrict-plus-operands startSvr([absScript, 'env=' + opts.env, 'daemon=' + opts.daemon]); } else { let serverIds: string[] = []; for (const id of opts.serverIds) { serverIds.push(...parseServerId(id)); } serverIds = Array.from(new Set(serverIds)); for (const id of serverIds) { // eslint-disable-next-line @typescript-eslint/restrict-plus-operands startSvr([absScript, 'env=' + opts.env, 'daemon=' + opts.daemon, 'id=' + id]); } } if (opts.daemon) { console.log('The application is running in the background now.\n'); process.exit(0); } function startSvr(params: string[]) { let ls; if (opts.daemon) { ls = spawn(process.execPath, params, { detached: true, stdio: 'ignore' }); ls.unref(); } else { ls = spawn(process.execPath, params); ls.stdout.on('data', function (data) { console.log(data.toString()); }); ls.stderr.on('data', function (data) { console.log(data.toString()); }); } } } function cli_list(opts: { 'host': string; 'port': number; 'token': string; 'interval': number }) { let interval = Math.ceil(opts.interval); if (interval < 1) { interval = 1; } connectToMaster(opts.host, opts.port, opts.token, function (client) { console.log(''); requestList(); let rowNum = 0; function requestList() { client.request({ func: 'list' }, 10, function (err, msg: { name: string; env: string; serverTypeSort: string[]; infoArr: string[][] }) { if (err) { abort(err); return; } const titles = msg.infoArr.shift(); titles?.splice(1, 1); const serverTypes: Record = {}; for (const one of msg.serverTypeSort) { serverTypes[one] = []; } for (const one of msg.infoArr) { serverTypes[one[1]].push(one); one.splice(1, 1); } for (const x in serverTypes) { serverTypes[x].sort(comparer); } let id = 1; titles?.unshift(''); const endArr: string[][] = []; // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion endArr.push(titles as string[]); serverTypes.master[0].unshift(' ' + id.toString()); id++; endArr.push(serverTypes.master[0]); delete serverTypes.master; for (const x in serverTypes) { for (const one of serverTypes[x]) { one.unshift(' ' + id.toString()); id++; endArr.push(one); } } clearScreen(rowNum); rowNum = endArr.length + 1; omelotListPrint(msg.name, msg.env, endArr); setTimeout(requestList, interval * 1000); }); } }); const comparer = function (a: string[], b: string[]) { if (a[0] < b[0]) { return -1; } else { return 1; } }; function omelotListPrint(appName: string, env: string, infoArr: string[][]) { const consoleMaxColumns = process.stdout.columns - 2; const nameEnv = ' appName: ' + appName + ' env: ' + env; console.log('\x1b[35m' + getRealStr(nameEnv) + '\x1b[0m'); const widthArr: number[][] = []; const columnWidth: number[] = []; const titleLen = infoArr[0].length; for (let i = 0; i < titleLen; i++) { columnWidth.push(0); } for (let i = 0; i < infoArr.length; i++) { const one = infoArr[i]; if (one.length > titleLen) { one.splice(titleLen); } else if (one.length < titleLen) { for (let j = titleLen - one.length - 1; j >= 0; j--) { one.push(''); } } const tmpArr: number[] = []; for (let j = 0; j < titleLen; j++) { one[j] = one[j].toString(); const tmpLen = getDisplayLength(one[j]); tmpArr.push(tmpLen); if (tmpLen > columnWidth[j]) { columnWidth[j] = tmpLen; } } widthArr[i] = tmpArr; } for (let i = 0; i < titleLen; i++) { columnWidth[i] += 3; } for (let i = 0; i < infoArr.length; i++) { const one = infoArr[i]; const tmpWidthArr = widthArr[i]; for (let j = 0; j < titleLen; j++) { one[j] += ' '.repeat(columnWidth[j] - tmpWidthArr[j]); } if (i === 0) { console.log('\x1b[31m' + getRealStr(one.join('')) + '\x1b[0m'); } else { console.log(getRealStr(one.join(''))); } } function getRealStr(str: string) { while (getDisplayLength(str) > consoleMaxColumns) { str = str.substring(0, str.length - 2); } return str; } function getDisplayLength(str: string) { let realLength = 0; const len = str.length; let charCode = -1; for (let i = 0; i < len; i++) { charCode = str.charCodeAt(i); if (charCode >= 0 && charCode <= 128) { realLength += 1; } else { realLength += 2; } } return realLength; } } } function cli_stop(opts: { 'host': string; 'port': number; 'token': string }) { confirm('stop the server ? (y/n) [no] ', (yes) => { if (!yes) { abort('[ canceled ]'); return; } connectToMaster(opts.host, opts.port, opts.token, function (client) { waitMsg('connect ok! waiting'); client.request({ func: 'stop' }, 3600, function (err) { clearWait(); if (err) { abort(err); return; } abort('the application has stopped, please confirm!'); }); }); }); } function cli_remove(opts: { 'host': string; 'port': number; 'token': string; 'serverIds': string[] }) { if (opts.serverIds.length === 0) { abort('no server input, please use like `omelot remove serverId-1 [serverId-2 ...]` '); return; } let serverIds: string[] = []; for (const id of opts.serverIds) { serverIds.push(...parseServerId(id)); } serverIds = Array.from(new Set(serverIds)); confirm(`remove server: ${serverIds.join(' ')} ? (y/n) [no] `, (yes) => { if (!yes) { abort('[ canceled ]'); return; } connectToMaster(opts.host, opts.port, opts.token, function (client) { waitMsg('connect ok! waiting'); client.request({ func: 'remove', args: serverIds }, 3600, function (err) { clearWait(); if (err) { abort(err); return; } abort('the servers have been removed, please confirm!'); }); }); }); } function cli_removeT(opts: { 'host': string; 'port': number; 'token': string; 'serverTypes': string[] }) { if (opts.serverTypes.length === 0) { abort('no serverType input, please use like `omelot removeT serverType-1 [serverType-2 ...]` '); return; } opts.serverTypes = Array.from(new Set(opts.serverTypes)); confirm(`remove serverType: ${opts.serverTypes.join(' ')} ? (y/n) [no] `, (yes) => { if (!yes) { abort('[ canceled ]'); return; } connectToMaster(opts.host, opts.port, opts.token, function (client) { waitMsg('connect ok! waiting'); client.request({ func: 'removeT', args: opts.serverTypes }, 3600, function (err) { clearWait(); if (err) { abort(err); return; } abort('the serverTypes have been removed, please confirm!'); }); }); }); } function cli_cmd(lans: string[]) { lans = Array.from(new Set(lans)); const routePath = 'config/sys/route.ts'; const serverPath = 'config/cmd.ts'; const nowPath = process.cwd(); const filepath = path.join(nowPath, routePath); if (!fs.existsSync(filepath)) { abort(' -> Not find the script: ' + filepath); } const readStream = fs.createReadStream(filepath); const read_l = readline.createInterface({ input: readStream }); let hasStart = false; const cmdObjArr: Array<{ 'cmd': string; 'note': string }> = []; read_l.on('line', function (line) { line = line.trim(); if (line === '') { return; } if (!hasStart) { if (line.indexOf('export') === 0) hasStart = true; return; } if (line.indexOf(']') === 0) { serverCmd(); clientCmd(); read_l.close(); return; } if (line.indexOf('"') !== 0) { return; } line = line.substring(1); let index = line.indexOf('"'); if (index === -1) { return; } const cmd = line.substring(0, index); let note = ''; index = line.indexOf('//'); if (index !== -1) { note = line.substring(index + 2).trim(); } cmdObjArr.push({ cmd, note }); }); read_l.on('close', function () { console.log('build cmd ok!'); }); function serverCmd() { let endStr = 'export const enum cmd {\n'; let index = 0; for (const one of cmdObjArr) { if (one.note) { endStr += `\t/**\n\t * ${one.note}\n\t */\n`; } let oneStr = one.cmd; if (one.cmd.includes('.')) { const tmpArr = one.cmd.split('.'); oneStr = tmpArr[0] + '_' + tmpArr[1] + '_' + tmpArr[2]; } endStr += `\t${oneStr} = ${index},\n`; index++; } endStr += '}'; const csFilename = path.join(nowPath, serverPath); fs.writeFileSync(csFilename, endStr); } function clientCmd() { const clipath = path.join(nowPath, 'omelot_cli.js'); if (!fs.existsSync(clipath)) { return; } const file = require(path.join(nowPath, 'omelot_cli.js')); if (file.omelot_cmd && typeof file.omelot_cmd === 'function') { file.omelot_cmd(lans, cmdObjArr); } } } function cli_send(opts: { 'host': string; 'port': number; 'token': string; 'serverId': string; 'serverType': string }, argv: string[]) { if (argv.length === 0) { abort('cannot send empty msg'); return; } for (let i = 0; i < argv.length; i++) { argv[i] = argv[i].replace(/ /g, ','); } let serverIds: string[] = []; let serverTypes: string[] = []; const endMsg: { 'serverIds': string[]; 'serverTypes': string[]; 'argv': string[] } = { serverIds: [], serverTypes: [], argv }; if (opts.serverId) { opts.serverId = opts.serverId.replace(/ /g, ','); for (const id of opts.serverId.split(',')) { serverIds.push(...parseServerId(id)); } serverIds = Array.from(new Set(serverIds)); endMsg.serverIds = serverIds; } if (opts.serverType) { opts.serverType = opts.serverType.replace(/ /g, ','); serverTypes = Array.from(new Set(opts.serverType.split(','))); endMsg.serverTypes = serverTypes; } const msg = `sendMsg: { "serverIds": ${JSON.stringify(serverIds)} "serverTypes": ${JSON.stringify(serverTypes)} "argv": ${JSON.stringify(argv)} } (y/n)[no] ? `; confirm(msg, (yes) => { if (!yes) { abort('[ canceled ]'); return; } connectToMaster(opts.host, opts.port, opts.token, function (client) { waitMsg('connect ok! waiting'); client.request({ func: 'send', args: endMsg }, 600, function (err, data: { 'err': string; 'timeoutIds': string[]; 'data': any[] }) { clearWait(); console.log(); client.close(false); if (err) { abort(err); return; } if (data.err) { abort(data.err); return; } const clipath = path.join(process.cwd(), 'omelot_cli.js'); if (!fs.existsSync(clipath)) { console.log(data); return; } const file = require(path.join(process.cwd(), 'omelot_cli.js')); if (file.omelot_send && typeof file.omelot_send === 'function') { file.omelot_send(endMsg, data.timeoutIds, data.data); } else { console.log(data); } }); }); }); } // #endregion function abort(str: string = '') { console.error(str); process.exit(1); } function confirm(msg: string, fn: (yes: boolean) => void) { prompt(msg, function (val) { val = val.trim().toLowerCase(); fn(val === 'y' || val === 'yes'); }); function prompt(msg: string, fn: (data: string) => void) { console.log(msg); process.stdin.setEncoding('ascii'); process.stdin.once('data', function (data) { process.stdin.destroy(); fn(data.toString()); }).resume(); } } function confirmFrontend(msg: string, fn: (engine: string) => void) { prompt(msg, function (val) { val = val.trim().toLowerCase(); if (val === "cocos" || val === "unity") { fn(val); } }); function prompt(msg: string, fn: (data: string) => void) { console.log(msg); process.stdin.setEncoding('ascii'); process.stdin.once('data', function (data) { process.stdin.destroy(); fn(data.toString()); }).resume(); } } function parseServerId(id: string): string[] { if (!id.includes('~')) { return [id]; } const arr = id.split('~'); if (arr.length !== 2) { abort('cannot parse serverId: ' + id); return []; } const numArr: string[] = []; for (let i = 0; i <= 9; i++) { numArr.push(i.toString()); } const startNumArr: string[] = []; const idStartArr: string[] = []; let getId = false; const charArr = arr[0].split(''); for (let i = charArr.length - 1; i >= 0; i--) { if (getId) { idStartArr.unshift(charArr[i]); continue; } if (numArr.includes(charArr[i])) { startNumArr.unshift(charArr[i]); } else { idStartArr.unshift(charArr[i]); getId = true; } } const idStartStr = idStartArr.join(''); if (startNumArr.length === 0) { abort('cannot parse serverId: ' + id); return []; } const startNum = parseInt(startNumArr.join('')); const endNum = parseInt(arr[1]); if (isNaN(startNum) || isNaN(endNum) || startNum > endNum) { abort('cannot parse serverId: ' + id); return []; } const endIds: string[] = []; for (let i = startNum; i <= endNum; i++) { endIds.push(idStartStr + i.toString()); } return endIds; } function clearWait() { clearInterval(waitInterval); } function waitMsg(msg: string) { clearWait(); let index = 0; console.log(msg); waitInterval = setInterval(() => { clearScreen(1); console.log(msg + ' ' + '.'.repeat(index)); index++; index = index % 4; }, 300); } function clearScreen(rowNum: number) { readline.cursorTo(process.stdout, 0); readline.moveCursor(process.stdout, 0, -rowNum); readline.clearScreenDown(process.stdout); } function connectToMaster(host: string, port: number, token: string, cb: (client: ClientProxy) => void) { waitMsg('connecting ' + host + ':' + port.toString()); const client = new ClientProxy(host, port, token, cb); }