@sciagent/cli 1.1.49 → 1.1.51
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 +410 -183
- package/package.json +1 -1
- package/scripts/postinstall.js +107 -61
package/bin/sciagent.js
CHANGED
|
@@ -25,10 +25,22 @@ const https = require('https');
|
|
|
25
25
|
const http = require('http');
|
|
26
26
|
|
|
27
27
|
// 当前版本号 - 与 postinstall.js 和 package.json 保持同步
|
|
28
|
-
const CURRENT_VERSION = '1.1.
|
|
29
|
-
|
|
30
|
-
// Releases
|
|
31
|
-
|
|
28
|
+
const CURRENT_VERSION = '1.1.51';
|
|
29
|
+
|
|
30
|
+
// Releases 下载源配置(优先级从高到低)
|
|
31
|
+
// JihuLab 通用包仓库(国内 CDN,速度快)作为主源
|
|
32
|
+
// sciagent.tech 自建服务器作为备用源
|
|
33
|
+
const JIHULAB_PROJECT_ID = '351778';
|
|
34
|
+
const JIHULAB_DEPLOY_TOKEN_USER = 'gitlab+deploy-token-15400';
|
|
35
|
+
const JIHULAB_DEPLOY_TOKEN = 'gldt-5kp7mgt4ztyyBMx1Uvn6';
|
|
36
|
+
const JIHULAB_BASE_URL = `https://jihulab.com/api/v4/projects/${JIHULAB_PROJECT_ID}/packages/generic/sciagent`;
|
|
37
|
+
const SELF_HOSTED_URL = process.env.RELEASE_SERVER_URL || 'https://sciagent.tech';
|
|
38
|
+
|
|
39
|
+
// 下载源列表(按优先级排序)
|
|
40
|
+
const DOWNLOAD_MIRRORS = [
|
|
41
|
+
{ name: 'JihuLab CDN', getUrl: (platform, arch, version, filename) => `${JIHULAB_BASE_URL}/${version}/${filename}`, auth: 'jihulab' },
|
|
42
|
+
{ name: 'Self-hosted', getUrl: (platform, arch, version, filename) => `${SELF_HOSTED_URL}/api/releases/download/${platform}/${arch}/${version}`, auth: 'none' },
|
|
43
|
+
];
|
|
32
44
|
|
|
33
45
|
// 平台和架构映射
|
|
34
46
|
const PLATFORM_MAP = {
|
|
@@ -156,8 +168,8 @@ function applyPendingUpdate(installDir) {
|
|
|
156
168
|
*/
|
|
157
169
|
function fetchLatestVersion() {
|
|
158
170
|
return new Promise((resolve) => {
|
|
159
|
-
//
|
|
160
|
-
const url = `${
|
|
171
|
+
// 使用自建服务器 /api/releases/list 获取所有版本,取最新的
|
|
172
|
+
const url = `${SELF_HOSTED_URL}/api/releases/list`;
|
|
161
173
|
|
|
162
174
|
const protocol = url.startsWith('https') ? https : http;
|
|
163
175
|
const request = protocol.get(url, {
|
|
@@ -196,16 +208,13 @@ function fetchLatestVersion() {
|
|
|
196
208
|
* 后台下载新版本到临时目录
|
|
197
209
|
* 下载完成后写入 .pending-update 标记
|
|
198
210
|
* 这是异步操作,不阻塞主进程启动
|
|
211
|
+
* 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
|
|
199
212
|
*/
|
|
200
213
|
function backgroundDownloadUpdate(platform, arch, latestVersion) {
|
|
201
214
|
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
202
215
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
203
216
|
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
204
217
|
|
|
205
|
-
const downloadUrl = (
|
|
206
|
-
`${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${latestVersion}`
|
|
207
|
-
);
|
|
208
|
-
|
|
209
218
|
const installDir = getInstallDir(platform);
|
|
210
219
|
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
211
220
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
@@ -216,73 +225,123 @@ function backgroundDownloadUpdate(platform, arch, latestVersion) {
|
|
|
216
225
|
|
|
217
226
|
console.log(`[AUTO-UPDATE] Downloading v${latestVersion} in background...`);
|
|
218
227
|
|
|
228
|
+
// 构建下载 URL 列表(按优先级)
|
|
229
|
+
const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
|
|
230
|
+
url: mirror.getUrl(platform, arch, latestVersion, filename),
|
|
231
|
+
name: mirror.name,
|
|
232
|
+
auth: mirror.auth
|
|
233
|
+
}));
|
|
234
|
+
|
|
219
235
|
if (platform === 'win32') {
|
|
220
|
-
// Windows: 使用 PowerShell
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
241
|
-
// 写入 pending update 标记
|
|
242
|
-
const pendingData = {
|
|
243
|
-
version: latestVersion,
|
|
244
|
-
tempPath: tempPath,
|
|
245
|
-
timestamp: new Date().toISOString(),
|
|
246
|
-
platform: process.platform
|
|
247
|
-
};
|
|
248
|
-
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
249
|
-
console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
|
|
250
|
-
}
|
|
236
|
+
// Windows: 使用 PowerShell 后台下载,尝试多个源
|
|
237
|
+
const tryDownload = (urlIndex) => {
|
|
238
|
+
if (urlIndex >= downloadUrls.length) {
|
|
239
|
+
console.log(`[AUTO-UPDATE] ⚠️ Background download failed from all mirrors.`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const source = downloadUrls[urlIndex];
|
|
244
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-bg-download.ps1');
|
|
245
|
+
let scriptLines = [
|
|
246
|
+
'$ProgressPreference = "SilentlyContinue"',
|
|
247
|
+
`$uri = "${source.url}"`,
|
|
248
|
+
`$out = "${tempPath}"`,
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
if (source.auth === 'jihulab') {
|
|
252
|
+
// JihuLab 需要 Basic Auth
|
|
253
|
+
const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
|
|
254
|
+
scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
|
|
255
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
|
|
251
256
|
} else {
|
|
252
|
-
|
|
253
|
-
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
257
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
|
|
254
258
|
}
|
|
255
|
-
|
|
259
|
+
scriptLines.push(`if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host "Downloaded: $s bytes" } else { exit 1 }`);
|
|
260
|
+
|
|
261
|
+
fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
|
|
262
|
+
|
|
263
|
+
const bgProcess = spawn('powershell', [
|
|
264
|
+
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', tmpScript
|
|
265
|
+
], { stdio: 'pipe', detached: true, windowsHide: true });
|
|
266
|
+
|
|
267
|
+
bgProcess.on('exit', (code) => {
|
|
268
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
269
|
+
if (code === 0 && fs.existsSync(tempPath)) {
|
|
270
|
+
const stats = fs.statSync(tempPath);
|
|
271
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
272
|
+
console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} from ${source.name} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
273
|
+
const pendingData = {
|
|
274
|
+
version: latestVersion,
|
|
275
|
+
tempPath: tempPath,
|
|
276
|
+
timestamp: new Date().toISOString(),
|
|
277
|
+
platform: process.platform
|
|
278
|
+
};
|
|
279
|
+
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
280
|
+
console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
|
|
281
|
+
} else {
|
|
282
|
+
console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} too small, trying next...`);
|
|
283
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
284
|
+
tryDownload(urlIndex + 1);
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} failed, trying next...`);
|
|
288
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
289
|
+
tryDownload(urlIndex + 1);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
bgProcess.unref();
|
|
294
|
+
};
|
|
256
295
|
|
|
257
|
-
|
|
296
|
+
tryDownload(0);
|
|
258
297
|
} else {
|
|
259
|
-
// Linux/macOS: 使用 curl
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
bgProcess.on('exit', (code) => {
|
|
265
|
-
if (code === 0 && fs.existsSync(tempPath)) {
|
|
266
|
-
const stats = fs.statSync(tempPath);
|
|
267
|
-
if (stats.size > 10 * 1024 * 1024) {
|
|
268
|
-
fs.chmodSync(tempPath, 0o755);
|
|
269
|
-
console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
270
|
-
const pendingData = {
|
|
271
|
-
version: latestVersion,
|
|
272
|
-
tempPath: tempPath,
|
|
273
|
-
timestamp: new Date().toISOString(),
|
|
274
|
-
platform: process.platform
|
|
275
|
-
};
|
|
276
|
-
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
277
|
-
console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
console.log(`[AUTO-UPDATE] ⚠️ Background download failed.`);
|
|
281
|
-
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
298
|
+
// Linux/macOS: 使用 curl 后台下载,尝试多个源
|
|
299
|
+
const tryDownload = (urlIndex) => {
|
|
300
|
+
if (urlIndex >= downloadUrls.length) {
|
|
301
|
+
console.log(`[AUTO-UPDATE] ⚠️ Background download failed from all mirrors.`);
|
|
302
|
+
return;
|
|
282
303
|
}
|
|
283
|
-
|
|
304
|
+
|
|
305
|
+
const source = downloadUrls[urlIndex];
|
|
306
|
+
let curlArgs = ['-fsSL', '-o', tempPath];
|
|
307
|
+
|
|
308
|
+
if (source.auth === 'jihulab') {
|
|
309
|
+
curlArgs.push('-u', `${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`);
|
|
310
|
+
}
|
|
311
|
+
curlArgs.push(source.url);
|
|
312
|
+
|
|
313
|
+
const bgProcess = spawn('curl', curlArgs, { stdio: 'pipe', detached: true });
|
|
314
|
+
|
|
315
|
+
bgProcess.on('exit', (code) => {
|
|
316
|
+
if (code === 0 && fs.existsSync(tempPath)) {
|
|
317
|
+
const stats = fs.statSync(tempPath);
|
|
318
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
319
|
+
fs.chmodSync(tempPath, 0o755);
|
|
320
|
+
console.log(`[AUTO-UPDATE] ✅ Downloaded v${latestVersion} from ${source.name} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
321
|
+
const pendingData = {
|
|
322
|
+
version: latestVersion,
|
|
323
|
+
tempPath: tempPath,
|
|
324
|
+
timestamp: new Date().toISOString(),
|
|
325
|
+
platform: process.platform
|
|
326
|
+
};
|
|
327
|
+
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
328
|
+
console.log(`[AUTO-UPDATE] 📋 Update will be applied on next restart.`);
|
|
329
|
+
} else {
|
|
330
|
+
console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} too small, trying next...`);
|
|
331
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
332
|
+
tryDownload(urlIndex + 1);
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
console.log(`[AUTO-UPDATE] ⚠️ Download from ${source.name} failed, trying next...`);
|
|
336
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
337
|
+
tryDownload(urlIndex + 1);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
bgProcess.unref();
|
|
342
|
+
};
|
|
284
343
|
|
|
285
|
-
|
|
344
|
+
tryDownload(0);
|
|
286
345
|
}
|
|
287
346
|
}
|
|
288
347
|
|
|
@@ -396,19 +455,23 @@ function getBinaryPath() {
|
|
|
396
455
|
checkAutoUpdate(platform, arch);
|
|
397
456
|
return binPath;
|
|
398
457
|
} else if (cmp < 0) {
|
|
399
|
-
// 版本过低 →
|
|
458
|
+
// 版本过低 → 同步下载新版本并替换,然后再启动
|
|
400
459
|
console.log(`[INFO] SciAgent version outdated: v${installedVersion} (${(stats.size / (1024 * 1024)).toFixed(1)} MB) → v${CURRENT_VERSION}`);
|
|
401
460
|
console.log(`[INFO] Downloading new version from server...`);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
461
|
+
const downloaded = downloadBinary(platform, arch, CURRENT_VERSION);
|
|
462
|
+
if (downloaded && fs.existsSync(downloaded)) {
|
|
463
|
+
console.log(`[INFO] ✅ Updated to v${CURRENT_VERSION}. Starting...`);
|
|
464
|
+
checkAutoUpdate(platform, arch);
|
|
465
|
+
return downloaded;
|
|
466
|
+
}
|
|
467
|
+
// 下载失败,尝试临时目录下载
|
|
468
|
+
const tempDownloaded = downloadBinaryToTemp(platform, arch, CURRENT_VERSION);
|
|
469
|
+
if (tempDownloaded) {
|
|
470
|
+
console.log(`[INFO] ✅ New version downloaded to temp. Will apply on next restart.`);
|
|
471
|
+
console.log(`[INFO] ⚠️ Continuing with current version v${installedVersion} this time.`);
|
|
472
|
+
} else {
|
|
473
|
+
console.log(`[INFO] ⚠️ Download failed. Continuing with current version v${installedVersion}.`);
|
|
409
474
|
}
|
|
410
|
-
// 下载失败,继续使用当前版本
|
|
411
|
-
console.log(`[INFO] ⚠️ Download failed. Continuing with current version.`);
|
|
412
475
|
return binPath;
|
|
413
476
|
} else {
|
|
414
477
|
// 版本更高 → 允许运行(用户可能手动安装了新版)
|
|
@@ -417,11 +480,21 @@ function getBinaryPath() {
|
|
|
417
480
|
return binPath;
|
|
418
481
|
}
|
|
419
482
|
} else {
|
|
420
|
-
// 没有.version文件 →
|
|
483
|
+
// 没有.version文件 → 版本未知,同步下载正确版本
|
|
421
484
|
console.log(`[INFO] SciAgent binary found but version unknown, downloading v${CURRENT_VERSION}...`);
|
|
422
|
-
const downloaded =
|
|
423
|
-
if (downloaded) {
|
|
424
|
-
console.log(`[INFO] ✅
|
|
485
|
+
const downloaded = downloadBinary(platform, arch, CURRENT_VERSION);
|
|
486
|
+
if (downloaded && fs.existsSync(downloaded)) {
|
|
487
|
+
console.log(`[INFO] ✅ Installed v${CURRENT_VERSION}. Starting...`);
|
|
488
|
+
checkAutoUpdate(platform, arch);
|
|
489
|
+
return downloaded;
|
|
490
|
+
}
|
|
491
|
+
// 下载失败,尝试临时目录
|
|
492
|
+
const tempDownloaded = downloadBinaryToTemp(platform, arch, CURRENT_VERSION);
|
|
493
|
+
if (tempDownloaded) {
|
|
494
|
+
console.log(`[INFO] ✅ New version downloaded to temp. Will apply on next restart.`);
|
|
495
|
+
console.log(`[INFO] ⚠️ Continuing with unknown version this time.`);
|
|
496
|
+
} else {
|
|
497
|
+
console.log(`[INFO] ⚠️ Download failed. Continuing with unknown version.`);
|
|
425
498
|
}
|
|
426
499
|
return binPath;
|
|
427
500
|
}
|
|
@@ -459,6 +532,7 @@ function getBinaryPath() {
|
|
|
459
532
|
/**
|
|
460
533
|
* 从 Release Server 下载二进制文件到临时目录(不替换当前运行的二进制)
|
|
461
534
|
* 下载完成后写入 .pending-update 标记,下次启动时自动应用
|
|
535
|
+
* 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
|
|
462
536
|
* 返回: tempPath 或 null
|
|
463
537
|
*/
|
|
464
538
|
function downloadBinaryToTemp(platform, arch, version) {
|
|
@@ -466,10 +540,6 @@ function downloadBinaryToTemp(platform, arch, version) {
|
|
|
466
540
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
467
541
|
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
468
542
|
|
|
469
|
-
const downloadUrl = (
|
|
470
|
-
`${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`
|
|
471
|
-
);
|
|
472
|
-
|
|
473
543
|
const installDir = getInstallDir(platform);
|
|
474
544
|
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
475
545
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
@@ -481,132 +551,289 @@ function downloadBinaryToTemp(platform, arch, version) {
|
|
|
481
551
|
console.log(` Downloading ${filename} v${version} to temp...`);
|
|
482
552
|
console.log(` Temp: ${tempPath}`);
|
|
483
553
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
|
|
494
|
-
].join('\r\n');
|
|
495
|
-
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
496
|
-
|
|
497
|
-
try {
|
|
498
|
-
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
499
|
-
stdio: 'inherit',
|
|
500
|
-
timeout: 600000
|
|
501
|
-
});
|
|
502
|
-
} finally {
|
|
503
|
-
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
504
|
-
}
|
|
505
|
-
} else {
|
|
506
|
-
execSync(`curl -fsSL -o '${tempPath}' '${downloadUrl}'`, {
|
|
507
|
-
stdio: 'pipe',
|
|
508
|
-
timeout: 600000
|
|
509
|
-
});
|
|
510
|
-
fs.chmodSync(tempPath, 0o755);
|
|
511
|
-
}
|
|
554
|
+
// 构建下载 URL 列表(按优先级)
|
|
555
|
+
const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
|
|
556
|
+
url: mirror.getUrl(platform, arch, version, filename),
|
|
557
|
+
name: mirror.name,
|
|
558
|
+
auth: mirror.auth
|
|
559
|
+
}));
|
|
560
|
+
|
|
561
|
+
for (const source of downloadUrls) {
|
|
562
|
+
console.log(` Trying ${source.name}...`);
|
|
512
563
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
564
|
+
try {
|
|
565
|
+
if (platform === 'win32') {
|
|
566
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
|
|
567
|
+
let scriptLines = [
|
|
568
|
+
'$ProgressPreference = "SilentlyContinue"',
|
|
569
|
+
`$uri = "${source.url}"`,
|
|
570
|
+
`$out = "${tempPath}"`,
|
|
571
|
+
'Write-Host " Downloading..."',
|
|
572
|
+
];
|
|
573
|
+
|
|
574
|
+
if (source.auth === 'jihulab') {
|
|
575
|
+
const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
|
|
576
|
+
scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
|
|
577
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
|
|
578
|
+
} else {
|
|
579
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
|
|
580
|
+
}
|
|
581
|
+
scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }');
|
|
582
|
+
|
|
583
|
+
fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
587
|
+
stdio: 'inherit',
|
|
588
|
+
timeout: 600000
|
|
589
|
+
});
|
|
590
|
+
} finally {
|
|
591
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
592
|
+
}
|
|
527
593
|
} else {
|
|
528
|
-
|
|
529
|
-
|
|
594
|
+
let curlCmd = `curl -fsSL -o '${tempPath}'`;
|
|
595
|
+
if (source.auth === 'jihulab') {
|
|
596
|
+
curlCmd += ` -u '${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}'`;
|
|
597
|
+
}
|
|
598
|
+
curlCmd += ` '${source.url}'`;
|
|
599
|
+
execSync(curlCmd, { stdio: 'pipe', timeout: 600000 });
|
|
600
|
+
fs.chmodSync(tempPath, 0o755);
|
|
530
601
|
}
|
|
602
|
+
|
|
603
|
+
// 验证下载
|
|
604
|
+
if (fs.existsSync(tempPath)) {
|
|
605
|
+
const stats = fs.statSync(tempPath);
|
|
606
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
607
|
+
console.log(` ✅ Downloaded from ${source.name}: ${(stats.size / (1024 * 1024)).toFixed(1)} MB`);
|
|
608
|
+
// 写入 .pending-update 标记
|
|
609
|
+
const pendingData = {
|
|
610
|
+
version: version,
|
|
611
|
+
tempPath: tempPath,
|
|
612
|
+
timestamp: new Date().toISOString(),
|
|
613
|
+
platform: process.platform
|
|
614
|
+
};
|
|
615
|
+
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
616
|
+
return tempPath;
|
|
617
|
+
} else {
|
|
618
|
+
console.error(` ❌ Downloaded from ${source.name} too small: ${(stats.size / 1024).toFixed(0)} KB`);
|
|
619
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} catch (e) {
|
|
623
|
+
console.error(` ❌ Download from ${source.name} failed: ${e.message}`);
|
|
624
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
|
|
531
625
|
}
|
|
532
|
-
} catch (e) {
|
|
533
|
-
console.error(` ❌ Download failed: ${e.message}`);
|
|
534
|
-
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
|
|
535
626
|
}
|
|
536
627
|
|
|
628
|
+
console.error(` ❌ All download mirrors failed.`);
|
|
629
|
+
|
|
537
630
|
return null;
|
|
538
631
|
}
|
|
539
632
|
|
|
540
633
|
/**
|
|
541
|
-
* 从 Release Server
|
|
634
|
+
* 从 Release Server 下载二进制文件(直接到目标路径,用于首次安装或版本更新)
|
|
635
|
+
* 如果目标文件已存在,先下载到临时目录再替换
|
|
636
|
+
* 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
|
|
542
637
|
*/
|
|
543
638
|
function downloadBinary(platform, arch, version) {
|
|
544
639
|
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
545
640
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
546
641
|
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
547
642
|
|
|
548
|
-
const downloadUrl = (
|
|
549
|
-
`${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`
|
|
550
|
-
);
|
|
551
|
-
|
|
552
643
|
const installDir = getInstallDir(platform);
|
|
553
644
|
fs.mkdirSync(installDir, { recursive: true });
|
|
554
645
|
const targetPath = path.join(installDir, binName);
|
|
646
|
+
const targetExists = fs.existsSync(targetPath);
|
|
555
647
|
|
|
556
|
-
|
|
557
|
-
|
|
648
|
+
// 构建下载 URL 列表(按优先级)
|
|
649
|
+
const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
|
|
650
|
+
url: mirror.getUrl(platform, arch, version, filename),
|
|
651
|
+
name: mirror.name,
|
|
652
|
+
auth: mirror.auth
|
|
653
|
+
}));
|
|
558
654
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
].join('\r\n');
|
|
572
|
-
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
655
|
+
// 如果目标文件已存在,先下载到临时目录再替换
|
|
656
|
+
if (targetExists) {
|
|
657
|
+
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
658
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
659
|
+
const tempPath = path.join(tempDir, binName);
|
|
660
|
+
// 清理旧的临时文件
|
|
661
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
662
|
+
|
|
663
|
+
console.log(` Downloading ${filename} v${version} to temp...`);
|
|
664
|
+
|
|
665
|
+
for (const source of downloadUrls) {
|
|
666
|
+
console.log(` Trying ${source.name}...`);
|
|
573
667
|
|
|
574
668
|
try {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
669
|
+
if (platform === 'win32') {
|
|
670
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
|
|
671
|
+
let scriptLines = [
|
|
672
|
+
'$ProgressPreference = "SilentlyContinue"',
|
|
673
|
+
`$uri = "${source.url}"`,
|
|
674
|
+
`$out = "${tempPath}"`,
|
|
675
|
+
'Write-Host " Downloading..."',
|
|
676
|
+
];
|
|
677
|
+
|
|
678
|
+
if (source.auth === 'jihulab') {
|
|
679
|
+
const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
|
|
680
|
+
scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
|
|
681
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
|
|
682
|
+
} else {
|
|
683
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
|
|
684
|
+
}
|
|
685
|
+
scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }');
|
|
686
|
+
|
|
687
|
+
fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
691
|
+
stdio: 'inherit',
|
|
692
|
+
timeout: 600000
|
|
693
|
+
});
|
|
694
|
+
} finally {
|
|
695
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
let curlCmd = `curl -fsSL -o '${tempPath}'`;
|
|
699
|
+
if (source.auth === 'jihulab') {
|
|
700
|
+
curlCmd += ` -u '${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}'`;
|
|
701
|
+
}
|
|
702
|
+
curlCmd += ` '${source.url}'`;
|
|
703
|
+
execSync(curlCmd, { stdio: 'pipe', timeout: 600000 });
|
|
704
|
+
fs.chmodSync(tempPath, 0o755);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// 验证临时文件
|
|
708
|
+
if (fs.existsSync(tempPath)) {
|
|
709
|
+
const tempStats = fs.statSync(tempPath);
|
|
710
|
+
if (tempStats.size > 10 * 1024 * 1024) {
|
|
711
|
+
console.log(` ✅ Downloaded from ${source.name}: ${(tempStats.size / (1024 * 1024)).toFixed(1)} MB`);
|
|
712
|
+
|
|
713
|
+
// 尝试替换目标文件
|
|
714
|
+
try {
|
|
715
|
+
if (platform === 'win32') {
|
|
716
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-replace.ps1');
|
|
717
|
+
const scriptContent = [
|
|
718
|
+
`Copy-Item -Path "${tempPath}" -Destination "${targetPath}" -Force`,
|
|
719
|
+
'if (Test-Path $out) { Write-Host "OK" } else { Write-Error "Copy failed"; exit 1 }'
|
|
720
|
+
].join('\r\n');
|
|
721
|
+
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
722
|
+
try {
|
|
723
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
724
|
+
stdio: 'pipe', timeout: 30000
|
|
725
|
+
});
|
|
726
|
+
} finally {
|
|
727
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
fs.copyFileSync(tempPath, targetPath);
|
|
731
|
+
fs.chmodSync(targetPath, 0o755);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// 验证替换成功
|
|
735
|
+
if (fs.existsSync(targetPath)) {
|
|
736
|
+
const targetStats = fs.statSync(targetPath);
|
|
737
|
+
if (targetStats.size > 10 * 1024 * 1024) {
|
|
738
|
+
fs.writeFileSync(path.join(installDir, '.version'), version);
|
|
739
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
740
|
+
return targetPath;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
} catch (replaceErr) {
|
|
744
|
+
// 替换失败(可能被锁定),写入 pending update
|
|
745
|
+
console.log(` ⚠️ Cannot replace running binary. Writing pending update...`);
|
|
746
|
+
const pendingData = {
|
|
747
|
+
version: version,
|
|
748
|
+
tempPath: tempPath,
|
|
749
|
+
timestamp: new Date().toISOString(),
|
|
750
|
+
platform: process.platform
|
|
751
|
+
};
|
|
752
|
+
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
753
|
+
console.log(` 📋 Update will be applied on next restart.`);
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
console.error(` ❌ Download from ${source.name} too small: ${(tempStats.size / 1024).toFixed(0)} KB`);
|
|
758
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
} catch (e) {
|
|
762
|
+
console.error(` ❌ Download from ${source.name} failed: ${e.message}`);
|
|
763
|
+
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
|
|
581
764
|
}
|
|
582
|
-
} else {
|
|
583
|
-
// Linux/macOS: 使用 curl
|
|
584
|
-
execSync(`curl -fsSL -o '${targetPath}' '${downloadUrl}'`, {
|
|
585
|
-
stdio: 'pipe',
|
|
586
|
-
timeout: 600000
|
|
587
|
-
});
|
|
588
|
-
fs.chmodSync(targetPath, 0o755);
|
|
589
765
|
}
|
|
590
766
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
767
|
+
console.error(` ❌ All download mirrors failed.`);
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// 目标文件不存在(首次安装),直接下载到目标路径,尝试多个源
|
|
772
|
+
console.log(` Downloading ${filename} v${version}...`);
|
|
773
|
+
console.log(` Target: ${targetPath}`);
|
|
774
|
+
|
|
775
|
+
for (const source of downloadUrls) {
|
|
776
|
+
console.log(` Trying ${source.name}...`);
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
if (platform === 'win32') {
|
|
780
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
|
|
781
|
+
let scriptLines = [
|
|
782
|
+
'$ProgressPreference = "SilentlyContinue"',
|
|
783
|
+
`$uri = "${source.url}"`,
|
|
784
|
+
`$out = "${targetPath}"`,
|
|
785
|
+
'Write-Host " Downloading..."',
|
|
786
|
+
];
|
|
787
|
+
|
|
788
|
+
if (source.auth === 'jihulab') {
|
|
789
|
+
const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
|
|
790
|
+
scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
|
|
791
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
|
|
792
|
+
} else {
|
|
793
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
|
|
794
|
+
}
|
|
795
|
+
scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }');
|
|
796
|
+
|
|
797
|
+
fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
|
|
798
|
+
|
|
799
|
+
try {
|
|
800
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
801
|
+
stdio: 'inherit',
|
|
802
|
+
timeout: 600000
|
|
803
|
+
});
|
|
804
|
+
} finally {
|
|
805
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
806
|
+
}
|
|
599
807
|
} else {
|
|
600
|
-
|
|
601
|
-
|
|
808
|
+
let curlCmd = `curl -fsSL -o '${targetPath}'`;
|
|
809
|
+
if (source.auth === 'jihulab') {
|
|
810
|
+
curlCmd += ` -u '${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}'`;
|
|
811
|
+
}
|
|
812
|
+
curlCmd += ` '${source.url}'`;
|
|
813
|
+
execSync(curlCmd, { stdio: 'pipe', timeout: 600000 });
|
|
814
|
+
fs.chmodSync(targetPath, 0o755);
|
|
602
815
|
}
|
|
816
|
+
|
|
817
|
+
// 验证下载
|
|
818
|
+
if (fs.existsSync(targetPath)) {
|
|
819
|
+
const stats = fs.statSync(targetPath);
|
|
820
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
821
|
+
console.log(` ✅ Downloaded from ${source.name}: ${(stats.size / (1024 * 1024)).toFixed(1)} MB`);
|
|
822
|
+
// 写入版本文件
|
|
823
|
+
fs.writeFileSync(path.join(installDir, '.version'), version);
|
|
824
|
+
return targetPath;
|
|
825
|
+
} else {
|
|
826
|
+
console.error(` ❌ Download from ${source.name} too small: ${(stats.size / 1024).toFixed(0)} KB`);
|
|
827
|
+
try { fs.unlinkSync(targetPath); } catch (e) {}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
} catch (e) {
|
|
831
|
+
console.error(` ❌ Download from ${source.name} failed: ${e.message}`);
|
|
832
|
+
try { if (fs.existsSync(targetPath)) fs.unlinkSync(targetPath); } catch (e2) {}
|
|
603
833
|
}
|
|
604
|
-
} catch (e) {
|
|
605
|
-
console.error(` ❌ Download failed: ${e.message}`);
|
|
606
|
-
// 清理可能的部分下载
|
|
607
|
-
try { if (fs.existsSync(targetPath)) fs.unlinkSync(targetPath); } catch (e2) {}
|
|
608
834
|
}
|
|
609
835
|
|
|
836
|
+
console.error(` ❌ All download mirrors failed.`);
|
|
610
837
|
return null;
|
|
611
838
|
}
|
|
612
839
|
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -27,15 +27,25 @@ const ARCH_MAP = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// 当前版本号 - 每次发布时同步更新
|
|
30
|
-
const CURRENT_VERSION = '1.1.
|
|
30
|
+
const CURRENT_VERSION = '1.1.51';
|
|
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'];
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
35
|
+
// JihuLab 通用包仓库配置(国内 CDN,速度快)
|
|
36
|
+
const JIHULAB_PROJECT_ID = '351778';
|
|
37
|
+
const JIHULAB_DEPLOY_TOKEN_USER = 'gitlab+deploy-token-15400';
|
|
38
|
+
const JIHULAB_DEPLOY_TOKEN = 'gldt-5kp7mgt4ztyyBMx1Uvn6';
|
|
39
|
+
const JIHULAB_BASE_URL = `https://jihulab.com/api/v4/projects/${JIHULAB_PROJECT_ID}/packages/generic/sciagent`;
|
|
40
|
+
|
|
41
|
+
// 自建服务器配置(备用源)
|
|
42
|
+
const SELF_HOSTED_URL = process.env.RELEASE_SERVER_URL || 'https://sciagent.tech';
|
|
43
|
+
|
|
44
|
+
// 下载源列表(按优先级排序)
|
|
45
|
+
const DOWNLOAD_MIRRORS = [
|
|
46
|
+
{ name: 'JihuLab CDN', getUrl: (platform, arch, version, filename) => `${JIHULAB_BASE_URL}/${version}/${filename}`, auth: 'jihulab' },
|
|
47
|
+
{ name: 'Self-hosted', getUrl: (platform, arch, version, filename) => `${SELF_HOSTED_URL}/api/releases/download/${platform}/${arch}/${version}`, auth: 'none' },
|
|
48
|
+
];
|
|
39
49
|
|
|
40
50
|
// PyPI 镜像源列表(国内优先)
|
|
41
51
|
const PYPI_MIRRORS = [
|
|
@@ -110,13 +120,21 @@ function checkBinaryInstalled(platform, arch) {
|
|
|
110
120
|
} else {
|
|
111
121
|
console.log(` ℹ️ 已安装版本 ${installedVersion} != 目标版本 ${CURRENT_VERSION},需要更新`);
|
|
112
122
|
// 版本不匹配,删除旧文件强制重新下载
|
|
113
|
-
_removeBinaryFiles(homeBinDir, homeBinPath);
|
|
123
|
+
const allRemoved = _removeBinaryFiles(homeBinDir, homeBinPath);
|
|
124
|
+
if (!allRemoved) {
|
|
125
|
+
// 删除失败(可能被锁定),标记需要更新但不阻塞
|
|
126
|
+
// downloadFromServer 会下载到临时目录并处理锁定情况
|
|
127
|
+
console.log(` ℹ️ 旧文件删除失败(可能被占用),将下载新版本到临时目录`);
|
|
128
|
+
}
|
|
114
129
|
return { installed: false };
|
|
115
130
|
}
|
|
116
131
|
} else {
|
|
117
132
|
console.log(` ℹ️ 二进制版本未知(无.version文件),需要重新下载`);
|
|
118
133
|
// 版本未知,删除文件强制重新下载
|
|
119
|
-
_removeBinaryFiles(homeBinDir, homeBinPath);
|
|
134
|
+
const allRemoved = _removeBinaryFiles(homeBinDir, homeBinPath);
|
|
135
|
+
if (!allRemoved) {
|
|
136
|
+
console.log(` ℹ️ 旧文件删除失败(可能被占用),将下载新版本到临时目录`);
|
|
137
|
+
}
|
|
120
138
|
return { installed: false };
|
|
121
139
|
}
|
|
122
140
|
} else {
|
|
@@ -144,14 +162,22 @@ function _removeBinaryFiles(binDir, binPath) {
|
|
|
144
162
|
if (fs.existsSync(versionFile)) {
|
|
145
163
|
filesToRemove.push(versionFile);
|
|
146
164
|
}
|
|
165
|
+
// 也删除 .pending-update 文件
|
|
166
|
+
const pendingFile = path.join(binDir, '.pending-update');
|
|
167
|
+
if (fs.existsSync(pendingFile)) {
|
|
168
|
+
filesToRemove.push(pendingFile);
|
|
169
|
+
}
|
|
170
|
+
let allRemoved = true;
|
|
147
171
|
for (const f of filesToRemove) {
|
|
148
172
|
try {
|
|
149
173
|
fs.unlinkSync(f);
|
|
150
174
|
console.log(` ℹ️ 已删除: ${f}`);
|
|
151
175
|
} catch (e) {
|
|
152
176
|
console.log(` ⚠️ 无法删除 ${f}: ${e.message}`);
|
|
177
|
+
allRemoved = false;
|
|
153
178
|
}
|
|
154
179
|
}
|
|
180
|
+
return allRemoved;
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
function checkCodebuddyBinaryInstalled() {
|
|
@@ -169,7 +195,7 @@ function checkCodebuddyBinaryInstalled() {
|
|
|
169
195
|
return { installed: false };
|
|
170
196
|
}
|
|
171
197
|
|
|
172
|
-
function downloadFile(url, destPath, timeout = 120000) {
|
|
198
|
+
function downloadFile(url, destPath, timeout = 120000, extraHeaders = {}) {
|
|
173
199
|
return new Promise((resolve, reject) => {
|
|
174
200
|
const protocol = url.startsWith('https') ? https : http;
|
|
175
201
|
|
|
@@ -205,7 +231,7 @@ function downloadFile(url, destPath, timeout = 120000) {
|
|
|
205
231
|
}
|
|
206
232
|
|
|
207
233
|
const request = protocol.get(requestUrl, {
|
|
208
|
-
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
234
|
+
headers: { 'User-Agent': 'sciagent-cli/1.0', ...extraHeaders },
|
|
209
235
|
timeout: 60000
|
|
210
236
|
}, (response) => {
|
|
211
237
|
// Handle redirects
|
|
@@ -497,7 +523,7 @@ sys.exit(1)
|
|
|
497
523
|
fs.mkdirSync(binDir, { recursive: true });
|
|
498
524
|
const binaryName = BINARY_NAMES[platform] || 'codebuddy-headless';
|
|
499
525
|
const targetPath = path.join(binDir, binaryName);
|
|
500
|
-
const downloadUrl = `${
|
|
526
|
+
const downloadUrl = `${SELF_HOSTED_URL}/api/releases/download/codebuddy/${platform}/${arch}/latest`;
|
|
501
527
|
|
|
502
528
|
await downloadFile(downloadUrl, targetPath, 600000);
|
|
503
529
|
|
|
@@ -679,10 +705,6 @@ async function downloadFromServer(platform, arch, version) {
|
|
|
679
705
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
680
706
|
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
681
707
|
|
|
682
|
-
const downloadUrl = `${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`;
|
|
683
|
-
|
|
684
|
-
console.log(`\n [Server] Downloading ${filename} v${version}...`);
|
|
685
|
-
|
|
686
708
|
// 确定安装目录
|
|
687
709
|
const installDir = getHomeBinDir();
|
|
688
710
|
fs.mkdirSync(installDir, { recursive: true });
|
|
@@ -696,68 +718,92 @@ async function downloadFromServer(platform, arch, version) {
|
|
|
696
718
|
// 清理旧的临时文件
|
|
697
719
|
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
698
720
|
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
712
|
-
|
|
721
|
+
// 构建下载 URL 列表(按优先级)
|
|
722
|
+
const downloadUrls = DOWNLOAD_MIRRORS.map(mirror => ({
|
|
723
|
+
url: mirror.getUrl(platform, arch, version, filename),
|
|
724
|
+
name: mirror.name,
|
|
725
|
+
auth: mirror.auth
|
|
726
|
+
}));
|
|
727
|
+
|
|
728
|
+
for (const source of downloadUrls) {
|
|
729
|
+
console.log(`\n [${source.name}] Downloading ${filename} v${version}...`);
|
|
730
|
+
|
|
731
|
+
// Windows: 优先使用 PowerShell(更可靠的大文件下载)
|
|
732
|
+
if (platform === 'win32') {
|
|
713
733
|
try {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
734
|
+
const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
|
|
735
|
+
let scriptLines = [
|
|
736
|
+
'$ProgressPreference = "SilentlyContinue"',
|
|
737
|
+
`$uri = "${source.url}"`,
|
|
738
|
+
`$out = "${tempPath}"`,
|
|
739
|
+
'Write-Host " Downloading..."',
|
|
740
|
+
];
|
|
741
|
+
|
|
742
|
+
if (source.auth === 'jihulab') {
|
|
743
|
+
const b64 = Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64');
|
|
744
|
+
scriptLines.push(`$headers = @{ "Authorization" = "Basic ${b64}" }`);
|
|
745
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing -Headers $headers');
|
|
746
|
+
} else {
|
|
747
|
+
scriptLines.push('Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing');
|
|
748
|
+
}
|
|
749
|
+
scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }');
|
|
750
|
+
|
|
751
|
+
fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
|
|
752
|
+
|
|
753
|
+
try {
|
|
754
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
755
|
+
stdio: 'inherit',
|
|
756
|
+
timeout: 600000
|
|
757
|
+
});
|
|
758
|
+
} finally {
|
|
759
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// 验证下载到临时文件
|
|
763
|
+
if (fs.existsSync(tempPath)) {
|
|
764
|
+
const stats = fs.statSync(tempPath);
|
|
765
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
766
|
+
console.log(` [OK] Downloaded from ${source.name}: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
767
|
+
// 尝试替换
|
|
768
|
+
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
console.log(` [WARN] ${source.name} download validation failed, trying next...`);
|
|
773
|
+
} catch (e) {
|
|
774
|
+
console.log(` [WARN] ${source.name} download failed: ${e.message}, trying next...`);
|
|
720
775
|
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// 通用方式: Node.js https 下载到临时文件
|
|
779
|
+
try {
|
|
780
|
+
// 如果是 JihuLab,需要添加认证头
|
|
781
|
+
const headers = source.auth === 'jihulab'
|
|
782
|
+
? { 'Authorization': `Basic ${Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64')}` }
|
|
783
|
+
: {};
|
|
784
|
+
await downloadFile(source.url, tempPath, 600000, headers);
|
|
721
785
|
|
|
722
|
-
//
|
|
786
|
+
// 验证下载
|
|
723
787
|
if (fs.existsSync(tempPath)) {
|
|
724
788
|
const stats = fs.statSync(tempPath);
|
|
725
789
|
if (stats.size > 10 * 1024 * 1024) {
|
|
726
|
-
console.log(` [OK] Downloaded
|
|
790
|
+
console.log(` [OK] Downloaded from ${source.name}: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
791
|
+
if (platform !== 'win32') {
|
|
792
|
+
fs.chmodSync(tempPath, 0o755);
|
|
793
|
+
}
|
|
727
794
|
// 尝试替换
|
|
728
795
|
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
729
796
|
}
|
|
730
797
|
}
|
|
731
798
|
|
|
732
|
-
console.log(` [WARN]
|
|
799
|
+
console.log(` [WARN] ${source.name} download validation failed, trying next...`);
|
|
733
800
|
} catch (e) {
|
|
734
|
-
console.log(` [WARN]
|
|
801
|
+
console.log(` [WARN] ${source.name} download failed: ${e.message}, trying next...`);
|
|
735
802
|
}
|
|
736
803
|
}
|
|
737
804
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
await downloadFile(downloadUrl, tempPath, 600000); // 10 分钟超时
|
|
741
|
-
|
|
742
|
-
// 验证下载
|
|
743
|
-
if (fs.existsSync(tempPath)) {
|
|
744
|
-
const stats = fs.statSync(tempPath);
|
|
745
|
-
if (stats.size > 10 * 1024 * 1024) {
|
|
746
|
-
console.log(` [OK] Downloaded to temp: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
747
|
-
if (platform !== 'win32') {
|
|
748
|
-
fs.chmodSync(tempPath, 0o755);
|
|
749
|
-
}
|
|
750
|
-
// 尝试替换
|
|
751
|
-
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
console.log(` [WARN] Downloaded file validation failed`);
|
|
756
|
-
return false;
|
|
757
|
-
} catch (e) {
|
|
758
|
-
console.log(` [WARN] Server download failed: ${e.message}`);
|
|
759
|
-
return false;
|
|
760
|
-
}
|
|
805
|
+
console.log(` [WARN] All download mirrors failed.`);
|
|
806
|
+
return false;
|
|
761
807
|
}
|
|
762
808
|
|
|
763
809
|
/**
|