| import process from "node:process"; |
| import { expectDefined } from "@openclaw/normalization-core"; |
| import { toErrorObject } from "@openclaw/normalization-core/error-coercion"; |
| import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; |
| import { isPromiseLike } from "@openclaw/normalization-core/promise-like"; |
| import { |
| decodeWindowsOutputBuffer, |
| resolveWindowsConsoleEncoding, |
| } from "../infra/windows-encoding.js"; |
| import { releaseChildProcessOutputAfterExit } from "./child-process.js"; |
| import { |
| appendCapturedOutput, |
| appendPreservedOutputLines, |
| createCapturedOutputBuffers, |
| finalizeCapturedOutput, |
| flushPreservedOutputLine, |
| MAX_PRESERVED_PENDING_LINE_BYTES, |
| resolveMaxOutputBytes, |
| resolveOutputCapture, |
| shouldTerminateOnOutputError, |
| shouldTerminateOnOutputLimit, |
| type CapturedOutputBuffers, |
| type CommandOutputCaptureMode, |
| type CommandOutputCaptureOption, |
| type CommandOutputErrorOption, |
| type CommandOutputLimitOption, |
| type CommandOutputStream, |
| type PreserveOutputLine, |
| } from "./exec-output.js"; |
| import { |
| createSanitizedCommandError, |
| isPlainCommandExitFailure, |
| isPlainCommandSignalFailure, |
| resolveProcessExitCode, |
| TIMEOUT_EXIT_CODE, |
| type SpawnResult, |
| } from "./exec-result.js"; |
| import { |
| COMMAND_PROCESS_TREE_KILL_GRACE_MS, |
| resolveCommandProcessSignal, |
| spawnCommandWithInvocation, |
| } from "./exec-spawn.js"; |
| import { createCommandTerminationController } from "./exec-termination.js"; |
|
|
| const WINDOWS_CLOSE_STATE_SETTLE_TIMEOUT_MS = 250; |
| const WINDOWS_CLOSE_STATE_POLL_MS = 10; |
|
|
| type CommandTerminationReason = SpawnResult["termination"] | "output-limit"; |
|
|
| export type CommandOptions = { |
| timeoutMs?: number; |
| cwd?: string; |
| input?: string | Uint8Array; |
| |
| beforeInput?: (pid: number, argv?: readonly string[]) => void; |
| baseEnv?: NodeJS.ProcessEnv; |
| env?: NodeJS.ProcessEnv; |
| windowsVerbatimArguments?: boolean; |
| noOutputTimeoutMs?: number; |
| signal?: AbortSignal; |
| maxOutputBytes?: number | { stdout?: number; stderr?: number }; |
| maxCombinedOutputBytes?: number; |
| outputCapture?: CommandOutputCaptureOption; |
| |
| onOutputChunk?: (chunk: Buffer, stream: CommandOutputStream) => boolean | void; |
| |
| tolerateOutputError?: { stdout?: boolean; stderr?: boolean }; |
| |
| terminateOnOutputError?: CommandOutputErrorOption; |
| terminateOnOutputLimit?: CommandOutputLimitOption; |
| maxPreservedOutputLines?: number; |
| preserveOutputLine?: PreserveOutputLine; |
| killProcessTree?: boolean; |
| |
| requireProcessTreeExtinction?: boolean; |
| |
| killSignal?: NodeJS.Signals | number; |
| |
| killGraceMs?: number; |
| }; |
|
|
| export async function runCommandWithTimeout( |
| argv: string[], |
| optionsOrTimeout: number | CommandOptions, |
| ): Promise<SpawnResult> { |
| return await runCommandWithOutputEncoding(argv, optionsOrTimeout, false); |
| } |
|
|
| |
| export async function runUtf8CommandWithTimeout( |
| argv: string[], |
| optionsOrTimeout: number | CommandOptions, |
| ): Promise<SpawnResult> { |
| return await runCommandWithOutputEncoding(argv, optionsOrTimeout, true); |
| } |
|
|
| export type BufferSpawnResult = Omit<SpawnResult, "stdout" | "stderr"> & { |
| stdout: Buffer; |
| stderr: Buffer; |
| windowsEncoding: string | null; |
| }; |
|
|
| |
| export async function runCommandBuffersWithTimeout( |
| argv: string[], |
| optionsOrTimeout: number | CommandOptions, |
| ): Promise<BufferSpawnResult> { |
| return await runCommandWithOutputEncoding(argv, optionsOrTimeout, false, true); |
| } |
|
|
| async function runCommandWithOutputEncoding( |
| argv: string[], |
| optionsOrTimeout: number | CommandOptions, |
| forceUtf8: boolean, |
| ): Promise<SpawnResult>; |
| async function runCommandWithOutputEncoding( |
| argv: string[], |
| optionsOrTimeout: number | CommandOptions, |
| forceUtf8: boolean, |
| raw: true, |
| ): Promise<BufferSpawnResult>; |
| async function runCommandWithOutputEncoding( |
| argv: string[], |
| optionsOrTimeout: number | CommandOptions, |
| forceUtf8: boolean, |
| raw = false, |
| ): Promise<SpawnResult | BufferSpawnResult> { |
| const options: CommandOptions = |
| typeof optionsOrTimeout === "number" ? { timeoutMs: optionsOrTimeout } : optionsOrTimeout; |
| const { |
| timeoutMs, |
| cwd, |
| input, |
| baseEnv, |
| env, |
| noOutputTimeoutMs, |
| killProcessTree, |
| killSignal, |
| killGraceMs, |
| } = options; |
| const signal = resolveCommandProcessSignal(options.signal); |
| const resolvedTimeoutMs = |
| typeof timeoutMs === "number" ? resolveTimerTimeoutMs(timeoutMs, 1) : undefined; |
| if (options.requireProcessTreeExtinction && !killProcessTree) { |
| throw new Error("Process-tree extinction requires process-tree ownership"); |
| } |
| const hasInput = input !== undefined; |
| if (options.beforeInput && !hasInput) { |
| throw new Error("Child input admission requires explicit input"); |
| } |
| const resolvedKillGraceMs = resolveTimerTimeoutMs( |
| killGraceMs, |
| COMMAND_PROCESS_TREE_KILL_GRACE_MS, |
| 0, |
| ); |
|
|
| if (signal?.aborted) { |
| const interrupted = { |
| code: null, |
| signal: null, |
| killed: false, |
| termination: "signal" as const, |
| cleanup: "normal" as const, |
| noOutputTimedOut: false, |
| }; |
| return raw |
| ? { ...interrupted, stdout: Buffer.alloc(0), stderr: Buffer.alloc(0), windowsEncoding: null } |
| : { ...interrupted, stdout: "", stderr: "" }; |
| } |
|
|
| const stdoutCapture = createCapturedOutputBuffers(); |
| const stderrCapture = createCapturedOutputBuffers(); |
| const maxStdoutBytes = resolveMaxOutputBytes(options.maxOutputBytes, "stdout"); |
| const maxStderrBytes = resolveMaxOutputBytes(options.maxOutputBytes, "stderr"); |
| const maxCombinedOutputBytes = |
| typeof options.maxCombinedOutputBytes === "number" && |
| Number.isFinite(options.maxCombinedOutputBytes) && |
| options.maxCombinedOutputBytes > 0 |
| ? Math.max(1, Math.floor(options.maxCombinedOutputBytes)) |
| : undefined; |
| const stdoutCaptureMode = resolveOutputCapture(options.outputCapture, "stdout"); |
| const stderrCaptureMode = resolveOutputCapture(options.outputCapture, "stderr"); |
| if (maxCombinedOutputBytes !== undefined && stdoutCaptureMode !== stderrCaptureMode) { |
| throw new Error("maxCombinedOutputBytes requires matching stdout and stderr capture modes"); |
| } |
| const usesCombinedTailCapture = |
| maxCombinedOutputBytes !== undefined && |
| stdoutCaptureMode === "tail" && |
| stderrCaptureMode === "tail"; |
| const maxPreservedPendingLineBytes = Math.min( |
| Math.max(maxStdoutBytes, maxStderrBytes), |
| MAX_PRESERVED_PENDING_LINE_BYTES, |
| ); |
| const maxPreservedOutputLines = Math.max(0, Math.floor(options.maxPreservedOutputLines ?? 16)); |
| const windowsEncoding = forceUtf8 ? null : resolveWindowsConsoleEncoding(); |
| const cancelController = new AbortController(); |
| let termination: CommandTerminationReason | undefined; |
| let childExitState: { code: number | null; signal: NodeJS.Signals | null } | undefined; |
| let commandSettled = false; |
| let combinedOutputBytes = 0; |
| let combinedCapturedBytes = 0; |
| const outputBytesByStream = { stdout: 0, stderr: 0 }; |
| const combinedCapturedBytesByStream = { stdout: 0, stderr: 0 }; |
| const combinedTailChunks: Array<{ stream: CommandOutputStream; buffer: Buffer }> = []; |
| let noOutputTimer: NodeJS.Timeout | undefined; |
| let outputObserverError: unknown; |
| let outputErrorStream: CommandOutputStream | undefined; |
| let terminatingOutputError: Error | undefined; |
|
|
| const { child, invocation } = spawnCommandWithInvocation(argv, { |
| buffer: false, |
| cancelSignal: cancelController.signal, |
| inheritScopeCancellation: false, |
| cwd, |
| detached: Boolean(killProcessTree && process.platform !== "win32"), |
| encoding: "buffer", |
| baseEnv, |
| env, |
| forceKillAfterDelay: resolvedKillGraceMs, |
| killSignal, |
| ...(hasInput && !options.beforeInput ? { input } : {}), |
| reject: false, |
| stdio: [hasInput ? "pipe" : "inherit", "pipe", "pipe"], |
| stripFinalNewline: false, |
| windowsVerbatimArguments: options.windowsVerbatimArguments, |
| }); |
| const nodeChild = child.nodeChildProcess; |
| const ownsExitedProcessTree = Boolean(killProcessTree && process.platform !== "win32"); |
| const shouldTrackOutputTimeout = |
| typeof noOutputTimeoutMs === "number" && |
| Number.isFinite(noOutputTimeoutMs) && |
| noOutputTimeoutMs > 0; |
| const resolvedNoOutputTimeoutMs = shouldTrackOutputTimeout |
| ? resolveTimerTimeoutMs(noOutputTimeoutMs, 1) |
| : undefined; |
| const ownsOutputDeadline = |
| ownsExitedProcessTree && |
| (resolvedTimeoutMs !== undefined || resolvedNoOutputTimeoutMs !== undefined); |
| let releaseOutput: (() => void) | undefined; |
| const terminationController = createCommandTerminationController({ |
| child: nodeChild, |
| cancelController, |
| baseEnv, |
| env, |
| processTree: killProcessTree ? { mode: "graceful" } : undefined, |
| isChildExited: () => childExitState !== undefined, |
| isCommandSettled: () => commandSettled, |
| killGraceMs: resolvedKillGraceMs, |
| killSignal, |
| }); |
| nodeChild.once("exit", (code, signalValue) => { |
| childExitState = { code, signal: signalValue }; |
| |
| |
| if (!ownsOutputDeadline || code !== 0 || termination) { |
| releaseOutput = releaseChildProcessOutputAfterExit(nodeChild); |
| } |
| |
| |
| if (killProcessTree && !termination && (code !== 0 || options.requireProcessTreeExtinction)) { |
| terminationController.terminate(); |
| } |
| }); |
|
|
| const clearNoOutputTimer = () => { |
| if (noOutputTimer) { |
| clearTimeout(noOutputTimer); |
| noOutputTimer = undefined; |
| } |
| }; |
| const cancel = (reason: Exclude<CommandTerminationReason, "exit">) => { |
| |
| |
| |
| if ( |
| termination || |
| commandSettled || |
| (childExitState && |
| reason !== "output-limit" && |
| (!ownsExitedProcessTree || childExitState.code !== 0)) |
| ) { |
| return; |
| } |
| termination = reason; |
| if (childExitState) { |
| |
| releaseOutput ??= releaseChildProcessOutputAfterExit(nodeChild); |
| } |
| const abortDeferred = terminationController.terminate(); |
| if (!abortDeferred) { |
| cancelController.abort(); |
| } |
| }; |
| const armNoOutputTimer = () => { |
| if ( |
| resolvedNoOutputTimeoutMs === undefined || |
| commandSettled || |
| termination || |
| (childExitState && !ownsExitedProcessTree) |
| ) { |
| return; |
| } |
| clearNoOutputTimer(); |
| noOutputTimer = setTimeout(() => cancel("no-output-timeout"), resolvedNoOutputTimeoutMs); |
| }; |
|
|
| const timeoutTimer = |
| resolvedTimeoutMs === undefined |
| ? undefined |
| : setTimeout(() => cancel("timeout"), resolvedTimeoutMs); |
| const onAbort = () => cancel("signal"); |
| signal?.addEventListener("abort", onAbort, { once: true }); |
| armNoOutputTimer(); |
|
|
| const captureOutput = ( |
| capture: CapturedOutputBuffers, |
| chunk: Buffer | string, |
| maxBytes: number, |
| stream: CommandOutputStream, |
| captureMode: CommandOutputCaptureMode, |
| ) => { |
| const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); |
| outputBytesByStream[stream] += buffer.byteLength; |
| const streamLimitExceeded = outputBytesByStream[stream] > maxBytes; |
| if (maxCombinedOutputBytes === undefined) { |
| appendCapturedOutput(capture, buffer, maxBytes, captureMode); |
| if ( |
| streamLimitExceeded && |
| shouldTerminateOnOutputLimit(options.terminateOnOutputLimit, stream) |
| ) { |
| cancel("output-limit"); |
| } |
| return; |
| } |
|
|
| const combinedBytesBeforeChunk = combinedOutputBytes; |
| combinedOutputBytes += buffer.byteLength; |
| const combinedLimitExceeded = combinedOutputBytes > maxCombinedOutputBytes; |
| if (usesCombinedTailCapture) { |
| combinedTailChunks.push({ stream, buffer }); |
| combinedCapturedBytes += buffer.byteLength; |
| combinedCapturedBytesByStream[stream] += buffer.byteLength; |
|
|
| const removeCapturedBytes = (index: number, requestedBytes: number) => { |
| const entry = expectDefined(combinedTailChunks[index], "combined tail chunk"); |
| const removedBytes = Math.min(requestedBytes, entry.buffer.byteLength); |
| if (removedBytes === entry.buffer.byteLength) { |
| combinedTailChunks.splice(index, 1); |
| } else { |
| entry.buffer = Buffer.from(entry.buffer.subarray(removedBytes)); |
| } |
| combinedCapturedBytes -= removedBytes; |
| combinedCapturedBytesByStream[entry.stream] -= removedBytes; |
| (entry.stream === "stdout" ? stdoutCapture : stderrCapture).truncatedBytes += removedBytes; |
| }; |
|
|
| while (combinedCapturedBytesByStream[stream] > maxBytes) { |
| const index = combinedTailChunks.findIndex((entry) => entry.stream === stream); |
| if (index < 0) { |
| break; |
| } |
| removeCapturedBytes(index, combinedCapturedBytesByStream[stream] - maxBytes); |
| } |
| let combinedOverflow = combinedCapturedBytes - maxCombinedOutputBytes; |
| while (combinedOverflow > 0) { |
| removeCapturedBytes(0, combinedOverflow); |
| combinedOverflow = combinedCapturedBytes - maxCombinedOutputBytes; |
| } |
| } else { |
| const remaining = Math.max(0, maxCombinedOutputBytes - combinedBytesBeforeChunk); |
| const maxCaptureBytes = Math.min(maxBytes, capture.bytes + remaining); |
| appendCapturedOutput(capture, buffer, maxCaptureBytes, captureMode); |
| } |
| if ( |
| (combinedLimitExceeded && |
| shouldTerminateOnOutputLimit(options.terminateOnOutputLimit, "combined")) || |
| (streamLimitExceeded && shouldTerminateOnOutputLimit(options.terminateOnOutputLimit, stream)) |
| ) { |
| cancel("output-limit"); |
| } |
| }; |
|
|
| const observeOutputChunk = (chunk: Buffer | string, stream: CommandOutputStream): Buffer => { |
| const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); |
| if (termination || !options.onOutputChunk) { |
| return buffer; |
| } |
| try { |
| if (options.onOutputChunk(buffer, stream) === false) { |
| cancel("output-limit"); |
| } |
| } catch (error) { |
| outputObserverError = error; |
| cancel("output-limit"); |
| } |
| return buffer; |
| }; |
|
|
| const onOutputError = (error: unknown, stream: CommandOutputStream) => { |
| outputErrorStream ??= stream; |
| if ( |
| termination || |
| options.tolerateOutputError?.[stream] === true || |
| !shouldTerminateOnOutputError(options.terminateOnOutputError, stream) |
| ) { |
| return; |
| } |
| terminatingOutputError = toErrorObject(error, `Command ${stream} stream failed`); |
| Object.assign(terminatingOutputError, { outputErrorStream: stream }); |
| cancel("signal"); |
| }; |
| child.stdout?.once("error", (error) => onOutputError(error, "stdout")); |
| child.stderr?.once("error", (error) => onOutputError(error, "stderr")); |
| child.stdout?.on("data", (chunk) => { |
| const buffer = observeOutputChunk(chunk, "stdout"); |
| appendPreservedOutputLines({ |
| capture: stdoutCapture, |
| chunk: buffer, |
| stream: "stdout", |
| preserveOutputLine: options.preserveOutputLine, |
| maxPreservedOutputLines, |
| maxPendingLineBytes: maxPreservedPendingLineBytes, |
| }); |
| captureOutput(stdoutCapture, buffer, maxStdoutBytes, "stdout", stdoutCaptureMode); |
| armNoOutputTimer(); |
| }); |
| child.stderr?.on("data", (chunk) => { |
| const buffer = observeOutputChunk(chunk, "stderr"); |
| appendPreservedOutputLines({ |
| capture: stderrCapture, |
| chunk: buffer, |
| stream: "stderr", |
| preserveOutputLine: options.preserveOutputLine, |
| maxPreservedOutputLines, |
| maxPendingLineBytes: maxPreservedPendingLineBytes, |
| }); |
| captureOutput(stderrCapture, buffer, maxStderrBytes, "stderr", stderrCaptureMode); |
| armNoOutputTimer(); |
| }); |
|
|
| let inputAdmissionError: Error | undefined; |
| if (options.beforeInput) { |
| nodeChild.stdin?.once("error", (cause) => { |
| inputAdmissionError ??= toErrorObject(cause, "Command input failed"); |
| cancel("signal"); |
| }); |
| try { |
| if (nodeChild.pid === undefined || !nodeChild.stdin) { |
| throw new Error("Child input admission has no spawned process"); |
| } |
| const admitted: unknown = options.beforeInput(nodeChild.pid, nodeChild.spawnargs); |
| if (admitted !== undefined) { |
| if (isPromiseLike(admitted)) { |
| void Promise.resolve(admitted).catch(() => undefined); |
| } |
| throw new TypeError("Child input admission must complete synchronously"); |
| } |
| nodeChild.stdin.end(input); |
| } catch (cause) { |
| inputAdmissionError = toErrorObject(cause, "Child input admission failed"); |
| nodeChild.stdin?.destroy(); |
| cancel("signal"); |
| } |
| } |
|
|
| const result = await child.finally(() => { |
| commandSettled = true; |
| if (timeoutTimer) { |
| clearTimeout(timeoutTimer); |
| } |
| clearNoOutputTimer(); |
| signal?.removeEventListener("abort", onAbort); |
| releaseOutput?.(); |
| }); |
| let cleanup = await terminationController.settle(); |
| const resolvedSignal = result.signal ?? childExitState?.signal ?? nodeChild.signalCode ?? null; |
| if (cleanup !== "forced" && resolvedSignal) { |
| cleanup = "uncertain"; |
| } |
| if (inputAdmissionError) { |
| throw Object.assign(inputAdmissionError, { cleanup }); |
| } |
| if (terminatingOutputError) { |
| throw Object.assign(terminatingOutputError, { cleanup }); |
| } |
| if (outputObserverError !== undefined) { |
| throw Object.assign(toErrorObject(outputObserverError, "Command output observer failed"), { |
| cleanup, |
| }); |
| } |
| |
| |
| const isCauseLessWindowsShimResult = |
| !termination && |
| invocation.usesWindowsExitCodeShim && |
| typeof nodeChild.pid === "number" && |
| result.code === undefined && |
| result.cause === undefined && |
| !result.timedOut && |
| !result.isCanceled && |
| !result.isMaxBuffer && |
| !result.isTerminated; |
| if (isCauseLessWindowsShimResult) { |
| |
| |
| for ( |
| let elapsedMs = 0; |
| elapsedMs < WINDOWS_CLOSE_STATE_SETTLE_TIMEOUT_MS; |
| elapsedMs += WINDOWS_CLOSE_STATE_POLL_MS |
| ) { |
| if ( |
| childExitState?.code != null || |
| childExitState?.signal != null || |
| nodeChild.exitCode != null || |
| nodeChild.signalCode != null |
| ) { |
| break; |
| } |
| await new Promise<void>((resolve) => { |
| setTimeout(resolve, WINDOWS_CLOSE_STATE_POLL_MS); |
| }); |
| } |
| } |
| if ( |
| result.failed && |
| !termination && |
| !isPlainCommandExitFailure(result) && |
| !isPlainCommandSignalFailure(result) && |
| !isCauseLessWindowsShimResult && |
| !( |
| result.exitCode === 0 && |
| outputErrorStream !== undefined && |
| options.tolerateOutputError?.[outputErrorStream] === true |
| ) |
| ) { |
| const error = createSanitizedCommandError(result); |
| Object.assign(error, { |
| cleanup: |
| typeof nodeChild.pid === "number" |
| ? cleanup === "normal" |
| ? "uncertain" |
| : cleanup |
| : "normal", |
| }); |
| if (outputErrorStream) { |
| Object.assign(error, { outputErrorStream }); |
| } |
| throw error; |
| } |
|
|
| const resolvedCode = resolveProcessExitCode({ |
| explicitCode: result.exitCode ?? childExitState?.code, |
| childExitCode: nodeChild.exitCode, |
| resolvedSignal, |
| usesWindowsExitCodeShim: invocation.usesWindowsExitCodeShim, |
| timedOut: termination === "timeout", |
| noOutputTimedOut: termination === "no-output-timeout", |
| killIssuedByTimeout: termination === "timeout" || termination === "no-output-timeout", |
| killIssuedByAbort: termination === "signal" || termination === "output-limit", |
| }); |
| termination ??= resolvedSignal != null || result.isTerminated ? "signal" : "exit"; |
| const normalizedCode = |
| termination === "timeout" || termination === "no-output-timeout" |
| ? resolvedCode == null || resolvedCode === 0 |
| ? TIMEOUT_EXIT_CODE |
| : resolvedCode |
| : resolvedCode; |
|
|
| flushPreservedOutputLine({ |
| capture: stdoutCapture, |
| stream: "stdout", |
| preserveOutputLine: options.preserveOutputLine, |
| maxPreservedOutputLines, |
| maxPendingLineBytes: maxPreservedPendingLineBytes, |
| }); |
| flushPreservedOutputLine({ |
| capture: stderrCapture, |
| stream: "stderr", |
| preserveOutputLine: options.preserveOutputLine, |
| maxPreservedOutputLines, |
| maxPendingLineBytes: maxPreservedPendingLineBytes, |
| }); |
|
|
| if (usesCombinedTailCapture) { |
| for (const entry of combinedTailChunks) { |
| const capture = entry.stream === "stdout" ? stdoutCapture : stderrCapture; |
| capture.chunks.push(entry.buffer); |
| capture.bytes += entry.buffer.byteLength; |
| } |
| } |
|
|
| const stdout = finalizeCapturedOutput(stdoutCapture, stdoutCaptureMode, forceUtf8); |
| const stderr = finalizeCapturedOutput(stderrCapture, stderrCaptureMode, forceUtf8); |
| const settled = { |
| pid: nodeChild.pid, |
| stdoutTruncatedBytes: stdoutCapture.truncatedBytes || undefined, |
| stderrTruncatedBytes: stderrCapture.truncatedBytes || undefined, |
| preservedStdoutLines: |
| stdoutCapture.preservedLines.length > 0 ? stdoutCapture.preservedLines : undefined, |
| preservedStderrLines: |
| stderrCapture.preservedLines.length > 0 ? stderrCapture.preservedLines : undefined, |
| code: normalizedCode, |
| signal: resolvedSignal, |
| killed: nodeChild.killed, |
| cleanup, |
| termination: termination === "output-limit" ? ("signal" as const) : termination, |
| noOutputTimedOut: termination === "no-output-timeout", |
| outputLimitExceeded: termination === "output-limit" || undefined, |
| ...(outputErrorStream ? { outputErrorStream } : {}), |
| }; |
| return raw |
| ? { ...settled, stdout, stderr, windowsEncoding } |
| : { |
| ...settled, |
| stdout: forceUtf8 |
| ? stdout.toString("utf8") |
| : decodeWindowsOutputBuffer({ buffer: stdout, windowsEncoding }), |
| stderr: forceUtf8 |
| ? stderr.toString("utf8") |
| : decodeWindowsOutputBuffer({ buffer: stderr, windowsEncoding }), |
| }; |
| } |
|
|