@0xobelisk/sui-cli 1.2.0-pre.42 → 1.2.0-pre.44

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.
@@ -1,12 +1,10 @@
1
1
  import type { CommandModule } from 'yargs';
2
2
  import chalk from 'chalk';
3
3
  import { spawn } from 'child_process';
4
+ import { existsSync } from 'fs';
5
+ import { homedir } from 'os';
6
+ import { join } from 'path';
4
7
  import Table from 'cli-table3';
5
- import inquirer from 'inquirer';
6
- import * as cliProgress from 'cli-progress';
7
- import * as fs from 'fs';
8
- import * as path from 'path';
9
- import * as os from 'os';
10
8
 
11
9
  // Check result type
12
10
  interface CheckResult {
@@ -16,90 +14,6 @@ interface CheckResult {
16
14
  fixSuggestion?: string;
17
15
  }
18
16
 
19
- // GitHub Release type
20
- interface GitHubRelease {
21
- tag_name: string;
22
- name: string;
23
- assets: Array<{
24
- name: string;
25
- browser_download_url: string;
26
- }>;
27
- published_at: string;
28
- }
29
-
30
- // Tool configuration
31
- interface ToolConfig {
32
- name: string;
33
- repo: string;
34
- binaryName: string;
35
- installDir: string;
36
- }
37
-
38
- // System information
39
- interface SystemInfo {
40
- platform: string;
41
- arch: string;
42
- platformForAsset: string;
43
- archForAsset: string;
44
- }
45
-
46
- // Get system information
47
- function getSystemInfo(): SystemInfo {
48
- const platform = process.platform;
49
- const arch = process.arch;
50
-
51
- let platformForAsset: string;
52
- let archForAsset: string;
53
-
54
- switch (platform) {
55
- case 'darwin':
56
- platformForAsset = 'macos';
57
- break;
58
- case 'win32':
59
- platformForAsset = 'windows';
60
- break;
61
- case 'linux':
62
- platformForAsset = 'ubuntu';
63
- break;
64
- default:
65
- platformForAsset = platform;
66
- }
67
-
68
- switch (arch) {
69
- case 'x64':
70
- archForAsset = 'x86_64';
71
- break;
72
- case 'arm64':
73
- archForAsset = 'aarch64';
74
- break;
75
- default:
76
- archForAsset = arch;
77
- }
78
-
79
- return {
80
- platform,
81
- arch,
82
- platformForAsset,
83
- archForAsset
84
- };
85
- }
86
-
87
- // Tool configurations
88
- const TOOL_CONFIGS: Record<string, ToolConfig> = {
89
- sui: {
90
- name: 'sui',
91
- repo: 'MystenLabs/sui',
92
- binaryName: 'sui',
93
- installDir: path.join(os.homedir(), '.dubhe', 'bin')
94
- },
95
- 'dubhe-indexer': {
96
- name: 'dubhe-indexer',
97
- repo: '0xobelisk/dubhe-wip',
98
- binaryName: 'dubhe-indexer',
99
- installDir: path.join(os.homedir(), '.dubhe', 'bin')
100
- }
101
- };
102
-
103
17
  // Execute shell command and return output
104
18
  async function execCommand(
105
19
  command: string,
@@ -128,605 +42,6 @@ async function execCommand(
128
42
  });
129
43
  }
130
44
 
