@@ -59,6 +59,7 @@ type CleanOptions = {
5959 purgeImages : boolean ;
6060 purgeBuildCache : boolean ;
6161 purgeNetworks : boolean ;
62+ allFhevmProjects : boolean ;
6263} ;
6364
6465type TestOptions = {
@@ -129,7 +130,7 @@ function usage(): void {
129130 console . log ( ` ${ COLORS . yellow } unpause${ COLORS . reset } ${ COLORS . cyan } [CONTRACTS]${ COLORS . reset } Unpause specific contracts (host|gateway)` ) ;
130131 console . log ( ` ${ COLORS . yellow } test${ COLORS . reset } ${ COLORS . cyan } [TYPE]${ COLORS . reset } Run tests (${ testTypes } )` ) ;
131132 console . log ( ` ${ COLORS . yellow } upgrade${ COLORS . reset } ${ COLORS . cyan } [SERVICE]${ COLORS . reset } Upgrade specific service` ) ;
132- console . log ( ` ${ COLORS . yellow } clean${ COLORS . reset } ${ COLORS . cyan } [--purge] [--purge-images] [--purge-build-cache] [--purge-networks] [--purge-local-cache]${ COLORS . reset } Clean stack resources` ) ;
133+ console . log ( ` ${ COLORS . yellow } clean${ COLORS . reset } ${ COLORS . cyan } [--purge] [--purge-images] [--purge-build-cache] [--purge-networks] [--purge-local-cache] [--all-fhevm-projects] ${ COLORS . reset } Clean stack resources` ) ;
133134 console . log ( ` ${ COLORS . yellow } trace${ COLORS . reset } ${ COLORS . cyan } [up|down|status]${ COLORS . reset } Manage local tracing stack (Jaeger + Prometheus)` ) ;
134135 console . log ( ` ${ COLORS . yellow } logs${ COLORS . reset } ${ COLORS . cyan } [SERVICE]${ COLORS . reset } View logs for a specific service` ) ;
135136 console . log ( ` ${ COLORS . yellow } telemetry-smoke${ COLORS . reset } Validate Jaeger telemetry services` ) ;
@@ -160,6 +161,7 @@ function usage(): void {
160161 console . log ( ` ${ COLORS . cyan } --purge-build-cache${ COLORS . reset } Remove local fhevm Buildx cache directory` ) ;
161162 console . log ( ` ${ COLORS . cyan } --purge-networks${ COLORS . reset } Remove networks labeled for the active fhevm compose project only` ) ;
162163 console . log ( ` ${ COLORS . cyan } --purge-local-cache${ COLORS . reset } Alias of --purge-build-cache (kept for compatibility)` ) ;
164+ console . log ( ` ${ COLORS . cyan } --all-fhevm-projects${ COLORS . reset } Clean every Docker resource prefixed with ${ COLORS . green } fhevm-${ COLORS . reset } ` ) ;
163165 console . log ( "" ) ;
164166 console . log ( `${ COLORS . bold } ${ COLORS . lightBlue } Examples:${ COLORS . reset } ` ) ;
165167 console . log ( ` ${ COLORS . purple } ./fhevm-cli up --network testnet${ COLORS . reset } ` ) ;
@@ -182,6 +184,7 @@ function usage(): void {
182184 console . log ( ` ${ COLORS . purple } ./fhevm-cli upgrade coprocessor${ COLORS . reset } ` ) ;
183185 console . log ( ` ${ COLORS . purple } ./fhevm-cli telemetry-smoke${ COLORS . reset } ` ) ;
184186 console . log ( ` ${ COLORS . purple } ./fhevm-cli clean --purge${ COLORS . reset } ` ) ;
187+ console . log ( ` ${ COLORS . purple } ./fhevm-cli clean --all-fhevm-projects${ COLORS . reset } ` ) ;
185188 console . log ( ` ${ COLORS . purple } ./fhevm-cli down --purge${ COLORS . reset } ` ) ;
186189 console . log ( ` ${ COLORS . purple } ./fhevm-cli clean --purge-build-cache${ COLORS . reset } ` ) ;
187190 console . log ( ` ${ COLORS . purple } ./fhevm-cli trace down${ COLORS . reset } ` ) ;
@@ -3108,6 +3111,7 @@ function parseCleanArgs(args: string[]): CleanOptions {
31083111 purgeImages : false ,
31093112 purgeBuildCache : false ,
31103113 purgeNetworks : false ,
3114+ allFhevmProjects : false ,
31113115 } ;
31123116
31133117 for ( const arg of args ) {
@@ -3133,16 +3137,154 @@ function parseCleanArgs(args: string[]): CleanOptions {
31333137 options . purgeBuildCache = true ;
31343138 continue ;
31353139 }
3140+ if ( arg === "--all-fhevm-projects" ) {
3141+ options . allFhevmProjects = true ;
3142+ continue ;
3143+ }
31363144 usageError ( `Unknown option for clean: ${ arg } ` ) ;
31373145 }
31383146
31393147 return options ;
31403148}
31413149
3150+ function listComposeProjects ( ) : string [ ] {
3151+ const result = runCommand ( [ "docker" , "compose" , "ls" , "--format" , "json" ] , {
3152+ capture : true ,
3153+ check : false ,
3154+ allowFailure : true ,
3155+ } ) ;
3156+ if ( result . status !== 0 || ! result . stdout . trim ( ) ) {
3157+ return [ ] ;
3158+ }
3159+ try {
3160+ const parsed = JSON . parse ( result . stdout ) as Array < { Name ?: string } > ;
3161+ if ( ! Array . isArray ( parsed ) ) {
3162+ return [ ] ;
3163+ }
3164+ return parsed
3165+ . map ( ( entry ) => entry ?. Name ?. trim ( ) ?? "" )
3166+ . filter ( ( name ) => name !== "" ) ;
3167+ } catch {
3168+ return [ ] ;
3169+ }
3170+ }
3171+
3172+ function removeDockerResourcesByPrefix ( prefix : string , removeVolumes : boolean ) : void {
3173+ const containerNames = runCommand ( [ "docker" , "ps" , "-a" , "--format" , "{{.Names}}" ] , {
3174+ capture : true ,
3175+ check : false ,
3176+ allowFailure : true ,
3177+ } ) . stdout
3178+ . split ( "\n" )
3179+ . map ( ( line ) => line . trim ( ) )
3180+ . filter ( ( name ) => name . startsWith ( prefix ) ) ;
3181+ if ( containerNames . length > 0 ) {
3182+ runCommand ( [ "docker" , "rm" , "-f" , ...containerNames ] , {
3183+ check : false ,
3184+ allowFailure : true ,
3185+ } ) ;
3186+ }
3187+
3188+ const networks = runCommand ( [ "docker" , "network" , "ls" , "--format" , "{{.Name}}" ] , {
3189+ capture : true ,
3190+ check : false ,
3191+ allowFailure : true ,
3192+ } ) . stdout
3193+ . split ( "\n" )
3194+ . map ( ( line ) => line . trim ( ) )
3195+ . filter ( ( name ) => name . startsWith ( prefix ) ) ;
3196+ for ( const network of networks ) {
3197+ runCommand ( [ "docker" , "network" , "rm" , network ] , {
3198+ check : false ,
3199+ allowFailure : true ,
3200+ } ) ;
3201+ }
3202+
3203+ if ( removeVolumes ) {
3204+ const volumes = runCommand ( [ "docker" , "volume" , "ls" , "--format" , "{{.Name}}" ] , {
3205+ capture : true ,
3206+ check : false ,
3207+ allowFailure : true ,
3208+ } ) . stdout
3209+ . split ( "\n" )
3210+ . map ( ( line ) => line . trim ( ) )
3211+ . filter ( ( name ) => name . startsWith ( prefix ) ) ;
3212+ for ( const volume of volumes ) {
3213+ runCommand ( [ "docker" , "volume" , "rm" , volume ] , {
3214+ check : false ,
3215+ allowFailure : true ,
3216+ } ) ;
3217+ }
3218+ }
3219+ }
3220+
3221+ function cleanupAllFhevmProjects ( removeVolumes : boolean ) : void {
3222+ const prefix = "fhevm-" ;
3223+ const projects = listComposeProjects ( ) . filter ( ( name ) => name . startsWith ( prefix ) ) ;
3224+ if ( projects . length > 0 ) {
3225+ logInfo ( `Cleaning all fhevm compose projects: ${ projects . join ( ", " ) } ` ) ;
3226+ } else {
3227+ logInfo ( "No active fhevm compose projects found; cleaning prefixed leftovers." ) ;
3228+ }
3229+
3230+ for ( const project of projects ) {
3231+ const containerIds = runCommand (
3232+ [ "docker" , "ps" , "-a" , "--filter" , `label=com.docker.compose.project=${ project } ` , "--format" , "{{.ID}}" ] ,
3233+ { capture : true , check : false , allowFailure : true } ,
3234+ ) . stdout
3235+ . split ( "\n" )
3236+ . map ( ( line ) => line . trim ( ) )
3237+ . filter ( Boolean ) ;
3238+ if ( containerIds . length > 0 ) {
3239+ runCommand ( [ "docker" , "rm" , "-f" , ...containerIds ] , {
3240+ check : false ,
3241+ allowFailure : true ,
3242+ } ) ;
3243+ }
3244+
3245+ const networkIds = runCommand (
3246+ [ "docker" , "network" , "ls" , "--filter" , `label=com.docker.compose.project=${ project } ` , "--format" , "{{.ID}}" ] ,
3247+ { capture : true , check : false , allowFailure : true } ,
3248+ ) . stdout
3249+ . split ( "\n" )
3250+ . map ( ( line ) => line . trim ( ) )
3251+ . filter ( Boolean ) ;
3252+ for ( const networkId of networkIds ) {
3253+ runCommand ( [ "docker" , "network" , "rm" , networkId ] , {
3254+ check : false ,
3255+ allowFailure : true ,
3256+ } ) ;
3257+ }
3258+
3259+ if ( removeVolumes ) {
3260+ const volumeNames = runCommand (
3261+ [ "docker" , "volume" , "ls" , "--filter" , `label=com.docker.compose.project=${ project } ` , "--format" , "{{.Name}}" ] ,
3262+ { capture : true , check : false , allowFailure : true } ,
3263+ ) . stdout
3264+ . split ( "\n" )
3265+ . map ( ( line ) => line . trim ( ) )
3266+ . filter ( Boolean ) ;
3267+ for ( const volumeName of volumeNames ) {
3268+ runCommand ( [ "docker" , "volume" , "rm" , volumeName ] , {
3269+ check : false ,
3270+ allowFailure : true ,
3271+ } ) ;
3272+ }
3273+ }
3274+ }
3275+
3276+ // Catch stale leftovers not labeled as compose resources.
3277+ removeDockerResourcesByPrefix ( prefix , removeVolumes ) ;
3278+ }
3279+
31423280function clean ( args : string [ ] ) : void {
31433281 const options = parseCleanArgs ( args ) ;
31443282 console . log ( `${ COLORS . lightBlue } [CLEAN]${ COLORS . reset } ${ COLORS . bold } Cleaning up FHEVM stack...${ COLORS . reset } ` ) ;
3145- cleanupKnownStack ( true ) ;
3283+ if ( options . allFhevmProjects ) {
3284+ cleanupAllFhevmProjects ( true ) ;
3285+ } else {
3286+ cleanupKnownStack ( true ) ;
3287+ }
31463288
31473289 if ( options . purgeNetworks ) {
31483290 const networkList = runCommand ( [ "docker" , "network" , "ls" , "--filter" , `label=com.docker.compose.project=${ PROJECT } ` , "--format" , "{{.Name}}" ] , {
0 commit comments