From 1f434145e2c54988c44408b1f2fcad0a5e5c4318 Mon Sep 17 00:00:00 2001 From: notgitika Date: Mon, 17 Aug 2026 14:27:15 -0400 Subject: [PATCH] fix(project): build a project that has no deployment targets `project create` scaffolds `agentcore/aws-targets.json` as an empty list, but the generated CDK app threw on an empty list and synthesized one stack per target, so a freshly created project had nothing to synthesize and `project build` failed: AgentCore CDK synthesis failed: No deployment targets configured. Please define targets in agentcore/aws-targets.json Build does not need to know where a project deploys. An empty target list now synthesizes a single environment-agnostic stack, which resolves its account and region from CloudFormation pseudo-parameters instead of pinning them at synth time. That is enough to compile the app and produce a template, which is what build is for. The fallback stack is named `AgentCore-` and carries no `agentcore:target-name` tag, so it is not a deploy candidate: only a stack synthesized for a real target is tagged, and selecting on that tag is how a stack is chosen to ship. A project that does configure targets is unaffected, still synthesizing one environment-pinned, target-tagged stack per entry. --- src/assets/cdk/bin/cdk.ts | 29 ++++++++++++++++++++--------- src/core/project/manager.tsx | 5 +++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/assets/cdk/bin/cdk.ts b/src/assets/cdk/bin/cdk.ts index c8450e3a9..83e54bb4a 100644 --- a/src/assets/cdk/bin/cdk.ts +++ b/src/assets/cdk/bin/cdk.ts @@ -110,9 +110,12 @@ async function main() { const spec = await configIO.readProjectSpec(); const targets = await configIO.readAWSDeploymentTargets(); - if (targets.length === 0) { - throw new Error('No deployment targets configured. Please define targets in agentcore/aws-targets.json'); - } + + // `project build` runs before a project has anywhere to deploy, so an empty target + // list is not an error: it synthesizes a single environment-agnostic stack, which is + // enough to typecheck the app and produce a template. Only a stack synthesized for a + // real target is a deploy candidate; the target tag below is what marks one. + const stackTargets: (AwsDeploymentTarget | undefined)[] = targets.length > 0 ? targets : [undefined]; const specAny: SpecWithLatestFields = spec; const projectRoot = path.resolve(configRoot, '..'); @@ -131,15 +134,19 @@ async function main() { const app = new App(); - for (const target of targets) { - const env = toEnvironment(target); - const stackName = toStackName(spec.name, target.name); + for (const target of stackTargets) { + // An environment-agnostic stack resolves its account and region from CloudFormation + // pseudo-parameters at deploy time instead of pinning them at synth time. + const env = target ? toEnvironment(target) : undefined; + const stackName = target ? toStackName(spec.name, target.name) : `AgentCore-${sanitize(spec.name)}`; // Extract credentials from deployed state for this target const targetState = (deployedState as Record)?.targets as | Record> | undefined; - const targetResources = targetState?.[target.name]?.resources as Record | undefined; + const targetResources = target + ? (targetState?.[target.name]?.resources as Record | undefined) + : undefined; const credentials = targetResources?.credentials as | Record | undefined; @@ -191,10 +198,14 @@ async function main() { harnesses: harnessConfigs.length > 0 ? harnessConfigs : undefined, paymentSpec, env, - description: `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`, + description: target + ? `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})` + : `AgentCore stack for ${spec.name} (no deployment target configured)`, + // Only a stack synthesized for a real target carries the target tag, which is + // how deploy selects the stack to ship. tags: { 'agentcore:project-name': spec.name, - 'agentcore:target-name': target.name, + ...(target ? { 'agentcore:target-name': target.name } : {}), }, }); } diff --git a/src/core/project/manager.tsx b/src/core/project/manager.tsx index 58351ff3b..238bd9976 100644 --- a/src/core/project/manager.tsx +++ b/src/core/project/manager.tsx @@ -262,6 +262,11 @@ export class FsProjectManager implements ProjectManager { // The generated package.json defines `cdk` as "npm run build && cdk", so this // single command compiles the app and then synthesizes it. Synthesis needs no // AWS credentials: each stack's environment comes from aws-targets.json. + // + // Build deliberately does not require a deployment target. A freshly created + // project has none, and building is how the user first typechecks their agent, so + // the generated app synthesizes one environment-agnostic stack when the list is + // empty. Only deploying somewhere needs a real target. yield { message: "Synthesizing CloudFormation templates" }; await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir); }