diff --git a/src/core/project/backends/cdk.test.ts b/src/core/project/backends/cdk.test.ts index 33f509877..f152b528b 100644 --- a/src/core/project/backends/cdk.test.ts +++ b/src/core/project/backends/cdk.test.ts @@ -628,8 +628,18 @@ describe("CdkBackend.resolveDeployedResources", () => { const resources = await subject.backend.resolveDeployedResources(input, { target: TARGET }); expect(resources).toEqual([ - { resourceType: "runtime", name: "checkout_agent", id: "checkout_agent-AbCdEf1234" }, - { resourceType: "harness", name: "support_agent", id: "support_agent-AbCdEf1234" }, + { + resourceType: "runtime", + name: "checkout_agent", + id: "checkout_agent-AbCdEf1234", + target: TARGET, + }, + { + resourceType: "harness", + name: "support_agent", + id: "support_agent-AbCdEf1234", + target: TARGET, + }, ]); expect(subject.stackReads).toHaveLength(1); }); diff --git a/src/core/project/backends/cdk.ts b/src/core/project/backends/cdk.ts index 78b5cac8d..516ce0aa9 100644 --- a/src/core/project/backends/cdk.ts +++ b/src/core/project/backends/cdk.ts @@ -3,10 +3,10 @@ import { join } from "node:path"; import type { Stack } from "@aws-sdk/client-cloudformation"; import { MalformedServiceResponseError, ProjectStateError } from "../../../errors/errors"; import type { - DeployedProjectResource, DeployResult, Project, ProjectEvent, + ResolvedDeployedResource, } from "../../../handlers/project/types"; import { FsReadWriteJson, @@ -53,7 +53,7 @@ type StackDescriber = typeof describeStack; function findDeployedResourceId( stack: Stack, - input: Pick, + input: Pick, ): string | undefined { if (!stack.StackName) return undefined; const exportResourceName = input.name.replaceAll("_", "-"); @@ -265,7 +265,7 @@ export class CdkBackend implements ProjectBackend { public async resolveDeployedResources( project: Project, input: ResolveDeployedResourcesBackendInput, - ): Promise { + ): Promise { const { target } = input; const deployedState = await readDeployedState(this.json, project.rootPath); const stackArn = deployedState.targets[target.name]?.stackArn; @@ -291,7 +291,7 @@ export class CdkBackend implements ProjectBackend { ]; return resources.flatMap((resource) => { const id = findDeployedResourceId(stack, resource); - return id ? [{ ...resource, id }] : []; + return id ? [{ ...resource, id, target }] : []; }); } diff --git a/src/core/project/backends/types.ts b/src/core/project/backends/types.ts index 77c2b0751..dccb11da8 100644 --- a/src/core/project/backends/types.ts +++ b/src/core/project/backends/types.ts @@ -1,8 +1,8 @@ import type { - DeployedProjectResource, DeployResult, Project, ProjectEvent, + ResolvedDeployedResource, TeardownConfirmationHandler, } from "../../../handlers/project/types"; import type { AwsDeploymentTarget } from "../../../projectSchemas/aws-targets"; @@ -25,5 +25,5 @@ export interface ProjectBackend { resolveDeployedResources( project: Project, input: ResolveDeployedResourcesBackendInput, - ): Promise; + ): Promise; } diff --git a/src/core/project/manager.tsx b/src/core/project/manager.tsx index 45b99d4d6..4f1b016fb 100644 --- a/src/core/project/manager.tsx +++ b/src/core/project/manager.tsx @@ -889,7 +889,10 @@ export class FsProjectManager implements ProjectManager { const resource = resolved.resources.find( ({ resourceType, name }) => resourceType === input.resourceType && name === input.name, ); - if (resource) return { id: resource.id, target: resolved.target }; + // The declared target wins over the copy on the item: the manager resolved it + // from aws-targets.json, and both invoke handlers pin the AWS region off this + // value, so trusting a backend's echo would let it redirect the call. + if (resource) return { ...resource, target: resolved.target }; const label = input.resourceType === "runtime" ? "Runtime" : "Harness"; throw new ProjectStateError( diff --git a/src/handlers/project/invoke/index.test.tsx b/src/handlers/project/invoke/index.test.tsx index d5fb86b20..91ce9dd4f 100644 --- a/src/handlers/project/invoke/index.test.tsx +++ b/src/handlers/project/invoke/index.test.tsx @@ -85,11 +85,13 @@ function backend() { resourceType: "runtime" as const, name, id: RUNTIME_ID, + target: input.target, })), ...project.spec.harnesses.map(({ name }) => ({ resourceType: "harness" as const, name, id: HARNESS_ID, + target: input.target, })), ]; }, diff --git a/src/handlers/project/invoke/invoke.screen.test.tsx b/src/handlers/project/invoke/invoke.screen.test.tsx index a7c21e53d..30b6a49dc 100644 --- a/src/handlers/project/invoke/invoke.screen.test.tsx +++ b/src/handlers/project/invoke/invoke.screen.test.tsx @@ -7,7 +7,7 @@ import type { import { ProjectSpecSchema } from "../../../projectSchemas/project"; import { ProjectKey } from "../../../router"; import { cleanupScreens, renderScreen, TestCoreClient, waitForText } from "../../../testing"; -import type { DeployedProjectResource, Project } from "../types"; +import type { Project, ResolvedDeployedResource } from "../types"; afterEach(cleanupScreens); @@ -46,14 +46,16 @@ function endpoint(name: string): AgentRuntimeEndpoint { const TARGET = { name: "default", account: "111122223333", region: "eu-west-1" } as const; -const DEPLOYED_RESOURCES: DeployedProjectResource[] = [ - { resourceType: "runtime", name: "checkout", id: "runtime-123" }, - { resourceType: "harness", name: "support", id: "harness-123" }, +const DEPLOYED_RESOURCES: ResolvedDeployedResource[] = [ + { resourceType: "runtime", name: "checkout", id: "runtime-123", target: TARGET }, + { resourceType: "harness", name: "support", id: "harness-123", target: TARGET }, ]; -function core(resources: DeployedProjectResource[] = DEPLOYED_RESOURCES): TestCoreClient { +function core(resources: ResolvedDeployedResource[] = DEPLOYED_RESOURCES): TestCoreClient { const value = new TestCoreClient(); value.projectManager.resolveDeployedResource = async (_project, input) => ({ + resourceType: input.resourceType, + name: input.name, id: input.resourceType === "runtime" ? "runtime-123" : "harness-123", target: TARGET, }); @@ -76,7 +78,7 @@ function core(resources: DeployedProjectResource[] = DEPLOYED_RESOURCES): TestCo describe("project invoke picker", () => { test("lists only resources present in the deployed target", async () => { const screen = renderScreen("/agentcore/project/invoke", { - core: core([{ resourceType: "harness", name: "support", id: "harness-123" }]), + core: core([{ resourceType: "harness", name: "support", id: "harness-123", target: TARGET }]), withContext: (ctx) => ctx.withValue(ProjectKey, project), }); diff --git a/src/handlers/project/types.ts b/src/handlers/project/types.ts index 8fad8a811..5b0b2e7b2 100644 --- a/src/handlers/project/types.ts +++ b/src/handlers/project/types.ts @@ -145,19 +145,15 @@ export type ResolveDeployedResourcesInput = { target: string; }; -export type DeployedProjectResource = { +export type ResolvedDeployedResource = { resourceType: ProjectInvokableResource; name: string; id: string; -}; - -export type ResolvedDeployedResource = { - id: string; target: AwsDeploymentTarget; }; export type ResolvedDeployedResources = { - resources: DeployedProjectResource[]; + resources: ResolvedDeployedResource[]; target: AwsDeploymentTarget; };