{"version":3,"file":"shell-output.d.ts","sourceRoot":"","sources":["../../../src/harness/utils/shell-output.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,cAAc,EAGd,KAAK,MAAM,EAEX,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,UAAU,GAAG,UAAU,CAAC;IAClG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAQD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWxD;AAED,wBAAsB,uBAAuB,CAC5C,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC,CAgGrD","sourcesContent":["import {\n\ttype ExecutionEnv,\n\ttype ExecutionEnvExecOptions,\n\tExecutionError,\n\terr,\n\tok,\n\ttype Result,\n\ttoError,\n} from \"../types.ts\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.ts\";\n\nexport interface ShellCaptureOptions extends Omit<ExecutionEnvExecOptions, \"onStdout\" | \"onStderr\"> {\n\tonChunk?: (chunk: string) => void;\n}\n\nexport interface ShellCaptureResult {\n\toutput: string;\n\texitCode: number | undefined;\n\tcancelled: boolean;\n\ttruncated: boolean;\n\tfullOutputPath?: string;\n}\n\nfunction toExecutionError(error: unknown): ExecutionError {\n\tif (error instanceof ExecutionError) return error;\n\tconst cause = toError(error);\n\treturn new ExecutionError(\"unknown\", cause.message, cause);\n}\n\nexport function sanitizeBinaryOutput(str: string): string {\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\tconst code = char.codePointAt(0);\n\t\t\tif (code === undefined) return false;\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\t\t\tif (code <= 0x1f) return false;\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n\nexport async function executeShellWithCapture(\n\tenv: ExecutionEnv,\n\tcommand: string,\n\toptions?: ShellCaptureOptions,\n): Promise<Result<ShellCaptureResult, ExecutionError>> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\tconst encoder = new TextEncoder();\n\n\tlet totalBytes = 0;\n\tlet fullOutputPath: string | undefined;\n\tlet writeChain: Promise<Result<void, ExecutionError>> = Promise.resolve(ok(undefined));\n\tlet captureError: ExecutionError | undefined;\n\n\tconst appendFullOutput = (text: string): void => {\n\t\tif (!fullOutputPath || captureError) return;\n\t\tconst path = fullOutputPath;\n\t\twriteChain = writeChain.then(async (previous) => {\n\t\t\tif (!previous.ok) return previous;\n\t\t\tconst appendResult = await env.appendFile(path, text, options?.abortSignal);\n\t\t\treturn appendResult.ok ? ok(undefined) : err(toExecutionError(appendResult.error));\n\t\t});\n\t};\n\n\tconst ensureFullOutputFile = (initialContent: string): void => {\n\t\tif (fullOutputPath || captureError) return;\n\t\twriteChain = writeChain.then(async (previous) => {\n\t\t\tif (!previous.ok) return previous;\n\t\t\tconst tempFile = await env.createTempFile({\n\t\t\t\tprefix: \"bash-\",\n\t\t\t\tsuffix: \".log\",\n\t\t\t\tabortSignal: options?.abortSignal,\n\t\t\t});\n\t\t\tif (!tempFile.ok) return err(toExecutionError(tempFile.error));\n\t\t\tfullOutputPath = tempFile.value;\n\t\t\tconst appendResult = await env.appendFile(tempFile.value, initialContent, options?.abortSignal);\n\t\t\treturn appendResult.ok ? ok(undefined) : err(toExecutionError(appendResult.error));\n\t\t});\n\t};\n\n\tconst onChunk = (chunk: string) => {\n\t\ttry {\n\t\t\ttotalBytes += encoder.encode(chunk).byteLength;\n\t\t\tconst text = sanitizeBinaryOutput(chunk).replace(/\\r/g, \"\");\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !fullOutputPath) {\n\t\t\t\tensureFullOutputFile(outputChunks.join(\"\") + text);\n\t\t\t} else {\n\t\t\t\tappendFullOutput(text);\n\t\t\t}\n\t\t\toutputChunks.push(text);\n\t\t\toutputBytes += text.length;\n\t\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\t\tconst removed = outputChunks.shift()!;\n\t\t\t\toutputBytes -= removed.length;\n\t\t\t}\n\t\t\toptions?.onChunk?.(text);\n\t\t} catch (error) {\n\t\t\tcaptureError = toExecutionError(error);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await env.exec(command, {\n\t\t\t...(options ?? {}),\n\t\t\tonStdout: onChunk,\n\t\t\tonStderr: onChunk,\n\t\t});\n\t\tconst tailOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(tailOutput);\n\t\tif (truncationResult.truncated && !fullOutputPath) {\n\t\t\tensureFullOutputFile(tailOutput);\n\t\t}\n\t\tconst writeResult = await writeChain;\n\t\tif (!writeResult.ok) return err(writeResult.error);\n\t\tif (captureError) return err(captureError);\n\n\t\tif (!result.ok) {\n\t\t\tif (result.error.code === \"aborted\" || options?.abortSignal?.aborted) {\n\t\t\t\treturn ok({\n\t\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : tailOutput,\n\t\t\t\t\texitCode: undefined,\n\t\t\t\t\tcancelled: true,\n\t\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\t\tfullOutputPath,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn err(result.error);\n\t\t}\n\t\tconst cancelled = options?.abortSignal?.aborted ?? false;\n\t\treturn ok({\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : tailOutput,\n\t\t\texitCode: cancelled ? undefined : result.value.exitCode,\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath,\n\t\t});\n\t} catch (error) {\n\t\treturn err(toExecutionError(error));\n\t}\n}\n"]}