131
- // Download file with progress bar
132
- async function downloadFileWithProgress(url: string, outputPath: string): Promise<void> {
133
- const response = await fetch(url, {
134
- headers: {
135
- 'User-Agent': 'dubhe-cli'
136
- }
137
- });
138
-
139
- if (!response.ok) {
140
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
141
- }
142
-
143
- const contentLength = response.headers.get('content-length');
144
- const totalSize = contentLength ? parseInt(contentLength) : 0;
145
-
146
- // 创建进度条
147
- const progressBar = new cliProgress.SingleBar({
148
- format:
149
- chalk.cyan('Download Progress') +
150
- ' |{bar}| {percentage}% | {value}/{total} MB | Speed: {speed} MB/s | ETA: {eta}s',
151
- barCompleteChar: '\u2588',
152
- barIncompleteChar: '\u2591',
153
- hideCursor: true,
154
- barsize: 30,
155
- forceRedraw: true
156
- });
157
-
158
- if (totalSize > 0) {
159
- progressBar.start(Math.round((totalSize / 1024 / 1024) * 100) / 100, 0, {
160
- speed: '0.00'
161
- });
162
- } else {
163
- console.log(chalk.blue('📥 Downloading... (unable to get file size)'));
164
- }
165
-
166
- const reader = response.body?.getReader();
167
- if (!reader) {
168
- throw new Error('Unable to get response stream');
169
- }
170
-
171
- const chunks: Uint8Array[] = [];
172
- let downloadedBytes = 0;
173
- const startTime = Date.now();
174
-
175
- try {
176
- while (true) {
177
- const { done, value } = await reader.read();
178
-
179
- if (done) break;
180
-
181
- chunks.push(value);
182
- downloadedBytes += value.length;
183
-
184
- // 更新进度条
185
- if (totalSize > 0) {
186
- const downloadedMB = Math.round((downloadedBytes / 1024 / 1024) * 100) / 100;
187
- const elapsedTime = (Date.now() - startTime) / 1000;
188
- const speed = elapsedTime > 0 ? Math.round((downloadedMB / elapsedTime) * 100) / 100 : 0;
189
-
190
- progressBar.update(downloadedMB, {
191
- speed: speed.toFixed(2)
192
- });
193
- }
194
- }
195
-
196
- // 合并所有 chunks
197
- const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
198
- const buffer = new Uint8Array(totalLength);
199
- let offset = 0;
200
-
201
- for (const chunk of chunks) {
202
- buffer.set(chunk, offset);
203
- offset += chunk.length;
204
- }
205
-
206
- // 写入文件
207
- fs.writeFileSync(outputPath, buffer);
208
-
209
- if (totalSize > 0) {
210
- progressBar.stop();
211
- }
212
-
213
- const totalMB = Math.round((downloadedBytes / 1024 / 1024) * 100) / 100;
214
- const elapsedTime = (Date.now() - startTime) / 1000;
215
- const avgSpeed = elapsedTime > 0 ? Math.round((totalMB / elapsedTime) * 100) / 100 : 0;
216
-
217
- console.log(
218
- chalk.green(`✓ Download completed! ${totalMB} MB, average speed: ${avgSpeed} MB/s`)
219
- );
220
- } catch (error) {
221
- if (totalSize > 0) {
222
- progressBar.stop();
223
- }
224
- throw error;
225
- }
226
- }
227
-
228
- // Fetch GitHub releases with retry
229
- async function fetchGitHubReleases(
230
- repo: string,
231
- count: number = 10,
232
- retries: number = 3
233
- ): Promise<GitHubRelease[]> {
234
- const url = `https://api.github.com/repos/${repo}/releases?per_page=${count}`;
235
-
236
- for (let attempt = 1; attempt <= retries; attempt++) {
237
- try {
238
- if (attempt > 1) {
239
- console.log(chalk.gray(` Retry ${attempt}/${retries}...`));
240
- }
241
-
242
- const response = await fetch(url, {
243
- headers: {
244
- 'User-Agent': 'dubhe-cli',
245
- Accept: 'application/vnd.github.v3+json'
246
- }
247
- });
248
-
249
- if (!response.ok) {
250
- if (response.status === 403) {
251
- throw new Error(
252
- `GitHub API rate limit: ${response.status}. Please retry later or set GITHUB_TOKEN environment variable`
253
- );
254
- }
255
- throw new Error(`GitHub API request failed: ${response.status} ${response.statusText}`);
256
- }
257
-
258
- const releases = await response.json();
259
- return releases;
260
- } catch (error) {
261
- if (attempt > 1) {
262
- console.log(
263
- chalk.yellow(
264
- ` ⚠️ Attempt ${attempt} failed: ${error instanceof Error ? error.message : String(error)}`
265
- )
266
- );
267
- }
268
-
269
- if (attempt === retries) {
270
- console.error(chalk.red(` ❌ Failed to fetch releases after ${retries} attempts`));
271
- return [];
272
- }
273
-
274
- // Wait 1 second before retry
275
- await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
276
- }
277
- }
278
-
279
- return [];
280
- }
281
-
282
- // Find compatible assets for current system
283
- function findCompatibleAssets(release: GitHubRelease, systemInfo: SystemInfo): string[] {
284
- const assets = release.assets.filter((asset) => {
285
- const name = asset.name.toLowerCase();
286
-
287
- // Platform matching with various aliases
288
- const platformVariants = [
289
- systemInfo.platformForAsset.toLowerCase(),
290
- ...(systemInfo.platformForAsset === 'macos' ? ['darwin', 'apple'] : []),
291
- ...(systemInfo.platformForAsset === 'windows' ? ['win', 'win32', 'windows'] : []),
292
- ...(systemInfo.platformForAsset === 'ubuntu' ? ['linux', 'gnu'] : [])
293
- ];
294
-
295
- // Architecture matching with various aliases
296
- const archVariants = [
297
- systemInfo.archForAsset.toLowerCase(),
298
- ...(systemInfo.archForAsset === 'x86_64' ? ['amd64', 'x64'] : []),
299
- ...(systemInfo.archForAsset === 'aarch64' ? ['arm64'] : [])
300
- ];
301
-
302
- const platformMatch = platformVariants.some((variant) => name.includes(variant));
303
- const archMatch = archVariants.some((variant) => name.includes(variant));
304
-
305
- // Check for archive formats
306
- const isArchive =
307
- name.endsWith('.tar.gz') ||
308
- name.endsWith('.zip') ||
309
- name.endsWith('.tgz') ||
310
- name.endsWith('.tar.bz2') ||
311
- name.endsWith('.tar.xz');
312
-
313
- return platformMatch && archMatch && isArchive;
314
- });
315
-
316
- return assets.map((asset) => asset.name);
317
- }
318
-
319
- // Get available versions for a tool
320
- async function getAvailableVersions(
321
- toolName: string,
322
- systemInfo: SystemInfo
323
- ): Promise<Array<{ version: string; hasCompatibleAsset: boolean }>> {
324
- const config = TOOL_CONFIGS[toolName];
325
- if (!config) return [];
326
-
327
- const releases = await fetchGitHubReleases(config.repo, 10);
328
-
329
- return releases.map((release) => ({
330
- version: release.tag_name,
331
- hasCompatibleAsset: findCompatibleAssets(release, systemInfo).length > 0
332
- }));
333
- }
334
-
335
- // Auto-add PATH to shell configuration file
336
- async function autoAddToShellConfig(installDir: string): Promise<void> {
337
- try {
338
- // Detect current shell
339
- const shell = detectCurrentShell();
340
- if (!shell) {
341
- console.log(chalk.gray(`Please add to PATH: export PATH="$PATH:${installDir}"`));
342
- return;
343
- }
344
-
345
- const { shellName, configFile } = shell;
346
- const pathCommand =
347
- shellName === 'fish'
348
- ? `set -gx PATH $PATH ${installDir}`
349
- : `export PATH="$PATH:${installDir}"`;
350
-
351
- // Check if PATH is already added
352
- if (fs.existsSync(configFile)) {
353
- const content = fs.readFileSync(configFile, 'utf8');
354
- if (content.includes(installDir)) {
355
- console.log(chalk.green(` ✓ PATH already configured in ${configFile}`));
356
- return;
357
- }
358
- }
359
-
360
- // Add PATH to configuration file
361
- const comment = shellName === 'fish' ? '# Added by dubhe doctor' : '# Added by dubhe doctor';
362
- const pathLine = `${comment}\n${pathCommand}`;
363
-
364
- fs.appendFileSync(configFile, `\n${pathLine}\n`);
365
-
366
- console.log(chalk.green(` ✓ Automatically added to PATH in ${configFile}`));
367
- console.log(chalk.blue(` 📝 To apply changes: source ${configFile} or restart terminal`));
368
- } catch (error) {
369
- console.log(
370
- chalk.yellow(
371
- ` ⚠️ Could not auto-configure PATH: ${error instanceof Error ? error.message : String(error)}`
372
- )
373
- );
374
- console.log(chalk.gray(` Please manually add to PATH: export PATH="$PATH:${installDir}"`));
375
- }
376
- }
377
-
378
- // Detect current shell and return shell info
379
- function detectCurrentShell(): { shellName: string; configFile: string } | null {
380
- const homeDir = os.homedir();
381
-
382
- // Method 1: Check SHELL environment variable
383
- const shellEnv = process.env.SHELL;
384
- if (shellEnv) {
385
- if (shellEnv.includes('zsh')) {
386
- return {
387
- shellName: 'zsh',
388
- configFile: path.join(homeDir, '.zshrc')
389
- };
390
- } else if (shellEnv.includes('bash')) {
391
- // On macOS, prefer .bash_profile, on Linux prefer .bashrc
392
- const bashProfile = path.join(homeDir, '.bash_profile');
393
- const bashrc = path.join(homeDir, '.bashrc');
394
- return {
395
- shellName: 'bash',
396
- configFile:
397
- process.platform === 'darwin' && fs.existsSync(bashProfile) ? bashProfile : bashrc
398
- };
399
- } else if (shellEnv.includes('fish')) {
400
- const fishConfigDir = path.join(homeDir, '.config', 'fish');
401
- if (!fs.existsSync(fishConfigDir)) {
402
- fs.mkdirSync(fishConfigDir, { recursive: true });
403
- }
404
- return {
405
- shellName: 'fish',
406
- configFile: path.join(fishConfigDir, 'config.fish')
407
- };
408
- }
409
- }
410
-
411
- // Method 2: Check which config files exist
412
- const possibleConfigs = [
413
- { name: 'zsh', file: path.join(homeDir, '.zshrc') },
414
- {
415
- name: 'bash',
416
- file:
417
- process.platform === 'darwin'
418
- ? path.join(homeDir, '.bash_profile')
419
- : path.join(homeDir, '.bashrc')
420
- },
421
- { name: 'bash', file: path.join(homeDir, '.bashrc') }
422
- ];
423
-
424
- for (const config of possibleConfigs) {
425
- if (fs.existsSync(config.file)) {
426
- return {
427
- shellName: config.name,
428
- configFile: config.file
429
- };
430
- }
431
- }
432
-
433
- // Method 3: Try to create default based on common patterns
434
- if (process.env.ZSH || fs.existsSync('/bin/zsh')) {
435
- return {
436
- shellName: 'zsh',
437
- configFile: path.join(homeDir, '.zshrc')
438
- };
439
- }
440
-
441
- return null;
442
- }
443
-
444
- // Download and install tool
445
- async function downloadAndInstallTool(toolName: string, version?: string): Promise<boolean> {
446
- const config = TOOL_CONFIGS[toolName];
447
- if (!config) {
448
- console.error(`Unknown tool: ${toolName}`);
449
- return false;
450
- }
451
-
452
- const systemInfo = getSystemInfo();
453
- console.log(chalk.gray(` System: ${systemInfo.platform}/${systemInfo.arch}`));
454
-
455
- try {
456
- // Fetch releases
457
- console.log(chalk.gray(` Fetching release information...`));
458
- const releases = await fetchGitHubReleases(config.repo, 10);
459
- if (releases.length === 0) {
460
- console.error(chalk.red(` ❌ Unable to fetch releases for ${config.name}`));
461
- return false;
462
- }
463
-
464
- let selectedRelease: GitHubRelease | null = null;
465
-
466
- if (version) {
467
- // Find specific version
468
- selectedRelease = releases.find((r) => r.tag_name === version) || null;
469
- if (!selectedRelease) {
470
- console.error(`Version ${version} not found`);
471
- return false;
472
- }
473
- } else {
474
- // Find latest compatible version
475
- for (const release of releases) {
476
- const compatibleAssets = findCompatibleAssets(release, systemInfo);
477
- if (compatibleAssets.length > 0) {
478
- selectedRelease = release;
479
- break;
480
- }
481
- }
482
- }
483
-
484
- if (!selectedRelease) {
485
- console.error(`No compatible version found`);
486
- return false;
487
- }
488
-
489
- // Find compatible asset
490
- const compatibleAssets = findCompatibleAssets(selectedRelease, systemInfo);
491
- if (compatibleAssets.length === 0) {
492
- console.error(`Version ${selectedRelease.tag_name} has no compatible binaries`);
493
- return false;
494
- }
495
-
496
- const assetName = compatibleAssets[0];
497
- const asset = selectedRelease.assets.find((a) => a.name === assetName);
498
- if (!asset) {
499
- console.error(`Asset file not found: ${assetName}`);
500
- return false;
501
- }
502
-
503
- console.log(chalk.green(` ✓ Found compatible version: ${selectedRelease.tag_name}`));
504
- console.log(chalk.gray(` Download file: ${asset.name}`));
505
-
506
- // Verify download link
507
- try {
508
- const headResponse = await fetch(asset.browser_download_url, {
509
- method: 'HEAD',
510
- headers: { 'User-Agent': 'dubhe-cli' }
511
- });
512
- if (!headResponse.ok) {
513
- console.log(
514
- chalk.yellow(` ⚠️ Warning: Unable to access download file (${headResponse.status})`)
515
- );
516
- } else {
517
- const fileSize = headResponse.headers.get('content-length');
518
- if (fileSize) {
519
- console.log(
520
- chalk.gray(
521
- ` File size: ${Math.round((parseInt(fileSize) / 1024 / 1024) * 100) / 100} MB`
522
- )
523
- );
524
- }
525
- }
526
- } catch (error) {
527
- console.log(chalk.yellow(` ⚠️ Warning: Unable to verify download file`));
528
- }
529
-
530
- // Create install directory
531
- if (!fs.existsSync(config.installDir)) {
532
- fs.mkdirSync(config.installDir, { recursive: true });
533
- console.log(chalk.gray(` Created install directory: ${config.installDir}`));
534
- }
535
-
536
- // Download file with retry and progress bar
537
- console.log(chalk.blue(` 📥 Downloading...`));
538
-
539
- const tempFile = path.join(os.tmpdir(), asset.name);
540
- const maxRetries = 3;
541
-
542
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
543
- try {
544
- if (attempt > 1) {
545
- console.log(chalk.gray(` Attempt ${attempt} to download...`));
546
- }
547
-
548
- await downloadFileWithProgress(asset.browser_download_url, tempFile);
549
- break;
550
- } catch (error) {
551
- const errorMsg = error instanceof Error ? error.message : String(error);
552
- console.log(chalk.yellow(` ⚠️ Download failed (attempt ${attempt}): ${errorMsg}`));
553
-
554
- if (attempt === maxRetries) {
555
- throw new Error(`Download failed after ${maxRetries} attempts: ${errorMsg}`);
556
- }
557
-
558
- // Wait before retry
559
- console.log(chalk.gray(` Waiting ${attempt * 2} seconds before retry...`));
560
- await new Promise((resolve) => setTimeout(resolve, attempt * 2000));
561
- }
562
- }
563
-
564
- // Extract and install
565
- console.log(chalk.blue(' 📦 Extracting and installing...'));
566
-
567
- const extractDir = path.join(os.tmpdir(), `extract_${Date.now()}`);
568
- fs.mkdirSync(extractDir, { recursive: true });
569
-
570
- if (asset.name.endsWith('.tar.gz') || asset.name.endsWith('.tgz')) {
571
- // Extract tar.gz / tgz
572
- const tarResult = await execCommand('tar', ['-xzf', tempFile, '-C', extractDir]);
573
- if (tarResult.code !== 0) {
574
- throw new Error(`Extraction failed: ${tarResult.stderr}`);
575
- }
576
- } else if (asset.name.endsWith('.tar.bz2')) {
577
- // Extract tar.bz2
578
- const tarResult = await execCommand('tar', ['-xjf', tempFile, '-C', extractDir]);
579
- if (tarResult.code !== 0) {
580
- throw new Error(`Extraction failed: ${tarResult.stderr}`);
581
- }
582
- } else if (asset.name.endsWith('.tar.xz')) {
583
- // Extract tar.xz
584
- const tarResult = await execCommand('tar', ['-xJf', tempFile, '-C', extractDir]);
585
- if (tarResult.code !== 0) {
586
- throw new Error(`Extraction failed: ${tarResult.stderr}`);
587
- }
588
- } else if (asset.name.endsWith('.zip')) {
589
- // Extract zip (requires unzip command)
590
- const unzipResult = await execCommand('unzip', ['-q', tempFile, '-d', extractDir]);
591
- if (unzipResult.code !== 0) {
592
- throw new Error(`Extraction failed: ${unzipResult.stderr}`);
593
- }
594
- } else {
595
- throw new Error(`Unsupported compression format: ${asset.name}`);
596
- }
597
-
598
- // Find binary file
599
- const findBinary = (dir: string): string | null => {
600
- const files = fs.readdirSync(dir, { withFileTypes: true });
601
- for (const file of files) {
602
- const fullPath = path.join(dir, file.name);
603
- if (file.isDirectory()) {
604
- const result = findBinary(fullPath);
605
- if (result) return result;
606
- } else if (file.name === config.binaryName || file.name === `${config.binaryName}.exe`) {
607
- return fullPath;
608
- }
609
- }
610
- return null;
611
- };
612
-
613
- const binaryPath = findBinary(extractDir);
614
- if (!binaryPath) {
615
- throw new Error(`Cannot find ${config.binaryName} binary in extracted files`);
616
- }
617
-
618
- // Copy binary to install directory
619
- const targetPath = path.join(
620
- config.installDir,
621
- config.binaryName + (process.platform === 'win32' ? '.exe' : '')
622
- );
623
- fs.copyFileSync(binaryPath, targetPath);
624
-
625
- // Make executable on Unix systems
626
- if (process.platform !== 'win32') {
627
- fs.chmodSync(targetPath, 0o755);
628
- }
629
-
630
- // Cleanup
631
- fs.rmSync(tempFile, { force: true });
632
- fs.rmSync(extractDir, { recursive: true, force: true });
633
-
634
- console.log(chalk.green(` ✅ Installation completed!`));
635
- console.log(chalk.gray(` Location: ${targetPath}`));
636
- console.log(chalk.gray(` Version: ${selectedRelease.tag_name}`));
637
-
638
- // Check if install directory is in PATH
639
- const currentPath = process.env.PATH || '';
640
- if (!currentPath.includes(config.installDir)) {
641
- console.log(
642
- chalk.yellow(' ⚠️ Warning: Install directory is not in PATH environment variable')
643
- );
644
-
645
- if (process.platform === 'win32') {
646
- console.log(chalk.gray(` Please add to PATH: set PATH=%PATH%;${config.installDir}`));
647
- } else {
648
- // Auto-add to shell configuration file
649
- await autoAddToShellConfig(config.installDir);
650
- }
651
- }
652
-
653
- return true;
654
- } catch (error) {
655
- console.error(chalk.red(`❌ Installation failed: ${error}`));
656
- return false;
657
- }
658
- }
659
-
660
- // Interactive version selection
661
- async function selectVersion(toolName: string): Promise<string | null> {
662
- const systemInfo = getSystemInfo();
663
- const versions = await getAvailableVersions(toolName, systemInfo);
664
-
665
- if (versions.length === 0) {
666
- console.log(chalk.red(`Unable to get version information for ${toolName}`));
667
- return null;
668
- }
669
-
670
- const compatibleVersions = versions.filter((v) => v.hasCompatibleAsset).slice(0, 5);
671
-
672
- if (compatibleVersions.length === 0) {
673
- console.log(chalk.red(`No compatible versions found for current system`));
674
- return null;
675
- }
676
-
677
- console.log(chalk.blue(`\n📋 Select version for ${toolName}`));
678
- console.log(chalk.gray(`System: ${systemInfo.platform}/${systemInfo.arch}`));
679
- console.log(chalk.gray(`Compatible versions (latest 5):\n`));
680
-
681
- const choices = compatibleVersions.map((version, index) => ({
682
- name: `${version.version} ${index === 0 ? chalk.green('(latest)') : chalk.gray('(available)')}`,
683
- value: version.version,
684
- short: version.version
685
- }));
686
-
687
- try {
688
- const answer = await inquirer.prompt([
689
- {
690
- type: 'list',
691
- name: 'version',
692
- message: 'Please select a version to install:',
693
- choices: [
694
- ...choices,
695
- new inquirer.Separator(),
696
- {
697
- name: chalk.gray('Cancel installation'),
698
- value: 'cancel'
699
- }
700
- ],
701
- default: choices[0].value
702
- }
703
- ]);
704
-
705
- if (answer.version === 'cancel') {
706
- console.log(chalk.gray('Installation cancelled'));
707
- return null;
708
- }
709
-
710
- return answer.version;
711
- } catch (error) {
712
- console.log(chalk.gray('\nInstallation cancelled'));
713
- return null;
714
- }
715
- }
716
-
717
- // Check if binary exists in install directory
718
- function checkBinaryExists(toolName: string): boolean {
719
- const config = TOOL_CONFIGS[toolName];
720
- if (!config) return false;
721
-
722
- const binaryPath = path.join(
723
- config.installDir,
724
- config.binaryName + (process.platform === 'win32' ? '.exe' : '')
725
- );
726
-
727
- return fs.existsSync(binaryPath);
728
- }
729
-
730
45
  // Check if command is available in PATH
