codex-endpoint-switcher 1.6.2 → 1.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/package.json +1 -1
- package/src/main/auto-update-runner.js +81 -18
- package/src/main/update-service.js +43 -1
package/package.json
CHANGED
|
@@ -3,6 +3,8 @@ const { spawn } = require("node:child_process");
|
|
|
3
3
|
|
|
4
4
|
const WAIT_PARENT_EXIT_TIMEOUT_MS = 20000;
|
|
5
5
|
const WAIT_PARENT_POLL_MS = 400;
|
|
6
|
+
const INSTALL_RETRY_TIMES = 3;
|
|
7
|
+
const INSTALL_RETRY_BASE_DELAY_MS = 1500;
|
|
6
8
|
|
|
7
9
|
function sleep(timeoutMs) {
|
|
8
10
|
return new Promise((resolve) => {
|
|
@@ -38,9 +40,10 @@ function runProcess(command, args, options = {}) {
|
|
|
38
40
|
stderr += chunk.toString("utf8");
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
child.on("exit", (code) => {
|
|
43
|
+
child.on("exit", (code, signal) => {
|
|
42
44
|
resolve({
|
|
43
|
-
code:
|
|
45
|
+
code: normalizeExitCode(code),
|
|
46
|
+
signal: signal || "",
|
|
44
47
|
stdout,
|
|
45
48
|
stderr,
|
|
46
49
|
});
|
|
@@ -56,6 +59,78 @@ function runProcess(command, args, options = {}) {
|
|
|
56
59
|
});
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
function normalizeExitCode(code) {
|
|
63
|
+
const safeCode = Number(code || 0);
|
|
64
|
+
if (!Number.isFinite(safeCode)) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (safeCode > 0x7fffffff) {
|
|
69
|
+
return safeCode - 0x100000000;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return safeCode;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isBusyInstallFailure(result) {
|
|
76
|
+
const combined = `${result.stdout || ""}\n${result.stderr || ""}`.toUpperCase();
|
|
77
|
+
return (
|
|
78
|
+
result.code === -4082 ||
|
|
79
|
+
combined.includes("EBUSY") ||
|
|
80
|
+
combined.includes("RESOURCE BUSY OR LOCKED") ||
|
|
81
|
+
combined.includes("EPERM") ||
|
|
82
|
+
combined.includes("OPERATION NOT PERMITTED")
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function installLatestPackage(payload) {
|
|
87
|
+
let lastResult = null;
|
|
88
|
+
|
|
89
|
+
for (let attempt = 1; attempt <= INSTALL_RETRY_TIMES; attempt += 1) {
|
|
90
|
+
if (attempt > 1) {
|
|
91
|
+
await appendLog(
|
|
92
|
+
payload.logPath,
|
|
93
|
+
`[${new Date().toISOString()}] 检测到文件占用,开始第 ${attempt} 次重试安装。\n`,
|
|
94
|
+
);
|
|
95
|
+
await sleep(INSTALL_RETRY_BASE_DELAY_MS * attempt);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = await runProcess(
|
|
99
|
+
payload.nodePath,
|
|
100
|
+
[payload.npmCliPath, "install", "-g", `${payload.packageName}@latest`],
|
|
101
|
+
{
|
|
102
|
+
cwd: process.cwd(),
|
|
103
|
+
env: process.env,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (result.stdout) {
|
|
108
|
+
await appendLog(payload.logPath, result.stdout);
|
|
109
|
+
}
|
|
110
|
+
if (result.stderr) {
|
|
111
|
+
await appendLog(payload.logPath, result.stderr);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lastResult = result;
|
|
115
|
+
if (result.code === 0) {
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!isBusyInstallFailure(result) || attempt >= INSTALL_RETRY_TIMES) {
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
lastResult || {
|
|
126
|
+
code: 1,
|
|
127
|
+
signal: "",
|
|
128
|
+
stdout: "",
|
|
129
|
+
stderr: "自动更新安装没有返回结果。",
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
59
134
|
function isProcessAlive(pid) {
|
|
60
135
|
try {
|
|
61
136
|
process.kill(pid, 0);
|
|
@@ -145,25 +220,13 @@ async function main() {
|
|
|
145
220
|
await sleep(Number(payload.waitSeconds || 3) * 1000);
|
|
146
221
|
await waitForProcessExit(Number(payload.parentPid || 0), payload.logPath);
|
|
147
222
|
|
|
148
|
-
const installResult = await
|
|
149
|
-
payload.nodePath,
|
|
150
|
-
[payload.npmCliPath, "install", "-g", `${payload.packageName}@latest`],
|
|
151
|
-
{
|
|
152
|
-
cwd: process.cwd(),
|
|
153
|
-
env: process.env,
|
|
154
|
-
},
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
if (installResult.stdout) {
|
|
158
|
-
await appendLog(payload.logPath, installResult.stdout);
|
|
159
|
-
}
|
|
160
|
-
if (installResult.stderr) {
|
|
161
|
-
await appendLog(payload.logPath, installResult.stderr);
|
|
162
|
-
}
|
|
223
|
+
const installResult = await installLatestPackage(payload);
|
|
163
224
|
|
|
164
225
|
if (installResult.code !== 0) {
|
|
165
226
|
const finishedAt = new Date().toISOString();
|
|
166
|
-
const errorMessage =
|
|
227
|
+
const errorMessage = isBusyInstallFailure(installResult)
|
|
228
|
+
? `npm 安装失败:文件仍被占用,请稍后重试或先手动关闭占用进程。退出码:${installResult.code}`
|
|
229
|
+
: `npm 安装失败,退出码:${installResult.code}`;
|
|
167
230
|
await appendLog(payload.logPath, `[${finishedAt}] ${errorMessage}\n`);
|
|
168
231
|
await writeState(payload.statePath, {
|
|
169
232
|
status: "failed",
|
|
@@ -26,6 +26,36 @@ function getAutoUpdateStatePath() {
|
|
|
26
26
|
return path.join(stateDir, "codex-switcher-auto-update.json");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function getAutoUpdateRuntimeDir() {
|
|
30
|
+
const runtimeDir = path.join(os.homedir(), ".codex", "runtime");
|
|
31
|
+
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
32
|
+
return runtimeDir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cleanupStagedAutoUpdateRunners(runtimeDir) {
|
|
36
|
+
const staleBeforeMs = Date.now() - 24 * 60 * 60 * 1000;
|
|
37
|
+
|
|
38
|
+
for (const entry of fs.readdirSync(runtimeDir, { withFileTypes: true })) {
|
|
39
|
+
if (
|
|
40
|
+
!entry.isFile() ||
|
|
41
|
+
!entry.name.startsWith("codex-switcher-auto-update-runner-") ||
|
|
42
|
+
!entry.name.endsWith(".js")
|
|
43
|
+
) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fullPath = path.join(runtimeDir, entry.name);
|
|
48
|
+
try {
|
|
49
|
+
const stats = fs.statSync(fullPath);
|
|
50
|
+
if (stats.mtimeMs < staleBeforeMs) {
|
|
51
|
+
fs.unlinkSync(fullPath);
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// 文件被占用或刚被其他进程删除时忽略。
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
29
59
|
function readAutoUpdateState() {
|
|
30
60
|
try {
|
|
31
61
|
const content = fs.readFileSync(getAutoUpdateStatePath(), "utf8");
|
|
@@ -100,6 +130,17 @@ function resolveRuntimePaths() {
|
|
|
100
130
|
};
|
|
101
131
|
}
|
|
102
132
|
|
|
133
|
+
function stageAutoUpdateRunner() {
|
|
134
|
+
const runtimeDir = getAutoUpdateRuntimeDir();
|
|
135
|
+
cleanupStagedAutoUpdateRunners(runtimeDir);
|
|
136
|
+
const stagedRunnerPath = path.join(
|
|
137
|
+
runtimeDir,
|
|
138
|
+
`codex-switcher-auto-update-runner-${Date.now()}.js`,
|
|
139
|
+
);
|
|
140
|
+
fs.copyFileSync(runnerEntryPath, stagedRunnerPath);
|
|
141
|
+
return stagedRunnerPath;
|
|
142
|
+
}
|
|
143
|
+
|
|
103
144
|
function sleep(timeoutMs) {
|
|
104
145
|
return new Promise((resolve) => {
|
|
105
146
|
setTimeout(resolve, timeoutMs);
|
|
@@ -217,6 +258,7 @@ function scheduleAutoUpdate(options = {}) {
|
|
|
217
258
|
const logPath = getAutoUpdateLogPath();
|
|
218
259
|
const statePath = getAutoUpdateStatePath();
|
|
219
260
|
const runtimePaths = resolveRuntimePaths();
|
|
261
|
+
const stagedRunnerPath = stageAutoUpdateRunner();
|
|
220
262
|
|
|
221
263
|
writeAutoUpdateState({
|
|
222
264
|
status: "scheduled",
|
|
@@ -246,7 +288,7 @@ function scheduleAutoUpdate(options = {}) {
|
|
|
246
288
|
"utf8",
|
|
247
289
|
).toString("base64url");
|
|
248
290
|
|
|
249
|
-
const child = spawn(process.execPath, [
|
|
291
|
+
const child = spawn(process.execPath, [stagedRunnerPath, payload], {
|
|
250
292
|
cwd: os.homedir(),
|
|
251
293
|
detached: true,
|
|
252
294
|
stdio: "ignore",
|