@@ -41,6 +41,8 @@ interface DeployFlowState {
4141 /** True if the error is due to missing AWS credentials (not configured) */
4242 hasCredentialsError : boolean ;
4343 isComplete : boolean ;
44+ /** True if CloudFormation has started (received first resource event) */
45+ hasStartedCfn : boolean ;
4446 logFilePath : string ;
4547 /** Missing credentials that need to be provided */
4648 missingCredentials : MissingCredential [ ] ;
@@ -76,19 +78,27 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
7678 const switchableIoHost = preSynthesized ?. switchableIoHost ?? preflight . switchableIoHost ;
7779 const identityKmsKeyArn = preSynthesized ?. identityKmsKeyArn ?? preflight . identityKmsKeyArn ;
7880
81+ const [ publishAssetsStep , setPublishAssetsStep ] = useState < Step > ( { label : 'Publish assets' , status : 'pending' } ) ;
7982 const [ deployStep , setDeployStep ] = useState < Step > ( { label : 'Deploy to AWS' , status : 'pending' } ) ;
8083 const [ deployOutput , setDeployOutput ] = useState < string | null > ( null ) ;
8184 const [ deployMessages , setDeployMessages ] = useState < DeployMessage [ ] > ( [ ] ) ;
8285 const [ stackOutputs , setStackOutputs ] = useState < Record < string , string > > ( { } ) ;
8386 const [ shouldStartDeploy , setShouldStartDeploy ] = useState ( false ) ;
8487 const [ hasTokenExpiredError , setHasTokenExpiredError ] = useState ( false ) ;
88+ // Track if CloudFormation has started (received first resource event)
89+ const [ hasStartedCfn , setHasStartedCfn ] = useState ( false ) ;
90+ // Ref version for use in callbacks (avoids stale closure issues)
91+ const hasReceivedCfnEvent = useRef ( false ) ;
8592 // Ref to capture outputs from I5900 stream message (for immediate access in persistDeployedState)
8693 const streamOutputsRef = useRef < Record < string , string > | null > ( null ) ;
8794
8895 const startDeploy = useCallback ( ( ) => {
96+ setPublishAssetsStep ( { label : 'Publish assets' , status : 'pending' } ) ;
8997 setDeployStep ( { label : 'Deploy to AWS' , status : 'pending' } ) ;
9098 setDeployOutput ( null ) ;
9199 setHasTokenExpiredError ( false ) ; // Reset token expired state when retrying
100+ setHasStartedCfn ( false ) ;
101+ hasReceivedCfnEvent . current = false ;
92102 if ( skipPreflight ) {
93103 setShouldStartDeploy ( true ) ;
94104 } else {
@@ -152,11 +162,11 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
152162 if ( ! cdkToolkitWrapper ) return ;
153163
154164 const run = async ( ) => {
155- setDeployStep ( prev => ( { ...prev , status : 'running' } ) ) ;
165+ setPublishAssetsStep ( prev => ( { ...prev , status : 'running' } ) ) ;
156166 setShouldStartDeploy ( false ) ;
157167 setDeployMessages ( [ ] ) ; // Clear previous messages
158168 streamOutputsRef . current = null ; // Clear previous stream outputs
159- logger . startStep ( 'Deploy to AWS ' ) ;
169+ logger . startStep ( 'Publish assets ' ) ;
160170
161171 // Set up raw message callback to log ALL CDK output
162172 switchableIoHost ?. setOnRawMessage ( ( code , level , message ) => {
@@ -166,6 +176,15 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
166176 // Set up filtered message callback for TUI display
167177 switchableIoHost ?. setOnMessage ( msg => {
168178 setDeployMessages ( prev => [ ...prev , msg ] ) ;
179+ // When we receive the first CloudFormation event with progress, mark assets as published
180+ if ( ! hasReceivedCfnEvent . current && msg . progress ) {
181+ hasReceivedCfnEvent . current = true ;
182+ setHasStartedCfn ( true ) ;
183+ logger . endStep ( 'success' ) ;
184+ logger . startStep ( 'Deploy to AWS' ) ;
185+ setPublishAssetsStep ( prev => ( { ...prev , status : 'success' } ) ) ;
186+ setDeployStep ( prev => ( { ...prev , status : 'running' } ) ) ;
187+ }
169188 // Capture outputs from I5900 for immediate use in persistDeployedState
170189 if ( msg . code === 'CDK_TOOLKIT_I5900' && msg . outputs ) {
171190 streamOutputsRef . current = msg . outputs ;
@@ -191,6 +210,8 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
191210 logger . endStep ( 'success' ) ;
192211 logger . finalize ( true ) ;
193212 setDeployOutput ( `Deployed ${ stackNames . length } stack(s): ${ stackNames . join ( ', ' ) } ` ) ;
213+ // Mark both steps as success (in case CFn events were never received)
214+ setPublishAssetsStep ( prev => ( { ...prev , status : 'success' } ) ) ;
194215 setDeployStep ( prev => ( { ...prev , status : 'success' } ) ) ;
195216 } catch ( err ) {
196217 const errorMsg = getErrorMessage ( err ) ;
@@ -202,11 +223,20 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
202223 setHasTokenExpiredError ( true ) ;
203224 }
204225
205- setDeployStep ( prev => ( {
206- ...prev ,
207- status : 'error' ,
208- error : logger . getFailureMessage ( 'Deploy to AWS' ) ,
209- } ) ) ;
226+ // Mark the appropriate step as error based on whether CFn started
227+ if ( hasReceivedCfnEvent . current ) {
228+ setDeployStep ( prev => ( {
229+ ...prev ,
230+ status : 'error' ,
231+ error : logger . getFailureMessage ( 'Deploy to AWS' ) ,
232+ } ) ) ;
233+ } else {
234+ setPublishAssetsStep ( prev => ( {
235+ ...prev ,
236+ status : 'error' ,
237+ error : logger . getFailureMessage ( 'Publish assets' ) ,
238+ } ) ) ;
239+ }
210240 } finally {
211241 // Disable verbose output and clear callback after deploy
212242 switchableIoHost ?. setVerbose ( false ) ;
@@ -239,8 +269,8 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
239269 } , [ preflight . phase , preflight . cdkToolkitWrapper , logger , skipPreflight ] ) ;
240270
241271 const steps = useMemo (
242- ( ) => ( skipPreflight ? [ deployStep ] : [ ...preflight . steps , deployStep ] ) ,
243- [ preflight . steps , deployStep , skipPreflight ]
272+ ( ) => ( skipPreflight ? [ publishAssetsStep , deployStep ] : [ ...preflight . steps , publishAssetsStep , deployStep ] ) ,
273+ [ preflight . steps , publishAssetsStep , deployStep , skipPreflight ]
244274 ) ;
245275
246276 const phase : DeployPhase = useMemo ( ( ) => {
@@ -307,6 +337,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
307337 hasTokenExpiredError : combinedTokenExpiredError ,
308338 hasCredentialsError : preflight . hasCredentialsError ,
309339 isComplete,
340+ hasStartedCfn,
310341 logFilePath : logger . logFilePath ,
311342 missingCredentials : preflight . missingCredentials ,
312343 startDeploy,
0 commit comments