tt-help-cli-ycl 1.4.104 → 1.4.106

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.4.104",
3
+ "version": "1.4.106",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,7 +6,8 @@
6
6
  * 见 runUpgradeMode。旧 watchdog 收到 SIGTERM 后优雅退出,新版接管。
7
7
  */
8
8
 
9
- import { exec, spawn } from "child_process";
9
+ import { exec, spawn, execFile } from "child_process";
10
+ import { promisify } from "util";
10
11
  import { readFileSync, openSync } from "fs";
11
12
  import { fileURLToPath } from "url";
12
13
  import { dirname, join } from "path";
@@ -18,6 +19,7 @@ const PACKAGE = "tt-help-cli-ycl";
18
19
  const REGISTRY = "https://registry.npmjs.org";
19
20
  const homeDir = homedir() || process.env.HOME || process.env.USERPROFILE || "";
20
21
  const TT_HELP_CONFIG = join(homeDir, ".tt-help.json");
22
+ const execFileP = promisify(execFile);
21
23
 
22
24
  /**
23
25
  * 升级检测源:TT_HELP_REGISTRY 环境变量 > ~/.tt-help.json 的 npmRegistry > 官方 npmjs.org
@@ -253,6 +255,140 @@ async function terminateProcess(pid, waitMs = 5000) {
253
255
  return false;
254
256
  }
255
257
 
258
+ /** 枚举指定父进程的直接子进程 PID(Windows: PowerShell;POSIX: pgrep) */
259
+ async function listChildPids(parentPid) {
260
+ if (!parentPid) return [];
261
+ try {
262
+ if (process.platform === "win32") {
263
+ const ps =
264
+ `Get-CimInstance Win32_Process -Filter "ParentProcessId=${parentPid}" ` +
265
+ `| Select-Object -ExpandProperty ProcessId`;
266
+ const { stdout } = await execFileP(
267
+ "powershell.exe",
268
+ ["-NoProfile", "-Command", ps],
269
+ { timeout: 15000, windowsHide: true },
270
+ );
271
+ return String(stdout)
272
+ .split(/\r?\n/)
273
+ .map((s) => parseInt(s.trim(), 10))
274
+ .filter((n) => n > 0);
275
+ }
276
+ const { stdout } = await execFileP("pgrep", ["-P", String(parentPid)], {
277
+ timeout: 15000,
278
+ });
279
+ return String(stdout)
280
+ .split(/\r?\n/)
281
+ .map((s) => parseInt(s.trim(), 10))
282
+ .filter((n) => n > 0);
283
+ } catch {
284
+ return [];
285
+ }
286
+ }
287
+
288
+ /** 强杀单个进程及其子树(Windows: taskkill /T /F;POSIX: SIGKILL) */
289
+ async function forceKillTree(pid) {
290
+ try {
291
+ if (process.platform === "win32") {
292
+ await execFileP(
293
+ "taskkill.exe",
294
+ ["/PID", String(pid), "/T", "/F"],
295
+ { timeout: 15000, windowsHide: true },
296
+ );
297
+ } else {
298
+ process.kill(pid, "SIGKILL");
299
+ }
300
+ } catch {
301
+ /* 进程可能已退出 */
302
+ }
303
+ }
304
+
305
+ /**
306
+ * 清理旧 watchdog 残留的子进程(升级后不留孤儿占端口/资源)。
307
+ * 流程:枚举直接子进程(排除 excludePid)→ 逐个强杀整棵子树 → 等 1s → 复查残留再杀。
308
+ * 背景:Windows 上 SIGTERM=强杀,旧 watchdog 的业务子进程(webserver 等)会变孤儿;
309
+ * macOS/Linux 上优雅退出未完成(SIGKILL 兜底)时同样需要清理。
310
+ * @returns {Promise<number[]>} 被清理的 PID 列表
311
+ */
312
+ async function killChildProcesses(parentPid, excludePid = 0) {
313
+ const killed = [];
314
+ const firstRound = (await listChildPids(parentPid)).filter(
315
+ (p) => p !== excludePid,
316
+ );
317
+ for (const pid of firstRound) {
318
+ await forceKillTree(pid);
319
+ killed.push(pid);
320
+ }
321
+ if (killed.length > 0) {
322
+ console.error(
323
+ `[upgrade-proc] 已清理旧 watchdog 残留子进程: ${killed.join(", ")}`,
324
+ );
325
+ await new Promise((r) => setTimeout(r, 1000));
326
+ // 复查:仍有残留则再杀一轮
327
+ const remain = (await listChildPids(parentPid)).filter(
328
+ (p) => p !== excludePid,
329
+ );
330
+ for (const pid of remain) {
331
+ await forceKillTree(pid);
332
+ killed.push(pid);
333
+ }
334
+ }
335
+ return killed;
336
+ }
337
+
338
+ /** 枚举当前所有 watchdog 进程 PID(存活校验前后对比用;不含升级器 U 自身) */
339
+ async function listWatchdogPids() {
340
+ try {
341
+ if (process.platform === "win32") {
342
+ const ps =
343
+ `Get-CimInstance Win32_Process -Filter "Name='node.exe'" ` +
344
+ `| Where-Object { $_.CommandLine -match 'cli\\.js watchdog' } ` +
345
+ `| Select-Object -ExpandProperty ProcessId`;
346
+ const { stdout } = await execFileP(
347
+ "powershell.exe",
348
+ ["-NoProfile", "-Command", ps],
349
+ { timeout: 15000, windowsHide: true },
350
+ );
351
+ return String(stdout)
352
+ .split(/\r?\n/)
353
+ .map((s) => parseInt(s.trim(), 10))
354
+ .filter((n) => n > 0);
355
+ }
356
+ const { stdout } = await execFileP("pgrep", ["-f", "cli.js watchdog"], {
357
+ timeout: 15000,
358
+ });
359
+ return String(stdout)
360
+ .split(/\r?\n/)
361
+ .map((s) => parseInt(s.trim(), 10))
362
+ .filter((n) => n > 0);
363
+ } catch {
364
+ return [];
365
+ }
366
+ }
367
+
368
+ /**
369
+ * 启动新版 watchdog:
370
+ * - Windows:`cmd.exe /c start cmd /k <node> <cli.js> watchdog ...`
371
+ * start 弹出【独立交互 cmd 窗口】(与 U 完全解耦),watchdog 日志直接显示在窗口里,
372
+ * /k 保持窗口打开(watchdog 退出后提示符仍在,便于排障);中间 cmd 隐藏不闪窗。
373
+ * - 其他平台:直接 detached spawn node,日志落盘 logFd。
374
+ * @returns {import("child_process").ChildProcess}
375
+ */
376
+ function spawnNewWatchdog(cliPath, args, logFd) {
377
+ if (process.platform === "win32") {
378
+ const child = spawn(
379
+ "cmd.exe",
380
+ ["/c", "start", "cmd", "/k", process.execPath, cliPath, ...args],
381
+ { detached: true, stdio: "ignore", windowsHide: true },
382
+ );
383
+ child.unref();
384
+ child.on("error", (err) => {
385
+ console.error(`[upgrade-proc] 新版 watchdog 启动失败: ${err.message}`);
386
+ });
387
+ return child;
388
+ }
389
+ return spawnChild([cliPath, ...args], { logFd });
390
+ }
391
+
256
392
  /** 新版 watchdog 的 CLI 参数(透传 server/clientId/interval/noDaemon,保证与旧版一致) */
