tt-help-cli-ycl 1.4.90 → 1.4.92
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/explore.js +4 -0
- package/src/cli/refresh.js +4 -0
- package/src/lib/args.js +8 -0
- package/src/scraper/explore-core.js +4 -2
- package/src/watchdog/agent.js +26 -123
- package/src/watchdog/upgrader.js +199 -3
package/package.json
CHANGED
package/src/cli/explore.js
CHANGED
|
@@ -72,6 +72,7 @@ export async function handleExplore(options) {
|
|
|
72
72
|
exploreMaxVideos,
|
|
73
73
|
exploreProxy,
|
|
74
74
|
exploreWebserver,
|
|
75
|
+
exploreNoProduct,
|
|
75
76
|
} = options;
|
|
76
77
|
|
|
77
78
|
let userId = exploreUserId || configuredUserId;
|
|
@@ -472,6 +473,7 @@ export async function handleExplore(options) {
|
|
|
472
473
|
location: exploreLocation,
|
|
473
474
|
proxyServer: cdpOptions.proxyServer || null,
|
|
474
475
|
webserverUrl: exploreWebserver || null,
|
|
476
|
+
noProduct: !!exploreNoProduct,
|
|
475
477
|
},
|
|
476
478
|
console.error,
|
|
477
479
|
);
|
|
@@ -498,6 +500,7 @@ export async function handleExplore(options) {
|
|
|
498
500
|
location: exploreLocation,
|
|
499
501
|
proxyServer: cdpOptions.proxyServer || null,
|
|
500
502
|
webserverUrl: exploreWebserver || null,
|
|
503
|
+
noProduct: !!exploreNoProduct,
|
|
501
504
|
browser,
|
|
502
505
|
},
|
|
503
506
|
console.error,
|
|
@@ -546,6 +549,7 @@ export async function handleExplore(options) {
|
|
|
546
549
|
location: exploreLocation,
|
|
547
550
|
proxyServer: cdpOptions.proxyServer || null,
|
|
548
551
|
webserverUrl: exploreWebserver || null,
|
|
552
|
+
noProduct: !!exploreNoProduct,
|
|
549
553
|
browser,
|
|
550
554
|
},
|
|
551
555
|
console.error,
|
package/src/cli/refresh.js
CHANGED
|
@@ -70,6 +70,7 @@ export async function handleRefresh(options) {
|
|
|
70
70
|
exploreRedoMaxAge,
|
|
71
71
|
exploreProxy,
|
|
72
72
|
exploreWebserver,
|
|
73
|
+
exploreNoProduct,
|
|
73
74
|
} = options;
|
|
74
75
|
const effectiveProxy = exploreProxy || configuredProxy;
|
|
75
76
|
let resolvedProxy = effectiveProxy;
|
|
@@ -489,6 +490,7 @@ export async function handleRefresh(options) {
|
|
|
489
490
|
browser,
|
|
490
491
|
proxyServer: cdpOptions.proxyServer || null,
|
|
491
492
|
webserverUrl: exploreWebserver || null,
|
|
493
|
+
noProduct: !!exploreNoProduct,
|
|
492
494
|
},
|
|
493
495
|
console.error,
|
|
494
496
|
);
|
|
@@ -516,6 +518,7 @@ export async function handleRefresh(options) {
|
|
|
516
518
|
browser,
|
|
517
519
|
proxyServer: cdpOptions.proxyServer || null,
|
|
518
520
|
webserverUrl: exploreWebserver || null,
|
|
521
|
+
noProduct: !!exploreNoProduct,
|
|
519
522
|
},
|
|
520
523
|
console.error,
|
|
521
524
|
);
|
|
@@ -565,6 +568,7 @@ export async function handleRefresh(options) {
|
|
|
565
568
|
browser,
|
|
566
569
|
proxyServer: cdpOptions.proxyServer || null,
|
|
567
570
|
webserverUrl: exploreWebserver || null,
|
|
571
|
+
noProduct: !!exploreNoProduct,
|
|
568
572
|
},
|
|
569
573
|
console.error,
|
|
570
574
|
);
|
package/src/lib/args.js
CHANGED
|
@@ -182,6 +182,7 @@ function parseExploreArgs(args) {
|
|
|
182
182
|
let exploreMaxVideos = 16;
|
|
183
183
|
let exploreProxy = null;
|
|
184
184
|
let exploreWebserver = null;
|
|
185
|
+
let exploreNoProduct = false;
|
|
185
186
|
|
|
186
187
|
const positional = [];
|
|
187
188
|
const PRESETS = ["fast", "normal", "slow", "stealth"];
|
|
@@ -224,6 +225,8 @@ function parseExploreArgs(args) {
|
|
|
224
225
|
exploreProxy = args[++i];
|
|
225
226
|
} else if (arg === "--webserver") {
|
|
226
227
|
exploreWebserver = args[++i];
|
|
228
|
+
} else if (arg === "--no-product") {
|
|
229
|
+
exploreNoProduct = true;
|
|
227
230
|
} else {
|
|
228
231
|
positional.push(arg);
|
|
229
232
|
}
|
|
@@ -266,6 +269,7 @@ function parseExploreArgs(args) {
|
|
|
266
269
|
exploreMaxVideos,
|
|
267
270
|
exploreProxy,
|
|
268
271
|
exploreWebserver,
|
|
272
|
+
exploreNoProduct,
|
|
269
273
|
urls: [],
|
|
270
274
|
outputFormat: "json",
|
|
271
275
|
exploreCount: 0,
|
|
@@ -510,6 +514,7 @@ function parseRefreshArgs(args) {
|
|
|
510
514
|
let exploreProxy = null;
|
|
511
515
|
let exploreJobLocations = null;
|
|
512
516
|
let exploreWebserver = null;
|
|
517
|
+
let exploreNoProduct = false;
|
|
513
518
|
|
|
514
519
|
for (let i = 0; i < args.length; i++) {
|
|
515
520
|
const arg = args[i];
|
|
@@ -541,6 +546,8 @@ function parseRefreshArgs(args) {
|
|
|
541
546
|
exploreProxy = args[++i];
|
|
542
547
|
} else if (arg === "--webserver") {
|
|
543
548
|
exploreWebserver = args[++i];
|
|
549
|
+
} else if (arg === "--no-product") {
|
|
550
|
+
exploreNoProduct = true;
|
|
544
551
|
} else if (arg === "--location") {
|
|
545
552
|
exploreLocation = args[++i];
|
|
546
553
|
} else if (arg === "--job-locations") {
|
|
@@ -573,6 +580,7 @@ function parseRefreshArgs(args) {
|
|
|
573
580
|
exploreRedoMaxAge,
|
|
574
581
|
exploreProxy,
|
|
575
582
|
exploreWebserver,
|
|
583
|
+
exploreNoProduct,
|
|
576
584
|
serverUrl,
|
|
577
585
|
urls: [],
|
|
578
586
|
outputFormat: "json",
|
|
@@ -27,6 +27,7 @@ async function processExplore(page, username, options, log) {
|
|
|
27
27
|
locationMode = "explore", // "explore" | "refresh"
|
|
28
28
|
proxyServer = null,
|
|
29
29
|
webserverUrl = null,
|
|
30
|
+
noProduct = false,
|
|
30
31
|
} = options;
|
|
31
32
|
|
|
32
33
|
const result = {
|
|
@@ -130,8 +131,9 @@ async function processExplore(page, username, options, log) {
|
|
|
130
131
|
seoUrl: topVideo.seoUrl || null,
|
|
131
132
|
};
|
|
132
133
|
|
|
133
|
-
//
|
|
134
|
-
|
|
134
|
+
// 如果有商品链接且未禁用,通过 WebService API 抓取商品详情页(评分、销量等)
|
|
135
|
+
// --no-product 时跳过(由独立商品程序异步补抓,避免阻塞 explore/refresh 主流程)
|
|
136
|
+
if (topVideo.seoUrl && options.webserverUrl && !noProduct) {
|
|
135
137
|
try {
|
|
136
138
|
const resp = await fetch(
|
|
137
139
|
`${options.webserverUrl}/api/tiktok/product`,
|
package/src/watchdog/agent.js
CHANGED
|
@@ -16,14 +16,14 @@ import {
|
|
|
16
16
|
import { ProcessManager } from "./process-manager.js";
|
|
17
17
|
import {
|
|
18
18
|
getVersion,
|
|
19
|
-
|
|
19
|
+
spawnUpgraderAndWait,
|
|
20
20
|
isGlobalInstall,
|
|
21
21
|
startUpgradeChecker,
|
|
22
22
|
} from "./upgrader.js";
|
|
23
|
-
import { writeFileSync, existsSync, readFileSync
|
|
23
|
+
import { writeFileSync, existsSync, readFileSync } from "fs";
|
|
24
24
|
import { spawn } from "child_process";
|
|
25
25
|
import { join, dirname } from "path";
|
|
26
|
-
import { homedir
|
|
26
|
+
import { homedir } from "os";
|
|
27
27
|
import { fileURLToPath } from "url";
|
|
28
28
|
|
|
29
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -574,32 +574,39 @@ export class WatchdogAgent {
|
|
|
574
574
|
|
|
575
575
|
this.upgradeChecker = startUpgradeChecker({
|
|
576
576
|
onUpdateAvailable: async ({ current, latest }) => {
|
|
577
|
-
|
|
578
|
-
//
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (result.success) {
|
|
585
|
-
this.ttHelpVersion = latest;
|
|
577
|
+
// 简化升级:spawn 独立升级器下载,下载期间业务/心跳完全不受影响;
|
|
578
|
+
// 下载成功后才停业务并退出,由新版接管(避免"边停边起"与二次 taskkill 竞态)
|
|
579
|
+
console.error(
|
|
580
|
+
`[upgrade] ${current} → ${latest},启动升级程序(下载期间业务不受影响)`,
|
|
581
|
+
);
|
|
582
|
+
const outcome = await spawnUpgraderAndWait(this._buildRestartParams());
|
|
583
|
+
if (outcome !== "ok") {
|
|
586
584
|
console.error(
|
|
587
|
-
`[upgrade]
|
|
585
|
+
`[upgrade] 升级未完成(${outcome === "fail" ? "下载失败" : "升级程序无响应"}),保持当前版本运行`,
|
|
588
586
|
);
|
|
589
|
-
// 重启自身进程加载新代码(内部 daemon 路径优先),新进程会拉起新版 daemon
|
|
590
|
-
this._restartSelf();
|
|
591
587
|
return;
|
|
592
588
|
}
|
|
593
|
-
// 升级失败:恢复 daemon 与业务,保持当前进程运行
|
|
594
589
|
console.error(
|
|
595
|
-
`[upgrade]
|
|
590
|
+
`[upgrade] 新版本已下载,停止业务与 daemon 后退出,新版将接管`,
|
|
596
591
|
);
|
|
597
|
-
this.
|
|
598
|
-
|
|
592
|
+
await this.stop();
|
|
593
|
+
process.exit(0);
|
|
599
594
|
},
|
|
600
595
|
});
|
|
601
596
|
}
|
|
602
597
|
|
|
598
|
+
/** 构建新版 watchdog 重启所需参数(透传给独立升级器) */
|
|
599
|
+
_buildRestartParams() {
|
|
600
|
+
return {
|
|
601
|
+
serverUrl: this.serverUrl,
|
|
602
|
+
clientId: this.clientId,
|
|
603
|
+
intervalSec: Math.max(5, Math.round(this.intervalMs / 1000)),
|
|
604
|
+
spawnDaemon: this.spawnDaemon,
|
|
605
|
+
interactive: !!process.stdout.isTTY,
|
|
606
|
+
oldPid: process.pid,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
603
610
|
/**
|
|
604
611
|
* 启动 clash daemon 子进程
|
|
605
612
|
* daemon 负责代理节点健康巡检和故障切换,watchdog 不阻塞等待
|
|
@@ -724,110 +731,6 @@ export class WatchdogAgent {
|
|
|
724
731
|
this.clashDaemon = null;
|
|
725
732
|
}
|
|
726
733
|
|
|
727
|
-
/**
|
|
728
|
-
* 升级后重启自身进程(加载新代码)
|
|
729
|
-
* 用 cli.js watchdog 子进程替换当前进程,保证新版本内部 daemon 路径逻辑生效。
|
|
730
|
-
* 带存活确认与兜底:新进程确认存活后才退出旧进程;启动失败则回退保持旧进程运行。
|
|
731
|
-
*/
|
|
732
|
-
_restartSelf() {
|
|
733
|
-
const cliPath = join(__dirname, "..", "..", "cli.js");
|
|
734
|
-
const args = ["watchdog"];
|
|
735
|
-
if (this.serverUrl) args.push("--server", this.serverUrl);
|
|
736
|
-
if (this.clientId) args.push("--client-id", this.clientId);
|
|
737
|
-
if (!this.spawnDaemon) args.push("--no-daemon");
|
|
738
|
-
args.push(
|
|
739
|
-
"--interval",
|
|
740
|
-
String(Math.max(5, Math.round(this.intervalMs / 1000))),
|
|
741
|
-
);
|
|
742
|
-
|
|
743
|
-
// 交互式终端(用户在 cmd 窗口里手动跑 tt-help watchdog):
|
|
744
|
-
// 升级重启后接管原控制台窗口继续显示(stdio inherit + 不 detached),窗口不消失
|
|
745
|
-
const interactive = !!process.stdout.isTTY;
|
|
746
|
-
|
|
747
|
-
// 非交互(计划任务/脚本/无人值守)时,新进程日志重定向到文件,崩溃有据可查
|
|
748
|
-
let logFd = undefined;
|
|
749
|
-
try {
|
|
750
|
-
const logPath = join(
|
|
751
|
-
tmpdir(),
|
|
752
|
-
`tt-help-watchdog-restart-${Date.now()}.log`,
|
|
753
|
-
);
|
|
754
|
-
logFd = openSync(logPath, "a");
|
|
755
|
-
console.error(`[upgrade] 新进程日志: ${logPath}`);
|
|
756
|
-
} catch {}
|
|
757
|
-
const out = logFd ?? "ignore";
|
|
758
|
-
|
|
759
|
-
let child;
|
|
760
|
-
try {
|
|
761
|
-
child = spawn(process.execPath, [cliPath, ...args], {
|
|
762
|
-
// 交互式:继承原 cmd 窗口一直显示;非交互:后台运行 + 日志文件
|
|
763
|
-
detached: !interactive,
|
|
764
|
-
stdio: interactive ? "inherit" : ["ignore", out, out],
|
|
765
|
-
windowsHide: !interactive, // 交互时不隐藏,保留可见窗口
|
|
766
|
-
});
|
|
767
|
-
} catch (e) {
|
|
768
|
-
console.error(`[upgrade] 重启自身失败: ${e.message},保持旧进程运行`);
|
|
769
|
-
this._recoverAfterFailedUpgrade();
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
// 非交互后台进程独立存活(父进程退出不影响);交互式继承窗口无需 unref
|
|
773
|
-
if (!interactive) child.unref();
|
|
774
|
-
|
|
775
|
-
// 存活确认:轮询新进程是否正常存活,确认后才退出旧进程
|
|
776
|
-
let exited = false;
|
|
777
|
-
let exitCode = null;
|
|
778
|
-
let exitSignal = null;
|
|
779
|
-
child.once("exit", (c, s) => {
|
|
780
|
-
exited = true;
|
|
781
|
-
exitCode = c;
|
|
782
|
-
exitSignal = s;
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
console.error(
|
|
786
|
-
`[upgrade] 新版 watchdog 已启动 (pid=${child.pid}),等待存活确认...`,
|
|
787
|
-
);
|
|
788
|
-
let attempts = 0;
|
|
789
|
-
const confirm = () => {
|
|
790
|
-
attempts++;
|
|
791
|
-
if (exited) {
|
|
792
|
-
console.error(
|
|
793
|
-
`[upgrade] 新版 watchdog 启动后即退出 (code=${exitCode}, signal=${exitSignal}),回退保持旧进程`,
|
|
794
|
-
);
|
|
795
|
-
this._recoverAfterFailedUpgrade();
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
try {
|
|
799
|
-
process.kill(child.pid, 0); // 抛错说明进程已不存在
|
|
800
|
-
} catch {
|
|
801
|
-
console.error(
|
|
802
|
-
`[upgrade] 新版 watchdog 进程不存在 (pid=${child.pid}),回退保持旧进程`,
|
|
803
|
-
);
|
|
804
|
-
this._recoverAfterFailedUpgrade();
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
if (attempts >= 6) {
|
|
808
|
-
console.error(`[upgrade] 新版 watchdog 存活确认,旧进程退出`);
|
|
809
|
-
this.stop();
|
|
810
|
-
setTimeout(() => process.exit(0), 500);
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
setTimeout(confirm, 1000);
|
|
814
|
-
};
|
|
815
|
-
setTimeout(confirm, 1000);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* 升级/重启失败后的回退恢复:重新拉起 daemon 并同步业务进程
|
|
820
|
-
*/
|
|
821
|
-
_recoverAfterFailedUpgrade() {
|
|
822
|
-
this._daemonStopping = false;
|
|
823
|
-
if (this.spawnDaemon && !this.clashDaemon) {
|
|
824
|
-
console.error(`[upgrade] 恢复 clash daemon...`);
|
|
825
|
-
this._spawnClashDaemon();
|
|
826
|
-
}
|
|
827
|
-
console.error(`[upgrade] 恢复业务进程(等待心跳同步命令)...`);
|
|
828
|
-
this._sendHeartbeat().catch(() => {});
|
|
829
|
-
}
|
|
830
|
-
|
|
831
734
|
/**
|
|
832
735
|
* 信号处理
|
|
833
736
|
*/
|
package/src/watchdog/upgrader.js
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* 发现新版本 → 回调通知 agent → agent 负责停进程 → 升级 → 重启。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { exec } from "child_process";
|
|
9
|
-
import { readFileSync } from "fs";
|
|
8
|
+
import { exec, spawn } from "child_process";
|
|
9
|
+
import { readFileSync, writeFileSync, unlinkSync, openSync } from "fs";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import { dirname, join } from "path";
|
|
12
|
-
import { homedir } from "os";
|
|
12
|
+
import { homedir, tmpdir } from "os";
|
|
13
13
|
|
|
14
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const PKG_PATH = join(__dirname, "..", "..", "package.json");
|
|
@@ -156,3 +156,199 @@ export function startUpgradeChecker({
|
|
|
156
156
|
},
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
// ══════════════════════════════════════════════
|
|
161
|
+
// 独立升级器(直接运行模式)
|
|
162
|
+
// ══════════════════════════════════════════════
|
|
163
|
+
//
|
|
164
|
+
// 设计目标:让升级变得简单、无竞态。
|
|
165
|
+
// 旧 watchdog 检测到新版 → spawn 独立升级器(detached)→ 旧 watchdog 继续正常运行(业务不断)
|
|
166
|
+
// 升级器:npm install → 写结果文件 → 等旧 watchdog 退出 → spawn 新版 watchdog → 自身退出
|
|
167
|
+
// 旧 watchdog 看到结果 ok → 停心跳/停业务/停 daemon → 退出,由新版接管。
|
|
168
|
+
//
|
|
169
|
+
// 结果文件:tmpdir/tt-help-upgrade-result.json
|
|
170
|
+
// - 由升级器写入 { status: "ok" | "fail", error?, at },旧 watchdog 轮询读取
|
|
171
|
+
//
|
|
172
|
+
// 用法(旧 watchdog 调用):
|
|
173
|
+
// node upgrader.js --upgrade --old-pid <pid> [--interactive] [--server ...] [--client-id ...] [--interval ...] [--no-daemon]
|
|
174
|
+
|
|
175
|
+
const UPGRADE_RESULT_FILE = join(tmpdir(), "tt-help-upgrade-result.json");
|
|
176
|
+
|
|
177
|
+
function writeUpgradeResult(state) {
|
|
178
|
+
try {
|
|
179
|
+
writeFileSync(
|
|
180
|
+
UPGRADE_RESULT_FILE,
|
|
181
|
+
JSON.stringify({ ...state, at: Date.now() }, null, 2),
|
|
182
|
+
"utf-8",
|
|
183
|
+
);
|
|
184
|
+
} catch {}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** 轮询等待指定 pid 退出,返回是否已退出(超时返回 false) */
|
|
188
|
+
async function waitProcessExit(
|
|
189
|
+
pid,
|
|
190
|
+
{ timeoutMs = 60_000, pollMs = 1000 } = {},
|
|
191
|
+
) {
|
|
192
|
+
const start = Date.now();
|
|
193
|
+
while (Date.now() - start < timeoutMs) {
|
|
194
|
+
try {
|
|
195
|
+
process.kill(pid, 0); // 存在则继续等
|
|
196
|
+
} catch {
|
|
197
|
+
return true; // 已退出
|
|
198
|
+
}
|
|
199
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 独立升级器主流程(由旧 watchdog 以子进程方式启动)
|
|
206
|
+
* @param {string[]} argv - process.argv.slice(2)
|
|
207
|
+
*/
|
|
208
|
+
export async function runUpgradeMode(argv) {
|
|
209
|
+
const get = (f) => (argv.includes(f) ? argv[argv.indexOf(f) + 1] : undefined);
|
|
210
|
+
const oldPid = parseInt(get("--old-pid"), 10) || 0;
|
|
211
|
+
const interactive = argv.includes("--interactive");
|
|
212
|
+
const serverUrl = get("--server");
|
|
213
|
+
const clientId = get("--client-id");
|
|
214
|
+
const intervalSec = parseInt(get("--interval"), 10) || 15;
|
|
215
|
+
const noDaemon = argv.includes("--no-daemon");
|
|
216
|
+
|
|
217
|
+
console.error(
|
|
218
|
+
`[upgrade-proc] 开始下载新版: npm install -g ${PACKAGE}@latest ...`,
|
|
219
|
+
);
|
|
220
|
+
const result = await performUpgrade();
|
|
221
|
+
if (!result.success) {
|
|
222
|
+
const err = result.output.substring(0, 300);
|
|
223
|
+
console.error(`[upgrade-proc] 下载失败: ${err}`);
|
|
224
|
+
writeUpgradeResult({ status: "fail", error: err });
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
console.error(`[upgrade-proc] 下载完成,通知旧 watchdog 退出`);
|
|
228
|
+
writeUpgradeResult({ status: "ok" });
|
|
229
|
+
|
|
230
|
+
// 等旧 watchdog 退出,避免与其 stopAll 竞态(它退出后才会让新版接管业务)
|
|
231
|
+
if (oldPid) {
|
|
232
|
+
const exited = await waitProcessExit(oldPid);
|
|
233
|
+
if (!exited) {
|
|
234
|
+
console.error(
|
|
235
|
+
`[upgrade-proc] 等待旧 watchdog(pid=${oldPid}) 退出超时,继续启动新版`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 启动新版 watchdog(interactive 时继承控制台接管窗口;否则后台 + 日志文件)
|
|
241
|
+
const cliPath = join(
|
|
242
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
243
|
+
"..",
|
|
244
|
+
"..",
|
|
245
|
+
"cli.js",
|
|
246
|
+
);
|
|
247
|
+
const args = ["watchdog"];
|
|
248
|
+
if (serverUrl) args.push("--server", serverUrl);
|
|
249
|
+
if (clientId) args.push("--client-id", clientId);
|
|
250
|
+
args.push("--interval", String(intervalSec));
|
|
251
|
+
if (noDaemon) args.push("--no-daemon");
|
|
252
|
+
|
|
253
|
+
let logFd = undefined;
|
|
254
|
+
try {
|
|
255
|
+
const logPath = join(tmpdir(), `tt-help-watchdog-${Date.now()}.log`);
|
|
256
|
+
logFd = openSync(logPath, "a");
|
|
257
|
+
console.error(`[upgrade-proc] 新版 watchdog 日志: ${logPath}`);
|
|
258
|
+
} catch {}
|
|
259
|
+
|
|
260
|
+
let child;
|
|
261
|
+
try {
|
|
262
|
+
child = spawn(process.execPath, [cliPath, ...args], {
|
|
263
|
+
detached: !interactive,
|
|
264
|
+
stdio: interactive
|
|
265
|
+
? "inherit"
|
|
266
|
+
: ["ignore", logFd || "ignore", logFd || "ignore"],
|
|
267
|
+
windowsHide: !interactive,
|
|
268
|
+
});
|
|
269
|
+
if (!interactive) child.unref();
|
|
270
|
+
} catch (e) {
|
|
271
|
+
console.error(`[upgrade-proc] 新版 watchdog 启动失败: ${e.message}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
console.error(`[upgrade-proc] 新版 watchdog 已启动 (pid=${child.pid})`);
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 旧 watchdog 调用:spawn 独立升级器并轮询结果文件
|
|
280
|
+
* @returns {Promise<"ok"|"fail"|"timeout">}
|
|
281
|
+
*/
|
|
282
|
+
export function spawnUpgraderAndWait({
|
|
283
|
+
serverUrl,
|
|
284
|
+
clientId,
|
|
285
|
+
intervalSec = 15,
|
|
286
|
+
spawnDaemon = true,
|
|
287
|
+
interactive = false,
|
|
288
|
+
oldPid = 0,
|
|
289
|
+
timeoutMs = 180_000,
|
|
290
|
+
} = {}) {
|
|
291
|
+
return new Promise((resolve) => {
|
|
292
|
+
// 清理上次结果
|
|
293
|
+
try {
|
|
294
|
+
unlinkSync(UPGRADE_RESULT_FILE);
|
|
295
|
+
} catch {}
|
|
296
|
+
|
|
297
|
+
const upgraderPath = fileURLToPath(import.meta.url);
|
|
298
|
+
const args = ["--upgrade", "--old-pid", String(oldPid || process.pid)];
|
|
299
|
+
if (serverUrl) args.push("--server", serverUrl);
|
|
300
|
+
if (clientId) args.push("--client-id", clientId);
|
|
301
|
+
args.push("--interval", String(intervalSec));
|
|
302
|
+
if (!spawnDaemon) args.push("--no-daemon");
|
|
303
|
+
if (interactive) args.push("--interactive");
|
|
304
|
+
|
|
305
|
+
let child;
|
|
306
|
+
try {
|
|
307
|
+
child = spawn(process.execPath, [upgraderPath, ...args], {
|
|
308
|
+
// 交互:升级器接管控制台(后续新版 watchdog 也继承窗口);非交互:后台 + 日志
|
|
309
|
+
detached: !interactive,
|
|
310
|
+
stdio: interactive ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
311
|
+
windowsHide: !interactive,
|
|
312
|
+
});
|
|
313
|
+
if (!interactive) child.unref();
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.error(`[watchdog] 升级程序启动失败: ${e.message}`);
|
|
316
|
+
resolve("fail");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
child.stderr?.on("data", (d) => {
|
|
320
|
+
const m = d.toString().trim();
|
|
321
|
+
if (m) console.error(`[upgrade-proc] ${m.substring(0, 200)}`);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const started = Date.now();
|
|
325
|
+
const timer = setInterval(() => {
|
|
326
|
+
try {
|
|
327
|
+
const data = JSON.parse(readFileSync(UPGRADE_RESULT_FILE, "utf-8"));
|
|
328
|
+
if (data && data.status) {
|
|
329
|
+
clearInterval(timer);
|
|
330
|
+
resolve(data.status === "ok" ? "ok" : "fail");
|
|
331
|
+
}
|
|
332
|
+
} catch {}
|
|
333
|
+
if (Date.now() - started > timeoutMs) {
|
|
334
|
+
clearInterval(timer);
|
|
335
|
+
try {
|
|
336
|
+
unlinkSync(UPGRADE_RESULT_FILE);
|
|
337
|
+
} catch {}
|
|
338
|
+
console.error(
|
|
339
|
+
`[watchdog] 升级程序 ${timeoutMs / 1000}s 无结果,放弃等待`,
|
|
340
|
+
);
|
|
341
|
+
resolve("timeout");
|
|
342
|
+
}
|
|
343
|
+
}, 2000);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 直接运行时自动进入独立升级器模式
|
|
348
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
349
|
+
runUpgradeMode(process.argv.slice(2)).catch((e) => {
|
|
350
|
+
console.error(`[upgrade-proc] 升级器异常: ${e.message}`);
|
|
351
|
+
writeUpgradeResult({ status: "fail", error: e.message });
|
|
352
|
+
process.exit(1);
|
|
353
|
+
});
|
|
354
|
+
}
|