* Run Docker container with standard I/O. * Stdout/stderr are collected - stdout is returned for backwards compatibility. * Primary output method is the mounted output file.
( dockerArgs: string[], params: I, context: ExecutionContext, timeoutSeconds: number, stdinJson?: boolean, detached?: boolean, )
| 263 | * Primary output method is the mounted output file. |
| 264 | */ |
| 265 | async function runDockerWithStandardIO<I, O>( |
| 266 | dockerArgs: string[], |
| 267 | params: I, |
| 268 | context: ExecutionContext, |
| 269 | timeoutSeconds: number, |
| 270 | stdinJson?: boolean, |
| 271 | detached?: boolean, |
| 272 | ): Promise<string> { |
| 273 | const dockerPath = await resolveDockerPath(context); |
| 274 | return new Promise<string>((resolve, reject) => { |
| 275 | const stdoutEmitter = createTerminalChunkEmitter(context, 'stdout'); |
| 276 | const stderrEmitter = createTerminalChunkEmitter(context, 'stderr'); |
| 277 | |
| 278 | const timeout = setTimeout(() => { |
| 279 | proc.kill(); |
| 280 | reject(new TimeoutError(`Docker container timed out after ${timeoutSeconds}s`, timeoutSeconds * 1000, { |
| 281 | details: { dockerArgs: formatArgs(dockerArgs) }, |
| 282 | })); |
| 283 | }, timeoutSeconds * 1000); |
| 284 | |
| 285 | const proc = spawn(dockerPath, dockerArgs, { |
| 286 | stdio: ['pipe', 'pipe', 'pipe'], |
| 287 | env: process.env, |
| 288 | }); |
| 289 | |
| 290 | |
| 291 | |
| 292 | let stdout = ''; |
| 293 | let stderr = ''; |
| 294 | |
| 295 | proc.stdout.on('data', (data) => { |
| 296 | stdoutEmitter(data); |
| 297 | const chunk = data.toString(); |
| 298 | stdout += chunk; // Capture for fallback |
| 299 | |
| 300 | // Send to log collector (which has chunking support) |
| 301 | const logEntry = { |
| 302 | runId: context.runId, |
| 303 | nodeRef: context.componentRef, |
| 304 | stream: 'stdout' as const, |
| 305 | level: 'info' as const, |
| 306 | message: chunk, |
| 307 | timestamp: new Date().toISOString(), |
| 308 | }; |
| 309 | context.logCollector?.(logEntry); |
| 310 | |
| 311 | // NOTE: We intentionally do NOT emit stdout as trace progress events. |
| 312 | // Output data is written to /shipsec-output/result.json by the container. |
| 313 | // Stdout should only contain logs and progress messages from the component. |
| 314 | }); |
| 315 | |
| 316 | proc.stderr.on('data', (data) => { |
| 317 | stderrEmitter(data); |
| 318 | const chunk = data.toString(); |
| 319 | stderr += chunk; |
| 320 | const logEntry = { |
| 321 | runId: context.runId, |
| 322 | nodeRef: context.componentRef, |
no test coverage detected