include fractional portion of chunk that exceeds stdout/stderr limit

This commit is contained in:
Michael Bolin
2025-04-21 16:58:28 -07:00
parent e7a3eec942
commit 0f9dfe3b8a
3 changed files with 128 additions and 43 deletions

View File

@@ -0,0 +1,72 @@
/**
* Creates a collector that accumulates data Buffers from a stream up to
* specified byte and line limits. After either limit is exceeded, further
* data is ignored.
*/
export function createTruncatingCollector(
stream: NodeJS.ReadableStream,
byteLimit: number = MAX_OUTPUT_BYTES,
lineLimit: number = MAX_OUTPUT_LINES,
) {
const chunks: Array<Buffer> = [];
let totalBytes = 0;
let totalLines = 0;
let hitLimit = false;
stream?.on("data", (data: Buffer) => {
if (hitLimit) {
return;
}
const dataLength = data.length;
let newlineCount = 0;
for (let i = 0; i < dataLength; i++) {
if (data[i] === 0x0a) {
newlineCount++;
}
}
// If entire chunk fits within byte and line limits, take it whole
if (
totalBytes + dataLength <= byteLimit &&
totalLines + newlineCount <= lineLimit
) {
chunks.push(data);
totalBytes += dataLength;
totalLines += newlineCount;
} else {
// Otherwise, take a partial slice up to the first limit breach
const allowedBytes = byteLimit - totalBytes;
const allowedLines = lineLimit - totalLines;
let bytesTaken = 0;
let linesSeen = 0;
for (let i = 0; i < dataLength; i++) {
if (bytesTaken === allowedBytes) {
break;
}
const byte = data[i];
if (byte === 0x0a) {
if (linesSeen === allowedLines) {
break;
}
linesSeen++;
}
bytesTaken++;
}
if (bytesTaken > 0) {
chunks.push(data.slice(0, bytesTaken));
totalBytes += bytesTaken;
totalLines += linesSeen;
}
hitLimit = true;
}
});
return {
getString() {
return Buffer.concat(chunks).toString("utf8");
},
/** True if either byte or line limit was exceeded */
get hit(): boolean {
return hitLimit;
},
};
}

View File

@@ -9,6 +9,7 @@ import type {
import { log } from "../../logger/log.js";
import { adaptCommandForPlatform } from "../platform-commands.js";
import { createTruncatingCollector } from "./create-truncating-collector";
import { spawn } from "child_process";
import * as os from "os";
@@ -204,49 +205,6 @@ export function exec(
});
}
/**
* Creates a collector that accumulates data Buffers from a stream up to
* specified byte and line limits. After either limit is exceeded, further
* data is ignored.
*/
function createTruncatingCollector(
stream: NodeJS.ReadableStream,
byteLimit: number = MAX_OUTPUT_BYTES,
lineLimit: number = MAX_OUTPUT_LINES,
) {
const chunks: Array<Buffer> = [];
let totalBytes = 0;
let totalLines = 0;
let hitLimit = false;
stream?.on("data", (data: Buffer) => {
if (hitLimit) {
return;
}
totalBytes += data.length;
for (let i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
totalLines++;
}
}
if (totalBytes <= byteLimit && totalLines <= lineLimit) {
chunks.push(data);
} else {
hitLimit = true;
}
});
return {
getString() {
return Buffer.concat(chunks).toString("utf8");
},
/** True if either byte or line limit was exceeded */
get hit(): boolean {
return hitLimit;
},
};
}
/**
* Adds a truncation warnings to stdout and stderr, if appropriate.
*/