257
393
  function buildWatchdogArgs({ serverUrl, clientId, intervalSec, noDaemon }) {
258
394
  const args = ["watchdog"];
@@ -301,7 +437,9 @@ export async function runUpgradeMode(argv) {
301
437
  }
302
438
  console.error(`[upgrade-proc] 新版本验证通过 (${verify.version})`);
303
439
 
304
- // 3) 杀旧:SIGTERM 优雅优先,5s 未退强杀兜底(U 不卡死)
440
+ // 3) 杀旧:先优雅发退出信号 → 等待 → 检测残留子进程 → 强杀
441
+ // Windows 上 SIGTERM=强杀(无优雅),旧 watchdog 的业务子进程(webserver 等)
442
+ // 会变孤儿占端口,因此杀旧后必须检测并强杀残留子进程(排除 U 自己)。
305
443
  console.error(`[upgrade-proc] 通知旧 watchdog (pid=${oldPid}) 退出 ...`);
306
444
  const exited = await terminateProcess(oldPid, 5000);
307
445
  if (exited) {
@@ -310,9 +448,10 @@ export async function runUpgradeMode(argv) {
310
448
  console.error(`[upgrade-proc] 旧 watchdog 未在 5s 内退出,已强制终止`);
311
449
  await new Promise((r) => setTimeout(r, 1000)); // 给强杀一点生效时间
312
450
  }
451
+ await killChildProcesses(oldPid, process.pid);
313
452
 
314
453
  // 4) 旧进程已退,启新版 watchdog
315
- // 新版永远 detached + windowsHide:false → 独立控制台窗口(前端可见),
454
+ // Windows:显式弹独立交互 cmd 窗口(start → cmd /k),日志直接显示在窗口,
316
455
  // 与旧 cmd /k 窗口解耦(旧窗口控制台在旧 watchdog 被杀后不可复用)
317
456
  console.error(`[upgrade-proc] 启动新版 watchdog ...`);
318
457
  const cliPath = join(__dirname, "..", "..", "cli.js");
@@ -322,31 +461,40 @@ export async function runUpgradeMode(argv) {
322
461
  intervalSec,
323
462
  noDaemon,
324
463
  });
325
- const logFd = openLogFile(
326
- "tt-help-watchdog",
327
- "upgrade-proc",
328
- "新版 watchdog 日志",
329
- );
464
+ const logFd =
465
+ process.platform === "win32"
466
+ ? undefined
467
+ : openLogFile("tt-help-watchdog", "upgrade-proc", "新版 watchdog 日志");
468
+ // 记录启动前的 watchdog 进程集合,用于存活校验前后对比(Windows 经 start 启动时
469
+ // 中间 cmd 秒退,不能检查其 PID)
470
+ const beforePids = await listWatchdogPids();
330
471
  let child;
331
472
  try {
332
- // 新版永远独立控制台窗口(hide 默认 false),不再继承旧 cmd /k 窗口
333
- child = spawnChild([cliPath, ...args], { logFd });
334
- console.error(`[upgrade-proc] 新版 watchdog 已启动 (pid=${child.pid})`);
473
+ child = spawnNewWatchdog(cliPath, args, logFd);
474
+ if (process.platform === "win32") {
475
+ console.error(
476
+ `[upgrade-proc] 已弹独立 cmd 窗口启动新版 watchdog(窗口内可见运行日志)`,
477
+ );
478
+ } else {
479
+ console.error(`[upgrade-proc] 新版 watchdog 已启动 (pid=${child.pid})`);
480
+ }
335
481
  } catch (e) {
336
482
  console.error(`[upgrade-proc] 新版 watchdog 启动失败: ${e.message}`);
337
483
  process.exit(1);
338
484
  }
339
485
 
340
- // 5) 存活校验:3s 后确认新版进程存活;未存活则明确报错(可看日志定位),不静默
486
+ // 5) 存活校验:3s 后确认出现新增的 watchdog 进程;未存活则明确报错,不静默
341
487
  await new Promise((r) => setTimeout(r, 3000));
342
- try {
343
- process.kill(child.pid, 0);
488
+ const newPids = (await listWatchdogPids()).filter(
489
+ (p) => !beforePids.includes(p),
490
+ );
491
+ if (newPids.length > 0) {
344
492
  console.error(
345
- `[upgrade-proc] 新版 watchdog 存活确认 (pid=${child.pid}),升级完成`,
493
+ `[upgrade-proc] 新版 watchdog 存活确认 (pid=${newPids.join(",")}),升级完成`,
346
494
  );
347
- } catch {
495
+ } else {
348
496
  console.error(
349
- `[upgrade-proc] 新版 watchdog (pid=${child.pid}) 启动后未存活,请查看日志排障`,
497
+ `[upgrade-proc] 新版 watchdog 启动后未存活,请查看窗口/日志排障`,
350
498
  );
351
499
  process.exit(1);
352
500
  }