@@ -8,6 +8,113 @@ import { stat } from "node:fs/promises";
8
8
import { basename , join } from "node:path" ;
9
9
import { HUB_URL } from "./src/consts" ;
10
10
import { version } from "./package.json" ;
11
+ import type { CommitProgressEvent } from "./src/lib/commit" ;
12
+ import type { MultiBar , SingleBar } from "cli-progress" ;
13
+
14
+ // Progress bar manager for handling multiple file uploads
15
+ class UploadProgressManager {
16
+ private multibar : MultiBar | null = null ;
17
+ private fileBars : Map < string , SingleBar > = new Map ( ) ;
18
+ private readonly isQuiet : boolean ;
19
+ private cliProgressAvailable : boolean = false ;
20
+
21
+ constructor ( isQuiet : boolean = false ) {
22
+ this . isQuiet = isQuiet ;
23
+ }
24
+
25
+ async initialize ( ) : Promise < void > {
26
+ if ( this . isQuiet ) return ;
27
+
28
+ try {
29
+ const cliProgress = await import ( "cli-progress" ) ;
30
+ this . cliProgressAvailable = true ;
31
+ this . multibar = new cliProgress . MultiBar (
32
+ {
33
+ clearOnComplete : false ,
34
+ hideCursor : true ,
35
+ format : " {bar} | {filename} | {percentage}% | {state}" ,
36
+ barCompleteChar : "\u2588" ,
37
+ barIncompleteChar : "\u2591" ,
38
+ } ,
39
+ cliProgress . Presets . shades_grey
40
+ ) ;
41
+ } catch ( error ) {
42
+ // cli-progress is not available, fall back to simple logging
43
+ this . cliProgressAvailable = false ;
44
+ }
45
+ }
46
+
47
+ handleEvent ( event : CommitProgressEvent ) : void {
48
+ if ( this . isQuiet ) return ;
49
+
50
+ if ( event . event === "phase" ) {
51
+ this . logPhase ( event . phase ) ;
52
+ } else if ( event . event === "fileProgress" ) {
53
+ this . updateFileProgress ( event . path , event . progress , event . state ) ;
54
+ }
55
+ }
56
+
57
+ private logPhase ( phase : string ) : void {
58
+ if ( this . isQuiet ) return ;
59
+
60
+ const phaseMessages = {
61
+ preuploading : "📋 Preparing files for upload..." ,
62
+ uploadingLargeFiles : "⬆️ Uploading files..." ,
63
+ committing : "✨ Finalizing commit..." ,
64
+ } ;
65
+
66
+ console . log ( `\n${ phaseMessages [ phase as keyof typeof phaseMessages ] || phase } ` ) ;
67
+ }
68
+
69
+ private updateFileProgress ( path : string , progress : number , state : string ) : void {
70
+ if ( this . isQuiet ) return ;
71
+
72
+ if ( this . cliProgressAvailable && this . multibar ) {
73
+ // Use progress bars
74
+ let bar = this . fileBars . get ( path ) ;
75
+
76
+ if ( ! bar ) {
77
+ bar = this . multibar . create ( 100 , 0 , {
78
+ filename : this . truncateFilename ( path , 100 ) ,
79
+ state : state ,
80
+ } ) ;
81
+ this . fileBars . set ( path , bar ) ;
82
+ }
83
+
84
+ if ( progress >= 1 ) {
85
+ // If complete, mark it as done
86
+ bar . update ( 100 , { state : state === "hashing" ? "✓ hashed" : "✓ uploaded" } ) ;
87
+ } else {
88
+ // Update the progress (convert 0-1 to 0-100)
89
+ const percentage = Math . round ( progress * 100 ) ;
90
+ bar . update ( percentage , { state : state } ) ;
91
+ }
92
+ } else {
93
+ // Fall back to simple console logging
94
+ const percentage = Math . round ( progress * 100 ) ;
95
+ const truncatedPath = this . truncateFilename ( path , 100 ) ;
96
+
97
+ if ( progress >= 1 ) {
98
+ const statusIcon = state === "hashing" ? "✓ hashed" : "✓ uploaded" ;
99
+ console . log ( `${ statusIcon } : ${ truncatedPath } ` ) ;
100
+ } else if ( percentage % 25 === 0 ) {
101
+ // Only log every 25% to avoid spam
102
+ console . log ( `${ state } : ${ truncatedPath } (${ percentage } %)` ) ;
103
+ }
104
+ }
105
+ }
106
+
107
+ private truncateFilename ( filename : string , maxLength : number ) : string {
108
+ if ( filename . length <= maxLength ) return filename ;
109
+ return "..." + filename . slice ( - ( maxLength - 3 ) ) ;
110
+ }
111
+
112
+ stop ( ) : void {
113
+ if ( ! this . isQuiet && this . cliProgressAvailable && this . multibar ) {
114
+ this . multibar . stop ( ) ;
115
+ }
116
+ }
117
+ }
11
118
12
119
// Didn't find the import from "node:util", so duplicated it here
13
120
type OptionToken =
@@ -339,18 +446,31 @@ async function run() {
339
446
]
340
447
: [ { content : pathToFileURL ( localFolder ) , path : pathInRepo . replace ( / ^ [ . ] ? \/ / , "" ) } ] ;
341
448
342
- for await ( const event of uploadFilesWithProgress ( {
343
- repo : repoId ,
344
- files,
345
- branch : revision ,
346
- accessToken : token ,
347
- commitTitle : commitMessage ?. trim ( ) . split ( "\n" ) [ 0 ] ,
348
- commitDescription : commitMessage ?. trim ( ) . split ( "\n" ) . slice ( 1 ) . join ( "\n" ) . trim ( ) ,
349
- hubUrl : process . env . HF_ENDPOINT ?? HUB_URL ,
350
- } ) ) {
449
+ const progressManager = new UploadProgressManager ( ! ! quiet ) ;
450
+ await progressManager . initialize ( ) ;
451
+
452
+ try {
453
+ for await ( const event of uploadFilesWithProgress ( {
454
+ repo : repoId ,
455
+ files,
456
+ branch : revision ,
457
+ accessToken : token ,
458
+ commitTitle : commitMessage ?. trim ( ) . split ( "\n" ) [ 0 ] ,
459
+ commitDescription : commitMessage ?. trim ( ) . split ( "\n" ) . slice ( 1 ) . join ( "\n" ) . trim ( ) ,
460
+ hubUrl : process . env . HF_ENDPOINT ?? HUB_URL ,
461
+ useXet : true ,
462
+ } ) ) {
463
+ progressManager . handleEvent ( event ) ;
464
+ }
465
+
351
466
if ( ! quiet ) {
352
- console . log ( event ) ;
467
+ console . log ( "\n✅ Upload completed successfully!" ) ;
353
468
}
469
+ } catch ( error ) {
470
+ progressManager . stop ( ) ;
471
+ throw error ;
472
+ } finally {
473
+ progressManager . stop ( ) ;
354
474
}
355
475
break ;
356
476
}
0 commit comments