Skip to content

Commit 2d94064

Browse files
committed
chore: wip
1 parent 6c2dea8 commit 2d94064

7 files changed

Lines changed: 1621 additions & 17 deletions

File tree

packages/ts-cloud/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
"types": "./src/aws/index.ts",
3535
"import": "./src/aws/index.ts"
3636
},
37+
"./deploy": {
38+
"types": "./src/deploy/index.ts",
39+
"import": "./src/deploy/index.ts"
40+
},
3741
"./dns": {
3842
"types": "./src/dns/index.ts",
3943
"import": "./src/dns/index.ts"

packages/ts-cloud/src/aws/cloudformation.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)