From ae40682a5ee4c175a967654864e84ef1d8a17619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Aguilar?= Date: Mon, 18 May 2026 22:33:13 -0500 Subject: [PATCH] fix(deploy): respect TUI target selection in deploy flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TUI target selector (rendered when aws-targets.json has 2+ targets) captures the user's choice in awsConfig.selectedTargetIndices, but nothing downstream consumed it. validateProject() always loaded every target from aws-targets.json, so: 1. The Deploy header showed all targets (e.g. 'Target: us-east-1:123, eu-west-1:456') even when the user picked only one. 2. Worse, downstream code reads context.awsTargets[0] in several places (useDeployFlow.ts:221,626,644-645; preflight.ts:265), which silently routed deploy actions to the first target in the file rather than the one the user actually selected. Fix: - Plumb selectedTargets through DeployScreen -> useDeployFlow -> useCdkPreflight as an optional PreflightOptions field. - After validateProject(), if selectedTargets is provided, filter preflightContext.awsTargets to only those names. - DeployScreen derives selectedTargets from awsConfig (selectedTargetIndices into availableTargets). CLI mode (agentcore deploy --target X) is unaffected — it already filters via options.target before reaching this code path. Closes #1267 --- src/cli/tui/hooks/useCdkPreflight.ts | 34 +++++++++++++++++++-- src/cli/tui/screens/deploy/DeployScreen.tsx | 15 ++++++++- src/cli/tui/screens/deploy/useDeployFlow.ts | 11 +++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/cli/tui/hooks/useCdkPreflight.ts b/src/cli/tui/hooks/useCdkPreflight.ts index 4602c1c14..b5a2c39b9 100644 --- a/src/cli/tui/hooks/useCdkPreflight.ts +++ b/src/cli/tui/hooks/useCdkPreflight.ts @@ -1,6 +1,6 @@ import { ConfigIO, SecureCredentials } from '../../../lib'; import { AwsCredentialsError } from '../../../lib/errors/types'; -import type { DeployedState } from '../../../schema'; +import type { AwsDeploymentTarget, DeployedState } from '../../../schema'; import { applyTargetRegionToEnv } from '../../aws'; import { validateAwsCredentials } from '../../aws/account'; import { type CdkToolkitWrapper, type SwitchableIoHost, createSwitchableIoHost } from '../../cdk/toolkit-lib'; @@ -52,6 +52,14 @@ export interface PreflightOptions { isInteractive?: boolean; /** Skip identity provider check (for plan command which only synthesizes) */ skipIdentityCheck?: boolean; + /** + * Subset of targets the user picked in the TUI target selector. When provided, + * the preflight context's awsTargets is filtered to only these targets, so + * downstream deploy steps and the displayed Target header reflect what was + * actually selected. When omitted, all configured targets are used (CLI mode + * already filters via --target before reaching here). + */ + selectedTargets?: AwsDeploymentTarget[]; } export interface PreflightResult { @@ -113,7 +121,7 @@ const IDENTITY_STEP: Step = { label: LABEL_API_KEY, status: 'pending' }; const BOOTSTRAP_STEP: Step = { label: 'Bootstrap AWS environment', status: 'pending' }; export function useCdkPreflight(options: PreflightOptions): PreflightResult { - const { logger, isInteractive = false, skipIdentityCheck = false } = options; + const { logger, isInteractive = false, skipIdentityCheck = false, selectedTargets } = options; // Create switchable ioHost - starts silent, can be flipped to verbose for deploy const switchableIoHost = useMemo(() => createSwitchableIoHost(), []); @@ -301,6 +309,17 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { let preflightContext: PreflightContext; try { preflightContext = await validateProject(); + // Filter to only the targets the user picked in the TUI selector, + // if provided. Without this, the deploy header shows every target + // in aws-targets.json and downstream `awsTargets[0]` reads can + // resolve to a target the user did not select. See issue #1267. + if (selectedTargets && selectedTargets.length > 0) { + const selectedNames = new Set(selectedTargets.map(t => t.name)); + preflightContext = { + ...preflightContext, + awsTargets: preflightContext.awsTargets.filter(t => selectedNames.has(t.name)), + }; + } setContext(preflightContext); // Make aws-targets.json region authoritative for downstream SDK / CDK // toolkit-lib clients that bypass explicit region options. Restored on @@ -530,7 +549,16 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { return () => { process.off('unhandledRejection', handleUnhandledRejection); }; - }, [phase, logger, switchableIoHost, isInteractive, skipIdentityCheck, teardownConfirmed, restoreRegionEnv]); + }, [ + phase, + logger, + switchableIoHost, + isInteractive, + skipIdentityCheck, + teardownConfirmed, + restoreRegionEnv, + selectedTargets, + ]); // Handle identity-setup phase (after user provides credentials) useEffect(() => { diff --git a/src/cli/tui/screens/deploy/DeployScreen.tsx b/src/cli/tui/screens/deploy/DeployScreen.tsx index 319f970ec..0d1c3a3d1 100644 --- a/src/cli/tui/screens/deploy/DeployScreen.tsx +++ b/src/cli/tui/screens/deploy/DeployScreen.tsx @@ -67,6 +67,19 @@ export function DeployScreen({ // Load MCP spec for ResourceGraph const configIO = useMemo(() => new ConfigIO(), []); + // Resolve which targets the user picked in the AWS target selector. When the + // project has a single target, the selector skips the picker phase and pre- + // selects index 0; with multiple targets the user explicitly selects one (or + // several) and we pass that filter down so the deploy actually targets the + // right environments. See issue #1267. + const selectedTargets = useMemo( + () => + awsConfig.selectedTargetIndices + .map(i => awsConfig.availableTargets[i]) + .filter((t): t is NonNullable => t !== undefined), + [awsConfig.selectedTargetIndices, awsConfig.availableTargets] + ); + const { phase, steps, @@ -97,7 +110,7 @@ export function DeployScreen({ useEnvLocalCredentials, useManualCredentials, skipCredentials, - } = useDeployFlow({ preSynthesized, isInteractive, diffMode }); + } = useDeployFlow({ preSynthesized, isInteractive, diffMode, selectedTargets }); const allSuccess = !hasError && isComplete; const skipPreflight = !!preSynthesized; diff --git a/src/cli/tui/screens/deploy/useDeployFlow.ts b/src/cli/tui/screens/deploy/useDeployFlow.ts index cdeff0915..a3f66b54b 100644 --- a/src/cli/tui/screens/deploy/useDeployFlow.ts +++ b/src/cli/tui/screens/deploy/useDeployFlow.ts @@ -1,4 +1,5 @@ import { ConfigIO } from '../../../../lib'; +import type { AwsDeploymentTarget } from '../../../../schema'; import type { CdkToolkitWrapper, DeployMessage, SwitchableIoHost } from '../../../cdk/toolkit-lib'; import { buildDeployedState, @@ -65,6 +66,12 @@ interface DeployFlowOptions { isInteractive?: boolean; /** Run CDK diff instead of deploy */ diffMode?: boolean; + /** + * Subset of targets the user picked in the TUI target selector. When set, + * the preflight context's awsTargets is filtered to only these targets. + * See issue #1267. + */ + selectedTargets?: AwsDeploymentTarget[]; } interface DeployFlowState { @@ -118,14 +125,14 @@ interface DeployFlowState { } export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState { - const { preSynthesized, isInteractive = false, diffMode = false } = options; + const { preSynthesized, isInteractive = false, diffMode = false, selectedTargets } = options; const skipPreflight = !!preSynthesized; // Create logger once for the entire deploy flow const [logger] = useState(() => new ExecLogger({ command: 'deploy' })); // Always call the hook (React rules), but we won't use it when preSynthesized is provided - const preflight = useCdkPreflight({ logger, isInteractive }); + const preflight = useCdkPreflight({ logger, isInteractive, selectedTargets }); // Use pre-synthesized values when provided, otherwise use preflight values const cdkToolkitWrapper = preSynthesized?.cdkToolkitWrapper ?? preflight.cdkToolkitWrapper;