@@ -125,7 +125,7 @@ export interface DeployStackOptions {
125125 *
126126 * @default - Change set with defaults
127127 */
128- readonly deploymentMethod ?: DeploymentMethod ;
128+ readonly deploymentMethod ?: DeploymentMethod | ExecuteChangeSetDeployment ;
129129
130130 /**
131131 * The collection of extra parameters
@@ -346,6 +346,15 @@ type CommonExecuteOptions = keyof CreateStackCommandInput &
346346keyof UpdateStackCommandInput &
347347keyof ExecuteChangeSetCommandInput ;
348348
349+ /**
350+ * Execute a previously created change set.
351+ * This is an internal deployment method used by the two-phase deploy flow.
352+ */
353+ export interface ExecuteChangeSetDeployment {
354+ readonly method : 'execute-change-set' ;
355+ readonly changeSetName : string ;
356+ }
357+
349358/**
350359 * This class shares state and functionality between the different full deployment modes
351360 */
@@ -357,7 +366,7 @@ class FullCloudFormationDeployment {
357366 private readonly uuid : string ;
358367
359368 constructor (
360- private readonly deploymentMethod : DirectDeployment | ChangeSetDeployment ,
369+ private readonly deploymentMethod : DirectDeployment | ChangeSetDeployment | ExecuteChangeSetDeployment ,
361370 private readonly options : DeployStackOptions ,
362371 private readonly cloudFormationStack : CloudFormationStack ,
363372 private readonly stackArtifact : cxapi . CloudFormationStackArtifact ,
@@ -384,6 +393,9 @@ class FullCloudFormationDeployment {
384393 case 'change-set' :
385394 return this . changeSetDeployment ( deploymentMethod ) ;
386395
396+ case 'execute-change-set' :
397+ return this . executeExistingChangeSet ( deploymentMethod ) ;
398+
387399 case 'direct' :
388400 return this . directDeployment ( ) ;
389401 }
@@ -437,10 +449,40 @@ class FullCloudFormationDeployment {
437449 noOp : false ,
438450 outputs : this . cloudFormationStack . outputs ,
439451 stackArn : changeSetDescription . StackId ! ,
452+ changeSet : changeSetDescription ,
440453 } ;
441454 }
442455
443456 // If there are replacements in the changeset, check the rollback flag and stack status
457+ return this . checkAndExecuteChangeSet ( changeSetDescription ) ;
458+ }
459+
460+ private async executeExistingChangeSet ( deploymentMethod : ExecuteChangeSetDeployment ) : Promise < DeployStackResult > {
461+ await this . updateTerminationProtection ( ) ;
462+
463+ // The change set was already created and validated during the prepare phase,
464+ // just describe it to get the info needed for execution.
465+ const changeSetDescription = await this . cfn . describeChangeSet ( {
466+ StackName : this . stackName ,
467+ ChangeSetName : deploymentMethod . changeSetName ,
468+ } ) ;
469+
470+ return this . checkAndExecuteChangeSet ( changeSetDescription ) ;
471+ }
472+
473+ /**
474+ * Check rollback/replacement constraints and execute the change set if all checks pass.
475+ */
476+ private async checkAndExecuteChangeSet ( changeSetDescription : DescribeChangeSetCommandOutput ) : Promise < DeployStackResult > {
477+ if ( changeSetDescription . Status !== 'CREATE_COMPLETE' ) {
478+ const status = changeSetDescription . Status ?? 'UNKNOWN' ;
479+ const reason = changeSetDescription . StatusReason ? `: ${ changeSetDescription . StatusReason } ` : '' ;
480+ throw new ToolkitError (
481+ 'ChangeSetNotReady' ,
482+ `Change set '${ changeSetDescription . ChangeSetName } ' on stack '${ this . stackName } ' is not ready for execution (status: ${ status } ${ reason } )` ,
483+ ) ;
484+ }
485+
444486 const replacement = hasReplacement ( changeSetDescription ) ;
445487 const isPausedFailState = this . cloudFormationStack . stackStatus . isRollbackable ;
446488 const rollback = this . options . rollback ?? true ;
@@ -737,6 +779,12 @@ async function canSkipDeploy(
737779 return false ;
738780 }
739781
782+ // Executing an existing change set, never skip
783+ if ( deployStackOptions . deploymentMethod ?. method === 'execute-change-set' ) {
784+ await ioHelper . defaults . debug ( `${ deployName } : executing existing change set, never skip` ) ;
785+ return false ;
786+ }
787+
740788 // Drift-aware
741789 if (
742790 deployStackOptions . deploymentMethod ?. method === 'change-set' &&
0 commit comments