mihomo-cli 2.6.1 → 2.6.3
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 +21 -1
- package/dist/index.js +171 -56
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.6.3] - 2026-05-03
|
|
4
|
+
|
|
5
|
+
### 改进
|
|
6
|
+
|
|
7
|
+
- **自动清理三轮重试** - 节点测试失败后自动重试两轮,三轮都失败才删除,减少网络抖动导致的误删
|
|
8
|
+
- **实时进度条** - 测试/清理过程中显示单行刷新进度条,测完输出按名称排序的最终结果
|
|
9
|
+
- **并发模型优化** - 节点测试从分批等待改为 worker pool,逐个完成即时反馈
|
|
10
|
+
- 重试通过的节点标注轮次(第N轮通过)
|
|
11
|
+
- start/test/clean 命令统一使用进度条
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## [2.6.2] - 2026-05-03
|
|
16
|
+
|
|
17
|
+
### 改进
|
|
18
|
+
|
|
19
|
+
- **配置校验增强** - 新增重名节点/分组去重、无效规则清理,覆盖更多启动失败场景
|
|
20
|
+
- 移除多余的 `mihomo -t` 预校验(启动本身即校验)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
3
24
|
## [2.6.1] - 2026-05-03
|
|
4
25
|
|
|
5
26
|
### 修复
|
|
6
27
|
|
|
7
28
|
- **启动前配置校验** - 自动检测并修复 proxy-group 中引用不存在的节点/分组,避免内核启动失败
|
|
8
|
-
- 写入配置后使用 `mihomo -t` 做内核级校验,提前发现配置错误
|
|
9
29
|
|
|
10
30
|
### 改进
|
|
11
31
|
|
package/dist/index.js
CHANGED
|
@@ -3113,19 +3113,42 @@ function excludeOverwriteProxiesFromIncludeAll(config, overwriteFiles) {
|
|
|
3113
3113
|
}
|
|
3114
3114
|
}
|
|
3115
3115
|
var BUILTIN_PROXY_NAMES = /* @__PURE__ */ new Set(["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"]);
|
|
3116
|
-
function
|
|
3116
|
+
function deduplicateByName(items) {
|
|
3117
|
+
const names = /* @__PURE__ */ new Set();
|
|
3118
|
+
const duplicates = [];
|
|
3119
|
+
const result = items.filter((item) => {
|
|
3120
|
+
if (names.has(item.name)) {
|
|
3121
|
+
duplicates.push(item.name);
|
|
3122
|
+
return false;
|
|
3123
|
+
}
|
|
3124
|
+
names.add(item.name);
|
|
3125
|
+
return true;
|
|
3126
|
+
});
|
|
3127
|
+
return { result, names, duplicates };
|
|
3128
|
+
}
|
|
3129
|
+
function validateConfig(config) {
|
|
3117
3130
|
const warnings = [];
|
|
3118
3131
|
const proxies = config.proxies || [];
|
|
3119
3132
|
const groups = config["proxy-groups"] || [];
|
|
3120
|
-
|
|
3121
|
-
const
|
|
3122
|
-
|
|
3123
|
-
|
|
3133
|
+
const rules = config.rules || [];
|
|
3134
|
+
const proxyDedup = deduplicateByName(proxies);
|
|
3135
|
+
config.proxies = proxyDedup.result;
|
|
3136
|
+
if (proxyDedup.duplicates.length > 0) {
|
|
3137
|
+
const preview = proxyDedup.duplicates.slice(0, 3).map((n) => `"${n}"`).join(", ");
|
|
3138
|
+
warnings.push(`\u79FB\u9664\u4E86 ${proxyDedup.duplicates.length} \u4E2A\u91CD\u540D\u8282\u70B9: ${preview}${proxyDedup.duplicates.length > 3 ? " ..." : ""}`);
|
|
3139
|
+
}
|
|
3140
|
+
const groupDedup = deduplicateByName(groups);
|
|
3141
|
+
config["proxy-groups"] = groupDedup.result;
|
|
3142
|
+
if (groupDedup.duplicates.length > 0) {
|
|
3143
|
+
warnings.push(`\u79FB\u9664\u4E86 ${groupDedup.duplicates.length} \u4E2A\u91CD\u540D\u5206\u7EC4: ${groupDedup.duplicates.map((n) => `"${n}"`).join(", ")}`);
|
|
3144
|
+
}
|
|
3145
|
+
const validNames = /* @__PURE__ */ new Set([...BUILTIN_PROXY_NAMES, ...proxyDedup.names, ...groupDedup.names]);
|
|
3146
|
+
const activeGroups = groupDedup.result;
|
|
3124
3147
|
const removedGroups = /* @__PURE__ */ new Set();
|
|
3125
3148
|
let changed = true;
|
|
3126
3149
|
while (changed) {
|
|
3127
3150
|
changed = false;
|
|
3128
|
-
for (const group of
|
|
3151
|
+
for (const group of activeGroups) {
|
|
3129
3152
|
if (removedGroups.has(group.name)) continue;
|
|
3130
3153
|
if (!Array.isArray(group.proxies)) continue;
|
|
3131
3154
|
const invalid = group.proxies.filter((name) => !validNames.has(name));
|
|
@@ -3142,7 +3165,21 @@ function validateProxyGroupDependencies(config) {
|
|
|
3142
3165
|
}
|
|
3143
3166
|
}
|
|
3144
3167
|
if (removedGroups.size > 0) {
|
|
3145
|
-
config["proxy-groups"] =
|
|
3168
|
+
config["proxy-groups"] = activeGroups.filter((g) => !removedGroups.has(g.name));
|
|
3169
|
+
}
|
|
3170
|
+
if (rules.length > 0) {
|
|
3171
|
+
const removedRules = [];
|
|
3172
|
+
config.rules = rules.filter((rule) => {
|
|
3173
|
+
const parts = rule.split(",");
|
|
3174
|
+
if (parts.length < 2) return true;
|
|
3175
|
+
const target = parts[parts.length - 1].trim();
|
|
3176
|
+
if (!target || validNames.has(target)) return true;
|
|
3177
|
+
removedRules.push(rule);
|
|
3178
|
+
return false;
|
|
3179
|
+
});
|
|
3180
|
+
if (removedRules.length > 0) {
|
|
3181
|
+
warnings.push(`\u79FB\u9664\u4E86 ${removedRules.length} \u6761\u5F15\u7528\u4E0D\u5B58\u5728\u76EE\u6807\u7684\u89C4\u5219`);
|
|
3182
|
+
}
|
|
3146
3183
|
}
|
|
3147
3184
|
return warnings;
|
|
3148
3185
|
}
|
|
@@ -3199,23 +3236,9 @@ function buildConfig(subRawContent, mode) {
|
|
|
3199
3236
|
"skip-domain": ["+.push.apple.com"]
|
|
3200
3237
|
};
|
|
3201
3238
|
}
|
|
3202
|
-
const warnings =
|
|
3239
|
+
const warnings = validateConfig(merged);
|
|
3203
3240
|
return { config: merged, subscriptionConfig, overwriteFiles, systemConfig, warnings };
|
|
3204
3241
|
}
|
|
3205
|
-
function checkConfig() {
|
|
3206
|
-
if (!hasKernel() || !hasConfig()) return { ok: true, errors: [] };
|
|
3207
|
-
try {
|
|
3208
|
-
execSync(`"${PATHS.mihomoBinary}" -t -f "${PATHS.configFile}" 2>&1`, { encoding: "utf8" });
|
|
3209
|
-
return { ok: true, errors: [] };
|
|
3210
|
-
} catch (e) {
|
|
3211
|
-
const output = e.stdout || e.message || "";
|
|
3212
|
-
const errors = output.split("\n").filter((line) => line.includes("level=error") || line.includes("level=fatal")).map((line) => {
|
|
3213
|
-
const match = line.match(/msg="(.+)"/);
|
|
3214
|
-
return match ? match[1] : line;
|
|
3215
|
-
});
|
|
3216
|
-
return { ok: false, errors };
|
|
3217
|
-
}
|
|
3218
|
-
}
|
|
3219
3242
|
function writeMihomoConfig(configObj) {
|
|
3220
3243
|
ensureDirs();
|
|
3221
3244
|
const content = jsYaml.dump(configObj, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
@@ -4500,12 +4523,6 @@ function prepareConfigForStart(mode, subName = "default") {
|
|
|
4500
4523
|
}
|
|
4501
4524
|
writeMihomoConfig(buildResult.config);
|
|
4502
4525
|
writeDebugConfig(buildResult);
|
|
4503
|
-
const configCheck = checkConfig();
|
|
4504
|
-
if (!configCheck.ok) {
|
|
4505
|
-
const errorDetail = configCheck.errors.length > 0 ? configCheck.errors.join("\n ") : "\u672A\u77E5\u9519\u8BEF";
|
|
4506
|
-
throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25:
|
|
4507
|
-
${errorDetail}`);
|
|
4508
|
-
}
|
|
4509
4526
|
const proxies = buildResult.config.proxies;
|
|
4510
4527
|
const proxyGroups = buildResult.config["proxy-groups"];
|
|
4511
4528
|
return {
|
|
@@ -4589,17 +4606,20 @@ async function testSubscriptionProxies(subName, options = {}) {
|
|
|
4589
4606
|
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
4590
4607
|
}
|
|
4591
4608
|
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
4592
|
-
const results =
|
|
4609
|
+
const results = new Array(proxies.length);
|
|
4593
4610
|
let completedCount = 0;
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4611
|
+
let nextIndex = 0;
|
|
4612
|
+
async function runNext() {
|
|
4613
|
+
while (nextIndex < proxies.length) {
|
|
4614
|
+
const idx = nextIndex++;
|
|
4615
|
+
const result = await testProxyDelay(proxies[idx].name, timeout, testUrl, client, apiBase);
|
|
4616
|
+
results[idx] = result;
|
|
4599
4617
|
onResult?.(result, completedCount, proxies.length);
|
|
4600
4618
|
completedCount++;
|
|
4601
4619
|
}
|
|
4602
4620
|
}
|
|
4621
|
+
const workers = Array.from({ length: Math.min(concurrency, proxies.length) }, () => runNext());
|
|
4622
|
+
await Promise.all(workers);
|
|
4603
4623
|
const alive = results.filter((r) => r.delay !== null).length;
|
|
4604
4624
|
return { total: results.length, alive, dead: results.length - alive, results };
|
|
4605
4625
|
}
|
|
@@ -4659,7 +4679,13 @@ function cleanDeadProxies(parsed, deadNames) {
|
|
|
4659
4679
|
}
|
|
4660
4680
|
async function autoCleanSubscription(subName, options = {}) {
|
|
4661
4681
|
const parsed = loadSubscriptionConfig(subName);
|
|
4662
|
-
const
|
|
4682
|
+
const { onResult, onRetryRound, ...testOptions } = options;
|
|
4683
|
+
const wrapOnResult = (round) => onResult ? (r, i, t) => onResult(r, i, t, round) : void 0;
|
|
4684
|
+
const summary = await testSubscriptionProxies(subName, {
|
|
4685
|
+
...testOptions,
|
|
4686
|
+
parsed,
|
|
4687
|
+
onResult: wrapOnResult(1)
|
|
4688
|
+
});
|
|
4663
4689
|
let removedProxies = 0;
|
|
4664
4690
|
let updatedGroups = 0;
|
|
4665
4691
|
let removedGroups = 0;
|
|
@@ -4669,10 +4695,32 @@ async function autoCleanSubscription(subName, options = {}) {
|
|
|
4669
4695
|
skipped = true;
|
|
4670
4696
|
} else {
|
|
4671
4697
|
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
4672
|
-
const
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4698
|
+
const deadProxies = parsed.proxies.filter((p) => deadNames.has(p.name));
|
|
4699
|
+
for (let retry = 0; retry < 2; retry++) {
|
|
4700
|
+
const round = retry + 2;
|
|
4701
|
+
const retryTargets = deadProxies.filter((p) => deadNames.has(p.name));
|
|
4702
|
+
if (retryTargets.length === 0) break;
|
|
4703
|
+
onRetryRound?.(round, retryTargets.length);
|
|
4704
|
+
const retryParsed = { raw: {}, proxies: retryTargets, proxyGroups: [] };
|
|
4705
|
+
const retrySummary = await testSubscriptionProxies(subName, {
|
|
4706
|
+
...testOptions,
|
|
4707
|
+
parsed: retryParsed,
|
|
4708
|
+
onResult: wrapOnResult(round)
|
|
4709
|
+
});
|
|
4710
|
+
for (const r of retrySummary.results) {
|
|
4711
|
+
if (r.delay !== null) {
|
|
4712
|
+
deadNames.delete(r.name);
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
summary.dead = deadNames.size;
|
|
4717
|
+
summary.alive = summary.total - summary.dead;
|
|
4718
|
+
if (deadNames.size > 0) {
|
|
4719
|
+
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
4720
|
+
removedProxies = cleanResult.removedProxies;
|
|
4721
|
+
updatedGroups = cleanResult.updatedGroups;
|
|
4722
|
+
removedGroups = cleanResult.removedGroups;
|
|
4723
|
+
}
|
|
4676
4724
|
}
|
|
4677
4725
|
}
|
|
4678
4726
|
if (!skipped && removedProxies > 0) {
|
|
@@ -4916,8 +4964,12 @@ async function cmdStart(args) {
|
|
|
4916
4964
|
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${AUTO_CLEAN_THRESHOLD}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
|
|
4917
4965
|
console.log("");
|
|
4918
4966
|
await sleep(1e3);
|
|
4919
|
-
const
|
|
4920
|
-
|
|
4967
|
+
const progress = createProgressPrinter();
|
|
4968
|
+
const cleanResult = await autoCleanSubscription(sub.name, {
|
|
4969
|
+
onResult: progress.onResult,
|
|
4970
|
+
onRetryRound: progress.onRetryRound
|
|
4971
|
+
});
|
|
4972
|
+
progress.finish();
|
|
4921
4973
|
console.log(formatTestSummary(cleanResult.summary));
|
|
4922
4974
|
if (cleanResult.skipped) {
|
|
4923
4975
|
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"));
|
|
@@ -4940,14 +4992,71 @@ async function cmdStart(args) {
|
|
|
4940
4992
|
}
|
|
4941
4993
|
|
|
4942
4994
|
// src/commands/subscription.ts
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4995
|
+
var IS_TTY = process.stdout.isTTY === true;
|
|
4996
|
+
var BAR_WIDTH = 20;
|
|
4997
|
+
function createProgressPrinter() {
|
|
4998
|
+
let alive = 0;
|
|
4999
|
+
let dead = 0;
|
|
5000
|
+
let hasRetry = false;
|
|
5001
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5002
|
+
function render(done, total) {
|
|
5003
|
+
if (!IS_TTY) return;
|
|
5004
|
+
const pct = Math.round(done / total * 100);
|
|
5005
|
+
const filled = Math.round(done / total * BAR_WIDTH);
|
|
5006
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
|
|
5007
|
+
process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
|
|
4950
5008
|
}
|
|
5009
|
+
return {
|
|
5010
|
+
onResult(result, index, total, round = 1) {
|
|
5011
|
+
const prev = resultMap.get(result.name);
|
|
5012
|
+
if (prev) {
|
|
5013
|
+
if (prev.result.delay === null && result.delay !== null) {
|
|
5014
|
+
alive++;
|
|
5015
|
+
dead--;
|
|
5016
|
+
}
|
|
5017
|
+
} else {
|
|
5018
|
+
if (result.delay !== null) alive++;
|
|
5019
|
+
else dead++;
|
|
5020
|
+
}
|
|
5021
|
+
resultMap.set(result.name, { result, round });
|
|
5022
|
+
render(index + 1, total);
|
|
5023
|
+
},
|
|
5024
|
+
onRetryRound(round, count) {
|
|
5025
|
+
if (!hasRetry) {
|
|
5026
|
+
hasRetry = true;
|
|
5027
|
+
if (IS_TTY) {
|
|
5028
|
+
process.stdout.write("\n");
|
|
5029
|
+
}
|
|
5030
|
+
console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${resultMap.size} \u4E2A\u8282\u70B9) ---`);
|
|
5031
|
+
}
|
|
5032
|
+
if (IS_TTY) {
|
|
5033
|
+
process.stdout.write("\n");
|
|
5034
|
+
}
|
|
5035
|
+
console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
|
|
5036
|
+
},
|
|
5037
|
+
finish() {
|
|
5038
|
+
if (IS_TTY) {
|
|
5039
|
+
process.stdout.write("\n");
|
|
5040
|
+
}
|
|
5041
|
+
console.log("");
|
|
5042
|
+
const entries = [...resultMap.values()];
|
|
5043
|
+
entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
|
|
5044
|
+
const total = entries.length;
|
|
5045
|
+
console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
|
|
5046
|
+
for (let i = 0; i < entries.length; i++) {
|
|
5047
|
+
const { result, round } = entries[i];
|
|
5048
|
+
const prefix = `[${i + 1}/${total}]`;
|
|
5049
|
+
if (result.delay !== null) {
|
|
5050
|
+
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
5051
|
+
const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
|
|
5052
|
+
console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
|
|
5053
|
+
} else {
|
|
5054
|
+
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
console.log("");
|
|
5058
|
+
}
|
|
5059
|
+
};
|
|
4951
5060
|
}
|
|
4952
5061
|
function formatCleanSummary(result) {
|
|
4953
5062
|
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
@@ -5311,15 +5420,17 @@ async function cmdSubscription(args) {
|
|
|
5311
5420
|
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
5312
5421
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5313
5422
|
console.log("");
|
|
5423
|
+
const progress = createProgressPrinter();
|
|
5314
5424
|
const result = await withTestInstance(target.name, async (apiBase) => {
|
|
5315
5425
|
return autoCleanSubscription(target.name, {
|
|
5316
5426
|
timeout,
|
|
5317
5427
|
concurrency,
|
|
5318
5428
|
apiBase,
|
|
5319
|
-
onResult:
|
|
5429
|
+
onResult: progress.onResult,
|
|
5430
|
+
onRetryRound: progress.onRetryRound
|
|
5320
5431
|
});
|
|
5321
5432
|
});
|
|
5322
|
-
|
|
5433
|
+
progress.finish();
|
|
5323
5434
|
console.log(formatTestSummary(result.summary));
|
|
5324
5435
|
if (result.skipped) {
|
|
5325
5436
|
console.log("");
|
|
@@ -5339,15 +5450,16 @@ async function cmdSubscription(args) {
|
|
|
5339
5450
|
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
5340
5451
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5341
5452
|
console.log("");
|
|
5453
|
+
const progress = createProgressPrinter();
|
|
5342
5454
|
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
5343
5455
|
return testSubscriptionProxies(target.name, {
|
|
5344
5456
|
timeout,
|
|
5345
5457
|
concurrency,
|
|
5346
5458
|
apiBase,
|
|
5347
|
-
onResult:
|
|
5459
|
+
onResult: progress.onResult
|
|
5348
5460
|
});
|
|
5349
5461
|
});
|
|
5350
|
-
|
|
5462
|
+
progress.finish();
|
|
5351
5463
|
console.log(formatTestSummary(summary));
|
|
5352
5464
|
return;
|
|
5353
5465
|
}
|
|
@@ -6264,12 +6376,13 @@ async function cmdTest(args) {
|
|
|
6264
6376
|
console.log(`\u6D4B\u8BD5 "${activeSub.name}" \u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
6265
6377
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6266
6378
|
console.log("");
|
|
6379
|
+
const progress = createProgressPrinter();
|
|
6267
6380
|
const summary = await testSubscriptionProxies(activeSub.name, {
|
|
6268
6381
|
timeout,
|
|
6269
6382
|
concurrency,
|
|
6270
|
-
onResult:
|
|
6383
|
+
onResult: progress.onResult
|
|
6271
6384
|
});
|
|
6272
|
-
|
|
6385
|
+
progress.finish();
|
|
6273
6386
|
console.log(formatTestSummary(summary));
|
|
6274
6387
|
}
|
|
6275
6388
|
async function cmdClean(args) {
|
|
@@ -6280,12 +6393,14 @@ async function cmdClean(args) {
|
|
|
6280
6393
|
console.log(`\u6E05\u7406 "${activeSub.name}" \u5931\u8D25\u8282\u70B9...`);
|
|
6281
6394
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6282
6395
|
console.log("");
|
|
6396
|
+
const progress = createProgressPrinter();
|
|
6283
6397
|
const result = await autoCleanSubscription(activeSub.name, {
|
|
6284
6398
|
timeout,
|
|
6285
6399
|
concurrency,
|
|
6286
|
-
onResult:
|
|
6400
|
+
onResult: progress.onResult,
|
|
6401
|
+
onRetryRound: progress.onRetryRound
|
|
6287
6402
|
});
|
|
6288
|
-
|
|
6403
|
+
progress.finish();
|
|
6289
6404
|
console.log(formatTestSummary(result.summary));
|
|
6290
6405
|
if (result.skipped) {
|
|
6291
6406
|
console.log("");
|