@sciagent/cli 1.1.50 → 1.1.52
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 +357 -245
- package/package.json +1 -1
- package/scripts/postinstall.js +95 -59
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.52';
|
|
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
|
|
|
@@ -473,6 +532,7 @@ function getBinaryPath() {
|
|
|
473
532
|
/**
|
|
474
533
|
* 从 Release Server 下载二进制文件到临时目录(不替换当前运行的二进制)
|
|
475
534
|
* 下载完成后写入 .pending-update 标记,下次启动时自动应用
|
|
535
|
+
* 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
|
|
476
536
|
* 返回: tempPath 或 null
|
|
477
537
|
*/
|
|
478
538
|
function downloadBinaryToTemp(platform, arch, version) {
|
|
@@ -480,10 +540,6 @@ function downloadBinaryToTemp(platform, arch, version) {
|
|
|
480
540
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
481
541
|
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
482
542
|
|
|
483
|
-
const downloadUrl = (
|
|
484
|
-
`${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`
|
|
485
|
-
);
|
|
486
|
-
|
|
487
543
|
const installDir = getInstallDir(platform);
|
|
488
544
|
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
489
545
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
@@ -495,80 +551,107 @@ function downloadBinaryToTemp(platform, arch, version) {
|
|
|
495
551
|
console.log(` Downloading ${filename} v${version} to temp...`);
|
|
496
552
|
console.log(` Temp: ${tempPath}`);
|
|
497
553
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
|
|
508
|
-
].join('\r\n');
|
|
509
|
-
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
510
|
-
|
|
511
|
-
try {
|
|
512
|
-
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
513
|
-
stdio: 'inherit',
|
|
514
|
-
timeout: 600000
|
|
515
|
-
});
|
|
516
|
-
} finally {
|
|
517
|
-
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
518
|
-
}
|
|
519
|
-
} else {
|
|
520
|
-
execSync(`curl -fsSL -o '${tempPath}' '${downloadUrl}'`, {
|
|
521
|
-
stdio: 'pipe',
|
|
522
|
-
timeout: 600000
|
|
523
|
-
});
|
|
524
|
-
fs.chmodSync(tempPath, 0o755);
|
|
525
|
-
}
|
|
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}...`);
|
|
526
563
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
+
}
|
|
541
593
|
} else {
|
|
542
|
-
|
|
543
|
-
|
|
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);
|
|
544
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) {}
|
|
545
625
|
}
|
|
546
|
-
} catch (e) {
|
|
547
|
-
console.error(` ❌ Download failed: ${e.message}`);
|
|
548
|
-
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
|
|
549
626
|
}
|
|
550
627
|
|
|
628
|
+
console.error(` ❌ All download mirrors failed.`);
|
|
629
|
+
|
|
551
630
|
return null;
|
|
552
631
|
}
|
|
553
632
|
|
|
554
633
|
/**
|
|
555
634
|
* 从 Release Server 下载二进制文件(直接到目标路径,用于首次安装或版本更新)
|
|
556
635
|
* 如果目标文件已存在,先下载到临时目录再替换
|
|
636
|
+
* 支持多源下载:优先 JihuLab CDN,失败回退到自建服务器
|
|
557
637
|
*/
|
|
558
638
|
function downloadBinary(platform, arch, version) {
|
|
559
639
|
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
560
640
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
561
641
|
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
562
642
|
|
|
563
|
-
const downloadUrl = (
|
|
564
|
-
`${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`
|
|
565
|
-
);
|
|
566
|
-
|
|
567
643
|
const installDir = getInstallDir(platform);
|
|
568
644
|
fs.mkdirSync(installDir, { recursive: true });
|
|
569
645
|
const targetPath = path.join(installDir, binName);
|
|
570
646
|
const targetExists = fs.existsSync(targetPath);
|
|
571
647
|
|
|
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
|
+
}));
|
|
654
|
+
|
|
572
655
|
// 如果目标文件已存在,先下载到临时目录再替换
|
|
573
656
|
if (targetExists) {
|
|
574
657
|
const tempDir = path.join(os.tmpdir(), 'sciagent-update');
|
|
@@ -579,149 +662,178 @@ function downloadBinary(platform, arch, version) {
|
|
|
579
662
|
|
|
580
663
|
console.log(` Downloading ${filename} v${version} to temp...`);
|
|
581
664
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
|
|
585
|
-
const scriptContent = [
|
|
586
|
-
'$ProgressPreference = "SilentlyContinue"',
|
|
587
|
-
`$uri = "${downloadUrl}"`,
|
|
588
|
-
`$out = "${tempPath}"`,
|
|
589
|
-
'Write-Host " Downloading from server..."',
|
|
590
|
-
'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
|
|
591
|
-
'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
|
|
592
|
-
].join('\r\n');
|
|
593
|
-
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
594
|
-
|
|
595
|
-
try {
|
|
596
|
-
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
597
|
-
stdio: 'inherit',
|
|
598
|
-
timeout: 600000
|
|
599
|
-
});
|
|
600
|
-
} finally {
|
|
601
|
-
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
602
|
-
}
|
|
603
|
-
} else {
|
|
604
|
-
execSync(`curl -fsSL -o '${tempPath}' '${downloadUrl}'`, {
|
|
605
|
-
stdio: 'pipe',
|
|
606
|
-
timeout: 600000
|
|
607
|
-
});
|
|
608
|
-
fs.chmodSync(tempPath, 0o755);
|
|
609
|
-
}
|
|
665
|
+
for (const source of downloadUrls) {
|
|
666
|
+
console.log(` Trying ${source.name}...`);
|
|
610
667
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
668
|
+
try {
|
|
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');
|
|
616
688
|
|
|
617
|
-
// 尝试替换目标文件
|
|
618
689
|
try {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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`);
|
|
637
712
|
|
|
638
|
-
//
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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);
|
|
645
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;
|
|
646
755
|
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const pendingData = {
|
|
651
|
-
version: version,
|
|
652
|
-
tempPath: tempPath,
|
|
653
|
-
timestamp: new Date().toISOString(),
|
|
654
|
-
platform: process.platform
|
|
655
|
-
};
|
|
656
|
-
fs.writeFileSync(path.join(installDir, '.pending-update'), JSON.stringify(pendingData, null, 2), 'utf8');
|
|
657
|
-
console.log(` 📋 Update will be applied on next restart.`);
|
|
658
|
-
return null;
|
|
756
|
+
} else {
|
|
757
|
+
console.error(` ❌ Download from ${source.name} too small: ${(tempStats.size / 1024).toFixed(0)} KB`);
|
|
758
|
+
try { fs.unlinkSync(tempPath); } catch (e) {}
|
|
659
759
|
}
|
|
660
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) {}
|
|
661
764
|
}
|
|
662
|
-
} catch (e) {
|
|
663
|
-
console.error(` ❌ Download failed: ${e.message}`);
|
|
664
|
-
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e2) {}
|
|
665
765
|
}
|
|
666
766
|
|
|
767
|
+
console.error(` ❌ All download mirrors failed.`);
|
|
667
768
|
return null;
|
|
668
769
|
}
|
|
669
770
|
|
|
670
|
-
//
|
|
771
|
+
// 目标文件不存在(首次安装),直接下载到目标路径,尝试多个源
|
|
671
772
|
console.log(` Downloading ${filename} v${version}...`);
|
|
672
773
|
console.log(` Target: ${targetPath}`);
|
|
673
774
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
// Windows: 使用 PowerShell 下载
|
|
677
|
-
// 将URL和目标路径写入临时PS1脚本文件,避免命令行引号/特殊字符问题
|
|
678
|
-
const tmpScript = path.join(os.tmpdir(), 'sciagent-download.ps1');
|
|
679
|
-
const scriptContent = [
|
|
680
|
-
'$ProgressPreference = "SilentlyContinue"',
|
|
681
|
-
`$uri = "${downloadUrl}"`,
|
|
682
|
-
`$out = "${targetPath}"`,
|
|
683
|
-
'Write-Host " Downloading from server..."',
|
|
684
|
-
'Invoke-WebRequest -Uri $uri -OutFile $out -UseBasicParsing',
|
|
685
|
-
'if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes" } else { Write-Error "File not created"; exit 1 }'
|
|
686
|
-
].join('\r\n');
|
|
687
|
-
fs.writeFileSync(tmpScript, scriptContent, '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
|
-
// Linux/macOS: 使用 curl
|
|
699
|
-
execSync(`curl -fsSL -o '${targetPath}' '${downloadUrl}'`, {
|
|
700
|
-
stdio: 'pipe',
|
|
701
|
-
timeout: 600000
|
|
702
|
-
});
|
|
703
|
-
fs.chmodSync(targetPath, 0o755);
|
|
704
|
-
}
|
|
775
|
+
for (const source of downloadUrls) {
|
|
776
|
+
console.log(` Trying ${source.name}...`);
|
|
705
777
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
+
}
|
|
714
807
|
} else {
|
|
715
|
-
|
|
716
|
-
|
|
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);
|
|
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
|
+
}
|
|
717
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) {}
|
|
718
833
|
}
|
|
719
|
-
} catch (e) {
|
|
720
|
-
console.error(` ❌ Download failed: ${e.message}`);
|
|
721
|
-
// 清理可能的部分下载
|
|
722
|
-
try { if (fs.existsSync(targetPath)) fs.unlinkSync(targetPath); } catch (e2) {}
|
|
723
834
|
}
|
|
724
835
|
|
|
836
|
+
console.error(` ❌ All download mirrors failed.`);
|
|
725
837
|
return null;
|
|
726
838
|
}
|
|
727
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.52';
|
|
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 = [
|
|
@@ -185,7 +195,7 @@ function checkCodebuddyBinaryInstalled() {
|
|
|
185
195
|
return { installed: false };
|
|
186
196
|
}
|
|
187
197
|
|
|
188
|
-
function downloadFile(url, destPath, timeout = 120000) {
|
|
198
|
+
function downloadFile(url, destPath, timeout = 120000, extraHeaders = {}) {
|
|
189
199
|
return new Promise((resolve, reject) => {
|
|
190
200
|
const protocol = url.startsWith('https') ? https : http;
|
|
191
201
|
|
|
@@ -221,7 +231,7 @@ function downloadFile(url, destPath, timeout = 120000) {
|
|
|
221
231
|
}
|
|
222
232
|
|
|
223
233
|
const request = protocol.get(requestUrl, {
|
|
224
|
-
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
234
|
+
headers: { 'User-Agent': 'sciagent-cli/1.0', ...extraHeaders },
|
|
225
235
|
timeout: 60000
|
|
226
236
|
}, (response) => {
|
|
227
237
|
// Handle redirects
|
|
@@ -513,7 +523,7 @@ sys.exit(1)
|
|
|
513
523
|
fs.mkdirSync(binDir, { recursive: true });
|
|
514
524
|
const binaryName = BINARY_NAMES[platform] || 'codebuddy-headless';
|
|
515
525
|
const targetPath = path.join(binDir, binaryName);
|
|
516
|
-
const downloadUrl = `${
|
|
526
|
+
const downloadUrl = `${SELF_HOSTED_URL}/api/releases/download/codebuddy/${platform}/${arch}/latest`;
|
|
517
527
|
|
|
518
528
|
await downloadFile(downloadUrl, targetPath, 600000);
|
|
519
529
|
|
|
@@ -695,10 +705,6 @@ async function downloadFromServer(platform, arch, version) {
|
|
|
695
705
|
const ext = platform === 'win32' ? '.exe' : '';
|
|
696
706
|
const filename = `sciagent-${platform}-${arch}${ext}`;
|
|
697
707
|
|
|
698
|
-
const downloadUrl = `${RELEASE_SERVER_URL}/api/releases/download/${platform}/${arch}/${version}`;
|
|
699
|
-
|
|
700
|
-
console.log(`\n [Server] Downloading ${filename} v${version}...`);
|
|
701
|
-
|
|
702
708
|
// 确定安装目录
|
|
703
709
|
const installDir = getHomeBinDir();
|
|
704
710
|
fs.mkdirSync(installDir, { recursive: true });
|
|
@@ -712,68 +718,98 @@ async function downloadFromServer(platform, arch, version) {
|
|
|
712
718
|
// 清理旧的临时文件
|
|
713
719
|
try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch (e) {}
|
|
714
720
|
|
|
715
|
-
//
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
fs.writeFileSync(tmpScript, scriptContent, 'utf8');
|
|
728
|
-
|
|
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') {
|
|
729
733
|
try {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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 from ' + source.name + '..."',
|
|
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
|
+
// Use WebClient for large files - much faster than Invoke-WebRequest
|
|
746
|
+
scriptLines.push('$wc = New-Object System.Net.WebClient');
|
|
747
|
+
scriptLines.push('$wc.Headers.Add("Authorization", $headers["Authorization"])');
|
|
748
|
+
scriptLines.push('Write-Host " Using JihuLab CDN with auth..."');
|
|
749
|
+
scriptLines.push('$wc.DownloadFile($uri, $out)');
|
|
750
|
+
} else {
|
|
751
|
+
scriptLines.push('$wc = New-Object System.Net.WebClient');
|
|
752
|
+
scriptLines.push('Write-Host " Using ' + source.name + '..."');
|
|
753
|
+
scriptLines.push('$wc.DownloadFile($uri, $out)');
|
|
754
|
+
}
|
|
755
|
+
scriptLines.push('if (Test-Path $out) { $s = (Get-Item $out).Length; Write-Host " Downloaded: $s bytes ($([math]::Round($s/1MB,1)) MB)" } else { Write-Error "File not created"; exit 1 }');
|
|
756
|
+
|
|
757
|
+
fs.writeFileSync(tmpScript, scriptLines.join('\r\n'), 'utf8');
|
|
758
|
+
|
|
759
|
+
try {
|
|
760
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${tmpScript}"`, {
|
|
761
|
+
stdio: 'inherit',
|
|
762
|
+
timeout: 600000
|
|
763
|
+
});
|
|
764
|
+
} finally {
|
|
765
|
+
try { fs.unlinkSync(tmpScript); } catch (e) {}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// 验证下载到临时文件
|
|
769
|
+
if (fs.existsSync(tempPath)) {
|
|
770
|
+
const stats = fs.statSync(tempPath);
|
|
771
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
772
|
+
console.log(` [OK] Downloaded from ${source.name}: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
773
|
+
// 尝试替换
|
|
774
|
+
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
console.log(` [WARN] ${source.name} download validation failed, trying next...`);
|
|
779
|
+
} catch (e) {
|
|
780
|
+
console.log(` [WARN] ${source.name} download failed: ${e.message}, trying next...`);
|
|
736
781
|
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// 通用方式: Node.js https 下载到临时文件
|
|
785
|
+
try {
|
|
786
|
+
// 如果是 JihuLab,需要添加认证头
|
|
787
|
+
const headers = source.auth === 'jihulab'
|
|
788
|
+
? { 'Authorization': `Basic ${Buffer.from(`${JIHULAB_DEPLOY_TOKEN_USER}:${JIHULAB_DEPLOY_TOKEN}`).toString('base64')}` }
|
|
789
|
+
: {};
|
|
790
|
+
await downloadFile(source.url, tempPath, 600000, headers);
|
|
737
791
|
|
|
738
|
-
//
|
|
792
|
+
// 验证下载
|
|
739
793
|
if (fs.existsSync(tempPath)) {
|
|
740
794
|
const stats = fs.statSync(tempPath);
|
|
741
795
|
if (stats.size > 10 * 1024 * 1024) {
|
|
742
|
-
console.log(` [OK] Downloaded
|
|
796
|
+
console.log(` [OK] Downloaded from ${source.name}: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
797
|
+
if (platform !== 'win32') {
|
|
798
|
+
fs.chmodSync(tempPath, 0o755);
|
|
799
|
+
}
|
|
743
800
|
// 尝试替换
|
|
744
801
|
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
745
802
|
}
|
|
746
803
|
}
|
|
747
804
|
|
|
748
|
-
console.log(` [WARN]
|
|
805
|
+
console.log(` [WARN] ${source.name} download validation failed, trying next...`);
|
|
749
806
|
} catch (e) {
|
|
750
|
-
console.log(` [WARN]
|
|
807
|
+
console.log(` [WARN] ${source.name} download failed: ${e.message}, trying next...`);
|
|
751
808
|
}
|
|
752
809
|
}
|
|
753
810
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
await downloadFile(downloadUrl, tempPath, 600000); // 10 分钟超时
|
|
757
|
-
|
|
758
|
-
// 验证下载
|
|
759
|
-
if (fs.existsSync(tempPath)) {
|
|
760
|
-
const stats = fs.statSync(tempPath);
|
|
761
|
-
if (stats.size > 10 * 1024 * 1024) {
|
|
762
|
-
console.log(` [OK] Downloaded to temp: ${tempPath} (${(stats.size / (1024 * 1024)).toFixed(1)} MB)`);
|
|
763
|
-
if (platform !== 'win32') {
|
|
764
|
-
fs.chmodSync(tempPath, 0o755);
|
|
765
|
-
}
|
|
766
|
-
// 尝试替换
|
|
767
|
-
return await applyDownloadedBinary(tempPath, targetPath, installDir, version, platform);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
console.log(` [WARN] Downloaded file validation failed`);
|
|
772
|
-
return false;
|
|
773
|
-
} catch (e) {
|
|
774
|
-
console.log(` [WARN] Server download failed: ${e.message}`);
|
|
775
|
-
return false;
|
|
776
|
-
}
|
|
811
|
+
console.log(` [WARN] All download mirrors failed.`);
|
|
812
|
+
return false;
|
|
777
813
|
}
|
|
778
814
|
|
|
779
815
|
/**
|