tt-help-cli-ycl 1.4.159 → 1.4.160
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 +2 -2
- package/src/watchdog/agent.js +27 -0
- package/src/watchdog/upgrader.js +111 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tt-help-cli-ycl",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.160",
|
|
4
4
|
"description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"better-sqlite3": "^12.10.0"
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|
package/src/watchdog/agent.js
CHANGED
|
@@ -296,6 +296,7 @@ export class WatchdogAgent {
|
|
|
296
296
|
this._switchRestartEnabled = false; // 代理切换重启默认不启用(后端下发开启才生效)
|
|
297
297
|
this._switchRestartConfigKey = null; // 上次配置指纹(幂等打印用)
|
|
298
298
|
this._autoUpgradeEnabled = true; // 自动升级默认开启(后端下发关闭才停用)
|
|
299
|
+
this._loadedVersion = getVersion(); // 启动时包版本(磁盘版本变化 = 被手动升级,需自动重启)
|
|
299
300
|
this._errorTriggerEnabled = false; // 默认不启用(后端下发开启才生效)
|
|
300
301
|
this._errorTriggerCount = ERROR_TRIGGER_COUNT;
|
|
301
302
|
this._errorTriggerMinMs = ERROR_MIN_MS;
|
|
@@ -1068,6 +1069,7 @@ export class WatchdogAgent {
|
|
|
1068
1069
|
_startUpgradeChecker() {
|
|
1069
1070
|
if (!isGlobalInstall()) return;
|
|
1070
1071
|
if (!this._autoUpgradeEnabled) return;
|
|
1072
|
+
this._startSelfVersionWatcher();
|
|
1071
1073
|
|
|
1072
1074
|
this.upgradeChecker = startUpgradeChecker({
|
|
1073
1075
|
onUpdateAvailable: async ({ current, latest }) => {
|
|
@@ -1082,6 +1084,31 @@ export class WatchdogAgent {
|
|
|
1082
1084
|
});
|
|
1083
1085
|
}
|
|
1084
1086
|
|
|
1087
|
+
/**
|
|
1088
|
+
* 磁盘版本自检(每 60s):发现磁盘包版本 ≠ 本进程加载版本(被手动 npm i -g 升/降级)时,
|
|
1089
|
+
* 自动走 restart-only 重启(杀旧 → 启新 → 校验),无需人工重启。
|
|
1090
|
+
* 只比不等:手动降级同样重启,保证运行代码与磁盘一致。
|
|
1091
|
+
*/
|
|
1092
|
+
_startSelfVersionWatcher() {
|
|
1093
|
+
if (this._selfVersionTimer) return;
|
|
1094
|
+
this._selfVersionTimer = setInterval(() => {
|
|
1095
|
+
try {
|
|
1096
|
+
const disk = getVersion();
|
|
1097
|
+
if (disk && disk !== this._loadedVersion) {
|
|
1098
|
+
console.error(
|
|
1099
|
+
`[upgrade] 检测到包版本变化 ${this._loadedVersion} → ${disk}(手动升级),自动重启 watchdog...`,
|
|
1100
|
+
);
|
|
1101
|
+
clearInterval(this._selfVersionTimer);
|
|
1102
|
+
this._selfVersionTimer = null;
|
|
1103
|
+
spawnUpgrader({ ...this._buildRestartParams(), restartOnly: true });
|
|
1104
|
+
}
|
|
1105
|
+
} catch {
|
|
1106
|
+
/* 读版本失败,下轮重试 */
|
|
1107
|
+
}
|
|
1108
|
+
}, 60_000);
|
|
1109
|
+
if (this._selfVersionTimer.unref) this._selfVersionTimer.unref();
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1085
1112
|
/** 构建新版 watchdog 重启所需参数(透传给独立升级器) */
|
|
1086
1113
|
_buildRestartParams() {
|
|
1087
1114
|
return {
|
package/src/watchdog/upgrader.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { exec, spawn, execFile } from "child_process";
|
|
10
10
|
import { promisify } from "util";
|
|
11
|
-
import { readFileSync, openSync, unlinkSync } from "fs";
|
|
11
|
+
import { readFileSync, writeFileSync, openSync, unlinkSync } from "fs";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
import { dirname, join } from "path";
|
|
14
14
|
import { homedir, tmpdir } from "os";
|
|
@@ -549,19 +549,38 @@ async function listWatchdogPids() {
|
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
/**
|
|
552
|
-
* 启动新版 watchdog
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
552
|
+
* 启动新版 watchdog(Windows 方式 2026-09-25 重写 — 214 实测教训):
|
|
553
|
+
*
|
|
554
|
+
* 旧实现(node spawn 直接嵌 start 命令,windowsHide:true)起的 watchdog 会「起而复死」:
|
|
555
|
+
* 外层 cmd 带 CREATE_NO_WINDOW 隐藏启动,其内 start 弹出的新控制台随外层生命周期
|
|
556
|
+
* 被系统回收,watchdog 写完存活标记后数秒内消失(1.4.159 自动升级后「看门狗没拉起来」实因)。
|
|
557
|
+
*
|
|
558
|
+
* 新实现:写临时 bat,由 cmd /c 执行;bat 内用 start 弹独立交互窗口运行新版 ——
|
|
559
|
+
* 与运维手动双击/命令行启动完全同构(213/214 实测该方式进程持久不灭)。
|
|
560
|
+
* 窗口标题含 WINDOW_TITLE_TAG,供下次升级 killOldWatchdogWindow 关窗匹配。
|
|
561
|
+
*
|
|
562
|
+
* 其他平台:直接 detached spawn node,日志落盘 logFd。
|
|
557
563
|
* @returns {import("child_process").ChildProcess}
|
|
558
564
|
*/
|
|
559
565
|
function spawnNewWatchdog(cliPath, args, logFd) {
|
|
560
566
|
if (process.platform === "win32") {
|
|
567
|
+
// start 直接执行 node(不经 cmd /k 中转):cmd /k 后带多个引号段会触发 cmd 的
|
|
568
|
+
// 引号剥离规则(剥整行首尾引号 → 带空格的 node 路径被截断成 C:\Program → 启动失败)
|
|
569
|
+
const cmdLine = [cliPath, ...args].map((a) => `"${a}"`).join(" ");
|
|
570
|
+
const batPath = join(
|
|
571
|
+
tmpdir(),
|
|
572
|
+
`tt-help-watchdog-start-${Date.now()}.bat`,
|
|
573
|
+
);
|
|
574
|
+
const bat = [
|
|
575
|
+
"@echo off",
|
|
576
|
+
`start "${WINDOW_TITLE_TAG}" "${process.execPath}" ${cmdLine}`,
|
|
577
|
+
"exit",
|
|
578
|
+
].join("\r\n");
|
|
579
|
+
writeFileSync(batPath, bat + "\r\n", "utf-8");
|
|
561
580
|
const child = spawn(
|
|
562
581
|
"cmd.exe",
|
|
563
|
-
["/c",
|
|
564
|
-
//
|
|
582
|
+
["/c", batPath],
|
|
583
|
+
// 外层 cmd 隐藏且一闪即退;新窗口由 start 创建,独立持久
|
|
565
584
|
{ detached: true, stdio: "ignore", windowsHide: true, cwd: homeDir },
|
|
566
585
|
);
|
|
567
586
|
child.unref();
|
|
@@ -583,12 +602,94 @@ function buildWatchdogArgs({ serverUrl, clientId, intervalSec, noDaemon }) {
|
|
|
583
602
|
return args;
|
|
584
603
|
}
|
|
585
604
|
|
|
605
|
+
/**
|
|
606
|
+
* 仅重启模式(--restart-only):由运行中的 watchdog 在检测到磁盘包版本变化时调用。
|
|
607
|
+
* 复用完整升级模式的 杀旧→启新→存活校验(第 3/4/5 步),跳过下载/验证。
|
|
608
|
+
*/
|
|
609
|
+
async function runRestartOnlyMode(argv) {
|
|
610
|
+
const get = (f) => (argv.includes(f) ? argv[argv.indexOf(f) + 1] : undefined);
|
|
611
|
+
const serverUrl = get("--server");
|
|
612
|
+
const clientId = get("--client-id");
|
|
613
|
+
const intervalSec = parseInt(get("--interval"), 10) || 15;
|
|
614
|
+
const noDaemon = argv.includes("--no-daemon");
|
|
615
|
+
const oldPid =
|
|
616
|
+
(argv.includes("--old-pid") ? parseInt(get("--old-pid"), 10) : 0) ||
|
|
617
|
+
process.ppid;
|
|
618
|
+
if (!oldPid) {
|
|
619
|
+
console.error(`[restart-only] 无旧 pid,退出`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
console.error(`[restart-only] 重启 watchdog (旧 pid=${oldPid})...`);
|
|
623
|
+
|
|
624
|
+
// 杀旧:与完整模式一致(Ctrl+C 优雅 → 强杀 → 清树 → 孤儿 → 关窗)
|
|
625
|
+
const procSnapshot = await snapshotAllProcesses();
|
|
626
|
+
const descPids = listDescendantPids(procSnapshot, oldPid, process.pid);
|
|
627
|
+
let exited = false;
|
|
628
|
+
const gracefulSent =
|
|
629
|
+
process.platform === "win32" && (await sendConsoleCtrlC(oldPid));
|
|
630
|
+
if (gracefulSent) {
|
|
631
|
+
console.error(`[restart-only] 已发送 Ctrl+C,等待旧 watchdog 优雅停止业务...`);
|
|
632
|
+
exited = await waitProcessExit(oldPid, 8000);
|
|
633
|
+
}
|
|
634
|
+
if (!exited) {
|
|
635
|
+
if (gracefulSent)
|
|
636
|
+
console.error(`[restart-only] 旧 watchdog 未在 8s 内优雅退出,强制终止`);
|
|
637
|
+
exited = await terminateProcess(oldPid, 5000);
|
|
638
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
639
|
+
}
|
|
640
|
+
await killPidList(descPids, "[restart-only] 已清理旧 watchdog 进程树");
|
|
641
|
+
const orphanPids = listOrphanBusinessPids(await snapshotAllProcesses());
|
|
642
|
+
await killPidList(orphanPids, "[restart-only] 已清理孤儿业务进程");
|
|
643
|
+
await killOldWatchdogWindow();
|
|
644
|
+
|
|
645
|
+
// 启新(与完整模式一致:Windows 弹独立 cmd /k 窗口)
|
|
646
|
+
console.error(`[restart-only] 启动新版 watchdog ...`);
|
|
647
|
+
const cliPath = join(__dirname, "..", "..", "cli.js");
|
|
648
|
+
const args = buildWatchdogArgs({ serverUrl, clientId, intervalSec, noDaemon });
|
|
649
|
+
const logFd =
|
|
650
|
+
process.platform === "win32"
|
|
651
|
+
? undefined
|
|
652
|
+
: openLogFile("tt-help-watchdog", "restart-only", "新版 watchdog 日志");
|
|
653
|
+
if (process.platform === "win32") deleteStartedFile();
|
|
654
|
+
spawnNewWatchdog(cliPath, args, logFd);
|
|
655
|
+
|
|
656
|
+
// 存活校验(与完整模式一致)
|
|
657
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
658
|
+
if (process.platform === "win32") {
|
|
659
|
+
const deadline = Date.now() + 15_000;
|
|
660
|
+
let st = null;
|
|
661
|
+
while (Date.now() < deadline && !st) {
|
|
662
|
+
const cur = readStartedFile();
|
|
663
|
+
if (cur && cur.pid > 0 && (await isProcessAlive(cur.pid))) st = cur;
|
|
664
|
+
if (!st) await new Promise((r) => setTimeout(r, 1000));
|
|
665
|
+
}
|
|
666
|
+
if (st) {
|
|
667
|
+
console.error(`[restart-only] 新版 watchdog 存活确认 (pid=${st.pid}),重启完成`);
|
|
668
|
+
} else {
|
|
669
|
+
console.error(`[restart-only] 新版 watchdog 启动后未存活,请查看窗口/日志排障`);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
} else {
|
|
673
|
+
const newPids = (await listWatchdogPids()).filter((p) => p !== oldPid);
|
|
674
|
+
if (newPids.length > 0) {
|
|
675
|
+
console.error(`[restart-only] 新版 watchdog 存活确认 (pid=${newPids.join(",")}),重启完成`);
|
|
676
|
+
} else {
|
|
677
|
+
console.error(`[restart-only] 新版 watchdog 启动后未存活,请查看窗口/日志排障`);
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
process.exit(0);
|
|
682
|
+
}
|
|
683
|
+
|
|
586
684
|
/**
|
|
587
685
|
* 独立升级器主流程(由旧 watchdog 以子进程方式启动)
|
|
588
686
|
* 顺序:下载 → 验证版本 → 杀旧 → 启新。任一步失败都不杀旧,业务不断。
|
|
589
687
|
* @param {string[]} argv - process.argv.slice(2)
|
|
590
688
|
*/
|
|
591
689
|
export async function runUpgradeMode(argv) {
|
|
690
|
+
// 仅重启模式:磁盘包被手动升级(npm i -g)后由运行中的 watchdog 调用,
|
|
691
|
+
// 跳过下载与版本验证(此时磁盘与线上一致,会误判"未升级"),直接杀旧→启新→校验
|
|
692
|
+
if (argv.includes("--restart-only")) return runRestartOnlyMode(argv);
|
|
592
693
|
const get = (f) => (argv.includes(f) ? argv[argv.indexOf(f) + 1] : undefined);
|
|
593
694
|
const serverUrl = get("--server");
|
|
594
695
|
const clientId = get("--client-id");
|
|
@@ -759,9 +860,11 @@ export function spawnUpgrader({
|
|
|
759
860
|
intervalSec = 15,
|
|
760
861
|
spawnDaemon = true,
|
|
761
862
|
oldPid = 0,
|
|
863
|
+
restartOnly = false,
|
|
762
864
|
} = {}) {
|
|
763
865
|
const upgraderPath = fileURLToPath(import.meta.url);
|
|
764
866
|
const args = ["--upgrade", "--old-pid", String(oldPid || process.ppid)];
|
|
867
|
+
if (restartOnly) args.push("--restart-only");
|
|
765
868
|
if (serverUrl) args.push("--server", serverUrl);
|
|
766
869
|
if (clientId) args.push("--client-id", clientId);
|
|
767
870
|
args.push("--interval", String(intervalSec));
|