@@ -8,6 +8,113 @@ import { stat } from "node:fs/promises";
88import { basename , join } from "node:path" ;
99import { HUB_URL } from "./src/consts" ;
1010import { 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+ }
11118
12119// Didn't find the import from "node:util", so duplicated it here
13120type OptionToken =
@@ -339,18 +446,31 @@ async function run() {
339446 ]
340447 : [ { content : pathToFileURL ( localFolder ) , path : pathInRepo . replace ( / ^ [ . ] ? \/ / , "" ) } ] ;
341448
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+
351466 if ( ! quiet ) {
352- console . log ( event ) ;
467+ console . log ( "\n✅ Upload completed successfully!" ) ;
353468 }
469+ } catch ( error ) {
470+ progressManager . stop ( ) ;
471+ throw error ;
472+ } finally {
473+ progressManager . stop ( ) ;
354474 }
355475 break ;
356476 }
0 commit comments