| 244 | } |
| 245 | |
| 246 | async function runProcessCapture({ |
| 247 | command, |
| 248 | args, |
| 249 | cwd, |
| 250 | env, |
| 251 | stdoutPath, |
| 252 | stderrPath, |
| 253 | }) { |
| 254 | const startedAt = Date.now(); |
| 255 | const child = spawn(command, args, { |
| 256 | cwd, |
| 257 | env, |
| 258 | stdio: ["ignore", "pipe", "pipe"], |
| 259 | }); |
| 260 | |
| 261 | const stdoutChunks = []; |
| 262 | const stderrChunks = []; |
| 263 | |
| 264 | if (child.stdout) { |
| 265 | child.stdout.on("data", (chunk) => { |
| 266 | stdoutChunks.push(chunk); |
| 267 | }); |
| 268 | } |
| 269 | |
| 270 | if (child.stderr) { |
| 271 | child.stderr.on("data", (chunk) => { |
| 272 | stderrChunks.push(chunk); |
| 273 | }); |
| 274 | } |
| 275 | |
| 276 | const outcome = await new Promise((resolve, reject) => { |
| 277 | child.once("error", reject); |
| 278 | child.once("close", (code, signal) => { |
| 279 | resolve({ code: code ?? 1, signal }); |
| 280 | }); |
| 281 | }); |
| 282 | |
| 283 | const stdoutText = Buffer.concat(stdoutChunks).toString("utf8"); |
| 284 | const stderrText = Buffer.concat(stderrChunks).toString("utf8"); |
| 285 | |
| 286 | if (stdoutPath) { |
| 287 | await writeText(stdoutPath, stdoutText); |
| 288 | } |
| 289 | if (stderrPath) { |
| 290 | await writeText(stderrPath, stderrText); |
| 291 | } |
| 292 | |
| 293 | return { |
| 294 | ...outcome, |
| 295 | durationMs: Date.now() - startedAt, |
| 296 | stdoutText, |
| 297 | stderrText, |
| 298 | }; |
| 299 | } |
| 300 | |
| 301 | function buildObservedUsageLine(target, scenario, usage) { |
| 302 | return JSON.stringify({ |