mihomo-cli 2.4.2 → 2.6.0
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 +28 -0
- package/README.md +4 -5
- package/dist/index.js +297 -125
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.6.0] - 2026-05-03
|
|
4
|
+
|
|
5
|
+
### 改进
|
|
6
|
+
|
|
7
|
+
- **统一使用 mixed-port**:用 `mixed-port: 7890` 替代原来的 `port: 7890` + `socks-port: 7891`,单端口同时支持 HTTP 和 SOCKS5
|
|
8
|
+
- **BASE_CONFIG 优化**:新增 `unified-delay`、`tcp-concurrent`、`global-client-fingerprint`、`geo-auto-update`、`profile.store-selected`,不再依赖订阅自带这些配置
|
|
9
|
+
- **自动启用 sniffer**:检测到 `fake-ip` 模式时自动注入 sniffer 配置(嗅探 HTTP/TLS/QUIC),确保域名规则正常工作;订阅自带 sniffer 时不覆盖
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## [2.5.0] - 2026-05-03
|
|
14
|
+
|
|
15
|
+
### 新功能
|
|
16
|
+
|
|
17
|
+
- **test 命令** - `mihomo test` 快速测试当前运行实例的节点连通性
|
|
18
|
+
- **clean 命令** - `mihomo clean` 清理失败节点并自动重启
|
|
19
|
+
|
|
20
|
+
### 改进
|
|
21
|
+
|
|
22
|
+
- `sub test` / `sub clean` 改用独立临时进程测试,不影响当前代理,支持测试任意订阅(不限于活跃订阅)
|
|
23
|
+
- 启动时 auto-clean 使用当前运行实例直接测速,提升启动速度
|
|
24
|
+
|
|
25
|
+
### 移除
|
|
26
|
+
|
|
27
|
+
- 移除 `sub best` 命令
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
3
31
|
## [2.4.2] - 2026-05-02
|
|
4
32
|
|
|
5
33
|
### 改进
|
package/README.md
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
- 🌐 **订阅管理** - 添加/更新订阅,支持流量统计和到期时间显示
|
|
8
8
|
- 🔄 **自动更新** - 启动时自动检查并更新过期订阅
|
|
9
9
|
- 🔍 **模糊匹配** - `sub use` / `sub web` 支持订阅名称模糊匹配
|
|
10
|
-
- 🧹 **节点测速清理** - `
|
|
10
|
+
- 🧹 **节点测速清理** - `test` 快速测试、`clean` 清理并重启;`sub test/clean` 独立进程测试任意订阅
|
|
11
11
|
- 📊 **免费订阅基准测试** - `bench` 命令一键测试 20 个内置免费订阅源质量排名
|
|
12
12
|
- 🆓 **快速添加免费订阅** - `sub free <id>` 一键添加内置免费订阅源
|
|
13
|
-
- 🏆 **聚合订阅** - `sub best <id>` 一键添加自动更新的聚合订阅(每小时更新、去重、测活)
|
|
14
13
|
- 📝 **覆写配置** - 在订阅基础上进行自定义覆写,支持强制覆盖、数组合并
|
|
15
14
|
- 🔄 **智能重启** - `sub use` 切换订阅、`ow on/off` 切换覆写后自动重启
|
|
16
15
|
- 🚀 **进程管理** - 启动/停止/切换模式,自动清理残留进程
|
|
@@ -101,13 +100,14 @@ mihomo ui yacd # YACD
|
|
|
101
100
|
| `mihomo sub use <name>` | 切换当前订阅(支持模糊匹配,自动重启) |
|
|
102
101
|
| `mihomo sub add <url> [name]` | 添加订阅并自动切换(支持逗号分隔多 URL 合并) |
|
|
103
102
|
| `mihomo sub free <id>` | 添加内置免费订阅(`0`=合并 #1+#2,`sub free` 列出可用源)|
|
|
104
|
-
| `mihomo sub best <id>` | 添加聚合订阅(`sub best` 列出可用源)|
|
|
105
103
|
| `mihomo sub update` | 更新所有订阅 |
|
|
106
104
|
| `mihomo sub update <name>` | 更新指定订阅(支持模糊匹配) |
|
|
107
105
|
| `mihomo sub remove <name>` | 删除订阅(支持模糊匹配) |
|
|
108
106
|
| `mihomo sub web [name]` | 打开订阅页面(无参打开默认) |
|
|
109
107
|
| `mihomo sub test [name]` | 测试节点连通性(`-t` 超时,`-j` 并发) |
|
|
110
108
|
| `mihomo sub clean [name]` | 测速并清理失败节点 |
|
|
109
|
+
| `mihomo test` | 快速测试当前节点连通性(`-t` 超时,`-j` 并发) |
|
|
110
|
+
| `mihomo clean` | 清理失败节点并自动重启(`-t` 超时,`-j` 并发) |
|
|
111
111
|
|
|
112
112
|
### 覆写配置
|
|
113
113
|
|
|
@@ -301,8 +301,7 @@ sudo pkill -9 mihomo
|
|
|
301
301
|
|
|
302
302
|
默认端口(系统强制,不受订阅配置影响):
|
|
303
303
|
|
|
304
|
-
- HTTP
|
|
305
|
-
- SOCKS5 端口: `7891`
|
|
304
|
+
- 混合端口 (HTTP + SOCKS5): `7890`
|
|
306
305
|
- 外部控制器: `127.0.0.1:9090`
|
|
307
306
|
|
|
308
307
|
## 安全特性
|
package/dist/index.js
CHANGED
|
@@ -2687,30 +2687,34 @@ function getFreeSubscriptionSources() {
|
|
|
2687
2687
|
{ name: "NoMoreWalls", url: "https://gh-proxy.org/raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.meta.yml" }
|
|
2688
2688
|
];
|
|
2689
2689
|
}
|
|
2690
|
-
var GH_SUB = "https://gh-proxy.org/raw.githubusercontent.com/imaex/mihomo-free-sub/sub";
|
|
2691
|
-
function getBestSubscriptionSources() {
|
|
2692
|
-
return [
|
|
2693
|
-
{ name: "curated", url: `${GH_SUB}/curated.yaml`, description: "\u7CBE\u9009 29 \u7EC4\uFF08\u4EC5\u6D4B\u901F\u6E90\uFF09" },
|
|
2694
|
-
{ name: "acl4ssr", url: `${GH_SUB}/acl4ssr.yaml`, description: "ACL4SSR 29 \u7EC4\uFF08\u5168\u90E8\u6E90\uFF09" },
|
|
2695
|
-
{ name: "freesub", url: `${GH_SUB}/freesub.yaml`, description: "freeSub 24 \u7EC4" }
|
|
2696
|
-
];
|
|
2697
|
-
}
|
|
2698
2690
|
var BENCH_CONFIG = {
|
|
2691
|
+
"mixed-port": 17890,
|
|
2699
2692
|
"allow-lan": false,
|
|
2700
2693
|
"external-controller": "127.0.0.1:19090",
|
|
2701
|
-
|
|
2702
|
-
"
|
|
2694
|
+
"log-level": "error",
|
|
2695
|
+
"geodata-mode": true
|
|
2696
|
+
};
|
|
2697
|
+
var TEST_CONFIG = {
|
|
2698
|
+
"mixed-port": 27890,
|
|
2699
|
+
"allow-lan": false,
|
|
2700
|
+
"external-controller": "127.0.0.1:29090",
|
|
2703
2701
|
"log-level": "error",
|
|
2704
2702
|
"geodata-mode": true
|
|
2705
2703
|
};
|
|
2706
2704
|
var BASE_CONFIG = {
|
|
2705
|
+
"mixed-port": 7890,
|
|
2707
2706
|
"allow-lan": false,
|
|
2708
2707
|
"external-controller": "127.0.0.1:9090",
|
|
2709
|
-
|
|
2710
|
-
"
|
|
2711
|
-
"
|
|
2712
|
-
"
|
|
2708
|
+
"unified-delay": true,
|
|
2709
|
+
"tcp-concurrent": true,
|
|
2710
|
+
"global-client-fingerprint": "chrome",
|
|
2711
|
+
"geo-auto-update": true,
|
|
2713
2712
|
"geo-update-interval": 24,
|
|
2713
|
+
"geodata-mode": true,
|
|
2714
|
+
"log-level": "warning",
|
|
2715
|
+
profile: {
|
|
2716
|
+
"store-selected": true
|
|
2717
|
+
},
|
|
2714
2718
|
"geox-url": {
|
|
2715
2719
|
geoip: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip-lite.dat",
|
|
2716
2720
|
geosite: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite-lite.dat",
|
|
@@ -3128,9 +3132,10 @@ function buildConfig(subRawContent, mode) {
|
|
|
3128
3132
|
}
|
|
3129
3133
|
systemConfig["allow-lan"] = false;
|
|
3130
3134
|
systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
|
|
3131
|
-
systemConfig
|
|
3132
|
-
systemConfig["socks-port"] = BASE_CONFIG["socks-port"];
|
|
3135
|
+
systemConfig["mixed-port"] = BASE_CONFIG["mixed-port"];
|
|
3133
3136
|
delete withOverwrites["mixed-port"];
|
|
3137
|
+
delete withOverwrites.port;
|
|
3138
|
+
delete withOverwrites["socks-port"];
|
|
3134
3139
|
delete withOverwrites["external-ui"];
|
|
3135
3140
|
delete withOverwrites["external-ui-name"];
|
|
3136
3141
|
delete withOverwrites["external-ui-url"];
|
|
@@ -3149,6 +3154,18 @@ function buildConfig(subRawContent, mode) {
|
|
|
3149
3154
|
if (systemConfig.dns) {
|
|
3150
3155
|
merged.dns = { ...withOverwrites.dns || {}, ...systemConfig.dns };
|
|
3151
3156
|
}
|
|
3157
|
+
const mergedDns = merged.dns || {};
|
|
3158
|
+
if (mergedDns["enhanced-mode"] === "fake-ip" && !("sniffer" in withOverwrites)) {
|
|
3159
|
+
merged.sniffer = {
|
|
3160
|
+
enable: true,
|
|
3161
|
+
sniff: {
|
|
3162
|
+
HTTP: { ports: [80, "8080-8880"], "override-destination": true },
|
|
3163
|
+
TLS: { ports: [443, 8443] },
|
|
3164
|
+
QUIC: { ports: [443, 8443] }
|
|
3165
|
+
},
|
|
3166
|
+
"skip-domain": ["+.push.apple.com"]
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3152
3169
|
return { config: merged, subscriptionConfig, overwriteFiles, systemConfig };
|
|
3153
3170
|
}
|
|
3154
3171
|
function writeMihomoConfig(configObj) {
|
|
@@ -3397,6 +3414,15 @@ function parseMirrorArg(args) {
|
|
|
3397
3414
|
}
|
|
3398
3415
|
return { mirror: null, isOverride: false, type: "download" };
|
|
3399
3416
|
}
|
|
3417
|
+
function isProxyValid(proxy) {
|
|
3418
|
+
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3419
|
+
if (!proxy.type) return false;
|
|
3420
|
+
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3421
|
+
const pw = String(proxy.password || "");
|
|
3422
|
+
if (!/^[A-Za-z0-9+/]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3423
|
+
}
|
|
3424
|
+
return true;
|
|
3425
|
+
}
|
|
3400
3426
|
|
|
3401
3427
|
// src/bench.ts
|
|
3402
3428
|
var BENCH_DIR = path3.join(USER_DATA_DIR, "bench");
|
|
@@ -3541,15 +3567,6 @@ async function downloadAllSources(sources, onProgress) {
|
|
|
3541
3567
|
});
|
|
3542
3568
|
return await Promise.all(tasks);
|
|
3543
3569
|
}
|
|
3544
|
-
function isProxyValid(proxy) {
|
|
3545
|
-
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3546
|
-
if (!proxy.type) return false;
|
|
3547
|
-
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3548
|
-
const pw = String(proxy.password || "");
|
|
3549
|
-
if (!/^[A-Za-z0-9+/]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3550
|
-
}
|
|
3551
|
-
return true;
|
|
3552
|
-
}
|
|
3553
3570
|
function buildMergedBenchConfig(allProxies) {
|
|
3554
3571
|
ensureBenchDirs();
|
|
3555
3572
|
const validProxies = allProxies.filter(isProxyValid);
|
|
@@ -4484,9 +4501,9 @@ async function autoUpdateStaleSubscription() {
|
|
|
4484
4501
|
}
|
|
4485
4502
|
var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
|
|
4486
4503
|
var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
4487
|
-
async function testProxyDelay(proxyName, timeout, testUrl, client) {
|
|
4504
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API_BASE) {
|
|
4488
4505
|
const encodedName = encodeURIComponent(proxyName);
|
|
4489
|
-
const url = `${
|
|
4506
|
+
const url = `${apiBase}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4490
4507
|
try {
|
|
4491
4508
|
const response = await client.get(url);
|
|
4492
4509
|
const data = JSON.parse(response.data);
|
|
@@ -4506,7 +4523,7 @@ async function testProxyDelay(proxyName, timeout, testUrl, client) {
|
|
|
4506
4523
|
}
|
|
4507
4524
|
}
|
|
4508
4525
|
async function testSubscriptionProxies(subName, options = {}) {
|
|
4509
|
-
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, onResult } = options;
|
|
4526
|
+
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, apiBase = API_BASE, onResult } = options;
|
|
4510
4527
|
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4511
4528
|
if (proxies.length === 0) {
|
|
4512
4529
|
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
@@ -4516,7 +4533,7 @@ async function testSubscriptionProxies(subName, options = {}) {
|
|
|
4516
4533
|
let completedCount = 0;
|
|
4517
4534
|
for (let i = 0; i < proxies.length; i += concurrency) {
|
|
4518
4535
|
const batch = proxies.slice(i, i + concurrency);
|
|
4519
|
-
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client)));
|
|
4536
|
+
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client, apiBase)));
|
|
4520
4537
|
for (const result of batchResults) {
|
|
4521
4538
|
results.push(result);
|
|
4522
4539
|
onResult?.(result, completedCount, proxies.length);
|
|
@@ -4598,12 +4615,138 @@ async function autoCleanSubscription(subName, options = {}) {
|
|
|
4598
4615
|
removedGroups = cleanResult.removedGroups;
|
|
4599
4616
|
}
|
|
4600
4617
|
}
|
|
4601
|
-
if (!skipped) {
|
|
4618
|
+
if (!skipped && removedProxies > 0) {
|
|
4602
4619
|
saveSubscriptionConfig(subName, parsed);
|
|
4603
4620
|
}
|
|
4604
4621
|
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
4605
4622
|
}
|
|
4606
4623
|
|
|
4624
|
+
// src/test-instance.ts
|
|
4625
|
+
import { spawn as spawn3 } from "child_process";
|
|
4626
|
+
import fs7 from "fs";
|
|
4627
|
+
import path5 from "path";
|
|
4628
|
+
var TEST_DIR = path5.join(USER_DATA_DIR, "test");
|
|
4629
|
+
var TEST_DIRS = {
|
|
4630
|
+
data: path5.join(TEST_DIR, "data"),
|
|
4631
|
+
runtime: path5.join(TEST_DIR, "runtime")
|
|
4632
|
+
};
|
|
4633
|
+
var TEST_PATHS = {
|
|
4634
|
+
configFile: path5.join(TEST_DIRS.runtime, "config.yaml"),
|
|
4635
|
+
pidFile: path5.join(TEST_DIRS.runtime, "pid"),
|
|
4636
|
+
logFile: path5.join(TEST_DIR, "test.log")
|
|
4637
|
+
};
|
|
4638
|
+
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
4639
|
+
function ensureTestDirs() {
|
|
4640
|
+
for (const dir of Object.values(TEST_DIRS)) {
|
|
4641
|
+
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
function cleanupTestDir() {
|
|
4645
|
+
rmrf(TEST_DIR);
|
|
4646
|
+
}
|
|
4647
|
+
function buildTestConfig(subName) {
|
|
4648
|
+
ensureTestDirs();
|
|
4649
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4650
|
+
if (!rawContent) {
|
|
4651
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4652
|
+
}
|
|
4653
|
+
const parsed = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4654
|
+
const proxies = (parsed.proxies || []).filter(isProxyValid);
|
|
4655
|
+
if (proxies.length === 0) {
|
|
4656
|
+
throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
|
|
4657
|
+
}
|
|
4658
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
4659
|
+
for (const proxy of proxies) {
|
|
4660
|
+
const count = (nameCount.get(proxy.name) || 0) + 1;
|
|
4661
|
+
nameCount.set(proxy.name, count);
|
|
4662
|
+
if (count > 1) {
|
|
4663
|
+
proxy.name = `${proxy.name} #${count}`;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
const config = {
|
|
4667
|
+
...TEST_CONFIG,
|
|
4668
|
+
proxies,
|
|
4669
|
+
"proxy-groups": [
|
|
4670
|
+
{
|
|
4671
|
+
name: "PROXY",
|
|
4672
|
+
type: "select",
|
|
4673
|
+
proxies: proxies.map((p) => p.name)
|
|
4674
|
+
}
|
|
4675
|
+
],
|
|
4676
|
+
rules: ["MATCH,PROXY"]
|
|
4677
|
+
};
|
|
4678
|
+
const content = jsYaml.dump(config, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
4679
|
+
fs7.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
4680
|
+
}
|
|
4681
|
+
async function startTestInstance() {
|
|
4682
|
+
const binary2 = PATHS.mihomoBinary;
|
|
4683
|
+
if (!fs7.existsSync(binary2)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
4684
|
+
stopTestInstance();
|
|
4685
|
+
const logFd = fs7.openSync(TEST_PATHS.logFile, "a");
|
|
4686
|
+
const child = spawn3(binary2, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
4687
|
+
detached: true,
|
|
4688
|
+
stdio: ["ignore", logFd, logFd]
|
|
4689
|
+
});
|
|
4690
|
+
fs7.closeSync(logFd);
|
|
4691
|
+
child.unref();
|
|
4692
|
+
const pid = child.pid;
|
|
4693
|
+
fs7.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
4694
|
+
const client = createHttpClient({ timeout: 2e3 });
|
|
4695
|
+
let ready = false;
|
|
4696
|
+
for (let i = 0; i < 60; i++) {
|
|
4697
|
+
if (!isProcessRunning(pid)) break;
|
|
4698
|
+
try {
|
|
4699
|
+
await client.get(`${TEST_API}/version`);
|
|
4700
|
+
ready = true;
|
|
4701
|
+
break;
|
|
4702
|
+
} catch {
|
|
4703
|
+
await sleep(500);
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
if (!isProcessRunning(pid)) {
|
|
4707
|
+
let errorDetail = "";
|
|
4708
|
+
try {
|
|
4709
|
+
errorDetail = fs7.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
4710
|
+
} catch {
|
|
4711
|
+
}
|
|
4712
|
+
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
4713
|
+
${errorDetail}` : ""}`);
|
|
4714
|
+
}
|
|
4715
|
+
if (!ready) {
|
|
4716
|
+
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
function stopTestInstance() {
|
|
4720
|
+
let pid;
|
|
4721
|
+
try {
|
|
4722
|
+
pid = parseInt(fs7.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
4723
|
+
} catch {
|
|
4724
|
+
return;
|
|
4725
|
+
}
|
|
4726
|
+
if (pid > 0 && isProcessRunning(pid)) {
|
|
4727
|
+
process.kill(pid, "SIGKILL");
|
|
4728
|
+
for (let i = 0; i < 20; i++) {
|
|
4729
|
+
if (!isProcessRunning(pid)) break;
|
|
4730
|
+
sleepSync(100);
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
try {
|
|
4734
|
+
fs7.unlinkSync(TEST_PATHS.pidFile);
|
|
4735
|
+
} catch {
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
async function withTestInstance(subName, fn) {
|
|
4739
|
+
cleanupTestDir();
|
|
4740
|
+
buildTestConfig(subName);
|
|
4741
|
+
try {
|
|
4742
|
+
await startTestInstance();
|
|
4743
|
+
return await fn(TEST_API);
|
|
4744
|
+
} finally {
|
|
4745
|
+
stopTestInstance();
|
|
4746
|
+
cleanupTestDir();
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4607
4750
|
// src/commands/status.ts
|
|
4608
4751
|
function printStatus() {
|
|
4609
4752
|
const status = getStatus();
|
|
@@ -4741,9 +4884,9 @@ function printTestResult(result, index, total) {
|
|
|
4741
4884
|
const prefix = `[${index + 1}/${total}]`;
|
|
4742
4885
|
if (result.delay !== null) {
|
|
4743
4886
|
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
4744
|
-
console.log(
|
|
4887
|
+
console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}`);
|
|
4745
4888
|
} else {
|
|
4746
|
-
console.log(
|
|
4889
|
+
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
4747
4890
|
}
|
|
4748
4891
|
}
|
|
4749
4892
|
function formatCleanSummary(result) {
|
|
@@ -4760,7 +4903,7 @@ function githubRepoUrl(rawUrl) {
|
|
|
4760
4903
|
if (match) return `https://github.com/${match[1]}`;
|
|
4761
4904
|
return null;
|
|
4762
4905
|
}
|
|
4763
|
-
function
|
|
4906
|
+
function resolveTestTarget(args) {
|
|
4764
4907
|
const subs = getSubscriptions();
|
|
4765
4908
|
if (subs.length === 0) {
|
|
4766
4909
|
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
@@ -4769,28 +4912,18 @@ function resolveActiveTestTarget(args) {
|
|
|
4769
4912
|
const nameArg = getNonFlagArg(args, 2);
|
|
4770
4913
|
const timeout = parseIntArg(args, "-t", "--timeout", 2e3);
|
|
4771
4914
|
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
4772
|
-
const activeSub = getActiveSubscription();
|
|
4773
4915
|
let target;
|
|
4774
4916
|
if (nameArg) {
|
|
4775
4917
|
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
4776
4918
|
target = pickSingleSubscription(matches, nameArg);
|
|
4777
4919
|
} else {
|
|
4920
|
+
const activeSub = getActiveSubscription();
|
|
4778
4921
|
if (!activeSub) {
|
|
4779
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
4922
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4780
4923
|
process.exit(1);
|
|
4781
4924
|
}
|
|
4782
4925
|
target = activeSub;
|
|
4783
4926
|
}
|
|
4784
|
-
const status = getStatus();
|
|
4785
|
-
if (!status.running) {
|
|
4786
|
-
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
4787
|
-
process.exit(1);
|
|
4788
|
-
}
|
|
4789
|
-
if (!activeSub || activeSub.name !== target.name) {
|
|
4790
|
-
console.error(`\u9519\u8BEF: \u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605\u662F "${activeSub?.name}"\uFF0C\u4E0D\u662F "${target.name}"`);
|
|
4791
|
-
console.log(`\u8BF7\u5148\u5207\u6362: mihomo sub use ${target.name}`);
|
|
4792
|
-
process.exit(1);
|
|
4793
|
-
}
|
|
4794
4927
|
return { target, timeout, concurrency };
|
|
4795
4928
|
}
|
|
4796
4929
|
async function printSubscriptionList(options) {
|
|
@@ -4899,36 +5032,6 @@ async function addFreeSubscription(freeId) {
|
|
|
4899
5032
|
console.log("");
|
|
4900
5033
|
await printSubscriptionList();
|
|
4901
5034
|
}
|
|
4902
|
-
function printBestSourceList() {
|
|
4903
|
-
const bestSources = getBestSubscriptionSources();
|
|
4904
|
-
for (let i = 0; i < bestSources.length; i++) {
|
|
4905
|
-
console.log(` ${i + 1} ${bestSources[i].name} \u2014 ${bestSources[i].description}`);
|
|
4906
|
-
}
|
|
4907
|
-
}
|
|
4908
|
-
async function addBestSubscription(bestId) {
|
|
4909
|
-
const bestSources = getBestSubscriptionSources();
|
|
4910
|
-
if (bestId < 1 || bestId > bestSources.length) {
|
|
4911
|
-
console.error(`\u9519\u8BEF: best \u8BA2\u9605 ID \u8303\u56F4 1-${bestSources.length}`);
|
|
4912
|
-
console.log("\n\u53EF\u7528\u6E90:");
|
|
4913
|
-
printBestSourceList();
|
|
4914
|
-
process.exit(1);
|
|
4915
|
-
}
|
|
4916
|
-
const source = bestSources[bestId - 1];
|
|
4917
|
-
const name = `best${bestId}`;
|
|
4918
|
-
console.log(`\u6DFB\u52A0 best \u8BA2\u9605: ${name} (${source.description})`);
|
|
4919
|
-
try {
|
|
4920
|
-
addSubscription(source.url, name);
|
|
4921
|
-
const info = await downloadSubscription(source.url, name);
|
|
4922
|
-
setDefaultSubscription(name);
|
|
4923
|
-
saveSubscriptionCache(name, { web_page_url: "https://github.com/imaex/mihomo-free-sub" });
|
|
4924
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4925
|
-
} catch (e) {
|
|
4926
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4927
|
-
process.exit(1);
|
|
4928
|
-
}
|
|
4929
|
-
console.log("");
|
|
4930
|
-
await printSubscriptionList();
|
|
4931
|
-
}
|
|
4932
5035
|
async function cmdSubscription(args) {
|
|
4933
5036
|
const action = args[1];
|
|
4934
5037
|
if (!action || action === "list") {
|
|
@@ -4946,17 +5049,6 @@ async function cmdSubscription(args) {
|
|
|
4946
5049
|
await addFreeSubscription(id);
|
|
4947
5050
|
return;
|
|
4948
5051
|
}
|
|
4949
|
-
if (action === "best") {
|
|
4950
|
-
const id = parseInt(args[2], 10);
|
|
4951
|
-
if (Number.isNaN(id)) {
|
|
4952
|
-
console.log("\u7528\u6CD5: mihomo sub best <id>\n");
|
|
4953
|
-
console.log("\u53EF\u7528\u6E90:");
|
|
4954
|
-
printBestSourceList();
|
|
4955
|
-
process.exit(1);
|
|
4956
|
-
}
|
|
4957
|
-
await addBestSubscription(id);
|
|
4958
|
-
return;
|
|
4959
|
-
}
|
|
4960
5052
|
if (action === "add") {
|
|
4961
5053
|
const freeId = parseIntArg(args, "--free", "--free", -1);
|
|
4962
5054
|
if (freeId > 0) {
|
|
@@ -5155,14 +5247,17 @@ async function cmdSubscription(args) {
|
|
|
5155
5247
|
return;
|
|
5156
5248
|
}
|
|
5157
5249
|
if (action === "clean") {
|
|
5158
|
-
const { target, timeout, concurrency } =
|
|
5250
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
5159
5251
|
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
5160
5252
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5161
5253
|
console.log("");
|
|
5162
|
-
const result = await
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5254
|
+
const result = await withTestInstance(target.name, async (apiBase) => {
|
|
5255
|
+
return autoCleanSubscription(target.name, {
|
|
5256
|
+
timeout,
|
|
5257
|
+
concurrency,
|
|
5258
|
+
apiBase,
|
|
5259
|
+
onResult: printTestResult
|
|
5260
|
+
});
|
|
5166
5261
|
});
|
|
5167
5262
|
console.log("");
|
|
5168
5263
|
console.log(formatTestSummary(result.summary));
|
|
@@ -5171,20 +5266,26 @@ async function cmdSubscription(args) {
|
|
|
5171
5266
|
console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
|
|
5172
5267
|
} else if (result.removedProxies > 0) {
|
|
5173
5268
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
5174
|
-
|
|
5175
|
-
|
|
5269
|
+
const status = getStatus();
|
|
5270
|
+
if (status.running) {
|
|
5271
|
+
console.log("");
|
|
5272
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
5273
|
+
}
|
|
5176
5274
|
}
|
|
5177
5275
|
return;
|
|
5178
5276
|
}
|
|
5179
5277
|
if (action === "test") {
|
|
5180
|
-
const { target, timeout, concurrency } =
|
|
5278
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
5181
5279
|
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
5182
5280
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5183
5281
|
console.log("");
|
|
5184
|
-
const summary = await
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5282
|
+
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
5283
|
+
return testSubscriptionProxies(target.name, {
|
|
5284
|
+
timeout,
|
|
5285
|
+
concurrency,
|
|
5286
|
+
apiBase,
|
|
5287
|
+
onResult: printTestResult
|
|
5288
|
+
});
|
|
5188
5289
|
});
|
|
5189
5290
|
console.log("");
|
|
5190
5291
|
console.log(formatTestSummary(summary));
|
|
@@ -5295,7 +5396,7 @@ async function cmdBench(args) {
|
|
|
5295
5396
|
for (const d of downloaded) {
|
|
5296
5397
|
d.proxies = d.proxies.filter((p) => survivingSet.has(p));
|
|
5297
5398
|
}
|
|
5298
|
-
const benchPort = BENCH_CONFIG
|
|
5399
|
+
const benchPort = BENCH_CONFIG["mixed-port"];
|
|
5299
5400
|
const benchApi = BENCH_CONFIG["external-controller"];
|
|
5300
5401
|
console.log(colors.cyan("\u542F\u52A8\u6D4B\u8BD5\u5B9E\u4F8B..."));
|
|
5301
5402
|
await startBenchInstance();
|
|
@@ -5431,6 +5532,8 @@ ${colors.cyan("\u8BA2\u9605:")}
|
|
|
5431
5532
|
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
5432
5533
|
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
5433
5534
|
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
5535
|
+
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
5536
|
+
${colors.bold("clean")} [-t ms] [-j N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
5434
5537
|
${colors.bold("bench")} [name] [-t ms] [-j N] \u6D4B\u8BD5\u514D\u8D39\u8BA2\u9605\u6E90\u8D28\u91CF\u6392\u540D
|
|
5435
5538
|
|
|
5436
5539
|
${colors.cyan("\u914D\u7F6E:")}
|
|
@@ -5472,8 +5575,8 @@ function printVersion() {
|
|
|
5472
5575
|
|
|
5473
5576
|
// src/kernel.ts
|
|
5474
5577
|
import { execSync as execSync4, spawnSync } from "child_process";
|
|
5475
|
-
import
|
|
5476
|
-
import
|
|
5578
|
+
import fs8 from "fs";
|
|
5579
|
+
import path6 from "path";
|
|
5477
5580
|
|
|
5478
5581
|
// node_modules/compare-versions/lib/esm/utils.js
|
|
5479
5582
|
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
@@ -5598,10 +5701,10 @@ async function checkUpdate(mirror) {
|
|
|
5598
5701
|
};
|
|
5599
5702
|
}
|
|
5600
5703
|
function findBinaryInDir(dir) {
|
|
5601
|
-
const files =
|
|
5704
|
+
const files = fs8.readdirSync(dir);
|
|
5602
5705
|
for (const f of files) {
|
|
5603
|
-
const fullPath =
|
|
5604
|
-
const stat =
|
|
5706
|
+
const fullPath = path6.join(dir, f);
|
|
5707
|
+
const stat = fs8.statSync(fullPath);
|
|
5605
5708
|
if (stat.isDirectory()) {
|
|
5606
5709
|
const found = findBinaryInDir(fullPath);
|
|
5607
5710
|
if (found) return found;
|
|
@@ -5627,7 +5730,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5627
5730
|
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5628
5731
|
}
|
|
5629
5732
|
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
5630
|
-
const tempPath =
|
|
5733
|
+
const tempPath = path6.join(DIRS.kernel, asset.name);
|
|
5631
5734
|
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
5632
5735
|
if (progressCallback) {
|
|
5633
5736
|
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
@@ -5639,12 +5742,12 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5639
5742
|
);
|
|
5640
5743
|
if (curlResult.status !== 0) {
|
|
5641
5744
|
try {
|
|
5642
|
-
|
|
5745
|
+
fs8.unlinkSync(tempPath);
|
|
5643
5746
|
} catch {
|
|
5644
5747
|
}
|
|
5645
5748
|
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
5646
5749
|
}
|
|
5647
|
-
if (!
|
|
5750
|
+
if (!fs8.existsSync(tempPath)) {
|
|
5648
5751
|
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
|
|
5649
5752
|
}
|
|
5650
5753
|
if (progressCallback) {
|
|
@@ -5656,14 +5759,14 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5656
5759
|
if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
|
|
5657
5760
|
execSync4(`tar -xzf "${tempPath}" -C "${extractPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
5658
5761
|
} else if (tempPath.endsWith(".gz")) {
|
|
5659
|
-
const baseName =
|
|
5660
|
-
const outputPath =
|
|
5762
|
+
const baseName = path6.basename(tempPath, ".gz");
|
|
5763
|
+
const outputPath = path6.join(extractPath, baseName);
|
|
5661
5764
|
execSync4(`gzip -dc "${tempPath}" > "${outputPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
5662
5765
|
extractedBinary = outputPath;
|
|
5663
5766
|
}
|
|
5664
5767
|
} catch (e) {
|
|
5665
5768
|
try {
|
|
5666
|
-
|
|
5769
|
+
fs8.unlinkSync(tempPath);
|
|
5667
5770
|
} catch {
|
|
5668
5771
|
}
|
|
5669
5772
|
throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
|
|
@@ -5671,25 +5774,25 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5671
5774
|
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
5672
5775
|
if (!foundBinary) {
|
|
5673
5776
|
try {
|
|
5674
|
-
|
|
5777
|
+
fs8.unlinkSync(tempPath);
|
|
5675
5778
|
} catch {
|
|
5676
5779
|
}
|
|
5677
5780
|
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
5678
5781
|
}
|
|
5679
5782
|
const targetPath = PATHS.mihomoBinary;
|
|
5680
5783
|
if (foundBinary !== targetPath) {
|
|
5681
|
-
if (
|
|
5682
|
-
|
|
5784
|
+
if (fs8.existsSync(targetPath)) {
|
|
5785
|
+
fs8.chmodSync(targetPath, 493);
|
|
5683
5786
|
try {
|
|
5684
|
-
|
|
5787
|
+
fs8.unlinkSync(targetPath);
|
|
5685
5788
|
} catch {
|
|
5686
5789
|
}
|
|
5687
5790
|
}
|
|
5688
|
-
|
|
5791
|
+
fs8.renameSync(foundBinary, targetPath);
|
|
5689
5792
|
}
|
|
5690
|
-
|
|
5793
|
+
fs8.chmodSync(targetPath, 493);
|
|
5691
5794
|
try {
|
|
5692
|
-
|
|
5795
|
+
fs8.unlinkSync(tempPath);
|
|
5693
5796
|
} catch {
|
|
5694
5797
|
}
|
|
5695
5798
|
clearKernelVersionCache();
|
|
@@ -5830,7 +5933,7 @@ function cmdLogs(args) {
|
|
|
5830
5933
|
}
|
|
5831
5934
|
|
|
5832
5935
|
// src/commands/overwrite.ts
|
|
5833
|
-
import
|
|
5936
|
+
import path7 from "path";
|
|
5834
5937
|
function printOverwriteList() {
|
|
5835
5938
|
const info = listOverwriteFile();
|
|
5836
5939
|
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
@@ -5840,8 +5943,8 @@ function printOverwriteList() {
|
|
|
5840
5943
|
if (info.files.length === 0) {
|
|
5841
5944
|
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
5842
5945
|
console.log("");
|
|
5843
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${
|
|
5844
|
-
console.log(` \u6216 ${
|
|
5946
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path7.join(info.dir, "overwrite.yaml")}`);
|
|
5947
|
+
console.log(` \u6216 ${path7.join(info.dir, "overwrite.dns.yaml")}`);
|
|
5845
5948
|
console.log("");
|
|
5846
5949
|
} else {
|
|
5847
5950
|
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
@@ -5905,7 +6008,7 @@ async function cmdOverwrite(args) {
|
|
|
5905
6008
|
}
|
|
5906
6009
|
|
|
5907
6010
|
// src/commands/reset.ts
|
|
5908
|
-
import
|
|
6011
|
+
import fs9 from "fs";
|
|
5909
6012
|
import readline from "readline";
|
|
5910
6013
|
var RESET_TARGETS = [
|
|
5911
6014
|
{
|
|
@@ -5960,8 +6063,8 @@ var RESET_TARGETS = [
|
|
|
5960
6063
|
label: "\u8986\u5199",
|
|
5961
6064
|
paths: () => {
|
|
5962
6065
|
const dir = USER_DATA_DIR;
|
|
5963
|
-
if (!
|
|
5964
|
-
return
|
|
6066
|
+
if (!fs9.existsSync(dir)) return [];
|
|
6067
|
+
return fs9.readdirSync(dir).filter((f) => f === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(f)).map((f) => `${dir}/${f}`);
|
|
5965
6068
|
},
|
|
5966
6069
|
needsStop: false
|
|
5967
6070
|
}
|
|
@@ -6077,6 +6180,69 @@ async function cmdStop() {
|
|
|
6077
6180
|
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
6078
6181
|
}
|
|
6079
6182
|
|
|
6183
|
+
// src/commands/test.ts
|
|
6184
|
+
function requireRunning() {
|
|
6185
|
+
const status = getStatus();
|
|
6186
|
+
if (!status.running) {
|
|
6187
|
+
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
6188
|
+
process.exit(1);
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
function requireActiveSub() {
|
|
6192
|
+
const activeSub = getActiveSubscription();
|
|
6193
|
+
if (!activeSub) {
|
|
6194
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
6195
|
+
process.exit(1);
|
|
6196
|
+
}
|
|
6197
|
+
return activeSub;
|
|
6198
|
+
}
|
|
6199
|
+
async function cmdTest(args) {
|
|
6200
|
+
requireRunning();
|
|
6201
|
+
const activeSub = requireActiveSub();
|
|
6202
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 1500);
|
|
6203
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
6204
|
+
console.log(`\u6D4B\u8BD5 "${activeSub.name}" \u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
6205
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6206
|
+
console.log("");
|
|
6207
|
+
const summary = await testSubscriptionProxies(activeSub.name, {
|
|
6208
|
+
timeout,
|
|
6209
|
+
concurrency,
|
|
6210
|
+
onResult: printTestResult
|
|
6211
|
+
});
|
|
6212
|
+
console.log("");
|
|
6213
|
+
console.log(formatTestSummary(summary));
|
|
6214
|
+
}
|
|
6215
|
+
async function cmdClean(args) {
|
|
6216
|
+
requireRunning();
|
|
6217
|
+
const activeSub = requireActiveSub();
|
|
6218
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 1500);
|
|
6219
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
6220
|
+
console.log(`\u6E05\u7406 "${activeSub.name}" \u5931\u8D25\u8282\u70B9...`);
|
|
6221
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6222
|
+
console.log("");
|
|
6223
|
+
const result = await autoCleanSubscription(activeSub.name, {
|
|
6224
|
+
timeout,
|
|
6225
|
+
concurrency,
|
|
6226
|
+
onResult: printTestResult
|
|
6227
|
+
});
|
|
6228
|
+
console.log("");
|
|
6229
|
+
console.log(formatTestSummary(result.summary));
|
|
6230
|
+
if (result.skipped) {
|
|
6231
|
+
console.log("");
|
|
6232
|
+
console.log("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548");
|
|
6233
|
+
} else if (result.removedProxies === 0) {
|
|
6234
|
+
console.log("\u6240\u6709\u8282\u70B9\u6B63\u5E38\uFF0C\u65E0\u9700\u6E05\u7406");
|
|
6235
|
+
} else {
|
|
6236
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6237
|
+
console.log("");
|
|
6238
|
+
console.log("\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548...");
|
|
6239
|
+
stop();
|
|
6240
|
+
const configInfo = prepareConfigForStart("mixed", activeSub.name);
|
|
6241
|
+
const startResult = await start("mixed");
|
|
6242
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${startResult.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
|
|
6080
6246
|
// src/commands/ui.ts
|
|
6081
6247
|
function cmdUI(args) {
|
|
6082
6248
|
const uiName = args[1] || "zash";
|
|
@@ -6095,7 +6261,7 @@ function cmdUI(args) {
|
|
|
6095
6261
|
}
|
|
6096
6262
|
|
|
6097
6263
|
// src/commands/update.ts
|
|
6098
|
-
import { exec, spawn as
|
|
6264
|
+
import { exec, spawn as spawn4 } from "child_process";
|
|
6099
6265
|
import { promisify } from "util";
|
|
6100
6266
|
var execAsync = promisify(exec);
|
|
6101
6267
|
async function cmdUpdate() {
|
|
@@ -6104,7 +6270,7 @@ async function cmdUpdate() {
|
|
|
6104
6270
|
console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
|
|
6105
6271
|
console.log("");
|
|
6106
6272
|
await new Promise((resolve) => {
|
|
6107
|
-
const npm =
|
|
6273
|
+
const npm = spawn4("npm", ["install", "-g", "mihomo-cli"], { stdio: "inherit" });
|
|
6108
6274
|
npm.on("close", (code) => {
|
|
6109
6275
|
if (code === 0) {
|
|
6110
6276
|
resolve();
|
|
@@ -6252,6 +6418,12 @@ async function main() {
|
|
|
6252
6418
|
case "bench":
|
|
6253
6419
|
await cmdBench(args);
|
|
6254
6420
|
break;
|
|
6421
|
+
case "test":
|
|
6422
|
+
await cmdTest(args);
|
|
6423
|
+
break;
|
|
6424
|
+
case "clean":
|
|
6425
|
+
await cmdClean(args);
|
|
6426
|
+
break;
|
|
6255
6427
|
default:
|
|
6256
6428
|
console.error(`\u672A\u77E5\u547D\u4EE4: ${cmd}`);
|
|
6257
6429
|
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|