tt-help-cli-ycl 1.4.123 → 1.4.125
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/package.json +1 -1
- package/src/cli/comments.js +4 -2
- package/src/cli/open.js +11 -5
- package/src/cli/tag.js +4 -2
- package/src/lib/api-interceptor.js +47 -8
- package/src/lib/browser/cdp.js +59 -30
- package/src/watchdog/process-manager.js +21 -3
- package/src/watchdog/upgrader.js +6 -2
package/package.json
CHANGED
package/src/cli/comments.js
CHANGED
|
@@ -165,10 +165,12 @@ function getBrowserMemory(port = null) {
|
|
|
165
165
|
`}; ` +
|
|
166
166
|
`Write-Output $sum`;
|
|
167
167
|
} else {
|
|
168
|
+
// 无端口场景:Get-Process(.NET API,不依赖 WMI)统计全部 msedge 工作集,
|
|
169
|
+
// 与原 Get-CimInstance 全量求和行为等价;WMI 损坏/禁用的机器也能返回
|
|
168
170
|
ps1Content =
|
|
169
|
-
`$procs = Get-
|
|
171
|
+
`$procs = Get-Process msedge -ErrorAction SilentlyContinue; ` +
|
|
170
172
|
`$sum = 0; ` +
|
|
171
|
-
`foreach ($p in $procs) { $sum += $p.
|
|
173
|
+
`foreach ($p in $procs) { $sum += $p.WorkingSet64 }; ` +
|
|
172
174
|
`Write-Output $sum`;
|
|
173
175
|
}
|
|
174
176
|
writeFileSync(ps1Path, ps1Content);
|
package/src/cli/open.js
CHANGED
|
@@ -93,14 +93,20 @@ export async function handleOpen(parsed) {
|
|
|
93
93
|
// Ctrl+C 关闭浏览器
|
|
94
94
|
process.on("SIGINT", async () => {
|
|
95
95
|
console.error("\n正在关闭浏览器...");
|
|
96
|
+
// 先优雅断开 CDP 连接(带 3s 超时保护):进程已被强杀时 close() 会挂起等 close 事件
|
|
96
97
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
await Promise.race([
|
|
99
|
+
browser.close(),
|
|
100
|
+
new Promise((r) => setTimeout(r, 3000)),
|
|
101
|
+
]);
|
|
101
102
|
} catch (e) {
|
|
102
|
-
console.error(`[SIGINT]
|
|
103
|
+
console.error(`[SIGINT] 断开浏览器连接时出错: ${e.message}`);
|
|
103
104
|
}
|
|
105
|
+
// 再强杀 Edge 进程兜底(优雅关闭不保证退出浏览器进程)
|
|
106
|
+
console.error(`[SIGINT] 正在终止 Edge 进程 (端口: ${port})`);
|
|
107
|
+
await killEdgeProcesses(userDataDir, port).catch((e) => {
|
|
108
|
+
console.error(`[SIGINT] 终止 Edge 进程失败: ${e.message}`);
|
|
109
|
+
});
|
|
104
110
|
console.error("浏览器已关闭");
|
|
105
111
|
process.exit(0);
|
|
106
112
|
});
|
package/src/cli/tag.js
CHANGED
|
@@ -109,10 +109,12 @@ function getBrowserMemory(port = null) {
|
|
|
109
109
|
`}; ` +
|
|
110
110
|
`Write-Output $sum`;
|
|
111
111
|
} else {
|
|
112
|
+
// 无端口场景:Get-Process(.NET API,不依赖 WMI)统计全部 msedge 工作集,
|
|
113
|
+
// 与原 Get-CimInstance 全量求和行为等价;WMI 损坏/禁用的机器也能返回
|
|
112
114
|
ps1Content =
|
|
113
|
-
`$procs = Get-
|
|
115
|
+
`$procs = Get-Process msedge -ErrorAction SilentlyContinue; ` +
|
|
114
116
|
`$sum = 0; ` +
|
|
115
|
-
`foreach ($p in $procs) { $sum += $p.
|
|
117
|
+
`foreach ($p in $procs) { $sum += $p.WorkingSet64 }; ` +
|
|
116
118
|
`Write-Output $sum`;
|
|
117
119
|
}
|
|
118
120
|
writeFileSync(ps1Path, ps1Content);
|
|
@@ -260,11 +260,23 @@ async function fetchUserVideosAPI(
|
|
|
260
260
|
) {
|
|
261
261
|
log(` [API拦截] 获取 @${username} 视频 ...`);
|
|
262
262
|
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
const {
|
|
264
|
+
blockedRetryWaitsMs = DEFAULT_BLOCKED_RETRY_WAITS,
|
|
265
|
+
cdpProbeTimeoutMs = 10000, // 单次探活超时
|
|
266
|
+
cdpObserveWaitMs = 20000, // 观察等待间隔(页面转圈加载期)
|
|
267
|
+
cdpObserveRounds = 2, // 观察轮数
|
|
268
|
+
cdpReloadSettleMs = 5000, // 刷新后缓冲等待,让加载高峰过去
|
|
269
|
+
cdpReloadProbeTimeoutMs = 20000, // 刷新后重检窗口
|
|
270
|
+
} = options;
|
|
271
|
+
|
|
272
|
+
// CDP 健康检查:带超时保护,区分"页面忙"与"页面死"。
|
|
273
|
+
// evaluate 排队在页面主线程上,加载中的慢页面(肉眼可见转圈)10s 轮不到执行很正常,
|
|
274
|
+
// 此时是忙不是死:先观察等待(默认 2 轮 x 20s),慢页面通常在此期间自然加载完自愈。
|
|
275
|
+
// 观察后仍无响应才刷新页面(实测刷新可解除主线程死锁);刷新后页面处于最忙的加载期,
|
|
276
|
+
// 先缓冲等待再以更宽松的窗口重检,避免误杀正在恢复的页面。
|
|
265
277
|
const evalWithTimeout = (label, ms) =>
|
|
266
278
|
Promise.race([
|
|
267
|
-
page.evaluate(() =>
|
|
279
|
+
page.evaluate(() => document.readyState),
|
|
268
280
|
new Promise((_, reject) =>
|
|
269
281
|
setTimeout(
|
|
270
282
|
() => reject(new Error(`${label} 超时(${ms / 1000}s)`)),
|
|
@@ -273,23 +285,50 @@ async function fetchUserVideosAPI(
|
|
|
273
285
|
),
|
|
274
286
|
]);
|
|
275
287
|
|
|
288
|
+
let healthy = false;
|
|
276
289
|
try {
|
|
277
|
-
await evalWithTimeout("CDP 健康检查",
|
|
290
|
+
const state = await evalWithTimeout("CDP 健康检查", cdpProbeTimeoutMs);
|
|
291
|
+
healthy = true;
|
|
292
|
+
if (state !== "complete") {
|
|
293
|
+
log(` [API拦截] 健康检查通过,页面加载中 (readyState=${state})`);
|
|
294
|
+
}
|
|
278
295
|
} catch (e) {
|
|
279
|
-
|
|
296
|
+
// 观察阶段:慢页面几十秒内通常自愈,期间不做任何操作
|
|
297
|
+
log(
|
|
298
|
+
` [API拦截] CDP 探活超时: ${e.message},页面可能仍在加载,观察等待...`,
|
|
299
|
+
);
|
|
300
|
+
for (let round = 1; round <= cdpObserveRounds && !healthy; round++) {
|
|
301
|
+
await new Promise((r) => setTimeout(r, cdpObserveWaitMs));
|
|
302
|
+
try {
|
|
303
|
+
await evalWithTimeout(
|
|
304
|
+
`观察重检 ${round}/${cdpObserveRounds}`,
|
|
305
|
+
cdpProbeTimeoutMs,
|
|
306
|
+
);
|
|
307
|
+
healthy = true;
|
|
308
|
+
log(` [API拦截] 页面已恢复(观察 ${round} 轮后)`);
|
|
309
|
+
} catch (e2) {
|
|
310
|
+
log(
|
|
311
|
+
` [API拦截] 观察 ${round}/${cdpObserveRounds} 后仍无响应: ${e2.message}`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!healthy) {
|
|
318
|
+
log(` [API拦截] 观察后仍无响应,刷新页面恢复...`);
|
|
280
319
|
await page
|
|
281
320
|
.reload({ waitUntil: "domcontentloaded", timeout: 30000 })
|
|
282
321
|
.catch(() => {});
|
|
322
|
+
await new Promise((r) => setTimeout(r, cdpReloadSettleMs));
|
|
283
323
|
try {
|
|
284
|
-
await evalWithTimeout("刷新后重检",
|
|
285
|
-
log(` [API拦截]
|
|
324
|
+
await evalWithTimeout("刷新后重检", cdpReloadProbeTimeoutMs);
|
|
325
|
+
log(` [API拦截] 页面已恢复(刷新后)`);
|
|
286
326
|
} catch (e2) {
|
|
287
327
|
throw new Error(`CDP 连接异常(刷新后仍失败): ${e2.message}`);
|
|
288
328
|
}
|
|
289
329
|
}
|
|
290
330
|
|
|
291
331
|
let apiRequestUrlRef = { value: null };
|
|
292
|
-
const { blockedRetryWaitsMs = DEFAULT_BLOCKED_RETRY_WAITS } = options;
|
|
293
332
|
const retryWaits = Array.isArray(blockedRetryWaitsMs)
|
|
294
333
|
? blockedRetryWaitsMs
|
|
295
334
|
: DEFAULT_BLOCKED_RETRY_WAITS;
|
package/src/lib/browser/cdp.js
CHANGED
|
@@ -70,6 +70,25 @@ function checkEdgeArgs() {
|
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* 解析 netstat -ano 输出,返回监听指定端口的 PID 列表(去重)。
|
|
75
|
+
* 不依赖 WMI,适配 WMI 损坏/禁用的 Windows 机器。
|
|
76
|
+
* @param {string} netstatOutput
|
|
77
|
+
* @param {number} port
|
|
78
|
+
* @returns {string[]}
|
|
79
|
+
*/
|
|
80
|
+
export function parseNetstatListeningPids(netstatOutput, port) {
|
|
81
|
+
const pids = [];
|
|
82
|
+
for (const line of netstatOutput.split(/\r?\n/)) {
|
|
83
|
+
// LISTENING 状态 + 精确端口匹配(端口后必须跟空白,避免 8222 误匹配 18222)
|
|
84
|
+
if (!line.includes("LISTENING") || !line.includes(`:${port} `)) continue;
|
|
85
|
+
const parts = line.trim().split(/\s+/);
|
|
86
|
+
const pid = parts[parts.length - 1];
|
|
87
|
+
if (/^\d+$/.test(pid) && !pids.includes(pid)) pids.push(pid);
|
|
88
|
+
}
|
|
89
|
+
return pids;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
function killEdgeProcesses(targetDir, port) {
|
|
74
93
|
return new Promise((resolve) => {
|
|
75
94
|
const platform = os.platform();
|
|
@@ -120,39 +139,49 @@ function killEdgeProcesses(targetDir, port) {
|
|
|
120
139
|
}
|
|
121
140
|
} else if (platform === "win32") {
|
|
122
141
|
if (port) {
|
|
123
|
-
//
|
|
142
|
+
// 不依赖 WMI:Get-CimInstance 在 WMI 损坏/禁用的机器上查不到进程(KILLED_PIDS 为空)。
|
|
143
|
+
// 改为 netstat 按监听端口找 PID(CDP 端口由浏览器主进程监听),taskkill /F /T 杀进程树
|
|
144
|
+
let killCmds = "";
|
|
124
145
|
try {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
`Write-Output $count`;
|
|
141
|
-
fs.writeFileSync(ps1Path, ps1Content);
|
|
142
|
-
const result = execSync(
|
|
143
|
-
`powershell.exe -ExecutionPolicy Bypass -File "${ps1Path}"`,
|
|
144
|
-
)
|
|
145
|
-
.toString()
|
|
146
|
-
.trim();
|
|
147
|
-
console.error(`[killEdgeProcesses] Windows PS result: ${result}`);
|
|
148
|
-
try {
|
|
149
|
-
fs.unlinkSync(ps1Path);
|
|
150
|
-
} catch {}
|
|
151
|
-
command = "exit 0";
|
|
146
|
+
const netstatOut = execSync("netstat -ano", {
|
|
147
|
+
encoding: "utf-8",
|
|
148
|
+
timeout: 5000,
|
|
149
|
+
windowsHide: true,
|
|
150
|
+
});
|
|
151
|
+
const pids = parseNetstatListeningPids(netstatOut, port);
|
|
152
|
+
if (pids.length > 0) {
|
|
153
|
+
// 不重定向 stderr:taskkill 的错误信息(拒绝访问/找不到进程)需要可见
|
|
154
|
+
killCmds = pids
|
|
155
|
+
.map((pid) => `taskkill /F /T /PID ${pid}`)
|
|
156
|
+
.join(" & ");
|
|
157
|
+
console.error(
|
|
158
|
+
`[killEdgeProcesses] 端口 ${port} 监听进程 PID: ${pids.join(", ")}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
152
161
|
} catch (e) {
|
|
153
|
-
console.error(`[killEdgeProcesses]
|
|
154
|
-
|
|
162
|
+
console.error(`[killEdgeProcesses] netstat 查询失败: ${e.message}`);
|
|
163
|
+
}
|
|
164
|
+
if (killCmds) {
|
|
165
|
+
// 同步执行并打印结果:taskkill 失败(权限/会话不匹配等)必须可见,不能静默吞掉
|
|
166
|
+
const killCmd = `(${killCmds})`;
|
|
167
|
+
try {
|
|
168
|
+
const out = execSync(killCmd, {
|
|
169
|
+
encoding: "utf-8",
|
|
170
|
+
timeout: 15000,
|
|
171
|
+
windowsHide: true,
|
|
172
|
+
});
|
|
173
|
+
const lines = out.trim().split(/\r?\n/).filter(Boolean);
|
|
174
|
+
console.error(
|
|
175
|
+
`[killEdgeProcesses] taskkill 结果: ${lines.join(" | ") || "(无输出)"}`,
|
|
176
|
+
);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.error(`[killEdgeProcesses] taskkill 失败: ${e.message}`);
|
|
179
|
+
const detail = `${e.stdout || ""}${e.stderr || ""}`.trim();
|
|
180
|
+
if (detail) console.error(`[killEdgeProcesses] 详情: ${detail}`);
|
|
181
|
+
}
|
|
155
182
|
}
|
|
183
|
+
// 找不到监听进程 = 浏览器可能已退出,不做按进程名的兜底(避免误杀其他端口实例)
|
|
184
|
+
command = `del /f /q "%LOCALAPPDATA%\\Microsoft\\Edge\\Singleton*" 2>nul; exit 0`;
|
|
156
185
|
} else {
|
|
157
186
|
command = "taskkill /F /IM msedge.exe 2>nul || exit 0";
|
|
158
187
|
}
|
|
@@ -122,12 +122,13 @@ function splitCommand(cmdLine) {
|
|
|
122
122
|
function getProcessStats(pid) {
|
|
123
123
|
try {
|
|
124
124
|
if (IS_WIN) {
|
|
125
|
-
//
|
|
125
|
+
// 用 Get-Process(.NET System.Diagnostics.Process API),不依赖 WMI。
|
|
126
|
+
// 部分机器 WMI 损坏/禁用时 Get-CimInstance 返回空,导致内存读取为 0
|
|
126
127
|
const ps1Path = join(tmpdir(), `wd-proc-${pid}-${Date.now()}.ps1`);
|
|
127
128
|
try {
|
|
128
129
|
const ps1Content =
|
|
129
|
-
`$p = Get-
|
|
130
|
-
`if ($p) { Write-Output $p.
|
|
130
|
+
`$p = Get-Process -Id ${pid} -ErrorAction SilentlyContinue; ` +
|
|
131
|
+
`if ($p) { Write-Output $p.WorkingSet64 } else { Write-Output 0 }`;
|
|
131
132
|
writeFileSync(ps1Path, ps1Content);
|
|
132
133
|
const stdout = execSync(
|
|
133
134
|
`powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${ps1Path}"`,
|
|
@@ -176,6 +177,7 @@ function getProcessStats(pid) {
|
|
|
176
177
|
|
|
177
178
|
// Windows: 从 cmd.exe 往下找真正的 node.exe PID(shell:true 时 cmd.exe 是中间层)
|
|
178
179
|
function getChildPid(parentPid) {
|
|
180
|
+
// 主路径:WMI 查询(原逻辑不变,WMI 正常机器零变化)
|
|
179
181
|
try {
|
|
180
182
|
const out = execSync(
|
|
181
183
|
`powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter 'ParentProcessId=${parentPid}' | Where-Object { $_.Name -notlike 'conhost*' } | Select-Object -First 1 -ExpandProperty ProcessId"`,
|
|
@@ -187,6 +189,22 @@ function getChildPid(parentPid) {
|
|
|
187
189
|
},
|
|
188
190
|
).trim();
|
|
189
191
|
return parseInt(out, 10) || 0;
|
|
192
|
+
} catch {
|
|
193
|
+
// WMI 损坏/禁用时 Get-CimInstance 直接报错,落入 fallback
|
|
194
|
+
}
|
|
195
|
+
// fallback:不依赖 WMI,用 Get-Process(.NET API)按启动时间找
|
|
196
|
+
// cmd.exe 之后最早启动的 node.exe(watchdog spawn 后 3s 才调用,node 应已启动)
|
|
197
|
+
try {
|
|
198
|
+
const out = execSync(
|
|
199
|
+
`powershell -NoProfile -Command "$cmd = Get-Process -Id ${parentPid} -ErrorAction SilentlyContinue; if ($cmd) { Get-Process node -ErrorAction SilentlyContinue | Where-Object { $_.StartTime -ge $cmd.StartTime } | Sort-Object StartTime | Select-Object -First 1 -ExpandProperty Id }"`,
|
|
200
|
+
{
|
|
201
|
+
encoding: "utf-8",
|
|
202
|
+
timeout: 3000,
|
|
203
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
204
|
+
windowsHide: true,
|
|
205
|
+
},
|
|
206
|
+
).trim();
|
|
207
|
+
return parseInt(out, 10) || 0;
|
|
190
208
|
} catch {
|
|
191
209
|
return 0;
|
|
192
210
|
}
|
package/src/watchdog/upgrader.js
CHANGED
|
@@ -350,8 +350,9 @@ async function snapshotAllProcesses() {
|
|
|
350
350
|
});
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
|
-
} catch {
|
|
353
|
+
} catch (e) {
|
|
354
354
|
/* 快照失败时返回空,清理环节自然跳过 */
|
|
355
|
+
console.error(`[upgrader] 进程表快照失败(WMI 不可用?): ${e.message}`);
|
|
355
356
|
}
|
|
356
357
|
return rows;
|
|
357
358
|
}
|
|
@@ -465,7 +466,10 @@ async function listWatchdogPids() {
|
|
|
465
466
|
.split(/\r?\n/)
|
|
466
467
|
.map((s) => parseInt(s.trim(), 10))
|
|
467
468
|
.filter((n) => n > 0);
|
|
468
|
-
} catch {
|
|
469
|
+
} catch (e) {
|
|
470
|
+
console.error(
|
|
471
|
+
`[upgrader] 枚举 watchdog 进程失败(WMI 不可用?): ${e.message}`,
|
|
472
|
+
);
|
|
469
473
|
return [];
|
|
470
474
|
}
|
|
471
475
|
}
|