731
46
  async function checkCommand(
732
47
  command: string,
@@ -742,19 +57,6 @@ async function checkCommand(
742
57
  message: `Installed ${version}`
743
58
  };
744
59
  } else {
745
- // Check if binary exists in install directory but not in PATH
746
- if (checkBinaryExists(command)) {
747
- const shell = detectCurrentShell();
748
- const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
749
-
750
- return {
751
- name: command,
752
- status: 'warning',
753
- message: 'Installed but not in PATH',
754
- fixSuggestion: `Binary exists in install directory. Please apply PATH changes: source ${shellConfig} (or restart terminal), then run dubhe doctor again`
755
- };
756
- }
757
-
758
60
  return {
759
61
  name: command,
760
62
  status: 'error',
@@ -763,19 +65,6 @@ async function checkCommand(
763
65
  };
764
66
  }
765
67
  } catch (error) {
766
- // Check if binary exists in install directory but not in PATH
767
- if (checkBinaryExists(command)) {
768
- const shell = detectCurrentShell();
769
- const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
770
-
771
- return {
772
- name: command,
773
- status: 'warning',
774
- message: 'Installed but not in PATH',
775
- fixSuggestion: `Binary exists in install directory. Please apply PATH changes: source ${shellConfig} (or restart terminal), then run dubhe doctor again`
776
- };
777
- }
778
-
779
68
  return {
780
69
  name: command,
781
70
  status: 'error',
@@ -790,16 +79,26 @@ function getInstallSuggestion(command: string): string {
790
79
  const suggestions: Record<string, string> = {
791
80
  docker: 'Visit https://docs.docker.com/get-docker/ to install Docker',
792
81
  'docker-compose': 'Visit https://docs.docker.com/compose/install/ to install Docker Compose',
793
- sui: 'Run `dubhe doctor --install sui` to auto-install, or visit https://docs.sui.io/guides/developer/getting-started/sui-install',
794
- 'dubhe-indexer':
795
- 'Run `dubhe doctor --install dubhe-indexer` to auto-install, or download from https://github.com/0xobelisk/dubhe-wip/releases',
82
+ sui: 'Visit https://docs.sui.io/guides/developer/getting-started/sui-install to install Sui CLI',
796
83
  pnpm: 'Run: npm install -g pnpm',
797
- node: 'Visit https://nodejs.org/ to download and install Node.js'
84
+ node: 'Visit https://nodejs.org/ to download and install Node.js',
85
+ git: 'Visit https://git-scm.com/downloads to install Git'
798
86
  };
799
87
 
800
88
  return suggestions[command] || `Please install ${command}`;
801
89
  }
802
90
 
91
+ // Check if file exists
92
+ function checkFileExists(filePath: string, description: string): CheckResult {
93
+ const exists = existsSync(filePath);
94
+ return {
95
+ name: description,
96
+ status: exists ? 'success' : 'warning',
97
+ message: exists ? 'Available' : 'Not found',
98
+ fixSuggestion: exists ? undefined : `Please create file: ${filePath}`
99
+ };
100
+ }
101
+
803
102
  // Check Node.js version
804
103
  async function checkNodeVersion(): Promise<CheckResult> {
805
104
  try {
@@ -810,13 +109,13 @@ async function checkNodeVersion(): Promise<CheckResult> {
810
109
 
811
110
  if (versionNumber >= 18) {
812
111
  return {
813
- name: 'Node.js Version',
112
+ name: 'Node.js version',
814
113
  status: 'success',
815
114
  message: `${version} (meets requirement >=18.0)`
816
115
  };
817
116
  } else {
818
117
  return {
819
- name: 'Node.js Version',
118
+ name: 'Node.js version',
820
119
  status: 'warning',
821
120
  message: `${version} (recommend upgrade to >=18.0)`,
822
121
  fixSuggestion: 'Please upgrade Node.js to 18.0 or higher'
@@ -824,7 +123,7 @@ async function checkNodeVersion(): Promise<CheckResult> {
824
123
  }
825
124
  } else {
826
125
  return {
827
- name: 'Node.js Version',
126
+ name: 'Node.js version',
828
127
  status: 'error',
829
128
  message: 'Not installed',
830
129
  fixSuggestion: 'Please install Node.js 18.0 or higher'
@@ -832,7 +131,7 @@ async function checkNodeVersion(): Promise<CheckResult> {
832
131
  }
833
132
  } catch (error) {
834
133
  return {
835
- name: 'Node.js Version',
134
+ name: 'Node.js version',
836
135
  status: 'error',
837
136
  message: 'Check failed',
838
137
  fixSuggestion: 'Please install Node.js'
@@ -846,13 +145,13 @@ async function checkDockerService(): Promise<CheckResult> {
846
145
  const result = await execCommand('docker', ['info']);
847
146
  if (result.code === 0) {
848
147
  return {
849
- name: 'Docker Service',
148
+ name: 'Docker service',
850
149
  status: 'success',
851
150
  message: 'Running'
852
151
  };
853
152
  } else {
854
153
  return {
855
- name: 'Docker Service',
154
+ name: 'Docker service',
856
155
  status: 'warning',
857
156
  message: 'Not running',
858
157
  fixSuggestion: 'Please start Docker service'
@@ -860,7 +159,7 @@ async function checkDockerService(): Promise<CheckResult> {
860
159
  }
861
160
  } catch (error) {
862
161
  return {
863
- name: 'Docker Service',
162
+ name: 'Docker service',
864
163
  status: 'error',
865
164
  message: 'Check failed',
866
165
  fixSuggestion: 'Please install and start Docker'
@@ -875,21 +174,21 @@ async function checkNpmConfig(): Promise<CheckResult> {
875
174
  if (result.code === 0) {
876
175
  const registry = result.stdout.trim();
877
176
  return {
878
- name: 'NPM Configuration',
177
+ name: 'NPM configuration',
879
178
  status: 'success',
880
179
  message: `Configured (${registry})`
881
180
  };
882
181
  } else {
883
182
  return {
884
- name: 'NPM Configuration',
183
+ name: 'NPM configuration',
885
184
  status: 'warning',
886
185
  message: 'Configuration issue',
887
- fixSuggestion: 'Check NPM configuration: npm config list'
186
+ fixSuggestion: 'Check NPM config: npm config list'
888
187
  };
889
188
  }
890
189
  } catch (error) {
891
190
  return {
892
- name: 'NPM Configuration',
191
+ name: 'NPM configuration',
893
192
  status: 'warning',
894
193
  message: 'Check failed',
895
194
  fixSuggestion: 'Please install Node.js and NPM'
@@ -906,9 +205,9 @@ function formatTableRow(result: CheckResult): string[] {
906
205
  };
907
206
 
908
207
  const statusText = {
909
- success: chalk.green('Pass'),
910
- warning: chalk.yellow('Warning'),
911
- error: chalk.red('Fail')
208
+ success: chalk.green('YES'),
209
+ warning: chalk.yellow('WARN'),
210
+ error: chalk.red('NO')
912
211
  };
913
212
 
914
213
  // Decide what content to display based on status
@@ -923,108 +222,10 @@ function formatTableRow(result: CheckResult): string[] {
923
222
  }
924
223
 
925
224
  // Main check function
926
- async function runDoctorChecks(options: {
927
- install?: string;
928
- selectVersion?: boolean;
929
- listVersions?: string;
930
- debug?: boolean;
931
- }) {
932
- console.log(chalk.bold.blue('\n🔍 Dubhe Doctor - Development Environment Checker\n'));
933
-
934
- // Handle list-versions option
935
- if (options.listVersions) {
936
- const toolName = options.listVersions;
937
- if (!TOOL_CONFIGS[toolName]) {
938
- console.error(chalk.red(`❌ Unsupported tool: ${toolName}`));
939
- process.exit(1);
940
- }
941
-
942
- console.log(chalk.blue(`📋 Available versions for ${toolName}:`));
943
- const systemInfo = getSystemInfo();
944
- console.log(chalk.gray(`System: ${systemInfo.platform}/${systemInfo.arch}\n`));
945
-
946
- // Get 10 versions directly to avoid duplicate calls
947
- const config = TOOL_CONFIGS[toolName];
948
- const releases = await fetchGitHubReleases(config.repo, 10);
949
-
950
- if (releases.length === 0) {
951
- console.log(chalk.red('Unable to get version information'));
952
- process.exit(1);
953
- }
954
-
955
- // Process version compatibility check
956
- const versions = releases.map((release) => ({
957
- version: release.tag_name,
958
- hasCompatibleAsset: findCompatibleAssets(release, systemInfo).length > 0,
959
- publishDate: new Date(release.published_at).toLocaleDateString('en-US')
960
- }));
961
-
962
- const table = new Table({
963
- head: [
964
- chalk.bold.cyan('Version'),
965
- chalk.bold.cyan('Compatibility'),
966
- chalk.bold.cyan('Release Date')
967
- ],
968
- colWidths: [30, 15, 25]
969
- });
970
-
971
- versions.forEach((version) => {
972
- table.push([
973
- version.version,
974
- version.hasCompatibleAsset ? chalk.green('✓ Compatible') : chalk.red('✗ Incompatible'),
975
- version.publishDate
976
- ]);
977
- });
978
-
979
- console.log(table.toString());
980
-
981
- if (options.debug && versions.length > 0) {
982
- console.log(chalk.blue('\n🔍 Debug Information:'));
983
- const latestCompatible = versions.find((v) => v.hasCompatibleAsset);
984
- if (latestCompatible) {
985
- const release = releases.find((r) => r.tag_name === latestCompatible.version);
986
- if (release) {
987
- console.log(chalk.gray(`Latest compatible version: ${latestCompatible.version}`));
988
- console.log(chalk.gray(`Available asset files:`));
989
- release.assets.forEach((asset) => {
990
- console.log(chalk.gray(` - ${asset.name}`));
991
- });
992
-
993
- const compatibleAssets = findCompatibleAssets(release, systemInfo);
994
- console.log(chalk.gray(`Compatible asset files:`));
995
- compatibleAssets.forEach((asset) => {
996
- console.log(chalk.green(` ✓ ${asset}`));
997
- });
998
- }
999
- }
1000
- }
1001
-
1002
- process.exit(0);
1003
- }
1004
-
225
+ async function runDoctorChecks() {
226
+ console.log(chalk.bold.blue('\n🔍 Dubhe Doctor - Environment Check Tool\n'));
1005
227
  console.log(chalk.gray('Checking your development environment...\n'));
1006
228
 
1007
- // Handle install option
1008
- if (options.install) {
1009
- const toolName = options.install;
1010
- if (!TOOL_CONFIGS[toolName]) {
1011
- console.error(chalk.red(`❌ Unsupported tool: ${toolName}`));
1012
- console.log(chalk.gray(`Supported tools: ${Object.keys(TOOL_CONFIGS).join(', ')}`));
1013
- process.exit(1);
1014
- }
1015
-
1016
- let version: string | null = null;
1017
- if (options.selectVersion) {
1018
- version = await selectVersion(toolName);
1019
- if (!version) {
1020
- process.exit(1);
1021
- }
1022
- }
1023
-
1024
- const success = await downloadAndInstallTool(toolName, version || undefined);
1025
- process.exit(success ? 0 : 1);
1026
- }
1027
-
1028
229
  const results: CheckResult[] = [];
1029
230
 
1030
231
  // Execute all checks
@@ -1037,6 +238,9 @@ async function runDoctorChecks(options: {
1037
238
  const pnpmCheck = await checkCommand('pnpm');
1038
239
  results.push(pnpmCheck);
1039
240
 
241
+ const gitCheck = await checkCommand('git');
242
+ results.push(gitCheck);
243
+
1040
244
  // Package manager configuration check
1041
245
  const npmConfigCheck = await checkNpmConfig();
1042
246
  // Treat npm config as optional, don't affect overall status
@@ -1062,18 +266,14 @@ async function runDoctorChecks(options: {
1062
266
  const suiCheck = await checkCommand('sui');
1063
267
  results.push(suiCheck);
1064
268
 
1065
- // Dubhe indexer check
1066
- const dubheIndexerCheck = await checkCommand('dubhe-indexer');
1067
- results.push(dubheIndexerCheck);
269
+ // Configuration files check
270
+ const gitConfigCheck = checkFileExists(join(homedir(), '.gitconfig'), 'Git configuration');
271
+ results.push(gitConfigCheck);
1068
272
 
1069
273
  // Create and display table
1070
274
  const table = new Table({
1071
- head: [
1072
- chalk.bold.cyan('Check Item'),
1073
- chalk.bold.cyan('Result'),
1074
- chalk.bold.cyan('Description/Fix Suggestion')
1075
- ],
1076
- colWidths: [25, 15, 60],
275
+ head: [chalk.bold.cyan('Criteria'), chalk.bold.cyan('Result'), chalk.bold.cyan('Fix')],
276
+ colWidths: [25, 15, 50],
1077
277
  style: {
1078
278
  head: ['cyan'],
1079
279
  border: ['grey']
@@ -1098,161 +298,23 @@ async function runDoctorChecks(options: {
1098
298
  console.log('\n' + chalk.bold('📊 Check Summary:'));
1099
299
  console.log(` ${chalk.green('✓ Passed:')} ${summary.success} items`);
1100
300
  console.log(` ${chalk.yellow('! Warning:')} ${summary.warning} items`);
1101
- console.log(` ${chalk.red('✗ Failed:')} ${summary.error} items`);
1102
-
1103
- // Handle missing tools
1104
- const allFailedTools = results.filter((r) => r.status === 'error');
1105
- const autoInstallableTools = allFailedTools.filter((r) => TOOL_CONFIGS[r.name]);
1106
- const manualInstallTools = allFailedTools.filter((r) => !TOOL_CONFIGS[r.name]);
1107
-
1108
- // Show manual installation suggestions for non-auto-installable tools
1109
- if (manualInstallTools.length > 0) {
1110
- console.log(chalk.blue('\n🔧 Missing tools that require manual installation:'));
1111
- manualInstallTools.forEach((tool) => {
1112
- console.log(` ${chalk.red('✗')} ${tool.name}: ${tool.fixSuggestion || tool.message}`);
1113
- });
1114
- }
301
+ console.log(` ${chalk.red('✗ Error:')} ${summary.error} items`);
1115
302
 
1116
- // Auto-install missing tools that support it
1117
- if (autoInstallableTools.length > 0) {
1118
- // Check if any of the tools are already installed in the install directory
1119
- const alreadyInstalledTools = autoInstallableTools.filter((tool) =>
1120
- checkBinaryExists(tool.name)
303
+ if (summary.error > 0) {
304
+ console.log(
305
+ chalk.red(
306
+ '\n❌ Your environment has some issues, please fix them according to the suggestions above.'
307
+ )
1121
308
  );
1122
- const notInstalledTools = autoInstallableTools.filter((tool) => !checkBinaryExists(tool.name));
1123
-
1124
- if (alreadyInstalledTools.length > 0) {
1125
- const installedNames = alreadyInstalledTools.map((tool) => tool.name).join(', ');
1126
- const installDir = TOOL_CONFIGS[alreadyInstalledTools[0].name]?.installDir || '~/.dubhe/bin';
1127
- const shell = detectCurrentShell();
1128
- const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
1129
-
1130
- console.log(chalk.yellow(`\n⚠️ Tools already installed but not in PATH: ${installedNames}`));
1131
- console.log(chalk.gray(` Location: ${installDir}`));
1132
- console.log(chalk.blue(' To fix this, apply PATH changes:'));
1133
- console.log(chalk.green(` source ${shellConfig}`));
1134
- console.log(chalk.blue(' Or restart your terminal, then run: dubhe doctor'));
1135
- console.log(
1136
- chalk.gray(
1137
- ` If you want to reinstall, remove the files from ${installDir} and run dubhe doctor again`
1138
- )
1139
- );
1140
- }
1141
-
1142
- if (notInstalledTools.length > 0) {
1143
- const notInstalledNames = notInstalledTools.map((tool) => tool.name).join(', ');
1144
- console.log(chalk.blue(`\n🚀 Auto-installable tools detected: ${notInstalledNames}`));
1145
-
1146
- try {
1147
- const answer = await inquirer.prompt([
1148
- {
1149
- type: 'confirm',
1150
- name: 'installAll',
1151
- message: `Would you like to automatically install these tools? (${notInstalledNames})`,
1152
- default: true
1153
- }
1154
- ]);
1155
-
1156
- if (answer.installAll) {
1157
- console.log(chalk.blue('\n📦 Starting installation of auto-installable tools...\n'));
1158
-
1159
- let installationResults: Array<{ name: string; success: boolean }> = [];
1160
-
1161
- for (const tool of notInstalledTools) {
1162
- console.log(chalk.blue(`${'='.repeat(60)}`));
1163
- console.log(chalk.blue(`📦 Installing ${tool.name}...`));
1164
- console.log(chalk.blue(`${'='.repeat(60)}`));
1165
-
1166
- const success = await downloadAndInstallTool(tool.name);
1167
- installationResults.push({ name: tool.name, success });
1168
-
1169
- if (success) {
1170
- console.log(chalk.green(`\n✅ ${tool.name} installation completed successfully!`));
1171
- } else {
1172
- console.log(chalk.red(`\n❌ ${tool.name} installation failed`));
1173
- console.log(
1174
- chalk.gray(` Manual installation: dubhe doctor --install ${tool.name}`)
1175
- );
1176
- }
1177
- console.log(''); // Add spacing between tools
1178
- }
1179
-
1180
- // Show installation summary
1181
- console.log(chalk.blue(`${'='.repeat(60)}`));
1182
- console.log(chalk.bold('📋 Installation Summary:'));
1183
- console.log(chalk.blue(`${'='.repeat(60)}`));
1184
-
1185
- installationResults.forEach((result) => {
1186
- const status = result.success ? chalk.green('✅ Success') : chalk.red('❌ Failed');
1187
- console.log(` ${result.name}: ${status}`);
1188
- });
1189
-
1190
- const successCount = installationResults.filter((r) => r.success).length;
1191
- const failureCount = installationResults.length - successCount;
1192
-
1193
- console.log(
1194
- `\n ${chalk.green('Successful:')} ${successCount}/${installationResults.length}`
1195
- );
1196
- if (failureCount > 0) {
1197
- console.log(` ${chalk.red('Failed:')} ${failureCount}/${installationResults.length}`);
1198
- }
1199
-
1200
- // Check if any tools were successfully installed
1201
- if (successCount > 0) {
1202
- const shell = detectCurrentShell();
1203
- const shellConfig = shell ? shell.configFile : '~/.zshrc (or ~/.bashrc)';
1204
-
1205
- console.log(chalk.blue('\n🔄 Next Steps:'));
1206
- console.log(chalk.yellow(' 1. Apply PATH changes by running:'));
1207
- console.log(chalk.green(` source ${shellConfig}`));
1208
- console.log(chalk.yellow(' 2. Or restart your terminal'));
1209
- console.log(chalk.yellow(' 3. Then run the doctor check again:'));
1210
- console.log(chalk.green(' dubhe doctor'));
1211
- console.log(
1212
- chalk.gray('\n This will verify that all tools are properly configured.')
1213
- );
1214
- } else {
1215
- console.log(
1216
- chalk.red('\n❌ All installations failed. Please check the error messages above.')
1217
- );
1218
- }
1219
- } else {
1220
- console.log(
1221
- chalk.gray('\nAuto-installation skipped. You can install them manually later:')
1222
- );
1223
- notInstalledTools.forEach((tool) => {
1224
- console.log(chalk.gray(` dubhe doctor --install ${tool.name}`));
1225
- });
1226
- }
1227
- } catch (error) {
1228
- console.log(chalk.gray('\nInstallation cancelled. You can install them manually later:'));
1229
- notInstalledTools.forEach((tool) => {
1230
- console.log(chalk.gray(` dubhe doctor --install ${tool.name}`));
1231
- });
1232
- }
1233
- }
1234
- }
1235
-
1236
- // If no auto-installable tools are missing, show final status
1237
- if (autoInstallableTools.length === 0) {
1238
- if (summary.error > 0) {
1239
- console.log(
1240
- chalk.red(
1241
- '\n❌ Your environment has some issues. Please fix them according to the suggestions above.'
1242
- )
1243
- );
1244
- process.exit(1);
1245
- } else if (summary.warning > 0) {
1246
- console.log(
1247
- chalk.yellow(
1248
- '\n⚠️ Your environment is basically ready, but we recommend fixing the warnings for better development experience.'
1249
- )
1250
- );
1251
- } else {
1252
- console.log(
1253
- chalk.green('\n✅ Congratulations! Your development environment is fully ready!')
1254
- );
1255
- }
309
+ process.exit(1);
310
+ } else if (summary.warning > 0) {
311
+ console.log(
312
+ chalk.yellow(
313
+ '\n⚠️ Your environment is basically ready, but it is recommended to fix warning items for a better development experience.'
314
+ )
315
+ );
316
+ } else {
317
+ console.log(chalk.green('\n Congratulations! Your development environment is fully ready!'));
1256
318
  }
1257
319
 
1258
320
  console.log(
@@ -1266,36 +328,16 @@ const commandModule: CommandModule = {
1266
328
  command: 'doctor',
1267
329
  describe: 'Check if local development environment is ready',
1268
330
  builder(yargs) {
1269
- return yargs
1270
- .option('install', {
1271
- type: 'string',
1272
- describe: 'Auto-install specified tool (sui, dubhe-indexer)',
1273
- choices: Object.keys(TOOL_CONFIGS)
1274
- })
1275
- .option('select-version', {
1276
- type: 'boolean',
1277
- default: false,
1278
- describe: 'Select specific version for installation'
1279
- })
1280
- .option('list-versions', {
1281
- type: 'string',
1282
- describe: 'List available versions for specified tool',
1283
- choices: Object.keys(TOOL_CONFIGS)
1284
- })
1285
- .option('debug', {
1286
- type: 'boolean',
1287
- default: false,
1288
- describe: 'Show detailed debug information'
1289
- });
331
+ return yargs.option('verbose', {
332
+ alias: 'v',
333
+ type: 'boolean',
334
+ description: 'Show verbose output',
335
+ default: false
336
+ });
1290
337
  },
1291
- async handler(argv) {
338
+ async handler() {
1292
339
  try {
1293
- await runDoctorChecks({
1294
- install: argv.install as string | undefined,
1295
- selectVersion: argv['select-version'] as boolean,
1296
- listVersions: argv['list-versions'] as string | undefined,
1297
- debug: argv.debug as boolean
1298
- });
340
+ await runDoctorChecks();
1299
341
  } catch (error) {
1300
342
  console.error(chalk.red('Error occurred during check:'), error);
1301
343
  process.exit(1);