Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/handlers/keys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import { contextKey, globalFlag } from "../router";
// withRegion middleware can fall back to AWS_REGION / the shared config file.
export const RegionKey = globalFlag("region", "AWS region", z.string().optional());

// withRegion overwrites RegionKey with the fully-resolved region (flag -> env ->
// config -> DEFAULT_REGION) before any handler runs, so by the time a leaf reads
// ctx.value(RegionKey) it's never undefined. This key preserves whether --region
// was actually passed, for callers (like the v0 compat path) that must tell
// "user asked for a region" apart from "we picked one for them".
export const RegionExplicitKey = contextKey<boolean>("region.explicit");

export const DebugKey = globalFlag("debug", "debug logging", z.boolean().default(false));

export const JsonKey = globalFlag("json", "JSON output", z.boolean().default(false));
Expand Down
32 changes: 30 additions & 2 deletions src/handlers/project/deploy/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,13 @@ describe("project deploy handler", () => {
},
]);
expect(subject.io.stdout()).toBe("");
const regionWarning =
"--region is ignored for v0 projects; v0 uses the target's region from " +
"aws-targets.json, or AWS_REGION / your AWS profile.";
expect(subject.io.stderr()).toBe(
flag === "--json"
? ""
: "--region is ignored for v0 projects; v0 resolves region from aws-targets.json.\n" +
? regionWarning
: `${regionWarning}\n` +
"This is a v0 project; running the latest v0 AgentCore CLI deploy in compatibility mode.",
);
expect(
Expand All @@ -285,6 +288,31 @@ describe("project deploy handler", () => {
},
);

test("does not warn about --region when it was never passed", async () => {
const projectRoot = await inProjectWithTargets("[]");
const specPath = join(projectRoot, "agentcore", "agentcore.json");
const manifestPath = join(projectRoot, "agentcore", "cdk", "package.json");
await writeFile(specPath, JSON.stringify({ name: "orders", version: 1 }));
await writeFile(
manifestPath,
JSON.stringify({ dependencies: { "@aws/agentcore-cdk": "^0.1.0-alpha.19" } }),
);
const legacyDeploys: V0DeployInput[] = [];
const subject = await testDeployCommand({ outputs: {} }, [], {
runV0Deploy: async (input) => {
legacyDeploys.push(input);
return 0;
},
});

await subject.run(["--yes"]);

expect(legacyDeploys).toHaveLength(1);
expect(subject.io.stderr()).toBe(
"This is a v0 project; running the latest v0 AgentCore CLI deploy in compatibility mode.",
);
});

test("preserves the v0 deployer's non-zero exit code", async () => {
await inProjectWithTargets();
await writeFile(
Expand Down
42 changes: 18 additions & 24 deletions src/handlers/project/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "../../../router";
import { JsonRendererKey } from "../../../tui";
import { runWithProgress } from "../../../tui/progress";
import { JsonKey, RegionKey } from "../../keys";
import { JsonKey, RegionExplicitKey, RegionKey } from "../../keys";
import { renderJsonError } from "../../utils";
import type { DeployResult, Project, ProjectManager, TeardownConfirmationHandler } from "../types";

Expand Down Expand Up @@ -81,8 +81,7 @@ export const createDeployProjectHandler = (config: DeployProjectHandlerConfig) =
recordProjectResourceCounts(ctx, project);
const jsonOutput = ctx.require(JsonKey);
if (project.legacy) {
warnIfRegionIgnored(config, ctx, jsonOutput);
await delegateV0Deploy(config, {
await delegateV0Deploy(config, ctx, {
projectRoot: project.rootPath,
target: flags.target,
yes: flags.yes,
Expand Down Expand Up @@ -193,13 +192,11 @@ function withV0Deploy(config: DeployProjectHandlerConfig): Middleware {
handle: async (ctx, flags, args) => {
const projectRoot = enclosingProjectRoot(process.cwd());
if (projectRoot && (await isV0Project(projectRoot))) {
const jsonOutput = ctx.require(JsonKey);
warnIfRegionIgnored(config, ctx, jsonOutput);
await delegateV0Deploy(config, {
await delegateV0Deploy(config, ctx, {
projectRoot,
target: flags.target as string,
yes: flags.yes as boolean,
json: jsonOutput,
json: ctx.require(JsonKey),
});
return;
}
Expand All @@ -208,30 +205,27 @@ function withV0Deploy(config: DeployProjectHandlerConfig): Middleware {
});
}

// --region is a v1 global flag that still resolves (and defaults) for every
// command, but v0 deploys have no such flag -- they resolve region entirely
// from aws-targets.json, with env/profile as a fallback. An explicitly passed
// --region is otherwise silently discarded on this path, trading what used to
// be a loud "unknown option" failure for an invisible no-op.
function warnIfRegionIgnored(
config: DeployProjectHandlerConfig,
ctx: Context,
jsonOutput: boolean,
): void {
if (ctx.value(RegionKey) !== undefined && !jsonOutput) {
config.io.stderr.write(
"--region is ignored for v0 projects; v0 resolves region from aws-targets.json.\n",
);
}
}

async function delegateV0Deploy(
config: DeployProjectHandlerConfig,
ctx: Context,
input: Parameters<V0DeployRunner>[0],
): Promise<void> {
// A migration stopped while writing the files leaves a v0 spec beside a v1 CDK
// app; the v0 deployer mustn't deploy that.
await assertNoPendingMigration(input.projectRoot, input.target);
// --region is a v1 global flag that still resolves (and defaults) for every
// command, but v0 deploys have no such flag -- they resolve region entirely
// from aws-targets.json's saved target, falling back to AWS_REGION or the AWS
// profile. An explicitly passed --region is otherwise silently discarded on
// this path, trading what used to be a loud "unknown option" failure for an
// invisible no-op; warn regardless of --json since stderr is informational,
// not part of the JSON contract.
if (ctx.value(RegionExplicitKey)) {
config.io.stderr.write(
"--region is ignored for v0 projects; v0 uses the target's region from " +
"aws-targets.json, or AWS_REGION / your AWS profile.\n",
);
}
if (!input.json) {
config.io.stderr.write(
"This is a v0 project; running the latest v0 AgentCore CLI deploy in compatibility mode.\n",
Expand Down
4 changes: 3 additions & 1 deletion src/handlers/project/dev/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,9 @@ describe("project dev harnesses", () => {
},
]);
expect(subject.deployments).toEqual([]);
expect(subject.io.stderr()).toContain("running the latest v0 AgentCore CLI deploy");
expect(subject.io.stderr()).toBe(
"This is a v0 project; running the latest v0 AgentCore CLI deploy in compatibility mode.",
);
});

test("harness dev's deploy refuses a target a migration hasn't finished", async () => {
Expand Down
13 changes: 10 additions & 3 deletions src/middleware/withRegion.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { RegionKey } from "../handlers/keys";
import { RegionExplicitKey, RegionKey } from "../handlers/keys";
import { resolveRegion } from "../core/region";
import { type Middleware } from "../router";

// withRegion resolves the effective AWS region and pins it onto the context under
// RegionKey so the whole app uses a single, consistent value. There is always a
// region: it falls back to DEFAULT_REGION when nothing else is configured.
// region: it falls back to DEFAULT_REGION when nothing else is configured. It also
// records, under RegionExplicitKey, whether the raw --region flag was present
// before this resolution -- the only place that distinction is still observable.
export function withRegion(): Middleware {
return (h) => ({
name: () => h.name(),
Expand All @@ -14,8 +16,13 @@ export function withRegion(): Middleware {
doesSupportTui: () => h.doesSupportTui(),
children: () => h.children(),
handle: async (ctx, flags, args) => {
const explicit = ctx.value(RegionKey) !== undefined;
const region = await resolveRegion(ctx.value(RegionKey));
await h.handle(ctx.withValue(RegionKey, region), flags, args);
await h.handle(
ctx.withValue(RegionKey, region).withValue(RegionExplicitKey, explicit),
flags,
args,
);
},
});
}
Loading