1
+ /* eslint-disable no-console */
1
2
import { Buffer } from 'node:buffer'
2
3
import fs from 'node:fs'
3
4
import { arch , platform } from 'node:os'
@@ -6,6 +7,7 @@ import process from 'node:process'
6
7
import { aliases , packages } from 'ts-pkgx'
7
8
import { config } from './config'
8
9
import { Path } from './path'
10
+ import { ProgressBar , Spinner } from './progress'
9
11
10
12
// Extract all package alias names from ts-pkgx
11
13
export type PackageAlias = keyof typeof aliases
@@ -319,8 +321,61 @@ async function downloadPackage(
319
321
320
322
const response = await fetch ( url )
321
323
if ( response . ok ) {
322
- const buffer = await response . arrayBuffer ( )
323
- await fs . promises . writeFile ( file , Buffer . from ( buffer ) )
324
+ const contentLength = response . headers . get ( 'content-length' )
325
+ const totalBytes = contentLength ? Number . parseInt ( contentLength , 10 ) : 0
326
+
327
+ if ( ! config . verbose && totalBytes > 0 ) {
328
+ // Show progress bar for downloads
329
+ const progressBar = new ProgressBar ( totalBytes , {
330
+ showBytes : true ,
331
+ showSpeed : true ,
332
+ showETA : true ,
333
+ } )
334
+
335
+ console . log ( `📦 Downloading ${ domain } v${ version } ...` )
336
+
337
+ const reader = response . body ?. getReader ( )
338
+ if ( reader ) {
339
+ const chunks : Uint8Array [ ] = [ ]
340
+ let receivedBytes = 0
341
+
342
+ while ( true ) {
343
+ const { done, value } = await reader . read ( )
344
+ if ( done )
345
+ break
346
+
347
+ if ( value ) {
348
+ chunks . push ( value )
349
+ receivedBytes += value . length
350
+ progressBar . update ( receivedBytes , totalBytes )
351
+ }
352
+ }
353
+
354
+ progressBar . complete ( )
355
+
356
+ // Combine all chunks into a single buffer
357
+ const totalLength = chunks . reduce ( ( acc , chunk ) => acc + chunk . length , 0 )
358
+ const buffer = new Uint8Array ( totalLength )
359
+ let offset = 0
360
+ for ( const chunk of chunks ) {
361
+ buffer . set ( chunk , offset )
362
+ offset += chunk . length
363
+ }
364
+
365
+ await fs . promises . writeFile ( file , buffer )
366
+ }
367
+ else {
368
+ // Fallback for when reader is not available
369
+ const buffer = await response . arrayBuffer ( )
370
+ await fs . promises . writeFile ( file , Buffer . from ( buffer ) )
371
+ }
372
+ }
373
+ else {
374
+ // Fallback for when content-length is not available or verbose mode
375
+ const buffer = await response . arrayBuffer ( )
376
+ await fs . promises . writeFile ( file , Buffer . from ( buffer ) )
377
+ }
378
+
324
379
downloadUrl = url
325
380
archiveFile = file
326
381
break
@@ -347,6 +402,12 @@ async function downloadPackage(
347
402
348
403
const isXz = archiveFile . endsWith ( '.tar.xz' )
349
404
405
+ // Show spinner during extraction
406
+ const extractSpinner = new Spinner ( )
407
+ if ( ! config . verbose ) {
408
+ extractSpinner . start ( `🔧 Extracting ${ domain } v${ version } ...` )
409
+ }
410
+
350
411
// Use Bun's spawn directly to avoid shell dependency issues
351
412
const tarPath = process . platform === 'win32' ? 'tar' : '/usr/bin/tar'
352
413
const tarArgs = isXz
@@ -359,6 +420,11 @@ async function downloadPackage(
359
420
} )
360
421
361
422
const result = await proc . exited
423
+
424
+ if ( ! config . verbose ) {
425
+ extractSpinner . stop ( )
426
+ }
427
+
362
428
if ( result !== 0 ) {
363
429
const stderr = await new Response ( proc . stderr ) . text ( )
364
430
throw new Error ( `Failed to extract archive: ${ stderr } ` )
@@ -373,6 +439,12 @@ async function downloadPackage(
373
439
const binDir = path . join ( installPath , 'bin' )
374
440
await fs . promises . mkdir ( binDir , { recursive : true } )
375
441
442
+ // Show spinner during installation
443
+ const installSpinner = new Spinner ( )
444
+ if ( ! config . verbose ) {
445
+ installSpinner . start ( `⚡ Installing ${ domain } v${ version } ...` )
446
+ }
447
+
376
448
// Look for executables in common locations
377
449
const searchDirs = [
378
450
extractDir ,
@@ -414,6 +486,10 @@ async function downloadPackage(
414
486
}
415
487
}
416
488
489
+ if ( ! config . verbose ) {
490
+ installSpinner . stop ( `✅ Successfully installed ${ domain } v${ version } ` )
491
+ }
492
+
417
493
// Clean up temp directory
418
494
await fs . promises . rm ( tempDir , { recursive : true , force : true } )
419
495
@@ -450,6 +526,9 @@ export async function install(packages: PackageSpec | PackageSpec[], basePath?:
450
526
console . warn ( `Installing packages: ${ packageList . join ( ', ' ) } ` )
451
527
console . warn ( `Install path: ${ installPath } ` )
452
528
}
529
+ else if ( packageList . length > 1 ) {
530
+ console . log ( `🚀 Installing ${ packageList . length } packages...` )
531
+ }
453
532
454
533
const os = getPlatform ( )
455
534
const architecture = getArchitecture ( )
@@ -460,11 +539,15 @@ export async function install(packages: PackageSpec | PackageSpec[], basePath?:
460
539
461
540
const allInstalledFiles : string [ ] = [ ]
462
541
463
- for ( const pkg of packageList ) {
542
+ for ( let i = 0 ; i < packageList . length ; i ++ ) {
543
+ const pkg = packageList [ i ]
464
544
try {
465
545
if ( config . verbose ) {
466
546
console . warn ( `Processing package: ${ pkg } ` )
467
547
}
548
+ else if ( packageList . length > 1 ) {
549
+ console . log ( `📦 [${ i + 1 } /${ packageList . length } ] ${ pkg } ` )
550
+ }
468
551
469
552
// Parse package name and version
470
553
const { name : packageName , version : requestedVersion } = parsePackageSpec ( pkg )
@@ -505,6 +588,16 @@ export async function install(packages: PackageSpec | PackageSpec[], basePath?:
505
588
if ( config . verbose ) {
506
589
console . warn ( `Installation complete. Installed ${ allInstalledFiles . length } files.` )
507
590
}
591
+ else if ( packageList . length > 0 ) {
592
+ const packageCount = packageList . length
593
+ const fileCount = allInstalledFiles . length
594
+ if ( packageCount === 1 ) {
595
+ console . log ( `🎉 Installation complete!` )
596
+ }
597
+ else {
598
+ console . log ( `🎉 Successfully installed ${ packageCount } packages (${ fileCount } files)` )
599
+ }
600
+ }
508
601
509
602
return allInstalledFiles
510
603
}
0 commit comments