@sciagent/cli 1.1.9 → 1.1.11
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/bin/sciagent.js +395 -14
- package/package.json +1 -1
- package/scripts/postinstall.js +273 -26
package/bin/sciagent.js
CHANGED
|
@@ -21,9 +21,11 @@ const { spawn, execSync } = require('child_process');
|
|
|
21
21
|
const path = require('path');
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const os = require('os');
|
|
24
|
+
const https = require('https');
|
|
25
|
+
const http = require('http');
|
|
24
26
|
|
|
25
27
|
// 当前版本号 - 与 postinstall.js 和 package.json 保持同步
|
|
26
|
-
const CURRENT_VERSION = '1.1.
|
|
28
|
+
const CURRENT_VERSION = '1.1.11';
|
|
27
29
|
|
|
28
30
|
// 极狐GitLab下载配置
|
|
29
31
|
const JIHULAB_URL = 'https://jihulab.com';
|
|
@@ -60,6 +62,285 @@ function compareVersions(a, b) {
|
|
|
60
62
|
return 0;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* 检查并应用 .pending-update 标记的更新
|
|
67
|
+
* 在 sciagent 启动时调用(此时旧进程已退出,二进制文件不再被锁定)
|
|
68
|
+
* 返回: true 如果应用了更新,false 如果没有待更新
|
|
69
|
+
*/
|
|
70
|
+
function applyPendingUpdate(installDir) {
|
|
71
|
+
const pendingFile = path.join(installDir, '.pending-update');
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(pendingFile)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const pendingData = JSON.parse(fs.readFileSync(pendingFile, 'utf8'));
|
|
79
|
+
const { version, tempPath, timestamp } = pendingData;
|
|
80
|
+
|
|
81
|
+
console.log(`[UPDATE] Found pending update: v${version} (queued at ${timestamp})`);
|
|
82
|
+
|
|
83
|
+
// 检查临时文件是否存在
|
|
84
|
+
if (!fs.existsSync(tempPath)) {
|
|
85
|
+
console.log(`[UPDATE] Temp file not found: ${tempPath}`);
|
|
86
|
+
console.log(`[UPDATE] Clearing pending update marker.`);
|
|
87
|
+
fs.unlinkSync(pendingFile);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 验证临时文件大小
|
|
92
|
+
const tempStats = fs.statSync(tempPath);
|
|
93
|
+
if (tempStats.size < 10 * 1024 * 1024) {
|
|
94
|
+
console.log(`[UPDATE] Temp file too small (${(tempStats.size / 1024).toFixed(0)} KB), discarding.`);
|
|
95
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
96
|
+
fs.unlinkSync(pendingFile);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 确定目标路径
|
|
101
|
+
const platform = PLATFORM_MAP[process.platform];
|
|
102
|
+
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
103
|
+
const targetPath = path.join(installDir, binName);
|
|
104
|
+
|
|
105
|
+
// 尝试替换
|
|
106
|
+
try {
|
|
107
|
+
if (process.platform === 'win32') {
|
|
108
|
+
// Windows: 使用 PowerShell
|
|
109
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-apply-update.ps1');
|
|
110
|
+
const scriptContent = [
|
|
111
|
+
`Copy-Item -Path "${tempPath}" -Destination "${targetPath}" -Force`,
|
|
112
|
+
'if (Test-Path $out) { Write-Host "OK" } else { exit 1 }'
|
|
113
|
+
].join('\r\n');
|
|
114
|
+
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
115
|
+
try {
|
|
116
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
117
|
+
stdio: 'pipe', timeout: 30000
|
|
118
|
+
});
|
|
119
|
+
} finally {
|
|
120
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
fs.copyFileSync(tempPath, targetPath);
|
|
124
|
+
fs.chmodSync(targetPath, 0o755);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 验证替换成功
|
|
128
|
+
if (fs.existsSync(targetPath)) {
|
|
129
|
+
const targetStats = fs.statSync(targetPath);
|
|
130
|
+
if (targetStats.size > 10 * 1024 * 1024) {
|
|
131
|
+
// 更新版本文件
|
|
132
|
+
fs.writeFileSync(path.join(installDir, '.version'), version);
|
|
133
|
+
// 清理临时文件和标记
|
|
134
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
135
|
+
fs.unlinkSync(pendingFile);
|
|
136
|
+
console.log(`[UPDATE] ✅ Applied pending update: v${version} (${(targetStats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log(`[UPDATE] ⚠️ Replace succeeded but validation failed.`);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.log(`[UPDATE] ⚠️ Failed to apply pending update: ${e.message}`);
|
|
144
|
+
console.log(`[UPDATE] The update will be retried on next startup.`);
|
|
145
|
+
// 不删除 pending 文件,下次启动重试
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.log(`[UPDATE] ⚠️ Error reading pending update: ${e.message}`);
|
|
150
|
+
try { fs.unlinkSync(pendingFile); } catch (e2) {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 从极狐GitLab查询最新版本号
|
|
158
|
+
* 返回: 版本字符串 或 null
|
|
159
|
+
*/
|
|
160
|
+
function fetchLatestVersion() {
|
|
161
|
+
return new Promise((resolve) => {
|
|
162
|
+
const url = (
|
|
163
|
+
`${JIHULAB_URL}/api/v4/projects/${JIHULAB_PROJECT_ID}` +
|
|
164
|
+
`/packages?package_name=${JIHULAB_PACKAGE_NAME}&per_page=1&order_by=version&sort=desc` +
|
|
165
|
+
`&access_token=${JIHULAB_DOWNLOAD_TOKEN}`
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
169
|
+
const request = protocol.get(url, {
|
|
170
|
+
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
171
|
+
timeout: 10000
|
|
172
|
+
}, (response) => {
|
|
173
|
+
let data = '';
|
|
174
|
+
response.on('data', (chunk) => { data += chunk; });
|
|
175
|
+
response.on('end', () => {
|
|
176
|
+
try {
|
|
177
|
+
const packages = JSON.parse(data);
|
|
178
|
+
if (Array.isArray(packages) && packages.length > 0 && packages[0].version) {
|
|
179
|
+
resolve(packages[0].version);
|
|
180
|
+
} else {
|
|
181
|
+
resolve(null);
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
resolve(null);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
request.on('error', () => resolve(null));
|
|
190
|
+
request.on('timeout', () => { request.destroy(); resolve(null); });
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 后台下载新版本到临时目录
|
|
196
|
+
* 下载完成后写入 .pending-update 标记
|
|
197
|
+
* 这是异步操作,不阻塞主进程启动
|
|
198
|
+
*/
|
|
199
|
+
function backgroundDownloadUpdate(platform, arch, latestVersion) {
|
|
200
|
+
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
201
|
+
const ext = platform === 'win32' ? '.exe' : '';
|
|
202
|
+
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
203
|
+
|
|
204
|
+
const downloadUrl = (
|
|
205
|
+
`${JIHULAB_URL}/api/v4/projects/${JIHULAB_PROJECT_ID}` +
|
|
206
|
+
`/packages/generic/${JIHULAB_PACKAGE_NAME}/${latestVersion}/${filename}` +
|
|
207
|
+
`?access_token=${JIHULAB_DOWNLOAD_TOKEN}`
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const installDir = getInstallDir(platform);
|
|
211
|
+
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
212
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
213
|
+
const tempPath = path.join(tempDir, binName);
|
|
214
|
+
|
|
215
|
+
// 清理旧的临时文件
|
|
216
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
217
|
+
|
|
218
|
+
console.log(`[AUTO-UPDATE] Downloading v${latestVersion} in background...`);
|
|
219
|
+
|
|
220
|
+
if (platform === 'win32') {
|
|
221
|
+
// Windows: 使用 PowerShell 后台下载
|
|
222
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-bg-download.ps1');
|
|
223
|
+
const scriptContent = [
|
|
224
|
+
'$ProgressPreference = "SilentlyContinue"',
|
|
225
|
+
`$uri = "${downloadUrl}"`,
|
|
226
|
+
`$out = "${tempPath}"`,
|
|
227
|
+
'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
|
|
228
|
+
`if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host "Downloaded: $s bytes" } else { exit 1 }`
|
|
229
|
+
].join('\r\n');
|
|
230
|
+
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
231
|
+
|
|
232
|
+
const bgProcess = spawn('powershell', [
|
|
233
|
+
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', tmpScript
|
|
234
|
+
], { stdio: 'pipe', detached: true, windowsHide: true });
|
|
235
|
+
|
|
236
|
+
bgProcess.on('exit', (code) => {
|
|
237
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
238
|
+
if (code === 0 && fs.existsSync(tempPath)) {
|
|
239
|
+
const stats = fs.statSync(tempPath);
|
|
240
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
241
|
+
console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
242
|
+
// 写入 pending update 标记
|
|
243
|
+
const pendingData = {
|
|
244
|
+
version: latestVersion,
|
|
245
|
+
tempPath: tempPath,
|
|
246
|
+
timestamp: new Date().toISOString(),
|
|
247
|
+
platform: process.platform
|
|
248
|
+
};
|
|
249
|
+
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
250
|
+
console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
console.log(`[AUTO-UPDATE] ⚠️ Background download failed.`);
|
|
254
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
bgProcess.unref();
|
|
259
|
+
} else {
|
|
260
|
+
// Linux/macOS: 使用 curl 后台下载
|
|
261
|
+
const bgProcess = spawn('curl', [
|
|
262
|
+
'-fsSL', '-o', tempPath, downloadUrl
|
|
263
|
+
], { stdio: 'pipe', detached: true });
|
|
264
|
+
|
|
265
|
+
bgProcess.on('exit', (code) => {
|
|
266
|
+
if (code === 0 && fs.existsSync(tempPath)) {
|
|
267
|
+
const stats = fs.statSync(tempPath);
|
|
268
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
269
|
+
fs.chmodSync(tempPath, 0o755);
|
|
270
|
+
console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
271
|
+
const pendingData = {
|
|
272
|
+
version: latestVersion,
|
|
273
|
+
tempPath: tempPath,
|
|
274
|
+
timestamp: new Date().toISOString(),
|
|
275
|
+
platform: process.platform
|
|
276
|
+
};
|
|
277
|
+
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
278
|
+
console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
console.log(`[AUTO-UPDATE] ⚠️ Background download failed.`);
|
|
282
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
bgProcess.unref();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 检查自动更新(后台异步)
|
|
292
|
+
* 1. 查询极狐GitLab最新版本
|
|
293
|
+
* 2. 如果有新版本,后台下载到临时目录
|
|
294
|
+
* 3. 下载完成后写入 .pending-update 标记
|
|
295
|
+
* 4. 下次启动时自动应用
|
|
296
|
+
*/
|
|
297
|
+
function checkAutoUpdate(platform, arch) {
|
|
298
|
+
// 不在后台检查中阻塞主进程
|
|
299
|
+
const installDir = getInstallDir(platform);
|
|
300
|
+
const pendingFile = path.join(installDir, '.pending-update');
|
|
301
|
+
|
|
302
|
+
// 如果已有待更新,跳过检查
|
|
303
|
+
if (fs.existsSync(pendingFile)) {
|
|
304
|
+
try {
|
|
305
|
+
const pending = JSON.parse(fs.readFileSync(pendingFile, 'utf8'));
|
|
306
|
+
console.log(`[AUTO-UPDATE] Pending update v${pending.version} already queued. Will apply on next restart.`);
|
|
307
|
+
return;
|
|
308
|
+
} catch (e) {}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 检查上次检查时间(避免频繁查询,至少间隔4小时)
|
|
312
|
+
const lastCheckFile = path.join(installDir, '.last-update-check');
|
|
313
|
+
if (fs.existsSync(lastCheckFile)) {
|
|
314
|
+
try {
|
|
315
|
+
const lastCheck = new Date(fs.readFileSync(lastCheckFile, 'utf8').trim());
|
|
316
|
+
const hoursSinceLastCheck = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60);
|
|
317
|
+
if (hoursSinceLastCheck < 4) {
|
|
318
|
+
return; // 4小时内已检查过,跳过
|
|
319
|
+
}
|
|
320
|
+
} catch (e) {}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 记录检查时间
|
|
324
|
+
fs.writeFileSync(lastCheckFile, new Date().toISOString(), 'utf8');
|
|
325
|
+
|
|
326
|
+
// 异步查询最新版本
|
|
327
|
+
fetchLatestVersion().then((latestVersion) => {
|
|
328
|
+
if (!latestVersion) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const cmp = compareVersions(latestVersion, CURRENT_VERSION);
|
|
333
|
+
if (cmp > 0) {
|
|
334
|
+
console.log(`[AUTO-UPDATE] 🆕 New version available: v${latestVersion} (current: v${CURRENT_VERSION})`);
|
|
335
|
+
console.log(`[AUTO-UPDATE] Downloading in background...`);
|
|
336
|
+
backgroundDownloadUpdate(platform, arch, latestVersion);
|
|
337
|
+
}
|
|
338
|
+
// 版本相同或更高,无需更新
|
|
339
|
+
}).catch(() => {
|
|
340
|
+
// 静默失败,不影响正常使用
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
63
344
|
/**
|
|
64
345
|
* 获取二进制安装目录
|
|
65
346
|
*/
|
|
@@ -93,7 +374,14 @@ function getBinaryPath() {
|
|
|
93
374
|
const binPath = path.join(installDir, binName);
|
|
94
375
|
const versionFile = path.join(installDir, '.version');
|
|
95
376
|
|
|
96
|
-
//
|
|
377
|
+
// 1. 首先检查并应用 pending update(此时旧进程已退出,文件不再锁定)
|
|
378
|
+
const updated = applyPendingUpdate(installDir);
|
|
379
|
+
if (updated) {
|
|
380
|
+
// 更新已应用,重新读取版本信息
|
|
381
|
+
console.log(`[INFO] Update applied. Starting with new version...`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// 2. 检查已安装的二进制
|
|
97
385
|
if (fs.existsSync(binPath)) {
|
|
98
386
|
const stats = fs.statSync(binPath);
|
|
99
387
|
if (stats.size > 10 * 1024 * 1024) {
|
|
@@ -105,29 +393,38 @@ function getBinaryPath() {
|
|
|
105
393
|
if (cmp === 0) {
|
|
106
394
|
// 版本精确匹配 → 直接使用
|
|
107
395
|
console.log(`[INFO] SciAgent v${installedVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB, ${stats.mtime.toISOString().slice(0,10)})`);
|
|
396
|
+
// 后台检查自动更新
|
|
397
|
+
checkAutoUpdate(platform, arch);
|
|
108
398
|
return binPath;
|
|
109
399
|
} else if (cmp < 0) {
|
|
110
400
|
// 版本过低 → 自动下载新版本
|
|
111
401
|
console.log(`[INFO] SciAgent version outdated: v${installedVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB) → v${CURRENT_VERSION}`);
|
|
112
402
|
console.log(`[INFO] Downloading new version from JiHuLab...`);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
403
|
+
// 注意:此时当前进程正在运行旧二进制,无法直接替换
|
|
404
|
+
// 下载到临时目录,写入 .pending-update,下次启动时应用
|
|
405
|
+
const downloaded = downloadBinaryToTemp(platform, arch, CURRENT_VERSION);
|
|
406
|
+
if (downloaded) {
|
|
407
|
+
console.log(`[INFO] ✅ New version downloaded. Please restart sciagent to apply update.`);
|
|
408
|
+
// 继续使用当前版本
|
|
409
|
+
return binPath;
|
|
410
|
+
}
|
|
411
|
+
// 下载失败,继续使用当前版本
|
|
412
|
+
console.log(`[INFO] ⚠️ Download failed. Continuing with current version.`);
|
|
413
|
+
return binPath;
|
|
118
414
|
} else {
|
|
119
415
|
// 版本更高 → 允许运行(用户可能手动安装了新版)
|
|
120
416
|
console.log(`[INFO] SciAgent v${installedVersion} (newer than package v${CURRENT_VERSION}, ${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
417
|
+
checkAutoUpdate(platform, arch);
|
|
121
418
|
return binPath;
|
|
122
419
|
}
|
|
123
420
|
} else {
|
|
124
|
-
// 没有.version文件 →
|
|
421
|
+
// 没有.version文件 → 可能是旧安装,下载到临时目录
|
|
125
422
|
console.log(`[INFO] SciAgent binary found but version unknown, downloading v${CURRENT_VERSION}...`);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
423
|
+
const downloaded = downloadBinaryToTemp(platform, arch, CURRENT_VERSION);
|
|
424
|
+
if (downloaded) {
|
|
425
|
+
console.log(`[INFO] ✅ New version downloaded. Please restart sciagent to apply update.`);
|
|
426
|
+
}
|
|
427
|
+
return binPath;
|
|
131
428
|
}
|
|
132
429
|
} else {
|
|
133
430
|
// 文件太小,损坏
|
|
@@ -151,6 +448,7 @@ function getBinaryPath() {
|
|
|
151
448
|
|
|
152
449
|
const downloaded = downloadBinary(platform, arch, CURRENT_VERSION);
|
|
153
450
|
if (downloaded && fs.existsSync(downloaded)) {
|
|
451
|
+
checkAutoUpdate(platform, arch);
|
|
154
452
|
return downloaded;
|
|
155
453
|
}
|
|
156
454
|
|
|
@@ -160,7 +458,90 @@ function getBinaryPath() {
|
|
|
160
458
|
}
|
|
161
459
|
|
|
162
460
|
/**
|
|
163
|
-
* 从极狐GitLab
|
|
461
|
+
* 从极狐GitLab下载二进制文件到临时目录(不替换当前运行的二进制)
|
|
462
|
+
* 下载完成后写入 .pending-update 标记,下次启动时自动应用
|
|
463
|
+
* 返回: tempPath 或 null
|
|
464
|
+
*/
|
|
465
|
+
function downloadBinaryToTemp(platform, arch, version) {
|
|
466
|
+
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
467
|
+
const ext = platform === 'win32' ? '.exe' : '';
|
|
468
|
+
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
469
|
+
|
|
470
|
+
const downloadUrl = (
|
|
471
|
+
`${JIHULAB_URL}/api/v4/projects/${JIHULAB_PROJECT_ID}` +
|
|
472
|
+
`/packages/generic/${JIHULAB_PACKAGE_NAME}/${version}/${filename}` +
|
|
473
|
+
`?access_token=${JIHULAB_DOWNLOAD_TOKEN}`
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const installDir = getInstallDir(platform);
|
|
477
|
+
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
478
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
479
|
+
const tempPath = path.join(tempDir, binName);
|
|
480
|
+
|
|
481
|
+
// 清理旧的临时文件
|
|
482
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
483
|
+
|
|
484
|
+
console.log(` Downloading ${filename} v${version} to temp...`);
|
|
485
|
+
console.log(` Temp: ${tempPath}`);
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
if (platform === 'win32') {
|
|
489
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
|
|
490
|
+
const scriptContent = [
|
|
491
|
+
'$ProgressPreference = "SilentlyContinue"',
|
|
492
|
+
`$uri = "${downloadUrl}"`,
|
|
493
|
+
`$out = "${tempPath}"`,
|
|
494
|
+
'Write-Host " Downloading from JiHuLab..."',
|
|
495
|
+
'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
|
|
496
|
+
'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
|
|
497
|
+
].join('\r\n');
|
|
498
|
+
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
502
|
+
stdio: 'inherit',
|
|
503
|
+
timeout: 600000
|
|
504
|
+
});
|
|
505
|
+
} finally {
|
|
506
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
execSync(`curl -fsSL -o '${tempPath}' '${downloadUrl}'`, {
|
|
510
|
+
stdio: 'pipe',
|
|
511
|
+
timeout: 600000
|
|
512
|
+
});
|
|
513
|
+
fs.chmodSync(tempPath, 0o755);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 验证下载
|
|
517
|
+
if (fs.existsSync(tempPath)) {
|
|
518
|
+
const stats = fs.statSync(tempPath);
|
|
519
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
520
|
+
console.log(` ✅ Downloaded: ${(stats.size / (1024 * 1024)).toFixed(1)} MB`);
|
|
521
|
+
// 写入 .pending-update 标记
|
|
522
|
+
const pendingData = {
|
|
523
|
+
version: version,
|
|
524
|
+
tempPath: tempPath,
|
|
525
|
+
timestamp: new Date().toISOString(),
|
|
526
|
+
platform: process.platform
|
|
527
|
+
};
|
|
528
|
+
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
529
|
+
return tempPath;
|
|
530
|
+
} else {
|
|
531
|
+
console.error(` ❌ Downloaded file too small: ${(stats.size / 1024).toFixed(0)} KB`);
|
|
532
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
} catch (e) {
|
|
536
|
+
console.error(` ❌ Download failed: ${e.message}`);
|
|
537
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* 从极狐GitLab下载二进制文件(直接到目标路径,用于首次安装)
|
|
164
545
|
*/
|
|
165
546
|
function downloadBinary(platform, arch, version) {
|
|
166
547
|
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -27,7 +27,7 @@ const ARCH_MAP = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// 当前版本号 - 每次发布时同步更新
|
|
30
|
-
const CURRENT_VERSION = '1.1.
|
|
30
|
+
const CURRENT_VERSION = '1.1.11';
|
|
31
31
|
|
|
32
32
|
// GitHub Releases 回退版本列表(当当前版本的 release 不存在时尝试)
|
|
33
33
|
const GITHUB_FALLBACK_VERSIONS = ['1.1.3', '1.0.50', '1.0.48', '1.0.46', '1.0.40', '1.0.38', '1.0.36'];
|
|
@@ -528,6 +528,159 @@ const JIHULAB_PACKAGE_NAME = 'sciagent';
|
|
|
528
528
|
// Deploy token with read_package_registry permission (for downloading)
|
|
529
529
|
const JIHULAB_DOWNLOAD_TOKEN = '2qxfs606HfwESUYJxtRlgm86MQp1OjVnazAK.01.101vzs2qo';
|
|
530
530
|
|
|
531
|
+
/**
|
|
532
|
+
* 检查 sciagent 进程是否正在运行
|
|
533
|
+
*/
|
|
534
|
+
function isSciAgentRunning() {
|
|
535
|
+
try {
|
|
536
|
+
if (process.platform === 'win32') {
|
|
537
|
+
const result = execSync('tasklist /FI "IMAGENAME eq sciagent.exe" /NH', {
|
|
538
|
+
encoding: 'utf8',
|
|
539
|
+
timeout: 5000
|
|
540
|
+
});
|
|
541
|
+
return result.includes('sciagent.exe');
|
|
542
|
+
} else {
|
|
543
|
+
const result = execSync('pgrep -x sciagent || true', {
|
|
544
|
+
encoding: 'utf8',
|
|
545
|
+
timeout: 5000
|
|
546
|
+
});
|
|
547
|
+
return result.trim().length > 0;
|
|
548
|
+
}
|
|
549
|
+
} catch (e) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* 终止 sciagent 进程
|
|
556
|
+
*/
|
|
557
|
+
function killSciAgent() {
|
|
558
|
+
try {
|
|
559
|
+
if (process.platform === 'win32') {
|
|
560
|
+
execSync('taskkill /F /IM sciagent.exe', { timeout: 10000 });
|
|
561
|
+
} else {
|
|
562
|
+
execSync('pkill -x sciagent', { timeout: 10000 });
|
|
563
|
+
}
|
|
564
|
+
// 等待进程完全退出
|
|
565
|
+
let retries = 10;
|
|
566
|
+
while (retries-- > 0 && isSciAgentRunning()) {
|
|
567
|
+
const sleep = require('util').promisify(setTimeout);
|
|
568
|
+
sleep(500);
|
|
569
|
+
}
|
|
570
|
+
return !isSciAgentRunning();
|
|
571
|
+
} catch (e) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* 尝试将临时文件替换为目标二进制文件
|
|
578
|
+
* 返回: 'replaced' | 'locked' | 'error'
|
|
579
|
+
*/
|
|
580
|
+
function tryReplaceBinary(tempPath, targetPath, installDir, version, platform) {
|
|
581
|
+
try {
|
|
582
|
+
// 先尝试直接复制
|
|
583
|
+
if (process.platform === 'win32') {
|
|
584
|
+
// Windows: 使用 PowerShell Copy-Item
|
|
585
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-replace.ps1');
|
|
586
|
+
const scriptContent = [
|
|
587
|
+
`Copy-Item -Path "${tempPath}" -Destination "${targetPath}" -Force`,
|
|
588
|
+
'if (Test-Path $out) { Write-Host "OK" } else { Write-Error "Copy failed"; exit 1 }'
|
|
589
|
+
].join('\r\n');
|
|
590
|
+
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
591
|
+
try {
|
|
592
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
593
|
+
stdio: 'pipe',
|
|
594
|
+
timeout: 30000
|
|
595
|
+
});
|
|
596
|
+
} finally {
|
|
597
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
fs.copyFileSync(tempPath, targetPath);
|
|
601
|
+
fs.chmodSync(targetPath, 0o755);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 验证替换成功
|
|
605
|
+
if (fs.existsSync(targetPath)) {
|
|
606
|
+
const stats = fs.statSync(targetPath);
|
|
607
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
608
|
+
fs.writeFileSync(path.join(installDir, '.version'), version);
|
|
609
|
+
// 清理临时文件
|
|
610
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
611
|
+
return 'replaced';
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return 'error';
|
|
615
|
+
} catch (e) {
|
|
616
|
+
// 检查是否是文件锁定错误
|
|
617
|
+
const msg = (e.message || '').toLowerCase();
|
|
618
|
+
if (msg.includes('used by another process') || msg.includes('being used') ||
|
|
619
|
+
msg.includes('eperm') || msg.includes('eacces') || msg.includes('access denied') ||
|
|
620
|
+
msg.includes('锁定') || msg.includes('denied')) {
|
|
621
|
+
return 'locked';
|
|
622
|
+
}
|
|
623
|
+
return 'error';
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* 写入 .pending-update 标记文件
|
|
629
|
+
* 下次 sciagent 启动时会自动应用更新
|
|
630
|
+
*/
|
|
631
|
+
function writePendingUpdate(installDir, tempPath, version) {
|
|
632
|
+
const pendingFile = path.join(installDir, '.pending-update');
|
|
633
|
+
const pendingData = {
|
|
634
|
+
version: version,
|
|
635
|
+
tempPath: tempPath,
|
|
636
|
+
timestamp: new Date().toISOString(),
|
|
637
|
+
platform: process.platform
|
|
638
|
+
};
|
|
639
|
+
fs.writeFileSync(pendingFile, JSON.stringify(pendingData, null, 2), 'utf8');
|
|
640
|
+
console.log(` [PENDING] Update deferred. Written .pending-update marker.`);
|
|
641
|
+
console.log(` [PENDING] New version will be applied on next sciagent startup.`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* 交互式提示用户选择更新方式
|
|
646
|
+
* 返回: 'kill' | 'defer'
|
|
647
|
+
*/
|
|
648
|
+
function promptUpdateChoice() {
|
|
649
|
+
// 检查是否在交互式终端中
|
|
650
|
+
if (!process.stdin.isTTY) {
|
|
651
|
+
console.log(` [INFO] Non-interactive mode. Deferring update to next startup.`);
|
|
652
|
+
return 'defer';
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const readline = require('readline');
|
|
656
|
+
const rl = readline.createInterface({
|
|
657
|
+
input: process.stdin,
|
|
658
|
+
output: process.stdout
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
return new Promise((resolve) => {
|
|
662
|
+
console.log('');
|
|
663
|
+
console.log(' ╔══════════════════════════════════════════════════════════╗');
|
|
664
|
+
console.log(' ║ SciAgent is currently running. Update requires ║');
|
|
665
|
+
console.log(' ║ replacing the binary file which is locked. ║');
|
|
666
|
+
console.log(' ╠══════════════════════════════════════════════════════════╣');
|
|
667
|
+
console.log(' ║ [K] Kill SciAgent & update now ║');
|
|
668
|
+
console.log(' ║ [D] Defer - update on next startup ║');
|
|
669
|
+
console.log(' ╚══════════════════════════════════════════════════════════╝');
|
|
670
|
+
console.log('');
|
|
671
|
+
|
|
672
|
+
rl.question(' Choose [K/D] (default: D): ', (answer) => {
|
|
673
|
+
rl.close();
|
|
674
|
+
const choice = (answer || 'D').trim().toUpperCase();
|
|
675
|
+
if (choice === 'K' || choice === 'KILL') {
|
|
676
|
+
resolve('kill');
|
|
677
|
+
} else {
|
|
678
|
+
resolve('defer');
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
531
684
|
async function downloadFromJiHuLab(platform, arch, version) {
|
|
532
685
|
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
533
686
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
@@ -546,6 +699,14 @@ async function downloadFromJiHuLab(platform, arch, version) {
|
|
|
546
699
|
fs.mkdirSync(installDir, { recursive: true });
|
|
547
700
|
const targetPath = path.join(installDir, binName);
|
|
548
701
|
|
|
702
|
+
// 下载到临时目录(避免直接覆盖正在运行的二进制)
|
|
703
|
+
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
704
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
705
|
+
const tempPath = path.join(tempDir, binName);
|
|
706
|
+
|
|
707
|
+
// 清理旧的临时文件
|
|
708
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
709
|
+
|
|
549
710
|
// Windows: 优先使用 PowerShell(更可靠的大文件下载)
|
|
550
711
|
if (platform === 'win32') {
|
|
551
712
|
try {
|
|
@@ -553,7 +714,7 @@ async function downloadFromJiHuLab(platform, arch, version) {
|
|
|
553
714
|
const scriptContent = [
|
|
554
715
|
'$ProgressPreference = "SilentlyContinue"',
|
|
555
716
|
`$uri = "${downloadUrl}"`,
|
|
556
|
-
`$out = "${
|
|
717
|
+
`$out = "${tempPath}"`,
|
|
557
718
|
'Write-Host " [JiHuLab] Downloading..."',
|
|
558
719
|
'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
|
|
559
720
|
'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " [JiHuLab] Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
|
|
@@ -569,13 +730,13 @@ async function downloadFromJiHuLab(platform, arch, version) {
|
|
|
569
730
|
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
570
731
|
}
|
|
571
732
|
|
|
572
|
-
//
|
|
573
|
-
if (fs.existsSync(
|
|
574
|
-
const stats = fs.statSync(
|
|
733
|
+
// 验证下载到临时文件
|
|
734
|
+
if (fs.existsSync(tempPath)) {
|
|
735
|
+
const stats = fs.statSync(tempPath);
|
|
575
736
|
if (stats.size > 10 * 1024 * 1024) {
|
|
576
|
-
console.log(` [OK]
|
|
577
|
-
|
|
578
|
-
return
|
|
737
|
+
console.log(` [OK] Downloaded to temp: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
738
|
+
// 尝试替换
|
|
739
|
+
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
579
740
|
}
|
|
580
741
|
}
|
|
581
742
|
|
|
@@ -585,21 +746,20 @@ async function downloadFromJiHuLab(platform, arch, version) {
|
|
|
585
746
|
}
|
|
586
747
|
}
|
|
587
748
|
|
|
588
|
-
// 通用方式: Node.js https
|
|
749
|
+
// 通用方式: Node.js https 下载到临时文件
|
|
589
750
|
try {
|
|
590
|
-
await downloadFile(downloadUrl,
|
|
751
|
+
await downloadFile(downloadUrl, tempPath, 600000); // 10 分钟超时
|
|
591
752
|
|
|
592
753
|
// 验证下载
|
|
593
|
-
if (fs.existsSync(
|
|
594
|
-
const stats = fs.statSync(
|
|
754
|
+
if (fs.existsSync(tempPath)) {
|
|
755
|
+
const stats = fs.statSync(tempPath);
|
|
595
756
|
if (stats.size > 10 * 1024 * 1024) {
|
|
596
|
-
console.log(` [OK]
|
|
757
|
+
console.log(` [OK] Downloaded to temp: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
597
758
|
if (platform !== 'win32') {
|
|
598
|
-
fs.chmodSync(
|
|
759
|
+
fs.chmodSync(tempPath, 0o755);
|
|
599
760
|
}
|
|
600
|
-
//
|
|
601
|
-
|
|
602
|
-
return true;
|
|
761
|
+
// 尝试替换
|
|
762
|
+
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
603
763
|
}
|
|
604
764
|
}
|
|
605
765
|
|
|
@@ -611,6 +771,89 @@ async function downloadFromJiHuLab(platform, arch, version) {
|
|
|
611
771
|
}
|
|
612
772
|
}
|
|
613
773
|
|
|
774
|
+
/**
|
|
775
|
+
* 将已下载到临时路径的二进制文件应用到目标位置
|
|
776
|
+
* 处理文件锁定情况:交互式选择杀死进程或延迟更新
|
|
777
|
+
*/
|
|
778
|
+
async function applyDownloadedBinary(tempPath, targetPath, installDir, version, platform) {
|
|
779
|
+
// 如果目标文件不存在(首次安装),直接移动
|
|
780
|
+
if (!fs.existsSync(targetPath)) {
|
|
781
|
+
try {
|
|
782
|
+
if (process.platform === 'win32') {
|
|
783
|
+
// Windows: 使用 PowerShell 移动
|
|
784
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-move.ps1');
|
|
785
|
+
const scriptContent = `Move-Item -Path "${tempPath}" -Destination "${targetPath}" -Force`;
|
|
786
|
+
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
787
|
+
try {
|
|
788
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
789
|
+
stdio: 'pipe', timeout: 30000
|
|
790
|
+
});
|
|
791
|
+
} finally {
|
|
792
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
793
|
+
}
|
|
794
|
+
} else {
|
|
795
|
+
fs.renameSync(tempPath, targetPath);
|
|
796
|
+
fs.chmodSync(targetPath, 0o755);
|
|
797
|
+
}
|
|
798
|
+
fs.writeFileSync(path.join(installDir, '.version'), version);
|
|
799
|
+
console.log(` [OK] Installed: ${targetPath}`);
|
|
800
|
+
return true;
|
|
801
|
+
} catch (e) {
|
|
802
|
+
console.log(` [WARN] Move failed: ${e.message}, trying copy...`);
|
|
803
|
+
// fallback to copy
|
|
804
|
+
const result = tryReplaceBinary(tempPath, targetPath, installDir, version, platform);
|
|
805
|
+
return result === 'replaced';
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// 目标文件已存在,尝试替换
|
|
810
|
+
const replaceResult = tryReplaceBinary(tempPath, targetPath, installDir, version, platform);
|
|
811
|
+
|
|
812
|
+
if (replaceResult === 'replaced') {
|
|
813
|
+
console.log(` [OK] Updated: ${targetPath} (v${version})`);
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (replaceResult === 'locked') {
|
|
818
|
+
console.log(` [WARN] Binary file is locked (SciAgent is running).`);
|
|
819
|
+
|
|
820
|
+
// 交互式选择
|
|
821
|
+
const choice = await promptUpdateChoice();
|
|
822
|
+
|
|
823
|
+
if (choice === 'kill') {
|
|
824
|
+
console.log(` [INFO] Killing SciAgent process...`);
|
|
825
|
+
const killed = killSciAgent();
|
|
826
|
+
if (killed) {
|
|
827
|
+
console.log(` [OK] SciAgent process terminated.`);
|
|
828
|
+
// 重试替换
|
|
829
|
+
const retryResult = tryReplaceBinary(tempPath, targetPath, installDir, version, platform);
|
|
830
|
+
if (retryResult === 'replaced') {
|
|
831
|
+
console.log(` [OK] Updated: ${targetPath} (v${version})`);
|
|
832
|
+
return true;
|
|
833
|
+
} else {
|
|
834
|
+
console.log(` [WARN] Replace still failed after killing process. Deferring to next startup.`);
|
|
835
|
+
writePendingUpdate(installDir, tempPath, version);
|
|
836
|
+
return true; // 下载成功,只是替换延迟
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
console.log(` [WARN] Failed to kill SciAgent. Deferring update to next startup.`);
|
|
840
|
+
writePendingUpdate(installDir, tempPath, version);
|
|
841
|
+
return true; // 下载成功,只是替换延迟
|
|
842
|
+
}
|
|
843
|
+
} else {
|
|
844
|
+
// 用户选择延迟
|
|
845
|
+
writePendingUpdate(installDir, tempPath, version);
|
|
846
|
+
console.log(` [INFO] Update will be applied automatically on next startup.`);
|
|
847
|
+
return true; // 下载成功,只是替换延迟
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// 其他错误
|
|
852
|
+
console.log(` [WARN] Replace failed with unexpected error. Deferring to next startup.`);
|
|
853
|
+
writePendingUpdate(installDir, tempPath, version);
|
|
854
|
+
return true; // 下载成功,只是替换延迟
|
|
855
|
+
}
|
|
856
|
+
|
|
614
857
|
async function downloadFromServer(platform, arch, version) {
|
|
615
858
|
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
616
859
|
const downloadUrl = `${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`;
|
|
@@ -623,24 +866,28 @@ async function downloadFromServer(platform, arch, version) {
|
|
|
623
866
|
|
|
624
867
|
const targetPath = path.join(installDir, binName);
|
|
625
868
|
|
|
869
|
+
// 下载到临时目录
|
|
870
|
+
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
871
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
872
|
+
const tempPath = path.join(tempDir, binName);
|
|
873
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
874
|
+
|
|
626
875
|
try {
|
|
627
|
-
await downloadFile(downloadUrl,
|
|
876
|
+
await downloadFile(downloadUrl, tempPath, 600000); // 10 分钟超时
|
|
628
877
|
|
|
629
878
|
// 验证下载
|
|
630
|
-
if (fs.existsSync(
|
|
631
|
-
const stats = fs.statSync(
|
|
879
|
+
if (fs.existsSync(tempPath)) {
|
|
880
|
+
const stats = fs.statSync(tempPath);
|
|
632
881
|
if (stats.size > 10 * 1024 * 1024) { // 至少 10MB
|
|
633
|
-
console.log(` ✅
|
|
882
|
+
console.log(` ✅ 下载到临时文件: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
634
883
|
|
|
635
884
|
// Linux/macOS 添加执行权限
|
|
636
885
|
if (platform !== 'win32') {
|
|
637
|
-
fs.chmodSync(
|
|
886
|
+
fs.chmodSync(tempPath, 0o755);
|
|
638
887
|
}
|
|
639
888
|
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
return true;
|
|
889
|
+
// 尝试替换(处理文件锁定)
|
|
890
|
+
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
644
891
|
}
|
|
645
892
|
}
|
|
646
893
|
|