@sciagent/cli 1.0.36 → 1.0.38
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 +164 -164
- package/package.json +50 -50
- package/scripts/chmod.js +29 -29
- package/scripts/postinstall.js +484 -479
package/scripts/postinstall.js
CHANGED
|
@@ -1,479 +1,484 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SciAgent CLI postinstall 脚本
|
|
5
|
-
* 1. 自动检测并安装平台特定的二进制包
|
|
6
|
-
* 2. 自动下载 CodeBuddy SDK 二进制文件(使用国内镜像源)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { execSync } = require('child_process');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const https = require('https');
|
|
13
|
-
const http = require('http');
|
|
14
|
-
const os = require('os');
|
|
15
|
-
|
|
16
|
-
// 平台和架构映射
|
|
17
|
-
const PLATFORM_MAP = {
|
|
18
|
-
linux: 'linux',
|
|
19
|
-
darwin: 'darwin',
|
|
20
|
-
win32: 'win32'
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const ARCH_MAP = {
|
|
24
|
-
x64: 'x64',
|
|
25
|
-
arm64: 'arm64',
|
|
26
|
-
amd64: 'x64'
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// 当前版本号 - 每次发布时同步更新
|
|
30
|
-
const CURRENT_VERSION = '1.0.
|
|
31
|
-
|
|
32
|
-
// PyPI 镜像源列表(国内优先)
|
|
33
|
-
const PYPI_MIRRORS = [
|
|
34
|
-
'https://mirrors.cloud.tencent.com/pypi/simple', // 腾讯云
|
|
35
|
-
'https://mirrors.aliyun.com/pypi/simple', // 阿里云
|
|
36
|
-
'https://pypi.tuna.tsinghua.edu.cn/simple', // 清华
|
|
37
|
-
'https://pypi.org/simple' // 官方(备用)
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
// PyPI 下载URL的镜像(直接下载文件)
|
|
41
|
-
const PYPI_DOWNLOAD_MIRRORS = [
|
|
42
|
-
'https://mirrors.cloud.tencent.com/pypi/packages', // 腾讯云
|
|
43
|
-
'https://mirrors.aliyun.com/pypi/packages', // 阿里云
|
|
44
|
-
'https://pypi.tuna.tsinghua.edu.cn/packages', // 清华
|
|
45
|
-
'https://files.pythonhosted.org/packages' // 官方(备用)
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
// PyPI wheel 平台标识映射
|
|
49
|
-
const PYPI_PLATFORM_MAP = {
|
|
50
|
-
'win32-x64': 'win_amd64',
|
|
51
|
-
'darwin-arm64': 'macosx_11_0_arm64',
|
|
52
|
-
'darwin-x64': 'macosx_10_12_x86_64',
|
|
53
|
-
'linux-x64': 'manylinux_2_17_x86_64',
|
|
54
|
-
'linux-arm64': 'manylinux_2_17_aarch64'
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// 二进制文件名
|
|
58
|
-
const BINARY_NAMES = {
|
|
59
|
-
win32: 'codebuddy-headless.exe',
|
|
60
|
-
darwin: 'codebuddy-headless',
|
|
61
|
-
linux: 'codebuddy-headless'
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
function getCodebuddyBinDir() {
|
|
65
|
-
const platform = process.platform;
|
|
66
|
-
if (platform === 'win32') {
|
|
67
|
-
const base = process.env.LOCALAPPDATA || os.homedir();
|
|
68
|
-
return path.join(base, 'sciagent', 'bin');
|
|
69
|
-
} else {
|
|
70
|
-
return path.join(os.homedir(), '.sciagent', 'bin');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function checkBinaryInstalled(platform, arch) {
|
|
75
|
-
const packageName = `@sciagent/cli-${platform}-${arch}`;
|
|
76
|
-
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const packagePath = require.resolve(`${packageName}/bin/${binName}`);
|
|
80
|
-
return { installed: true, path: packagePath };
|
|
81
|
-
} catch (e) {
|
|
82
|
-
return { installed: false };
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function checkCodebuddyBinaryInstalled() {
|
|
87
|
-
const binDir = getCodebuddyBinDir();
|
|
88
|
-
const binaryName = BINARY_NAMES[process.platform] || 'codebuddy-headless';
|
|
89
|
-
const binaryPath = path.join(binDir, binaryName);
|
|
90
|
-
|
|
91
|
-
if (fs.existsSync(binaryPath)) {
|
|
92
|
-
const stats = fs.statSync(binaryPath);
|
|
93
|
-
// 检查文件大小是否合理(至少10MB,防止损坏的文件)
|
|
94
|
-
if (stats.size > 10 * 1024 * 1024) {
|
|
95
|
-
return { installed: true, path: binaryPath, size: stats.size };
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return { installed: false };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function downloadFile(url, destPath, timeout = 120000) {
|
|
102
|
-
return new Promise((resolve, reject) => {
|
|
103
|
-
const protocol = url.startsWith('https') ? https : http;
|
|
104
|
-
const file = fs.createWriteStream(destPath);
|
|
105
|
-
let completed = false;
|
|
106
|
-
|
|
107
|
-
const timer = setTimeout(() => {
|
|
108
|
-
if (!completed) {
|
|
109
|
-
file.close();
|
|
110
|
-
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
111
|
-
reject(new Error('Download timeout'));
|
|
112
|
-
}
|
|
113
|
-
}, timeout);
|
|
114
|
-
|
|
115
|
-
const request = protocol.get(url, {
|
|
116
|
-
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
117
|
-
timeout: 30000
|
|
118
|
-
}, (response) => {
|
|
119
|
-
// Handle redirects
|
|
120
|
-
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
121
|
-
file.close();
|
|
122
|
-
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
123
|
-
clearTimeout(timer);
|
|
124
|
-
downloadFile(response.headers.location, destPath, timeout).then(resolve).catch(reject);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (response.statusCode !== 200) {
|
|
129
|
-
file.close();
|
|
130
|
-
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
131
|
-
clearTimeout(timer);
|
|
132
|
-
reject(new Error(`HTTP ${response.statusCode}`));
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const totalSize = parseInt(response.headers['content-length'], 10);
|
|
137
|
-
let downloadedSize = 0;
|
|
138
|
-
|
|
139
|
-
response.on('data', (chunk) => {
|
|
140
|
-
downloadedSize += chunk.length;
|
|
141
|
-
if (totalSize) {
|
|
142
|
-
const percent = Math.floor((downloadedSize / totalSize) * 100);
|
|
143
|
-
const mb = (downloadedSize / (1024 * 1024)).toFixed(1);
|
|
144
|
-
const totalMb = (totalSize / (1024 * 1024)).toFixed(1);
|
|
145
|
-
process.stdout.write(`\r 下载进度: ${percent}% (${mb}/${totalMb} MB)`);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
response.pipe(file);
|
|
150
|
-
|
|
151
|
-
file.on('finish', () => {
|
|
152
|
-
completed = true;
|
|
153
|
-
clearTimeout(timer);
|
|
154
|
-
file.close();
|
|
155
|
-
console.log(); // New line after progress
|
|
156
|
-
resolve();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
file.on('error', (err) => {
|
|
160
|
-
completed = true;
|
|
161
|
-
clearTimeout(timer);
|
|
162
|
-
file.close();
|
|
163
|
-
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
164
|
-
reject(err);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
request.on('error', (err) => {
|
|
169
|
-
completed = true;
|
|
170
|
-
clearTimeout(timer);
|
|
171
|
-
file.close();
|
|
172
|
-
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
173
|
-
reject(err);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
request.on('timeout', () => {
|
|
177
|
-
request.destroy();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function getLatestSdkVersion() {
|
|
183
|
-
// 尝试从多个镜像获取版本信息
|
|
184
|
-
const mirrors = [
|
|
185
|
-
'https://mirrors.cloud.tencent.com/pypi/pypi/codebuddy-agent-sdk/json',
|
|
186
|
-
'https://mirrors.aliyun.com/pypi/pypi/codebuddy-agent-sdk/json',
|
|
187
|
-
'https://pypi.tuna.tsinghua.edu.cn/pypi/codebuddy-agent-sdk/json',
|
|
188
|
-
'https://pypi.org/pypi/codebuddy-agent-sdk/json'
|
|
189
|
-
];
|
|
190
|
-
|
|
191
|
-
for (const url of mirrors) {
|
|
192
|
-
try {
|
|
193
|
-
const version = await new Promise((resolve, reject) => {
|
|
194
|
-
const protocol = url.startsWith('https') ? https : http;
|
|
195
|
-
protocol.get(url, {
|
|
196
|
-
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
197
|
-
timeout: 15000
|
|
198
|
-
}, (response) => {
|
|
199
|
-
// Handle redirects
|
|
200
|
-
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
201
|
-
protocol.get(response.headers.location, {
|
|
202
|
-
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
203
|
-
timeout: 15000
|
|
204
|
-
}, (res2) => {
|
|
205
|
-
let data = '';
|
|
206
|
-
res2.on('data', (chunk) => { data += chunk; });
|
|
207
|
-
res2.on('end', () => {
|
|
208
|
-
try {
|
|
209
|
-
const json = JSON.parse(data);
|
|
210
|
-
resolve(json.info.version);
|
|
211
|
-
} catch (e) {
|
|
212
|
-
reject(new Error('Parse error'));
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
}).on('error', reject);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let data = '';
|
|
220
|
-
response.on('data', (chunk) => { data += chunk; });
|
|
221
|
-
response.on('end', () => {
|
|
222
|
-
try {
|
|
223
|
-
const json = JSON.parse(data);
|
|
224
|
-
resolve(json.info.version);
|
|
225
|
-
} catch (e) {
|
|
226
|
-
reject(new Error('Parse error'));
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
}).on('error', reject);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
if (version) return version;
|
|
233
|
-
} catch (e) {
|
|
234
|
-
// 继续尝试下一个镜像
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
throw new Error('无法获取SDK版本信息,请检查网络连接');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async function installCodebuddySdk() {
|
|
242
|
-
const existing = checkCodebuddyBinaryInstalled();
|
|
243
|
-
if (existing.installed) {
|
|
244
|
-
console.log(`✅ CodeBuddy SDK 已安装: ${existing.path}`);
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const platform = process.platform;
|
|
249
|
-
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
250
|
-
const platformKey = `${platform}-${arch}`;
|
|
251
|
-
const platformTag = PYPI_PLATFORM_MAP[platformKey];
|
|
252
|
-
|
|
253
|
-
if (!platformTag) {
|
|
254
|
-
console.log(`⚠️ 不支持的平台: ${platformKey},跳过 CodeBuddy SDK 安装`);
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
console.log('');
|
|
259
|
-
console.log('📦 正在安装 CodeBuddy SDK...');
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
// 获取最新版本
|
|
263
|
-
console.log(' 正在获取版本信息...');
|
|
264
|
-
const version = await getLatestSdkVersion();
|
|
265
|
-
console.log(` 版本: ${version}`);
|
|
266
|
-
console.log(` 平台: ${platformTag}`);
|
|
267
|
-
|
|
268
|
-
// 构建 wheel 文件名
|
|
269
|
-
const wheelFilename = `codebuddy_agent_sdk-${version}-py3-none-${platformTag}.whl`;
|
|
270
|
-
|
|
271
|
-
// 创建临时目录
|
|
272
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sciagent-sdk-'));
|
|
273
|
-
const wheelPath = path.join(tmpDir, wheelFilename);
|
|
274
|
-
|
|
275
|
-
// 尝试从多个镜像下载
|
|
276
|
-
let downloaded = false;
|
|
277
|
-
const binaryName = BINARY_NAMES[platform] || 'codebuddy-headless';
|
|
278
|
-
const binDir = getCodebuddyBinDir();
|
|
279
|
-
const targetPath = path.join(binDir, binaryName);
|
|
280
|
-
|
|
281
|
-
for (const mirror of PYPI_DOWNLOAD_MIRRORS) {
|
|
282
|
-
const downloadUrl = `${mirror}/source/c/codebuddy-agent-sdk/${wheelFilename}`;
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
console.log(` 尝试下载: ${mirror.split('/')[2]}...`);
|
|
286
|
-
await downloadFile(downloadUrl, wheelPath);
|
|
287
|
-
downloaded = true;
|
|
288
|
-
console.log(' ✅ 下载成功');
|
|
289
|
-
break;
|
|
290
|
-
} catch (e) {
|
|
291
|
-
console.log(` ❌ ${mirror.split('/')[2]}: ${e.message}`);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (!downloaded) {
|
|
297
|
-
console.error('❌ 所有镜像源下载失败');
|
|
298
|
-
console.error(' 请检查网络连接,或稍后运行 "sciagent install-sdk" 手动安装');
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// 提取二进制文件
|
|
303
|
-
console.log(' 正在提取二进制文件...');
|
|
304
|
-
fs.mkdirSync(binDir, { recursive: true });
|
|
305
|
-
|
|
306
|
-
// 使用 Python 解压 wheel 文件
|
|
307
|
-
const extractScript = `
|
|
308
|
-
import zipfile, sys, os
|
|
309
|
-
wheel_path = sys.argv[1]
|
|
310
|
-
target_dir = sys.argv[2]
|
|
311
|
-
binary_name = sys.argv[3]
|
|
312
|
-
|
|
313
|
-
with zipfile.ZipFile(wheel_path, 'r') as zf:
|
|
314
|
-
for name in zf.namelist():
|
|
315
|
-
if binary_name in name and '/bin/' in name:
|
|
316
|
-
with zf.open(name) as src:
|
|
317
|
-
target_path = os.path.join(target_dir, binary_name)
|
|
318
|
-
with open(target_path, 'wb') as dst:
|
|
319
|
-
dst.write(src.read())
|
|
320
|
-
if sys.platform != 'win32':
|
|
321
|
-
os.chmod(target_path, 0o755)
|
|
322
|
-
print(f'Extracted: {target_path}')
|
|
323
|
-
sys.exit(0)
|
|
324
|
-
print(f'Error: {binary_name} not found in wheel')
|
|
325
|
-
sys.exit(1)
|
|
326
|
-
`;
|
|
327
|
-
|
|
328
|
-
const scriptPath = path.join(tmpDir, 'extract.py');
|
|
329
|
-
fs.writeFileSync(scriptPath, extractScript);
|
|
330
|
-
|
|
331
|
-
try {
|
|
332
|
-
execSync(`python3 "${scriptPath}" "${wheelPath}" "${binDir}" "${binaryName}"`, {
|
|
333
|
-
stdio: 'inherit',
|
|
334
|
-
timeout: 120000
|
|
335
|
-
});
|
|
336
|
-
} catch (e) {
|
|
337
|
-
// 如果python3失败,尝试python
|
|
338
|
-
execSync(`python "${scriptPath}" "${wheelPath}" "${binDir}" "${binaryName}"`, {
|
|
339
|
-
stdio: 'inherit',
|
|
340
|
-
timeout: 120000
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// 验证安装
|
|
345
|
-
if (fs.existsSync(targetPath)) {
|
|
346
|
-
const stats = fs.statSync(targetPath);
|
|
347
|
-
const sizeMb = (stats.size / (1024 * 1024)).toFixed(1);
|
|
348
|
-
console.log(`✅ CodeBuddy SDK 安装成功!`);
|
|
349
|
-
console.log(` 路径: ${targetPath}`);
|
|
350
|
-
console.log(` 大小: ${sizeMb} MB`);
|
|
351
|
-
|
|
352
|
-
// 清理临时文件
|
|
353
|
-
try {
|
|
354
|
-
fs.unlinkSync(wheelPath);
|
|
355
|
-
fs.unlinkSync(scriptPath);
|
|
356
|
-
fs.rmdirSync(tmpDir);
|
|
357
|
-
} catch (e) {}
|
|
358
|
-
|
|
359
|
-
return true;
|
|
360
|
-
} else {
|
|
361
|
-
console.error('❌ 安装验证失败');
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
} catch (e) {
|
|
366
|
-
console.error(`❌ CodeBuddy SDK 安装失败: ${e.message}`);
|
|
367
|
-
console.error(' 你可以稍后运行 "sciagent install-sdk" 手动安装');
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function installBinaryPackage(platform, arch) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
installArgs = ['install', packageName];
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
console.log(
|
|
412
|
-
console.log(
|
|
413
|
-
console.log(
|
|
414
|
-
console.log('');
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
console.error('
|
|
423
|
-
process.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
console.error('
|
|
445
|
-
console.error(
|
|
446
|
-
console.error('');
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
console.log('
|
|
469
|
-
console.log('
|
|
470
|
-
console.log('');
|
|
471
|
-
console.log('
|
|
472
|
-
console.log('');
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
console.
|
|
478
|
-
|
|
479
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SciAgent CLI postinstall 脚本
|
|
5
|
+
* 1. 自动检测并安装平台特定的二进制包
|
|
6
|
+
* 2. 自动下载 CodeBuddy SDK 二进制文件(使用国内镜像源)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const https = require('https');
|
|
13
|
+
const http = require('http');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
|
|
16
|
+
// 平台和架构映射
|
|
17
|
+
const PLATFORM_MAP = {
|
|
18
|
+
linux: 'linux',
|
|
19
|
+
darwin: 'darwin',
|
|
20
|
+
win32: 'win32'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const ARCH_MAP = {
|
|
24
|
+
x64: 'x64',
|
|
25
|
+
arm64: 'arm64',
|
|
26
|
+
amd64: 'x64'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// 当前版本号 - 每次发布时同步更新
|
|
30
|
+
const CURRENT_VERSION = '1.0.38';
|
|
31
|
+
|
|
32
|
+
// PyPI 镜像源列表(国内优先)
|
|
33
|
+
const PYPI_MIRRORS = [
|
|
34
|
+
'https://mirrors.cloud.tencent.com/pypi/simple', // 腾讯云
|
|
35
|
+
'https://mirrors.aliyun.com/pypi/simple', // 阿里云
|
|
36
|
+
'https://pypi.tuna.tsinghua.edu.cn/simple', // 清华
|
|
37
|
+
'https://pypi.org/simple' // 官方(备用)
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// PyPI 下载URL的镜像(直接下载文件)
|
|
41
|
+
const PYPI_DOWNLOAD_MIRRORS = [
|
|
42
|
+
'https://mirrors.cloud.tencent.com/pypi/packages', // 腾讯云
|
|
43
|
+
'https://mirrors.aliyun.com/pypi/packages', // 阿里云
|
|
44
|
+
'https://pypi.tuna.tsinghua.edu.cn/packages', // 清华
|
|
45
|
+
'https://files.pythonhosted.org/packages' // 官方(备用)
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// PyPI wheel 平台标识映射
|
|
49
|
+
const PYPI_PLATFORM_MAP = {
|
|
50
|
+
'win32-x64': 'win_amd64',
|
|
51
|
+
'darwin-arm64': 'macosx_11_0_arm64',
|
|
52
|
+
'darwin-x64': 'macosx_10_12_x86_64',
|
|
53
|
+
'linux-x64': 'manylinux_2_17_x86_64',
|
|
54
|
+
'linux-arm64': 'manylinux_2_17_aarch64'
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// 二进制文件名
|
|
58
|
+
const BINARY_NAMES = {
|
|
59
|
+
win32: 'codebuddy-headless.exe',
|
|
60
|
+
darwin: 'codebuddy-headless',
|
|
61
|
+
linux: 'codebuddy-headless'
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function getCodebuddyBinDir() {
|
|
65
|
+
const platform = process.platform;
|
|
66
|
+
if (platform === 'win32') {
|
|
67
|
+
const base = process.env.LOCALAPPDATA || os.homedir();
|
|
68
|
+
return path.join(base, 'sciagent', 'bin');
|
|
69
|
+
} else {
|
|
70
|
+
return path.join(os.homedir(), '.sciagent', 'bin');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function checkBinaryInstalled(platform, arch) {
|
|
75
|
+
const packageName = `@sciagent/cli-${platform}-${arch}`;
|
|
76
|
+
const binName = platform === 'win32' ? 'sciagent.exe' : 'sciagent';
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const packagePath = require.resolve(`${packageName}/bin/${binName}`);
|
|
80
|
+
return { installed: true, path: packagePath };
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return { installed: false };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function checkCodebuddyBinaryInstalled() {
|
|
87
|
+
const binDir = getCodebuddyBinDir();
|
|
88
|
+
const binaryName = BINARY_NAMES[process.platform] || 'codebuddy-headless';
|
|
89
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
90
|
+
|
|
91
|
+
if (fs.existsSync(binaryPath)) {
|
|
92
|
+
const stats = fs.statSync(binaryPath);
|
|
93
|
+
// 检查文件大小是否合理(至少10MB,防止损坏的文件)
|
|
94
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
95
|
+
return { installed: true, path: binaryPath, size: stats.size };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { installed: false };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function downloadFile(url, destPath, timeout = 120000) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
104
|
+
const file = fs.createWriteStream(destPath);
|
|
105
|
+
let completed = false;
|
|
106
|
+
|
|
107
|
+
const timer = setTimeout(() => {
|
|
108
|
+
if (!completed) {
|
|
109
|
+
file.close();
|
|
110
|
+
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
111
|
+
reject(new Error('Download timeout'));
|
|
112
|
+
}
|
|
113
|
+
}, timeout);
|
|
114
|
+
|
|
115
|
+
const request = protocol.get(url, {
|
|
116
|
+
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
117
|
+
timeout: 30000
|
|
118
|
+
}, (response) => {
|
|
119
|
+
// Handle redirects
|
|
120
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
121
|
+
file.close();
|
|
122
|
+
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
downloadFile(response.headers.location, destPath, timeout).then(resolve).catch(reject);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (response.statusCode !== 200) {
|
|
129
|
+
file.close();
|
|
130
|
+
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
reject(new Error(`HTTP ${response.statusCode}`));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const totalSize = parseInt(response.headers['content-length'], 10);
|
|
137
|
+
let downloadedSize = 0;
|
|
138
|
+
|
|
139
|
+
response.on('data', (chunk) => {
|
|
140
|
+
downloadedSize += chunk.length;
|
|
141
|
+
if (totalSize) {
|
|
142
|
+
const percent = Math.floor((downloadedSize / totalSize) * 100);
|
|
143
|
+
const mb = (downloadedSize / (1024 * 1024)).toFixed(1);
|
|
144
|
+
const totalMb = (totalSize / (1024 * 1024)).toFixed(1);
|
|
145
|
+
process.stdout.write(`\r 下载进度: ${percent}% (${mb}/${totalMb} MB)`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
response.pipe(file);
|
|
150
|
+
|
|
151
|
+
file.on('finish', () => {
|
|
152
|
+
completed = true;
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
file.close();
|
|
155
|
+
console.log(); // New line after progress
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
file.on('error', (err) => {
|
|
160
|
+
completed = true;
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
file.close();
|
|
163
|
+
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
164
|
+
reject(err);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
request.on('error', (err) => {
|
|
169
|
+
completed = true;
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
file.close();
|
|
172
|
+
try { fs.unlinkSync(destPath); } catch (e) {}
|
|
173
|
+
reject(err);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
request.on('timeout', () => {
|
|
177
|
+
request.destroy();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function getLatestSdkVersion() {
|
|
183
|
+
// 尝试从多个镜像获取版本信息
|
|
184
|
+
const mirrors = [
|
|
185
|
+
'https://mirrors.cloud.tencent.com/pypi/pypi/codebuddy-agent-sdk/json',
|
|
186
|
+
'https://mirrors.aliyun.com/pypi/pypi/codebuddy-agent-sdk/json',
|
|
187
|
+
'https://pypi.tuna.tsinghua.edu.cn/pypi/codebuddy-agent-sdk/json',
|
|
188
|
+
'https://pypi.org/pypi/codebuddy-agent-sdk/json'
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
for (const url of mirrors) {
|
|
192
|
+
try {
|
|
193
|
+
const version = await new Promise((resolve, reject) => {
|
|
194
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
195
|
+
protocol.get(url, {
|
|
196
|
+
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
197
|
+
timeout: 15000
|
|
198
|
+
}, (response) => {
|
|
199
|
+
// Handle redirects
|
|
200
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
201
|
+
protocol.get(response.headers.location, {
|
|
202
|
+
headers: { 'User-Agent': 'sciagent-cli/1.0' },
|
|
203
|
+
timeout: 15000
|
|
204
|
+
}, (res2) => {
|
|
205
|
+
let data = '';
|
|
206
|
+
res2.on('data', (chunk) => { data += chunk; });
|
|
207
|
+
res2.on('end', () => {
|
|
208
|
+
try {
|
|
209
|
+
const json = JSON.parse(data);
|
|
210
|
+
resolve(json.info.version);
|
|
211
|
+
} catch (e) {
|
|
212
|
+
reject(new Error('Parse error'));
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}).on('error', reject);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let data = '';
|
|
220
|
+
response.on('data', (chunk) => { data += chunk; });
|
|
221
|
+
response.on('end', () => {
|
|
222
|
+
try {
|
|
223
|
+
const json = JSON.parse(data);
|
|
224
|
+
resolve(json.info.version);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
reject(new Error('Parse error'));
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}).on('error', reject);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (version) return version;
|
|
233
|
+
} catch (e) {
|
|
234
|
+
// 继续尝试下一个镜像
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
throw new Error('无法获取SDK版本信息,请检查网络连接');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function installCodebuddySdk() {
|
|
242
|
+
const existing = checkCodebuddyBinaryInstalled();
|
|
243
|
+
if (existing.installed) {
|
|
244
|
+
console.log(`✅ CodeBuddy SDK 已安装: ${existing.path}`);
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const platform = process.platform;
|
|
249
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
250
|
+
const platformKey = `${platform}-${arch}`;
|
|
251
|
+
const platformTag = PYPI_PLATFORM_MAP[platformKey];
|
|
252
|
+
|
|
253
|
+
if (!platformTag) {
|
|
254
|
+
console.log(`⚠️ 不支持的平台: ${platformKey},跳过 CodeBuddy SDK 安装`);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log('📦 正在安装 CodeBuddy SDK...');
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// 获取最新版本
|
|
263
|
+
console.log(' 正在获取版本信息...');
|
|
264
|
+
const version = await getLatestSdkVersion();
|
|
265
|
+
console.log(` 版本: ${version}`);
|
|
266
|
+
console.log(` 平台: ${platformTag}`);
|
|
267
|
+
|
|
268
|
+
// 构建 wheel 文件名
|
|
269
|
+
const wheelFilename = `codebuddy_agent_sdk-${version}-py3-none-${platformTag}.whl`;
|
|
270
|
+
|
|
271
|
+
// 创建临时目录
|
|
272
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sciagent-sdk-'));
|
|
273
|
+
const wheelPath = path.join(tmpDir, wheelFilename);
|
|
274
|
+
|
|
275
|
+
// 尝试从多个镜像下载
|
|
276
|
+
let downloaded = false;
|
|
277
|
+
const binaryName = BINARY_NAMES[platform] || 'codebuddy-headless';
|
|
278
|
+
const binDir = getCodebuddyBinDir();
|
|
279
|
+
const targetPath = path.join(binDir, binaryName);
|
|
280
|
+
|
|
281
|
+
for (const mirror of PYPI_DOWNLOAD_MIRRORS) {
|
|
282
|
+
const downloadUrl = `${mirror}/source/c/codebuddy-agent-sdk/${wheelFilename}`;
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
console.log(` 尝试下载: ${mirror.split('/')[2]}...`);
|
|
286
|
+
await downloadFile(downloadUrl, wheelPath);
|
|
287
|
+
downloaded = true;
|
|
288
|
+
console.log(' ✅ 下载成功');
|
|
289
|
+
break;
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.log(` ❌ ${mirror.split('/')[2]}: ${e.message}`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!downloaded) {
|
|
297
|
+
console.error('❌ 所有镜像源下载失败');
|
|
298
|
+
console.error(' 请检查网络连接,或稍后运行 "sciagent install-sdk" 手动安装');
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 提取二进制文件
|
|
303
|
+
console.log(' 正在提取二进制文件...');
|
|
304
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
305
|
+
|
|
306
|
+
// 使用 Python 解压 wheel 文件
|
|
307
|
+
const extractScript = `
|
|
308
|
+
import zipfile, sys, os
|
|
309
|
+
wheel_path = sys.argv[1]
|
|
310
|
+
target_dir = sys.argv[2]
|
|
311
|
+
binary_name = sys.argv[3]
|
|
312
|
+
|
|
313
|
+
with zipfile.ZipFile(wheel_path, 'r') as zf:
|
|
314
|
+
for name in zf.namelist():
|
|
315
|
+
if binary_name in name and '/bin/' in name:
|
|
316
|
+
with zf.open(name) as src:
|
|
317
|
+
target_path = os.path.join(target_dir, binary_name)
|
|
318
|
+
with open(target_path, 'wb') as dst:
|
|
319
|
+
dst.write(src.read())
|
|
320
|
+
if sys.platform != 'win32':
|
|
321
|
+
os.chmod(target_path, 0o755)
|
|
322
|
+
print(f'Extracted: {target_path}')
|
|
323
|
+
sys.exit(0)
|
|
324
|
+
print(f'Error: {binary_name} not found in wheel')
|
|
325
|
+
sys.exit(1)
|
|
326
|
+
`;
|
|
327
|
+
|
|
328
|
+
const scriptPath = path.join(tmpDir, 'extract.py');
|
|
329
|
+
fs.writeFileSync(scriptPath, extractScript);
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
execSync(`python3 "${scriptPath}" "${wheelPath}" "${binDir}" "${binaryName}"`, {
|
|
333
|
+
stdio: 'inherit',
|
|
334
|
+
timeout: 120000
|
|
335
|
+
});
|
|
336
|
+
} catch (e) {
|
|
337
|
+
// 如果python3失败,尝试python
|
|
338
|
+
execSync(`python "${scriptPath}" "${wheelPath}" "${binDir}" "${binaryName}"`, {
|
|
339
|
+
stdio: 'inherit',
|
|
340
|
+
timeout: 120000
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 验证安装
|
|
345
|
+
if (fs.existsSync(targetPath)) {
|
|
346
|
+
const stats = fs.statSync(targetPath);
|
|
347
|
+
const sizeMb = (stats.size / (1024 * 1024)).toFixed(1);
|
|
348
|
+
console.log(`✅ CodeBuddy SDK 安装成功!`);
|
|
349
|
+
console.log(` 路径: ${targetPath}`);
|
|
350
|
+
console.log(` 大小: ${sizeMb} MB`);
|
|
351
|
+
|
|
352
|
+
// 清理临时文件
|
|
353
|
+
try {
|
|
354
|
+
fs.unlinkSync(wheelPath);
|
|
355
|
+
fs.unlinkSync(scriptPath);
|
|
356
|
+
fs.rmdirSync(tmpDir);
|
|
357
|
+
} catch (e) {}
|
|
358
|
+
|
|
359
|
+
return true;
|
|
360
|
+
} else {
|
|
361
|
+
console.error('❌ 安装验证失败');
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
} catch (e) {
|
|
366
|
+
console.error(`❌ CodeBuddy SDK 安装失败: ${e.message}`);
|
|
367
|
+
console.error(' 你可以稍后运行 "sciagent install-sdk" 手动安装');
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function installBinaryPackage(platform, arch) {
|
|
373
|
+
// 版本回退列表:先尝试当前版本,再尝试已知存在的版本
|
|
374
|
+
const FALLBACK_VERSIONS = [CURRENT_VERSION, '1.0.36', '1.0.33'];
|
|
375
|
+
|
|
376
|
+
let npmCmd = 'npm';
|
|
377
|
+
const isGlobal = process.env.npm_config_global === 'true' ||
|
|
378
|
+
process.env.npm_lifecycle_event === 'postinstall';
|
|
379
|
+
|
|
380
|
+
for (const version of FALLBACK_VERSIONS) {
|
|
381
|
+
const packageName = `@sciagent/cli-${platform}-${arch}@${version}`;
|
|
382
|
+
console.log(`\n Installing platform binary: ${packageName}`);
|
|
383
|
+
console.log(' This may take a moment...\n');
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
let installArgs = ['install', '-g', packageName];
|
|
387
|
+
|
|
388
|
+
if (!isGlobal) {
|
|
389
|
+
installArgs = ['install', packageName];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
execSync(`${npmCmd} ${installArgs.join(' ')}`, {
|
|
393
|
+
stdio: 'inherit',
|
|
394
|
+
timeout: 300000
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
return true;
|
|
398
|
+
} catch (e) {
|
|
399
|
+
console.log(` ⚠️ ${packageName} not available, trying next version...`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
console.error(`\n ❌ Failed to install platform binary for ${platform}-${arch}`);
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function main() {
|
|
408
|
+
const platform = PLATFORM_MAP[process.platform];
|
|
409
|
+
const arch = ARCH_MAP[process.arch];
|
|
410
|
+
|
|
411
|
+
console.log('');
|
|
412
|
+
console.log('╔══════════════════════════════════════════════════════════╗');
|
|
413
|
+
console.log('║ SciAgent CLI - Post Install Setup ║');
|
|
414
|
+
console.log('╚══════════════════════════════════════════════════════════╝');
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log(` Platform: ${platform || process.platform}`);
|
|
417
|
+
console.log(` Architecture: ${arch || process.arch}`);
|
|
418
|
+
console.log(` Node.js: ${process.version}`);
|
|
419
|
+
console.log('');
|
|
420
|
+
|
|
421
|
+
if (!platform || !arch) {
|
|
422
|
+
console.error('❌ Unsupported platform or architecture');
|
|
423
|
+
console.error(` Platform: ${process.platform}`);
|
|
424
|
+
console.error(` Architecture: ${process.arch}`);
|
|
425
|
+
console.error('');
|
|
426
|
+
console.error(' Supported platforms: linux, darwin, win32');
|
|
427
|
+
console.error(' Supported architectures: x64, arm64');
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 检查 SciAgent CLI 二进制
|
|
432
|
+
const packageName = `@sciagent/cli-${platform}-${arch}`;
|
|
433
|
+
const result = checkBinaryInstalled(platform, arch);
|
|
434
|
+
|
|
435
|
+
if (result.installed) {
|
|
436
|
+
console.log(`✅ Platform binary already installed: ${packageName}`);
|
|
437
|
+
console.log(` Path: ${result.path}`);
|
|
438
|
+
} else {
|
|
439
|
+
console.log(`⚠️ Platform binary not found: ${packageName}`);
|
|
440
|
+
|
|
441
|
+
const success = installBinaryPackage(platform, arch);
|
|
442
|
+
|
|
443
|
+
if (!success) {
|
|
444
|
+
console.error('');
|
|
445
|
+
console.error('╔══════════════════════════════════════════════════════════╗');
|
|
446
|
+
console.error('║ Manual Installation Required ║');
|
|
447
|
+
console.error('╚══════════════════════════════════════════════════════════╝');
|
|
448
|
+
console.error('');
|
|
449
|
+
console.error(' Please run this command manually:');
|
|
450
|
+
console.error(` npm install -g ${packageName}@${CURRENT_VERSION}`);
|
|
451
|
+
console.error('');
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const verifyResult = checkBinaryInstalled(platform, arch);
|
|
456
|
+
if (verifyResult.installed) {
|
|
457
|
+
console.log(`\n✅ Platform binary installed successfully: ${packageName}`);
|
|
458
|
+
} else {
|
|
459
|
+
console.error(`\n❌ Installation verification failed. Please install manually:`);
|
|
460
|
+
console.error(` npm install -g ${packageName}@${CURRENT_VERSION}`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// 安装 CodeBuddy SDK(使用国内镜像)
|
|
466
|
+
await installCodebuddySdk();
|
|
467
|
+
|
|
468
|
+
console.log('');
|
|
469
|
+
console.log('Usage:');
|
|
470
|
+
console.log(' sciagent # Start with default ports');
|
|
471
|
+
console.log(' sciagent --port 8080 # Custom proxy port');
|
|
472
|
+
console.log(' sciagent --no-browser # Don\'t open browser');
|
|
473
|
+
console.log(' sciagent install-sdk # Install CodeBuddy SDK manually');
|
|
474
|
+
console.log(' sciagent --help # Show help');
|
|
475
|
+
console.log('');
|
|
476
|
+
console.log('Documentation: https://gitee.com/garva/research-agent');
|
|
477
|
+
console.log('');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// 运行主函数
|
|
481
|
+
main().catch(err => {
|
|
482
|
+
console.error('Post install error:', err.message);
|
|
483
|
+
process.exit(1);
|
|
484
|
+
});
|