mihomo-cli 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.2] - 2026-05-01
4
+
5
+ ### 修复
6
+
7
+ - **文件描述符泄漏**:修复 `startMixedMode` 中 spawn 后未关闭 fd 的问题
8
+ - **forceSudo 参数失效**:修复 `cleanupAll` 忽略调用方传入的强制 sudo 参数
9
+ - **formatBytes 溢出**:修复超大字节值(>1PB)导致显示 `undefined` 单位
10
+ - **YAML 解析类型检查**:`parseYamlOrJson` 现在拒绝非对象类型的 YAML 内容
11
+ - **spawn 错误处理**:`openUrl` 添加 error 事件处理,防止未捕获异常
12
+ - **UserInfo 类型转换**:移除 `parseUserInfo` 中多余的 `as unknown` 双重转换
13
+
14
+ ### 安全
15
+
16
+ - **订阅名称校验**:新增文件名安全校验,防止路径穿越等不安全名称
17
+
18
+ ---
19
+
3
20
  ## [2.2.1] - 2026-05-01
4
21
 
5
22
  ### 修复
package/dist/index.js CHANGED
@@ -2808,7 +2808,14 @@ function getSubscriptionsWithCache() {
2808
2808
  ...cache[s.name] || {}
2809
2809
  }));
2810
2810
  }
2811
+ var SAFE_NAME_RE = /^[\w\-\p{Unified_Ideograph}]{1,64}$/u;
2812
+ function validateSubscriptionName(name) {
2813
+ if (!name || !SAFE_NAME_RE.test(name)) {
2814
+ throw new Error(`\u8BA2\u9605\u540D\u79F0\u65E0\u6548: "${name}"\uFF0C\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u77ED\u6A2A\u7EBF\u548C\u4E2D\u6587\uFF08\u6700\u957F 64 \u5B57\u7B26\uFF09`);
2815
+ }
2816
+ }
2811
2817
  function addSubscription(url, name = "default") {
2818
+ validateSubscriptionName(name);
2812
2819
  const settings = readSettings();
2813
2820
  const subs = settings.subscriptions || [];
2814
2821
  const existingIndex = subs.findIndex((s) => s.name === name);
@@ -3006,7 +3013,7 @@ function parseYamlOrJson(content, errorMsg) {
3006
3013
  }
3007
3014
  try {
3008
3015
  const result = jsYaml.load(content);
3009
- if (result !== void 0) return result;
3016
+ if (result != null && typeof result === "object" && !Array.isArray(result)) return result;
3010
3017
  } catch {
3011
3018
  }
3012
3019
  try {
@@ -3160,7 +3167,7 @@ function formatBytes(bytes) {
3160
3167
  if (num === 0) return "0 B";
3161
3168
  const k = 1024;
3162
3169
  const sizes = ["B", "KB", "MB", "GB", "TB"];
3163
- const i = Math.floor(Math.log(num) / Math.log(k));
3170
+ const i = Math.min(Math.floor(Math.log(num) / Math.log(k)), sizes.length - 1);
3164
3171
  return `${parseFloat((num / k ** i).toFixed(2))} ${sizes[i]}`;
3165
3172
  }
3166
3173
  function formatTimestamp(ts) {
@@ -3405,14 +3412,14 @@ function killAllMihomo(forceSudo = false) {
3405
3412
  }
3406
3413
  }
3407
3414
  }
3408
- function cleanupAll(_forceSudo = false) {
3415
+ function cleanupAll(forceSudo = false) {
3409
3416
  const pids = getAllMihomoPids();
3410
3417
  if (pids.length === 0) {
3411
3418
  clearPid();
3412
3419
  return { killed: 0, failed: 0, remaining: [] };
3413
3420
  }
3414
3421
  const hasRootProcess = pids.some((p) => isProcessRoot(p));
3415
- const needsSudo = hasRootProcess;
3422
+ const needsSudo = forceSudo || hasRootProcess;
3416
3423
  let killedCount = 0;
3417
3424
  const failedPids = [];
3418
3425
  if (needsSudo) {
@@ -3560,12 +3567,12 @@ async function startMixedMode(staleState) {
3560
3567
  const configFile = PATHS.configFile;
3561
3568
  const logFile = PATHS.logFile;
3562
3569
  const args = ["-d", DIRS.data, "-f", configFile];
3563
- const out = fs5.openSync(logFile, "a");
3564
- const err = fs5.openSync(logFile, "a");
3570
+ const logFd = fs5.openSync(logFile, "a");
3565
3571
  const child = spawn(PATHS.mihomoBinary, args, {
3566
3572
  detached: true,
3567
- stdio: ["ignore", out, err]
3573
+ stdio: ["ignore", logFd, logFd]
3568
3574
  });
3575
+ fs5.closeSync(logFd);
3569
3576
  child.unref();
3570
3577
  const pid = child.pid;
3571
3578
  savePid(pid);
@@ -3735,7 +3742,10 @@ function getLogPathByName(name) {
3735
3742
  }
3736
3743
  function openUrl(url) {
3737
3744
  try {
3738
- spawn("open", [url], { stdio: "ignore", detached: true });
3745
+ const child = spawn("open", [url], { stdio: "ignore", detached: true });
3746
+ child.unref();
3747
+ child.on("error", () => {
3748
+ });
3739
3749
  return true;
3740
3750
  } catch {
3741
3751
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "type": "module",
5
5
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
6
6
  "bin": {