Skip to content

Commit fb6230f

Browse files
authored
fix: update deploy UI to add a publishing assets step before deploying (aws#69)
1 parent 82e95ac commit fb6230f

3 files changed

Lines changed: 44 additions & 12 deletions

File tree

src/cli/tui/components/DeployStatus.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export function DeployStatus({ messages, isComplete, hasError }: DeployStatusPro
168168

169169
return (
170170
<Box flexDirection="column" borderStyle="round" borderColor="gray" paddingX={1} minWidth={50}>
171-
<GradientText text="Deploy to AWS" />
171+
<GradientText text="Deploying to AWS" />
172172
{progress && (
173173
<Box marginTop={1}>
174174
<ProgressBar current={progress.current} total={progress.total} />

src/cli/tui/screens/deploy/DeployScreen.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function DeployScreen({ isInteractive, onExit, autoConfirm, onNavigate, p
4747
hasTokenExpiredError,
4848
hasCredentialsError,
4949
isComplete,
50+
hasStartedCfn,
5051
logFilePath,
5152
missingCredentials,
5253
startDeploy,
@@ -176,8 +177,8 @@ export function DeployScreen({ isInteractive, onExit, autoConfirm, onNavigate, p
176177

177178
const targetDisplay = context?.awsTargets.map(t => `${t.region}:${t.account}`).join(', ');
178179

179-
// Show deploy status box during deploy phase (replaces "Deploy to AWS" step)
180-
const showDeployStatus = phase === 'deploying' || isComplete;
180+
// Show deploy status box once CloudFormation has started (after asset publishing)
181+
const showDeployStatus = hasStartedCfn || isComplete;
181182

182183
// Filter out "Deploy to AWS" step when deploy status box is showing
183184
const displaySteps = showDeployStatus ? steps.filter(s => s.label !== 'Deploy to AWS') : steps;

src/cli/tui/screens/deploy/useDeployFlow.ts

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

Comments
 (0)