@@ -24,6 +24,7 @@ import type { AutoFundOptions } from '../payments/types.js'
2424import type { Spinner } from '../utils/cli-helpers.js'
2525import { cancel , formatFileSize } from '../utils/cli-helpers.js'
2626import { log } from '../utils/cli-logger.js'
27+ import { createSpinnerFlow } from '../utils/multi-operation-spinner.js'
2728
2829export interface UploadFlowOptions {
2930 /**
@@ -249,42 +250,13 @@ export async function performUpload(
249250) : Promise < UploadFlowResult > {
250251 const { contextType, logger, spinner } = options
251252
252- spinner ?. start ( 'Uploading to Filecoin...' )
253+ // Create spinner flow manager for tracking all operations
254+ const flow = createSpinnerFlow ( spinner )
253255
254- // Track parallel operations with their messages
255- const pendingOps = new Map < string , string > ( )
256- let transactionHash : string | undefined
257-
258- function getSpinnerMessage ( ) {
259- return Array . from ( pendingOps . values ( ) )
260- . map ( ( op ) => op )
261- . join ( ' & ' )
262- }
256+ // Start with upload operation
257+ flow . addOperation ( 'upload' , 'Uploading to Filecoin...' )
263258
264- function completeOperation (
265- operationKey : string ,
266- completionMessage : string ,
267- type : 'success' | 'warning' | 'info' | 'none' = 'success'
268- ) {
269- pendingOps . delete ( operationKey )
270-
271- switch ( type ) {
272- case 'success' :
273- spinner ?. stop ( `${ pc . green ( '✓' ) } ${ completionMessage } ` )
274- break
275- case 'warning' :
276- spinner ?. stop ( `${ pc . yellow ( '⚠' ) } ${ completionMessage } ` )
277- break
278- default :
279- spinner ?. stop ( completionMessage )
280- break
281- }
282-
283- // Restart spinner with remaining operations if any
284- if ( pendingOps . size > 0 ) {
285- spinner ?. start ( getSpinnerMessage ( ) )
286- }
287- }
259+ let transactionHash : string | undefined
288260
289261 let pieceCid : PieceCID | undefined
290262 function getIpniAdvertisementMsg ( attemptCount : number ) : string {
@@ -298,69 +270,78 @@ export async function performUpload(
298270 switch ( event . type ) {
299271 case 'onUploadComplete' : {
300272 pieceCid = event . data . pieceCid
301- spinner ?. stop ( `${ pc . green ( '✓' ) } Upload complete` )
302- const serviceURL = getServiceURL ( synapseService . providerInfo )
303- if ( serviceURL != null && serviceURL !== '' ) {
304- log . spinnerSection ( 'Download IPFS CAR from SP' , [
305- pc . gray ( `${ serviceURL . replace ( / \/ $ / , '' ) } /ipfs/${ rootCid } ` ) ,
306- ] )
307- }
308- spinner ?. start ( 'Adding piece to DataSet...' )
273+ flow . completeOperation ( 'upload' , 'Upload complete' , {
274+ type : 'success' ,
275+ details : ( ( ) => {
276+ const serviceURL = getServiceURL ( synapseService . providerInfo )
277+ if ( serviceURL != null && serviceURL !== '' ) {
278+ return {
279+ title : 'Download IPFS CAR from SP' ,
280+ content : [ pc . gray ( `${ serviceURL . replace ( / \/ $ / , '' ) } /ipfs/${ rootCid } ` ) ] ,
281+ }
282+ }
283+ return
284+ } ) ( ) ,
285+ } )
286+ // Start adding piece to dataset operation
287+ flow . addOperation ( 'add-to-dataset' , 'Adding piece to DataSet...' )
309288 break
310289 }
311290 case 'onPieceAdded' : {
312- spinner ?. stop ( `${ pc . green ( '✓' ) } Piece added to DataSet (unconfirmed on-chain)` )
313291 if ( event . data . txHash ) {
314292 transactionHash = event . data . txHash
315293 }
316- log . spinnerSection ( 'Explorer URLs' , [
317- pc . gray ( `Piece: https://pdp.vxb.ai/calibration/piece/${ pieceCid } ` ) ,
318- pc . gray (
319- `Transaction: https://${ synapseService . synapse . getNetwork ( ) } .filfox.info/en/message/${ transactionHash } `
320- ) ,
321- ] )
322-
323- pendingOps . set ( 'chain' , 'Confirming piece added to DataSet on-chain' )
324-
325- spinner ?. start ( getSpinnerMessage ( ) )
294+ flow . completeOperation ( 'add-to-dataset' , 'Piece added to DataSet (unconfirmed on-chain)' , {
295+ type : 'success' ,
296+ details : {
297+ title : 'Explorer URLs' ,
298+ content : [
299+ pc . gray ( `Piece: https://pdp.vxb.ai/calibration/piece/${ pieceCid } ` ) ,
300+ pc . gray (
301+ `Transaction: https://${ synapseService . synapse . getNetwork ( ) } .filfox.info/en/message/${ transactionHash } `
302+ ) ,
303+ ] ,
304+ } ,
305+ } )
306+ // Start chain confirmation operation
307+ flow . addOperation ( 'chain' , 'Confirming piece added to DataSet on-chain' )
326308 break
327309 }
328310 case 'onPieceConfirmed' : {
329- completeOperation ( 'chain' , `Piece added to DataSet (confirmed on-chain)` , 'success' )
311+ flow . completeOperation ( 'chain' , 'Piece added to DataSet (confirmed on-chain)' , {
312+ type : 'success' ,
313+ } )
330314 break
331315 }
332316
333317 case 'ipniAdvertisement.retryUpdate' : {
334- if ( event . data . retryCount === 0 ) {
335- pendingOps . set ( 'ipni' , getIpniAdvertisementMsg ( 1 ) )
336- }
337- pendingOps . set ( 'ipni' , getIpniAdvertisementMsg ( event . data . retryCount + 1 ) )
338- spinner ?. message ( getSpinnerMessage ( ) )
318+ const attemptCount = event . data . retryCount === 0 ? 1 : event . data . retryCount + 1
319+ flow . addOperation ( 'ipni' , getIpniAdvertisementMsg ( attemptCount ) )
339320 break
340321 }
341322 case 'ipniAdvertisement.complete' : {
342- const isIpniAdvertisementSuccessful = event . data . result
343- const message = isIpniAdvertisementSuccessful
344- ? `IPNI advertisement successful. IPFS retrieval possible.`
345- : `IPNI advertisement pending`
346-
347- completeOperation ( 'ipni' , message , isIpniAdvertisementSuccessful ? 'success' : 'warning' )
348-
349- if ( isIpniAdvertisementSuccessful ) {
350- log . spinnerSection ( 'IPFS Retrieval URLs' , [
351- pc . gray ( `ipfs://${ rootCid } ` ) ,
352- pc . gray ( `https://inbrowser.link/ipfs/${ rootCid } ` ) ,
353- pc . gray ( `https://dweb.link/ipfs/${ rootCid } ` ) ,
354- ] )
355- }
323+ // complete event is only emitted when result === true (success)
324+ flow . completeOperation ( 'ipni' , 'IPNI advertisement successful. IPFS retrieval possible.' , {
325+ type : 'success' ,
326+ details : {
327+ title : 'IPFS Retrieval URLs' ,
328+ content : [
329+ pc . gray ( `ipfs://${ rootCid } ` ) ,
330+ pc . gray ( `https://inbrowser.link/ipfs/${ rootCid } ` ) ,
331+ pc . gray ( `https://dweb.link/ipfs/${ rootCid } ` ) ,
332+ ] ,
333+ } ,
334+ } )
356335 break
357336 }
358337 case 'ipniAdvertisement.failed' : {
359- logger . error ( { error : event . data . error } , 'Error checking IPNI advertisement' )
360- completeOperation ( 'ipni' , `IPNI advertisement check failed` , 'warning' )
361- log . spinnerSection ( 'IPNI advertisement check failed' , [
362- pc . gray ( `IPNI advertisement does not exist at http://filecoinpin.contact/cid/${ rootCid } ` ) ,
363- ] )
338+ flow . completeOperation ( 'ipni' , 'IPNI advertisement failed.' , {
339+ type : 'warning' ,
340+ details : {
341+ title : 'IPFS retrieval is not possible yet.' ,
342+ content : [ pc . gray ( `IPNI advertisement does not exist at http://filecoinpin.contact/cid/${ rootCid } ` ) ] ,
343+ } ,
344+ } )
364345 break
365346 }
366347 default : {
0 commit comments