Skip to content

Commit 70f25ad

Browse files
committed
feat(dev): wire project dev to the CodeZip runner
Minimal headless wiring so the runner is drivable from the CLI: resolve the project via middleware, pick the first (or --runtime) runtime, spawn it on --port (default 8080), stream logs to stderr until Ctrl+C. The richer dev experience (Container, .env.local, OTEL, Inspector) layers on in the later PRs of the stack.
1 parent 970d944 commit 70f25ad

4 files changed

Lines changed: 77 additions & 8 deletions

File tree

src/handlers/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function createRootHandler(core: Core, config: RootHandlerConfig): Router
5050
root.handler(createGatewayHandler(core, io));
5151
root.handler(createEvalHandler(core, io));
5252
root.handler(createConfigHandler());
53-
root.handler(createProjectHandler({ projectManager: core.projectManager }));
53+
root.handler(createProjectHandler({ projectManager: core.projectManager, io }));
5454

5555
// Invoking with no subcommand launches the interactive TUI.
5656
root.default(renderTui(core, io));

src/handlers/project/dev/index.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,63 @@
1-
import { createHandler } from "../../../router";
2-
import { NotImplementedError } from "../../../errors";
1+
import z from "zod";
2+
import { InputValidationError } from "../../../errors";
3+
import type { AppIO } from "../../../io";
4+
import { CodeZipDevRunner } from "../../../core/dev";
5+
import { createHandler, flag, LoggerKey, ProjectKey } from "../../../router";
6+
import type { DevRunner } from "./types";
37

4-
export const createDevProjectHandler = () =>
8+
type DevProjectHandlerConfig = {
9+
io: AppIO;
10+
/** Injectable for tests; defaults to the CodeZip runner. */
11+
runner?: DevRunner;
12+
};
13+
14+
// TEMPORARY WIRING: runs the first (or --runtime) CodeZip runtime headlessly,
15+
// streaming logs to stderr until Ctrl+C. The full dev experience (Container,
16+
// .env.local, OTEL, Agent Inspector) arrives with the later PRs in the stack.
17+
export const createDevProjectHandler = (config: DevProjectHandlerConfig) =>
518
createHandler({
619
name: "dev",
720
description: "run the project locally for development",
8-
handle: async () => {
9-
throw new NotImplementedError("agentcore project dev is not implemented yet");
21+
flags: [
22+
flag("runtime", "runtime to run (defaults to the only runtime)", z.string().optional()),
23+
flag("port", "port for the dev server", z.coerce.number().int().min(1).default(8080)),
24+
],
25+
handle: async (ctx, flags) => {
26+
const project = ctx.require(ProjectKey);
27+
const logger = ctx.require(LoggerKey);
28+
29+
const runtime = flags.runtime
30+
? project.runtimes.find((r) => r.name === flags.runtime)
31+
: project.runtimes[0];
32+
if (!runtime) {
33+
const available = project.runtimes.map((r) => r.name).join(", ") || "none";
34+
throw new InputValidationError(
35+
flags.runtime
36+
? `runtime "${flags.runtime}" not found in project (available: ${available})`
37+
: "the project has no runtimes; run `agentcore project add` to add one",
38+
);
39+
}
40+
41+
const runner = config.runner ?? new CodeZipDevRunner({ logger });
42+
const handle = await runner.start({
43+
runtime,
44+
projectRoot: project.rootPath,
45+
port: flags.port,
46+
onLog: (level, message) => config.io.stderr.write(`[${level}] ${message}\n`),
47+
});
48+
49+
config.io.stderr.write(
50+
`Dev server for "${runtime.name}" on port ${flags.port}. Ctrl+C to stop.\n`,
51+
);
52+
const interrupt = () => void handle.stop();
53+
process.once("SIGINT", interrupt);
54+
try {
55+
const exit = await handle.exited;
56+
if (exit.kind === "exited" && exit.code !== 0) {
57+
throw new InputValidationError(`dev server exited with code ${exit.code}`);
58+
}
59+
} finally {
60+
process.off("SIGINT", interrupt);
61+
}
1062
},
1163
});

src/handlers/project/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { createDevProjectHandler } from "./dev";
77
import { createDeployProjectHandler } from "./deploy";
88
import { createStatusProjectHandler } from "./status";
99
import { createBuildProjectHandler } from "./build";
10+
import type { AppIO } from "../../io";
1011
import type { ProjectManager } from "./types";
1112

1213
type ProjectHandlerConfig = {
1314
projectManager: ProjectManager;
15+
io: AppIO;
1416
};
1517

1618
export function createProjectHandler(config: ProjectHandlerConfig): Router {
@@ -26,7 +28,7 @@ export function createProjectHandler(config: ProjectHandlerConfig): Router {
2628
project.handler(createCreateProjectHandler({ projectManager: config.projectManager }));
2729
project.handler(inProject(createAddProjectHandler()));
2830
project.handler(inProject(createRemoveProjectHandler()));
29-
project.handler(inProject(createDevProjectHandler()));
31+
project.handler(inProject(createDevProjectHandler({ io: config.io })));
3032
project.handler(inProject(createDeployProjectHandler()));
3133
project.handler(inProject(createStatusProjectHandler()));
3234
project.handler(inProject(createBuildProjectHandler()));

src/handlers/project/project.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async function run(args: string[]): Promise<void> {
2020
await root.route(["node", "agentcore", "project", ...args]);
2121
}
2222

23-
describe.each(["add", "remove", "dev", "deploy", "status", "build"])("project %s", (command) => {
23+
describe.each(["add", "remove", "deploy", "status", "build"])("project %s", (command) => {
2424
test("refuses to run outside a project", async () => {
2525
await inTempDirectory();
2626
await expect(run([command])).rejects.toThrow(/no AgentCore project found/);
@@ -35,6 +35,21 @@ describe.each(["add", "remove", "dev", "deploy", "status", "build"])("project %s
3535
});
3636
});
3737

38+
describe("project dev", () => {
39+
test("refuses to run outside a project", async () => {
40+
await inTempDirectory();
41+
await expect(run(["dev"])).rejects.toThrow(/no AgentCore project found/);
42+
});
43+
44+
test("rejects an unknown --runtime", async () => {
45+
const directory = await inTempDirectory();
46+
await run(["create", "--project-name", "MyAgent"]);
47+
process.chdir(join(directory, "MyAgent"));
48+
49+
await expect(run(["dev", "--runtime", "nope"])).rejects.toThrow(/not found in project/);
50+
});
51+
});
52+
3853
const originalCwd = process.cwd();
3954
const tempDirectories: string[] = [];
4055

0 commit comments

Comments
 (0)