@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
33import { mkdir , mkdtemp , readFile , rm , writeFile } from "node:fs/promises" ;
44import { tmpdir } from "node:os" ;
55import { join , resolve } from "node:path" ;
6- import { InputValidationError } from "../../errors" ;
6+ import { InputValidationError , InvalidEnvironmentError } from "../../errors" ;
77import type { DevEvent , DevServerInput } from "../../handlers/project/dev/types" ;
88import {
99 MissingToolError ,
@@ -125,7 +125,7 @@ function commandCall(calls: ProcessCall[], operation: "build" | "run"): ProcessC
125125}
126126
127127describe ( "ContainerDevRunner" , ( ) => {
128- test ( "builds with a widened context and keeps build arg values out of argv " , async ( ) => {
128+ test ( "builds with a widened context and redacts build arg values from errors " , async ( ) => {
129129 const projectRuntime = runtime ( {
130130 buildContextPath : "." ,
131131 dockerfile : "docker/Dockerfile" ,
@@ -148,18 +148,17 @@ describe("ContainerDevRunner", () => {
148148 "-t" ,
149149 imageTag ( root ) ,
150150 "--build-arg" ,
151- "AGENT_NAME" ,
151+ "AGENT_NAME=hello-world " ,
152152 "--build-arg" ,
153- "TARGET" ,
153+ "TARGET=development " ,
154154 "." ,
155155 ] ) ;
156156 expect ( build . options . cwd ) . toBe ( root ) ;
157- expect ( build . options . env ) . toMatchObject ( {
158- AGENT_NAME : "hello-world" ,
159- TARGET : "development" ,
160- } ) ;
161- expect ( build . command . join ( " " ) ) . not . toContain ( "hello-world" ) ;
162- expect ( build . command . join ( " " ) ) . not . toContain ( "development" ) ;
157+ expect ( build . options . env ) . toBe ( process . env ) ;
158+ expect ( build . options . redactedCommand ) . toContain ( "AGENT_NAME=<redacted>" ) ;
159+ expect ( build . options . redactedCommand ) . toContain ( "TARGET=<redacted>" ) ;
160+ expect ( build . options . redactedCommand ?. join ( " " ) ) . not . toContain ( "hello-world" ) ;
161+ expect ( build . options . redactedCommand ?. join ( " " ) ) . not . toContain ( "development" ) ;
163162
164163 const dockerignore = await readFile ( join ( root , ".dockerignore" ) , "utf8" ) ;
165164 for ( const pattern of [ ".env" , "**/.env" , "**/node_modules" , "agentcore/" ] ) {
@@ -190,21 +189,17 @@ describe("ContainerDevRunner", () => {
190189 "-p" ,
191190 `127.0.0.1:3000:${ containerPort } ` ,
192191 "-e" ,
193- "API_KEY" ,
192+ "API_KEY=super-secret " ,
194193 "-e" ,
195- " PORT" ,
194+ ` PORT= ${ containerPort } ` ,
196195 "-e" ,
197- "LOCAL_DEV" ,
198- ...( protocol === "MCP" ? [ "-e" , "FASTMCP_PORT" ] : [ ] ) ,
196+ "LOCAL_DEV=1 " ,
197+ ...( protocol === "MCP" ? [ "-e" , "FASTMCP_PORT=8000 " ] : [ ] ) ,
199198 imageTag ( root ) ,
200199 ] ) ;
201- expect ( run . options . env ) . toMatchObject ( {
202- API_KEY : "super-secret" ,
203- PORT : String ( containerPort ) ,
204- LOCAL_DEV : "1" ,
205- } ) ;
206- expect ( run . options . env ?. FASTMCP_PORT ) . toBe ( protocol === "MCP" ? "8000" : undefined ) ;
207- expect ( run . command . join ( " " ) ) . not . toContain ( "super-secret" ) ;
200+ expect ( run . options . env ) . toBe ( process . env ) ;
201+ expect ( run . options . redactedCommand ) . toContain ( "API_KEY=<redacted>" ) ;
202+ expect ( run . options . redactedCommand ?. join ( " " ) ) . not . toContain ( "super-secret" ) ;
208203 } ) ;
209204
210205 test ( "preserves an existing build context .dockerignore" , async ( ) => {
@@ -237,6 +232,33 @@ describe("ContainerDevRunner", () => {
237232 expect ( containerName ( firstRoot ) ) . not . toBe ( containerName ( secondRoot ) ) ;
238233 } ) ;
239234
235+ test ( "limits image names to two consecutive underscores" , async ( ) => {
236+ const projectRuntime = runtime ( { name : "Hello___World" } ) ;
237+ const root = await projectRoot ( projectRuntime ) ;
238+ const { calls, runner } = harness ( ) ;
239+
240+ await collect ( runner . run ( input ( root , projectRuntime ) ) ) ;
241+
242+ expect ( commandCall ( calls , "build" ) . command ) . toContain (
243+ `agentcore-dev/hello__world-${ hashString ( resolve ( root ) ) } ` ,
244+ ) ;
245+ } ) ;
246+
247+ test ( "keeps app variables out of the container CLI environment" , async ( ) => {
248+ const projectRuntime = runtime ( ) ;
249+ const root = await projectRoot ( projectRuntime ) ;
250+ const { calls, runner } = harness ( ) ;
251+ const runInput = input ( root , projectRuntime ) ;
252+ runInput . env = { ...runInput . env , DOCKER_HOST : "tcp://application-value" } ;
253+
254+ await collect ( runner . run ( runInput ) ) ;
255+
256+ const run = commandCall ( calls , "run" ) ;
257+ expect ( run . command ) . toContain ( "DOCKER_HOST=tcp://application-value" ) ;
258+ expect ( run . options . env ) . toBe ( process . env ) ;
259+ expect ( run . options . redactedCommand ) . toContain ( "DOCKER_HOST=<redacted>" ) ;
260+ } ) ;
261+
240262 test ( "selects the first tool that supports container builds" , async ( ) => {
241263 const probes : Array < [ string , string [ ] | undefined ] > = [ ] ;
242264 const projectRuntime = runtime ( ) ;
@@ -286,6 +308,31 @@ describe("ContainerDevRunner", () => {
286308 expect ( commandCall ( calls , "build" ) . command [ 0 ] ) . toBe ( "finch" ) ;
287309 } ) ;
288310
311+ test ( "passes explicit build arg values to finch" , async ( ) => {
312+ const projectRuntime = runtime ( { customDockerBuildArgs : { AGENT_NAME : "hello-world" } } ) ;
313+ const root = await projectRoot ( projectRuntime ) ;
314+ const { calls, runner } = harness ( {
315+ available : async ( tool ) => tool === "finch" ,
316+ } ) ;
317+
318+ await collect ( runner . run ( input ( root , projectRuntime ) ) ) ;
319+
320+ expect ( commandCall ( calls , "build" ) . command ) . toContain ( "AGENT_NAME=hello-world" ) ;
321+ } ) ;
322+
323+ test ( "suggests initializing the Finch VM when its build probe fails" , async ( ) => {
324+ const projectRuntime = runtime ( ) ;
325+ const root = await projectRoot ( projectRuntime ) ;
326+ const { runner } = harness ( {
327+ available : async ( tool , probeArgs ) => tool === "finch" && probeArgs === undefined ,
328+ } ) ;
329+
330+ const promise = collect ( runner . run ( input ( root , projectRuntime ) ) ) ;
331+
332+ await expect ( promise ) . rejects . toBeInstanceOf ( InvalidEnvironmentError ) ;
333+ await expect ( promise ) . rejects . toThrow ( "finch vm init" ) ;
334+ } ) ;
335+
289336 test ( "throws a useful error when no container runtime is available" , async ( ) => {
290337 const projectRuntime = runtime ( ) ;
291338 const root = await projectRoot ( projectRuntime ) ;
0 commit comments