@@ -336,17 +336,44 @@ export class CloudFormationClient {
336336 return // Stack deleted successfully
337337 }
338338 // For create/update operations, stack might not be visible yet - retry
339+ if ( attempts % 10 === 0 ) {
340+ console . log ( `[waitForStack] Attempt ${ attempts } : Stack not visible yet` )
341+ }
339342 await new Promise ( resolve => setTimeout ( resolve , 2000 ) )
340343 attempts ++
341344 continue
342345 }
343346
344347 const stack = result . Stacks [ 0 ]
345348
349+ if ( attempts % 10 === 0 ) {
350+ console . log ( `[waitForStack] Attempt ${ attempts } : Status = ${ stack . StackStatus } ${ stack . StackStatusReason ? ` (${ stack . StackStatusReason } )` : '' } ` )
351+ }
352+
346353 if ( targets . includes ( stack . StackStatus ) ) {
347354 return // Target status reached
348355 }
349356
357+ // If stack is being deleted but we're waiting for create/update, something went wrong
358+ if ( ( waitType === 'stack-create-complete' || waitType === 'stack-update-complete' ) &&
359+ ( stack . StackStatus === 'DELETE_IN_PROGRESS' || stack . StackStatus === 'DELETE_COMPLETE' ) ) {
360+ console . log ( `[waitForStack] Stack is being deleted (creation/update failed)` )
361+ // Try to get stack events to understand the failure
362+ try {
363+ const eventsResult = await this . describeStackEvents ( stackName )
364+ console . log ( '[waitForStack] Stack events (most recent first):' )
365+ for ( const event of eventsResult . StackEvents . slice ( 0 , 15 ) ) {
366+ if ( event . ResourceStatus . includes ( 'FAILED' ) || event . ResourceStatusReason ) {
367+ console . log ( ` ${ event . LogicalResourceId } : ${ event . ResourceStatus } - ${ event . ResourceStatusReason || 'No reason provided' } ` )
368+ }
369+ }
370+ }
371+ catch {
372+ // Ignore errors fetching events
373+ }
374+ throw new Error ( `Stack creation/update failed - stack is being deleted. Reason: ${ stack . StackStatusReason || 'Check CloudFormation console for details.' } ` )
375+ }
376+
350377 // Handle DELETE_FAILED specifically - might need to retain resources
351378 if ( stack . StackStatus === 'DELETE_FAILED' && waitType === 'stack-delete-complete' ) {
352379 const error : any = new Error ( `Stack deletion failed - may have resources that need to be retained` )
@@ -370,14 +397,19 @@ export class CloudFormationClient {
370397 }
371398 // For create operations, stack might not be visible yet - retry
372399 if ( waitType === 'stack-create-complete' && error . message ?. includes ( 'does not exist' ) ) {
400+ if ( attempts % 10 === 0 ) {
401+ console . log ( `[waitForStack] Attempt ${ attempts } : Stack does not exist (error), retrying...` )
402+ }
373403 await new Promise ( resolve => setTimeout ( resolve , 2000 ) )
374404 attempts ++
375405 continue
376406 }
407+ console . log ( `[waitForStack] Unexpected error: ${ error . message } ` )
377408 throw error
378409 }
379410 }
380411
412+ console . log ( `[waitForStack] Timeout after ${ attempts } attempts` )
381413 throw new Error ( `Timeout waiting for stack to reach ${ waitType } ` )
382414 }
383415
@@ -796,4 +828,62 @@ export class CloudFormationClient {
796828
797829 throw new Error ( `Timeout waiting for stack to reach ${ waitType } ` )
798830 }
831+
832+ /**
833+ * Wait for stack operation to complete (create, update, or delete)
834+ * Returns a result object with success status
835+ */
836+ async waitForStackComplete (
837+ stackName : string ,
838+ maxAttempts : number = 120 ,
839+ delayMs : number = 5000 ,
840+ ) : Promise < { success : boolean ; status : string ; reason ?: string } > {
841+ const successStatuses = [
842+ 'CREATE_COMPLETE' ,
843+ 'UPDATE_COMPLETE' ,
844+ 'DELETE_COMPLETE' ,
845+ ]
846+ const failureStatuses = [
847+ 'CREATE_FAILED' ,
848+ 'UPDATE_FAILED' ,
849+ 'DELETE_FAILED' ,
850+ 'ROLLBACK_COMPLETE' ,
851+ 'ROLLBACK_FAILED' ,
852+ 'UPDATE_ROLLBACK_COMPLETE' ,
853+ 'UPDATE_ROLLBACK_FAILED' ,
854+ ]
855+
856+ for ( let i = 0 ; i < maxAttempts ; i ++ ) {
857+ try {
858+ const result = await this . describeStacks ( { stackName } )
859+
860+ if ( result . Stacks . length === 0 ) {
861+ // Stack was deleted
862+ return { success : true , status : 'DELETE_COMPLETE' }
863+ }
864+
865+ const stack = result . Stacks [ 0 ]
866+ const status = stack . StackStatus
867+
868+ if ( successStatuses . includes ( status ) ) {
869+ return { success : true , status }
870+ }
871+
872+ if ( failureStatuses . includes ( status ) ) {
873+ return { success : false , status, reason : stack . StackStatusReason }
874+ }
875+
876+ // Still in progress, wait and retry
877+ await new Promise ( resolve => setTimeout ( resolve , delayMs ) )
878+ }
879+ catch ( error : any ) {
880+ if ( error . message ?. includes ( 'does not exist' ) ) {
881+ return { success : true , status : 'DELETE_COMPLETE' }
882+ }
883+ throw error
884+ }
885+ }
886+
887+ return { success : false , status : 'TIMEOUT' , reason : 'Timeout waiting for stack operation' }
888+ }
799889}
0 commit comments