node-karin
Version: 
Lightweight, efficient, concise, and stable robot framework.
661 lines (657 loc) • 23.3 kB
JavaScript
import os from 'node:os';
import fs from 'node:fs';
import require$$0 from 'fs';
import require$$1 from 'path';
import require$$2 from 'os';
import require$$3 from 'crypto';
import path from 'node:path';
import { createServer } from 'node:net';
import { fileURLToPath } from 'node:url';
import { fork } from 'node:child_process';
// src/start/index.ts
function _mergeNamespaces(n, m) {
  for (var i = 0; i < m.length; i++) {
    const e = m[i];
    if (typeof e !== "string" && !Array.isArray(e)) {
      for (const k in e) {
        if (k !== "default" && !(k in n)) {
          const d = Object.getOwnPropertyDescriptor(e, k);
          if (d) {
            Object.defineProperty(n, k, d.get ? d : {
              enumerable: true,
              get: () => e[k]
            });
          }
        }
      }
    }
  }
  return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: "Module" }));
}
function getDefaultExportFromCjs(x) {
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
}
var main$1 = { exports: {} };
var version = "16.5.0";
var require$$4 = {
  version
};
var hasRequiredMain;
function requireMain() {
  if (hasRequiredMain) return main$1.exports;
  hasRequiredMain = 1;
  const fs2 = require$$0;
  const path2 = require$$1;
  const os2 = require$$2;
  const crypto = require$$3;
  const packageJson = require$$4;
  const version2 = packageJson.version;
  const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
  function parse2(src) {
    const obj = {};
    let lines = src.toString();
    lines = lines.replace(/\r\n?/mg, "\n");
    let match;
    while ((match = LINE.exec(lines)) != null) {
      const key = match[1];
      let value = match[2] || "";
      value = value.trim();
      const maybeQuote = value[0];
      value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
      if (maybeQuote === '"') {
        value = value.replace(/\\n/g, "\n");
        value = value.replace(/\\r/g, "\r");
      }
      obj[key] = value;
    }
    return obj;
  }
  function _parseVault2(options) {
    const vaultPath = _vaultPath(options);
    const result = DotenvModule.configDotenv({ path: vaultPath });
    if (!result.parsed) {
      const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
      err.code = "MISSING_DATA";
      throw err;
    }
    const keys = _dotenvKey(options).split(",");
    const length = keys.length;
    let decrypted;
    for (let i = 0; i < length; i++) {
      try {
        const key = keys[i].trim();
        const attrs = _instructions(result, key);
        decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
        break;
      } catch (error) {
        if (i + 1 >= length) {
          throw error;
        }
      }
    }
    return DotenvModule.parse(decrypted);
  }
  function _warn(message) {
    console.log(`[dotenv@${version2}][WARN] ${message}`);
  }
  function _debug(message) {
    console.log(`[dotenv@${version2}][DEBUG] ${message}`);
  }
  function _dotenvKey(options) {
    if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
      return options.DOTENV_KEY;
    }
    if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
      return process.env.DOTENV_KEY;
    }
    return "";
  }
  function _instructions(result, dotenvKey) {
    let uri;
    try {
      uri = new URL(dotenvKey);
    } catch (error) {
      if (error.code === "ERR_INVALID_URL") {
        const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
        err.code = "INVALID_DOTENV_KEY";
        throw err;
      }
      throw error;
    }
    const key = uri.password;
    if (!key) {
      const err = new Error("INVALID_DOTENV_KEY: Missing key part");
      err.code = "INVALID_DOTENV_KEY";
      throw err;
    }
    const environment = uri.searchParams.get("environment");
    if (!environment) {
      const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
      err.code = "INVALID_DOTENV_KEY";
      throw err;
    }
    const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
    const ciphertext = result.parsed[environmentKey];
    if (!ciphertext) {
      const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
      err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
      throw err;
    }
    return { ciphertext, key };
  }
  function _vaultPath(options) {
    let possibleVaultPath = null;
    if (options && options.path && options.path.length > 0) {
      if (Array.isArray(options.path)) {
        for (const filepath of options.path) {
          if (fs2.existsSync(filepath)) {
            possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
          }
        }
      } else {
        possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
      }
    } else {
      possibleVaultPath = path2.resolve(process.cwd(), ".env.vault");
    }
    if (fs2.existsSync(possibleVaultPath)) {
      return possibleVaultPath;
    }
    return null;
  }
  function _resolveHome(envPath) {
    return envPath[0] === "~" ? path2.join(os2.homedir(), envPath.slice(1)) : envPath;
  }
  function _configVault2(options) {
    const debug = Boolean(options && options.debug);
    if (debug) {
      _debug("Loading env from encrypted .env.vault");
    }
    const parsed = DotenvModule._parseVault(options);
    let processEnv = process.env;
    if (options && options.processEnv != null) {
      processEnv = options.processEnv;
    }
    DotenvModule.populate(processEnv, parsed, options);
    return { parsed };
  }
  function configDotenv2(options) {
    const dotenvPath = path2.resolve(process.cwd(), ".env");
    let encoding = "utf8";
    const debug = Boolean(options && options.debug);
    if (options && options.encoding) {
      encoding = options.encoding;
    } else {
      if (debug) {
        _debug("No encoding is specified. UTF-8 is used by default");
      }
    }
    let optionPaths = [dotenvPath];
    if (options && options.path) {
      if (!Array.isArray(options.path)) {
        optionPaths = [_resolveHome(options.path)];
      } else {
        optionPaths = [];
        for (const filepath of options.path) {
          optionPaths.push(_resolveHome(filepath));
        }
      }
    }
    let lastError;
    const parsedAll = {};
    for (const path22 of optionPaths) {
      try {
        const parsed = DotenvModule.parse(fs2.readFileSync(path22, { encoding }));
        DotenvModule.populate(parsedAll, parsed, options);
      } catch (e) {
        if (debug) {
          _debug(`Failed to load ${path22} ${e.message}`);
        }
        lastError = e;
      }
    }
    let processEnv = process.env;
    if (options && options.processEnv != null) {
      processEnv = options.processEnv;
    }
    DotenvModule.populate(processEnv, parsedAll, options);
    if (lastError) {
      return { parsed: parsedAll, error: lastError };
    } else {
      return { parsed: parsedAll };
    }
  }
  function config2(options) {
    if (_dotenvKey(options).length === 0) {
      return DotenvModule.configDotenv(options);
    }
    const vaultPath = _vaultPath(options);
    if (!vaultPath) {
      _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
      return DotenvModule.configDotenv(options);
    }
    return DotenvModule._configVault(options);
  }
  function decrypt2(encrypted, keyStr) {
    const key = Buffer.from(keyStr.slice(-64), "hex");
    let ciphertext = Buffer.from(encrypted, "base64");
    const nonce = ciphertext.subarray(0, 12);
    const authTag = ciphertext.subarray(-16);
    ciphertext = ciphertext.subarray(12, -16);
    try {
      const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
      aesgcm.setAuthTag(authTag);
      return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
    } catch (error) {
      const isRange = error instanceof RangeError;
      const invalidKeyLength = error.message === "Invalid key length";
      const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
      if (isRange || invalidKeyLength) {
        const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
        err.code = "INVALID_DOTENV_KEY";
        throw err;
      } else if (decryptionFailed) {
        const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
        err.code = "DECRYPTION_FAILED";
        throw err;
      } else {
        throw error;
      }
    }
  }
  function populate2(processEnv, parsed, options = {}) {
    const debug = Boolean(options && options.debug);
    const override = Boolean(options && options.override);
    if (typeof parsed !== "object") {
      const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
      err.code = "OBJECT_REQUIRED";
      throw err;
    }
    for (const key of Object.keys(parsed)) {
      if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
        if (override === true) {
          processEnv[key] = parsed[key];
        }
        if (debug) {
          if (override === true) {
            _debug(`"${key}" is already defined and WAS overwritten`);
          } else {
            _debug(`"${key}" is already defined and was NOT overwritten`);
          }
        }
      } else {
        processEnv[key] = parsed[key];
      }
    }
  }
  const DotenvModule = {
    configDotenv: configDotenv2,
    _configVault: _configVault2,
    _parseVault: _parseVault2,
    config: config2,
    decrypt: decrypt2,
    parse: parse2,
    populate: populate2
  };
  main$1.exports.configDotenv = DotenvModule.configDotenv;
  main$1.exports._configVault = DotenvModule._configVault;
  main$1.exports._parseVault = DotenvModule._parseVault;
  main$1.exports.config = DotenvModule.config;
  main$1.exports.decrypt = DotenvModule.decrypt;
  main$1.exports.parse = DotenvModule.parse;
  main$1.exports.populate = DotenvModule.populate;
  main$1.exports = DotenvModule;
  return main$1.exports;
}
var mainExports = requireMain();
var main = /* @__PURE__ */ getDefaultExportFromCjs(mainExports);
var dotenv = /* @__PURE__ */ _mergeNamespaces({
  __proto__: null,
  default: main
}, [mainExports]);
// ../../node_modules/.pnpm/@karinjs+dotenv@1.1.2/node_modules/@karinjs/dotenv/dist/index.js
var {
  // @ts-ignore
  _configVault,
  _parseVault
} = dotenv;
mainExports.config;
mainExports.configDotenv;
mainExports.decrypt;
mainExports.parse;
mainExports.populate;
// ../../node_modules/.pnpm/@karinjs+dotenv@1.1.2/node_modules/@karinjs/dotenv/index.js
var app = dotenv.default;
var dotenv_default = app;
var ProcessManager = class {
  /** 是否已经启动 */
  isStarted = false;
  /** 子进程实例 */
  childProcess = null;
  /** 重启延迟时间 */
  RESTART_DELAY_MS = 200;
  /** 模块名称 */
  MODULE_NAME = "ProcessManager";
  /** 启动时间 */
  startTime = Date.now();
  /** 进程统计信息 */
  processStats = {
    /** 重启次数 */
    restartCount: 0,
    /** 最后重启时间 */
    lastRestartTime: 0
  };
  /** 默认HTTP端口 */
  DEFAULT_HTTP_PORT = 7777;
  // ANSI 颜色代码
  COLORS = {
    green: "\x1B[32m",
    yellow: "\x1B[33m",
    red: "\x1B[31m",
    blue: "\x1B[34m",
    magenta: "\x1B[35m",
    cyan: "\x1B[36m",
    gray: "\x1B[90m",
    reset: "\x1B[0m"
  };
  /** exit 事件监听器引用 */
  exitListenerRef = null;
  constructor() {
    process.on("exit", this.stop.bind(this));
    this.logSystemInfo();
  }
  /**
   * 启动子进程
   */
  start(options = {}) {
    if (this.isAlreadyRunning()) {
      this.log(`\u5B50\u8FDB\u7A0B\u5DF2\u5728\u8FD0\u884C | PID: ${this.childProcess.pid}`);
      return this.childProcess;
    }
    this.isStarted = true;
    const entryPath = this.resolveEntryPath();
    const args = options.reloadDeps ? [`--reload-timestamp=${Date.now()}`] : [];
    this.childProcess = fork(entryPath, args);
    const pid = this.childProcess.pid;
    this.setupEventListeners();
    this.log(`\u542F\u52A8\u5B50\u8FDB\u7A0B | PID: ${pid} | \u5165\u53E3: ${path.basename(entryPath)} | \u53C2\u6570: ${args.join(" ") || "\u65E0"}`);
    return this.childProcess;
  }
  /**
   * 重启子进程
   */
  async restart(reloadDeps = false) {
    if (!this.childProcess) {
      this.logWarn("\u91CD\u542F\u5931\u8D25 | \u539F\u56E0: \u5B50\u8FDB\u7A0B\u4E0D\u5B58\u5728 | \u64CD\u4F5C: \u76F4\u63A5\u542F\u52A8\u65B0\u8FDB\u7A0B");
      return this.start({ reloadDeps });
    }
    const oldPid = this.childProcess.pid;
    this.processStats.restartCount++;
    this.processStats.lastRestartTime = Date.now();
    this.log(`\u51C6\u5907\u91CD\u542F\u5B50\u8FDB\u7A0B | \u5F53\u524DPID: ${oldPid} | \u91CD\u8F7D\u4F9D\u8D56: ${reloadDeps ? "\u662F" : "\u5426"} | \u91CD\u542F\u6B21\u6570: ${this.processStats.restartCount}`);
    try {
      await this.terminateChildProcess();
      this.isStarted = false;
      const newChild = this.start({ reloadDeps });
      const duration = Date.now() - this.processStats.lastRestartTime;
      this.log(`\u91CD\u542F\u5B8C\u6210 | \u65E7PID: ${oldPid} | \u65B0PID: ${newChild.pid} | \u8017\u65F6: ${duration}ms`);
      return newChild;
    } catch (error) {
      if (error.code === "ESRCH") {
        this.logWarn(`\u91CD\u542F\u8FC7\u7A0B\u4E2D\u53D1\u73B0\u8FDB\u7A0B\u5DF2\u4E0D\u5B58\u5728 | \u9519\u8BEF\u7801: ${error.code} | \u64CD\u4F5C: \u542F\u52A8\u65B0\u8FDB\u7A0B`);
        this.isStarted = false;
        return this.start({ reloadDeps: true });
      }
      this.logError(`\u91CD\u542F\u5931\u8D25 | \u9519\u8BEF: ${error.message} | \u4EE3\u7801: ${error.code || "\u672A\u77E5"}`);
      return null;
    }
  }
  /**
   * 停止子进程并退出父进程
   */
  stop() {
    if (this.childProcess) {
      const pid = this.childProcess.pid;
      const uptime = this.getProcessUptime();
      this.log(`\u6B63\u5728\u5173\u95ED\u5B50\u8FDB\u7A0B | PID: ${pid} | \u8FD0\u884C\u65F6\u95F4: ${uptime}`);
      this.terminateChildProcess();
      this.childProcess = null;
      this.isStarted = false;
    }
    const totalUptime = this.getTotalUptime();
    this.log(`\u7236\u8FDB\u7A0B\u9000\u51FA | \u603B\u8FD0\u884C\u65F6\u95F4: ${totalUptime} | \u91CD\u542F\u6B21\u6570: ${this.processStats.restartCount}`);
    process.exit(0);
  }
  /**
   * 检查子进程是否已经在运行
   */
  isAlreadyRunning() {
    return this.isStarted && this.childProcess !== null;
  }
  /**
   * 设置子进程事件监听器
   */
  setupEventListeners() {
    if (!this.childProcess) return;
    this.childProcess.on("message", this.handleChildMessage.bind(this));
    if (this.exitListenerRef) {
      this.childProcess.off("exit", this.exitListenerRef);
    }
    this.exitListenerRef = this.handleChildExit.bind(this);
    this.childProcess.once("exit", this.exitListenerRef);
  }
  /**
   * 处理子进程发送的消息
   */
  async handleChildMessage(message) {
    if (typeof message !== "string") return;
    try {
      const { type, reloadDeps } = JSON.parse(message);
      if (type === "restart" || type === "rs") {
        this.log(`\u6536\u5230\u6D88\u606F | \u7C7B\u578B: restart | \u91CD\u8F7D\u4F9D\u8D56: ${reloadDeps ? "\u662F" : "\u5426"}`);
        const child = await this.restart(reloadDeps);
        if (!child) {
          this.logWarn("\u91CD\u542F\u64CD\u4F5C\u5931\u8D25 | \u65E0\u6CD5\u83B7\u53D6\u65B0\u7684\u5B50\u8FDB\u7A0B");
        } else {
          this.childProcess = child;
        }
        return;
      }
      if (type === "stop") {
        this.log("\u6536\u5230\u6D88\u606F | \u7C7B\u578B: stop | \u64CD\u4F5C: \u505C\u6B62\u8FDB\u7A0B");
        this.stop();
        return;
      }
      this.logWarn(`\u6536\u5230\u672A\u77E5\u6D88\u606F | \u7C7B\u578B: ${type || "\u672A\u77E5"} | \u5185\u5BB9: ${JSON.stringify(message).substring(0, 100)}`);
    } catch (error) {
      this.logError(`\u6D88\u606F\u5904\u7406\u9519\u8BEF | \u539F\u59CB\u6D88\u606F: ${String(message).substring(0, 50)}`, error);
    }
  }
  /**
   * 处理子进程退出事件
   */
  handleChildExit(code, signal) {
    const exitType = signal ? `\u4FE1\u53F7: ${signal}` : `\u9000\u51FA\u7801: ${code ?? "\u672A\u77E5"}`;
    const uptime = this.getProcessUptime();
    this.log(`\u5B50\u8FDB\u7A0B\u9000\u51FA | ${exitType} | \u8FD0\u884C\u65F6\u95F4: ${uptime}`);
    this.stop();
  }
  /**
   * 等待端口释放
   * @param port - 需要检查的端口号
   * @param maxAttempts - 最大尝试次数
   * @param interval - 检查间隔(ms)
   * @returns 端口是否可用
   */
  async waitForPortRelease(port, maxAttempts = 30, interval = 500) {
    const checkPort = (port2) => {
      return new Promise((resolve) => {
        const server = createServer();
        server.once("error", () => {
          server.close();
          resolve(false);
        });
        server.once("listening", () => {
          server.close();
          resolve(true);
        });
        server.listen(port2, "127.0.0.1");
      });
    };
    const isProcessAlive = (pid) => {
      try {
        process.kill(pid, 0);
        return true;
      } catch {
        return false;
      }
    };
    for (let attempt = 0; attempt < maxAttempts; attempt++) {
      if (this.childProcess?.pid && !isProcessAlive(this.childProcess.pid)) {
        this.log(`\u5B50\u8FDB\u7A0B\u5DF2\u9000\u51FA | PID: ${this.childProcess.pid}`);
        const portAvailable2 = await checkPort(port);
        if (portAvailable2) {
          this.log(`\u7AEF\u53E3 ${port} \u5DF2\u91CA\u653E\uFF0C\u53EF\u4EE5\u4F7F\u7528`);
          return true;
        } else {
          this.logWarn(`\u5B50\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0C\u4F46\u7AEF\u53E3 ${port} \u4ECD\u88AB\u5360\u7528 (\u53EF\u80FD\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5360\u7528)`);
        }
      }
      const portAvailable = await checkPort(port);
      if (portAvailable) {
        this.log(`\u7AEF\u53E3 ${port} \u5DF2\u91CA\u653E\uFF0C\u53EF\u4EE5\u4F7F\u7528`);
        return true;
      }
      this.logWarn(`\u7AEF\u53E3 ${port} \u4ECD\u88AB\u5360\u7528\uFF0C\u7B49\u5F85\u91CA\u653E... (${attempt + 1}/${maxAttempts})`);
      try {
        if (this?.childProcess?.pid && isProcessAlive(this.childProcess.pid)) {
          this.log(`\u5C1D\u8BD5\u7EC8\u6B62\u5B50\u8FDB\u7A0B | PID: ${this.childProcess.pid}`);
          process.kill(this.childProcess.pid);
        }
      } catch {
      }
      await new Promise((resolve) => setTimeout(resolve, interval));
    }
    this.logError(`\u7AEF\u53E3 ${port} \u5728 ${maxAttempts} \u6B21\u5C1D\u8BD5\u540E\u4ECD\u88AB\u5360\u7528`);
    return false;
  }
  /**
   * 获取HTTP端口号
   * @returns HTTP端口号
   */
  getHttpPort() {
    try {
      const envPath = path.resolve(process.cwd(), ".env");
      if (fs.existsSync(envPath)) {
        const envConfig = dotenv_default.parse(fs.readFileSync(envPath));
        if (envConfig.HTTP_PORT) {
          return Number(envConfig.HTTP_PORT);
        }
      }
      return this.DEFAULT_HTTP_PORT;
    } catch (error) {
      this.logWarn(`\u8BFB\u53D6HTTP\u7AEF\u53E3\u5931\u8D25: ${error.message}\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u7AEF\u53E3${this.DEFAULT_HTTP_PORT}`);
      return this.DEFAULT_HTTP_PORT;
    }
  }
  /**
   * 终止子进程
   */
  async terminateChildProcess() {
    if (!this.childProcess) return;
    if (this.exitListenerRef) {
      this.childProcess.off("exit", this.exitListenerRef);
      this.exitListenerRef = null;
    }
    try {
      const pid = this.childProcess.pid;
      this.log(`\u53D1\u9001\u7EC8\u6B62\u4FE1\u53F7 | PID: ${pid} | \u4FE1\u53F7: SIGTERM`);
      this.childProcess.kill("SIGTERM");
      this.log(`\u7B49\u5F85\u8FDB\u7A0B\u7EC8\u6B62 | PID: ${pid} | \u8D85\u65F6: ${this.RESTART_DELAY_MS}ms`);
      const port = this.getHttpPort();
      await this.waitForPortRelease(port);
      try {
        if (this?.childProcess?.pid) {
          this.log(`\u786E\u4FDD\u8FDB\u7A0B\u7EC8\u6B62 | PID: ${pid} | \u53D1\u9001\u5F3A\u5236\u7EC8\u6B62\u4FE1\u53F7`);
          process.kill(this.childProcess.pid);
        }
      } catch {
      }
    } catch {
    }
  }
  /**
   * 解析子进程入口文件路径
   */
  resolveEntryPath() {
    const currentFilePath = fileURLToPath(import.meta.url);
    const currentDirPath = path.dirname(currentFilePath);
    const isESM = import.meta.url.includes(".mjs");
    const localEntryPath = path.join(currentDirPath, isESM ? "app.mjs" : "app.ts");
    return fs.existsSync(localEntryPath) ? localEntryPath : path.join(process.cwd(), "node_modules", "node-karin", "dist", "start", "app.mjs");
  }
  /**
   * 获取当前时间戳,格式为 HH:MM:SS.mmm
   */
  getTimestamp() {
    const now = /* @__PURE__ */ new Date();
    const hours = now.getHours().toString().padStart(2, "0");
    const minutes = now.getMinutes().toString().padStart(2, "0");
    const seconds = now.getSeconds().toString().padStart(2, "0");
    const milliseconds = now.getMilliseconds().toString().padStart(3, "0");
    return `${hours}:${minutes}:${seconds}.${milliseconds}`;
  }
  /**
   * 创建日志前缀
   */
  createLogPrefix(level) {
    const timestamp = this.getTimestamp();
    const prefix = `[Karin][${timestamp}][${level}]`;
    return `${this.COLORS.green}${prefix}${this.COLORS.reset}`;
  }
  /**
   * 获取进程运行时间的友好字符串
   */
  getProcessUptime() {
    if (!this.childProcess?.pid) return "0s";
    const uptime = (Date.now() - this.processStats.lastRestartTime) / 1e3;
    if (uptime < 60) return `${uptime.toFixed(1)}s`;
    if (uptime < 3600) return `${(uptime / 60).toFixed(1)}m`;
    return `${(uptime / 3600).toFixed(1)}h`;
  }
  /**
   * 获取总运行时间的友好字符串
   */
  getTotalUptime() {
    const uptime = (Date.now() - this.startTime) / 1e3;
    if (uptime < 60) return `${uptime.toFixed(1)}s`;
    if (uptime < 3600) return `${(uptime / 60).toFixed(1)}m`;
    return `${(uptime / 3600).toFixed(1)}h`;
  }
  /**
   * 输出系统信息
   */
  logSystemInfo() {
    const nodeVersion = process.version;
    const platform = `${os.platform()} ${os.release()}`;
    const cpus = os.cpus().length;
    const memory = `${Math.round(os.totalmem() / 1024 / 1024 / 1024)}GB`;
    this.log(`\u7CFB\u7EDF\u4FE1\u606F | Node: ${nodeVersion} | \u5E73\u53F0: ${platform} | CPU\u6838\u5FC3: ${cpus} | \u5185\u5B58: ${memory}`);
  }
  /**
   * 日志输出方法
   */
  log(message) {
    console.log(`${this.createLogPrefix("INFO" /* INFO */)} [${this.MODULE_NAME}] ${message}`);
  }
  logWarn(message) {
    console.warn(`${this.createLogPrefix("WARN" /* WARN */)} [${this.MODULE_NAME}] ${message}`);
  }
  logError(message, error) {
    console.error(`${this.createLogPrefix("ERROR" /* ERROR */)} [${this.MODULE_NAME}] ${message}`, error || "");
  }
  // private logDebug(message: string): void {
  //   console.debug(`${this.createLogPrefix(LogLevel.DEBUG)} [${this.MODULE_NAME}] ${message}`)
  // }
};
new ProcessManager().start();