UNPKG

qe-cli

Version:

qe-cli

585 lines (584 loc) 24.8 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = require("path"); const PUBLIC_APPDATA_DIR = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share"); const updateNotifier = require('update-notifier'); const commander_1 = require("commander"); const fs = require("fs"); const https = require("https"); const axios_1 = require("axios"); const chalk = require("chalk"); const moment = require("moment"); const boxen = require("boxen"); const ws_client_1 = require("./util/ws_client"); const inquirer = require("inquirer"); const str_1 = require("./util/str"); const ps_1 = require("./util/ps"); const webpack = require("webpack"); let package_info; let version = require(__dirname + "/package.json").version; const pkg_info = require('./package.json'); const server = "https://api.quantengine.com/"; const ws_server = "wss://api.quantengine.com/websocket"; let quantengine_module_pkg_path = path.join(process.cwd(), "./node_modules/quantengine/package.json"); const program = new commander_1.Command(); const prompt = inquirer.createPromptModule(); const axiosInstance = axios_1.default.create({ httpsAgent: new https.Agent({ rejectUnauthorized: false }) }); const type_str_len = 7; let debug_start = false; let debug_session = ""; let debug_id = null; let all_version = ""; const compiler = webpack({ entry: './main.ts', mode: 'production', target: 'node', output: { filename: 'main_out.js', }, }); (function () { let version_list = [`CLI版本: ${chalk.cyanBright(version)}`]; let quantengine_pkg; if (fs.existsSync(quantengine_module_pkg_path)) { quantengine_pkg = require(quantengine_module_pkg_path); } version_list.push(`SDK版本: ${quantengine_pkg?.version ? chalk.cyanBright(quantengine_pkg.version) : chalk.gray("unknown")}`); all_version = version_list.join("\n"); })(); process.on('SIGINT', function () { try_stop().then(() => process.exit(0)).catch(() => { console.log(chalk.redBright("用户强制结束调试")); process.exit(1); }); }); if (fs.existsSync(path.join(process.cwd(), "./package.json"))) { package_info = require(path.join(process.cwd(), "./package.json")); } let key; if (fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json"))) { key = JSON.parse(fs.readFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json")).toString()).key; } if (fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/config.json"))) { let config = {}; try { config = JSON.parse(fs.readFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/config.json")).toString()); } catch (e) { console.error("Parse Error Occurred:", e); } } program .name("qe-cli " + version) .option('-l, --login', "使用开发者密钥登录qe-cli") .option('-i, --init', "初始化一个新策略") .option('-d, --debug ', '调试策略') .option('-p, --publish [patch|minor|major]', "发布策略") .option('-u, --update', "更新qe开发工具包") .helpOption('-h, --help', "帮助信息") .version(all_version, "-v, --version", "版本信息"); program.parse(process.argv); (async () => { await check_update(); const options = program.opts(); if (options.init) { await init(); } else if (options.login) { await login(); } else if (options.debug) { await debug(); } else if (options.publish) { await publish(); } else if (options.update) { await update_sdk(); } else { program.help(); } return 0; })().catch(e => { console.error("Error Occurred:", e); process.exit(1); }).then(() => { process.exit(0); }); async function check_update() { let cli_notifier; let sdk_notifier; let quantengine_pkg; let update_msg = []; let update_msg_str; if (pkg_info && (pkg_info?.name) && (pkg_info?.version)) { cli_notifier = updateNotifier({ pkg: pkg_info, updateCheckInterval: 300 * 1000 }); } if (fs.existsSync(quantengine_module_pkg_path)) { quantengine_pkg = require(quantengine_module_pkg_path); } if (quantengine_pkg && (quantengine_pkg?.name) && (quantengine_pkg?.version)) { sdk_notifier = updateNotifier({ pkg: quantengine_pkg, updateCheckInterval: 300 * 1000 }); } if (cli_notifier?.update && (cli_notifier?.update?.current !== cli_notifier?.update?.latest)) { update_msg.push(`CLI Update Available. ${chalk.gray(cli_notifier?.update?.current)}${chalk.greenBright(cli_notifier?.update?.latest)}`); } if (sdk_notifier?.update && (sdk_notifier?.update?.current !== sdk_notifier?.update?.latest)) { update_msg.push(`SDK Update Available. ${chalk.gray(sdk_notifier?.update?.current)}${chalk.greenBright(sdk_notifier?.update?.latest)}`); } if (update_msg.length > 0) { update_msg_str = update_msg.join("\n"); } if (update_msg_str) { console.log(boxen(`${update_msg_str}\nRun ${chalk.cyanBright('qe-cli -u')} to update.`, { borderColor: "yellow", padding: 1, margin: 1, borderStyle: 'round', textAlignment: 'center' })); } } async function init() { if (!(await try_login(key))) { return; } let init_sdk = false; if (package_info) { console.log(chalk.redBright("此目录已包含package.json配置,请在空目录下执行 qe-cli -i 初始化。")); return; } if (!package_info) { package_info = { name: "strategy_project", version: "1.0.0", description: "Strategy Project Workspace", QuantEngine: {}, dependencies: {} }; init_sdk = true; } package_info.QuantEngine = package_info.QuantEngine || {}; let project; while (true) { let result = await prompt([ { type: 'input', name: "project", message: "策略名", default: "strategy_" + str_1.str.randomStr(), prefix: ">", suffix: ":" } ]); if (package_info.QuantEngine[result.project]) { console.log(chalk.redBright(" 该策略已存在")); } else { project = result.project; break; } } process.stdout.write(chalk.blueBright("正在创建策略...")); let response = await safe_api("strategy/create", key, { name: project, launcher_id: 2 }); if (response.code === 1) { console.log(chalk.yellowBright(" 创建完成")); package_info.name = project; package_info.QuantEngine = response.data.id; package_info.scripts = { "debug": "qe-cli -d", "publish": "qe-cli -p" }; fs.writeFileSync("package.json", JSON.stringify(package_info, null, 2), { encoding: "utf-8" }); let ts_code_template = fs.readFileSync(path.join(__dirname, "./template/template_ts"), { encoding: "utf-8" }); fs.writeFileSync(path.join(process.cwd(), `./main.ts`), ts_code_template, { encoding: "utf-8" }); fs.writeFileSync(path.join(process.cwd(), `./config.json`), "[]", { encoding: "utf-8" }); fs.writeFileSync(path.join(process.cwd(), `./action.json`), "[]", { encoding: "utf-8" }); if (init_sdk) { process.stdout.write(chalk.blueBright("正在安装策略SDK...")); await ps_1.ps.exec("npm i qe-runtime@latest"); await ps_1.ps.exec("npm i -D typescript@4.3.5"); let tsconfig_template = fs.readFileSync(path.join(__dirname, "./template/tsconfig"), { encoding: "utf-8" }); fs.writeFileSync(path.join(process.cwd(), `./tsconfig.json`), tsconfig_template, { encoding: "utf-8" }); } console.log(chalk.yellowBright(" 策略创建成功, 名称:" + project + " ID:" + response.data.id + " 开发者权限使用量:" + response.data.created + "/" + response.data.limit)); } else { console.log(chalk.redBright(" " + response.error)); } } async function login() { let key; if (fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json"))) { key = JSON.parse(fs.readFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json")).toString()).key; } while (true) { let result = await prompt([ { type: 'input', name: "token", message: "请输入开发者密钥", default: key, prefix: ">", suffix: ":" } ]); process.stdout.write(chalk.blueBright("正在校验开发者密钥...")); key = result.token; let response = await safe_api("user/check", key, {}); if (response.code === 1) { if (!fs.existsSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli"))) { fs.mkdirSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli")); } fs.writeFileSync(path.join(PUBLIC_APPDATA_DIR, "qe-cli/token.json"), JSON.stringify({ key: key })); console.log(chalk.yellowBright(" 校验通过")); break; } else { console.log(chalk.redBright(response.error)); } } } async function debug() { if (!(await try_login(key))) { return; } let strategy_code; let actions; let config; process.stdout.write(chalk.blueBright("正在编译策略代码...")); let start = Date.now(); if (fs.existsSync(path.join(process.cwd(), "dist", "main_out.js"))) { await fs.unlinkSync(path.join(process.cwd(), "dist", "main_out.js")); } await new Promise((resolve, reject) => { compiler.run((err, stats) => { if (stats?.hasErrors()) { console.log(chalk.redBright("策略编译出错了...")); reject(stats.compilation.errors); } compiler.close((closeErr) => { if (closeErr) { console.log(closeErr); reject(closeErr); } return resolve(true); }); }); }); console.log(chalk.yellowBright(" 编译完成,耗时:" + (Date.now() - start) + "毫秒")); try { strategy_code = fs.readFileSync(path.join(process.cwd(), "dist", "main_out.js")).toString("utf8"); } catch (e) { console.log(chalk.redBright("无法读取策略代码文件,请检查策略代码。")); process.exit(0); } try { actions = JSON.parse(fs.readFileSync(path.join(process.cwd(), "action.json")).toString("utf8")); } catch (e) { console.log(chalk.redBright("无法读取策略动作文件["), chalk.blueBright(`action.json`), chalk.redBright("]。")); process.exit(0); } try { config = JSON.parse(fs.readFileSync(path.join(process.cwd(), "config.json")).toString("utf8")); } catch (e) { console.log(chalk.redBright("无法读取策略配置文件["), chalk.blueBright(`config.json`), chalk.redBright("]。")); process.exit(0); } if (!package_info.QuantEngine) { return console.log(chalk.redBright("无法找到这个策略的ID。")); } process.stdout.write(chalk.blueBright("正在连接调试日志服务器...")); let key_response = await safe_api("strategy/get_debug_info", key, { strategy_id: package_info.QuantEngine }); if (key_response.code !== 1) { return console.log(chalk.redBright("CODE:" + key_response.code + " Get Debug Token Error:" + key_response.error || "Internal Error")); } await ConnectLogServer(key_response.data.user_strategy_id); console.log(chalk.yellowBright(" 完成")); process.stdout.write(chalk.blueBright("正在启动调试节点...")); let response = await safe_api("strategy/debug", key, { strategy_id: package_info.QuantEngine, actions: actions, config_template: config, strategy_code_type: "js", strategy_code: strategy_code, bot_hook: false, strategy_hook: false, }); if (response.code !== 1) { return console.log(chalk.redBright("CODE:" + response.code + " Debug Service Error:" + response.error)); } else { console.log(chalk.yellowBright(" 完成")); console.log(chalk.cyanBright("======================================================")); console.log(chalk.yellowBright("开始调试策略,节点区域:" + response.node_name + " 实盘会话:" + response.session)); console.log(chalk.yellowBright("WebHook状态: "), "Bot ->", response.bot_hook_enable ? chalk.blueBright("Enable") : chalk.gray("Disable"), " Strategy ->", response?.strategy_hook_enable ? chalk.blueBright("Enable") : chalk.gray("Disable")); console.log(chalk.yellowBright("实盘监控地址: " + response.shout_url)); if (response.hook_enable) { try { let hook_address_response = await safe_api("user_strategy/get_hook_address", key, { user_strategy_id: response.debug_id_enc }); if (hook_address_response.code === 1) { console.log(chalk.greenBright("BotHookURL:"), hook_address_response?.data?.result || ""); } else { console.log(chalk.gray("Get BotHook URL Error ,CODE:"), chalk.gray(hook_address_response.code)); } } catch (e) { console.log(chalk.gray("Get Bot WebHook URL Error ,You Can Restart Debug If Necessary."), chalk.gray(e.toString())); } } if (response.strategy_hook_enable) { try { let hook_address_response = await safe_api("cli/strategy/get_hook_address", key, { uuid: package_info.QuantEngine }); if (hook_address_response.code === 1) { console.log(chalk.greenBright("StrategyHookURL:"), hook_address_response?.data?.result || ""); } else { console.log(chalk.gray("Get StrategyHook URL Error ,CODE:"), chalk.gray(hook_address_response.code)); } } catch (e) { console.log(chalk.gray("Get Strategy WebHook URL Error ,You Can Restart Debug If Necessary."), chalk.gray(e.toString())); } } console.log(chalk.cyanBright("======================================================")); debug_id = response.debug_id_enc; debug_start = true; debug_session = response.session; let offline_count = 0; await ps_1.ps.sleep(1000); while (true) { let result = await safe_api("bot/get_user_strategy", key, { session: response.session, }); if (result.code === 1 && !result.data.running) { if (offline_count % 3 === 0 && offline_count > 0) { console.log(chalk.gray("未能获取到策略运行状态...")); } if (offline_count >= 20) { console.log(chalk.blueBright("由于长时间未能获取到策略运行状态,本次调试结束。")); process.exit(0); } offline_count++; } else if (result.code !== 1) { offline_count++; } if (result.code === 1 && result.data.running) { offline_count = 0; } await ps_1.ps.sleep(1000); } } } async function publish() { if (!(await try_login(key))) { return; } let publish_type = process.argv[3] || "patch"; process.stdout.write(chalk.blueBright("正在编译策略代码...")); let start = Date.now(); if (fs.existsSync(path.join(process.cwd(), "dist", "main_out.js"))) { await fs.unlinkSync(path.join(process.cwd(), "dist", "main_out.js")); } await new Promise((resolve, reject) => { compiler.run((err, stats) => { if (stats?.hasErrors()) { console.log(chalk.redBright("策略编译出错了...")); reject(stats.compilation.errors); } compiler.close((closeErr) => { if (closeErr) { console.log(closeErr); reject(closeErr); } return resolve(true); }); }); }); console.log(chalk.yellowBright(" 编译完成,耗时:" + (Date.now() - start) + "毫秒")); let strategy_code; let actions; let config; try { strategy_code = fs.readFileSync(path.join(process.cwd(), "dist", "main_out.js")).toString("utf8"); } catch (e) { console.log(chalk.redBright("无法读取策略代码文件,请检查策略代码。")); process.exit(0); } try { actions = JSON.parse(fs.readFileSync(path.join(process.cwd(), "action.json")).toString("utf8")); } catch (e) { console.log(chalk.redBright("无法读取策略动作文件["), chalk.blueBright(`action.json`), chalk.redBright("]。")); process.exit(0); } try { config = JSON.parse(fs.readFileSync(path.join(process.cwd(), "config.json")).toString("utf8")); } catch (e) { console.log(chalk.redBright("无法读取策略配置文件["), chalk.blueBright(`config.json`), chalk.redBright("]。")); process.exit(0); } process.stdout.write(chalk.blueBright("正在发布策略...")); let response = await safe_api("strategy/publish", key, { strategy_id: package_info.QuantEngine, strategy_code: strategy_code, actions: actions, publish_type: publish_type, strategy_code_type: "js", strategy_code_src: strategy_code }); if (response.code !== 1) { return console.log(" " + chalk.redBright(response.error)); } else { console.log(chalk.yellowBright(" 发布成功,当前版本ID:" + response.data.id + " 版本号: " + response.data.old_version + " => " + response.data.current_version)); } } async function update_sdk() { const qe_runtime_old_version = package_info?.dependencies?.["qe-runtime"]?.replace("^", "") || null; if (qe_runtime_old_version) { process.stdout.write(chalk.blueBright("正在更新qe-runtime...")); let remote_version = await axios_1.default.get("https://registry.npmjs.org/qe-runtime/latest"); if (qe_runtime_old_version == remote_version?.data?.version) { console.log(chalk.yellowBright(" " + qe_runtime_old_version + " == " + remote_version?.data?.version)); } else { await ps_1.ps.exec("npm i qe-runtime@latest"); console.log(chalk.yellowBright(" " + qe_runtime_old_version + " => " + package_info?.dependencies?.["qe-runtime"]?.replace("^", ""))); } } let updated_cli_info = JSON.parse(fs.readFileSync(path.join(__dirname, "./package.json"), { encoding: "utf-8" })); const cli_old_version = updated_cli_info?.version || null; process.stdout.write(chalk.blueBright("正在更新qe-cli...")); let remote_version = await axios_1.default.get("https://registry.npmjs.org/qe-cli/latest"); if (cli_old_version == remote_version?.data?.version) { console.log(chalk.yellowBright(" " + cli_old_version + " == " + remote_version?.data?.version)); } else { await ps_1.ps.exec("npm i -g qe-cli@latest"); updated_cli_info = JSON.parse(fs.readFileSync(path.join(__dirname, "./package.json"), { encoding: "utf-8" })); console.log(chalk.yellowBright(" " + cli_old_version + " => " + updated_cli_info?.version)); } process.exit(0); } async function try_login(key) { process.stdout.write(chalk.blueBright("正在校验开发者密钥...")); let response = await safe_api("user/check", key, {}); if (response.code === 1) { console.log(chalk.yellowBright(" 校验通过")); return true; } else { console.log(chalk.redBright(" " + response.error)); return false; } } async function try_stop() { try { if (debug_id) { let response = await safe_api("strategy/stop", key, { user_strategy_id: debug_id, }); } } catch (e) { } } async function safe_api(api, token, data) { try { if (!token || token.length != 32) { return { code: -997, error: `Token Length Error.` }; } let response = await axiosInstance.post(server + api, { x: str_1.str.aes_encrypt(JSON.stringify(data), token, token.substring(0, 16)) }, { headers: { token: token, "user-agent": "qe-cli/" + version }, timeout: 10 * 1000, }); if (response.status == 200 && response.data.x) { return JSON.parse(str_1.str.aes_decrypt(response.data.x, token, token.substring(0, 16))); } else { return { code: -998, error: "Not Safe Api" }; } } catch (e) { return { code: -999, error: e.toString() }; } } async function ConnectLogServer(key, connect_info = true) { return new Promise(function (resolve, reject) { const client = new ws_client_1.ws_client({ url: ws_server, debug: false }); client.on("connect", (id) => { client.send({ event: "robot", data: [key] }); return resolve(true); }); client.on("disconnect", (id) => { console.log(chalk.redBright("日志服务器已断开,正在重连...")); }); client.on("onmessage", (event, args, cb) => { if (args?.log?.type) { switch (args?.log?.type) { case "info": console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.gray(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message); break; case "success": console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.green(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message); break; case "warning": console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.yellow(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message); break; case "error": console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.red(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message); break; case "debug": console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.yellow(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message); break; case "system": console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.cyan(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message); break; default: console.log(moment(args?.log?.add_time || 0).format("HH:mm:ss"), chalk.bold.gray(`${args?.log.type}`.padEnd(type_str_len, " ")), args?.log.message); } if (args?.log.stop_sig === true) { console.log(chalk.cyanBright("======================================================")); console.log(chalk.blueBright("策略运行结束,调试完毕。")); process.exit(0); } } }); client.connect().then(); }); }