104 lines
2.6 KiB
JavaScript
104 lines
2.6 KiB
JavaScript
import { Buffer } from 'node:buffer'
|
|
|
|
export const PROCESS_LINE_MAX_BYTES = 64 * 1024
|
|
export const PROCESS_OUTPUT_TAIL_MAX_CODE_UNITS = 64 * 1024
|
|
const TRUNCATED_LINE_SUFFIX = '… [line truncated]'
|
|
|
|
export function appendProcessOutputTail(
|
|
current,
|
|
line,
|
|
maxCodeUnits = PROCESS_OUTPUT_TAIL_MAX_CODE_UNITS
|
|
) {
|
|
if (!Number.isSafeInteger(maxCodeUnits) || maxCodeUnits < 0) {
|
|
throw new RangeError('Process output tail limit must be a non-negative safe integer')
|
|
}
|
|
if (maxCodeUnits === 0) {
|
|
return ''
|
|
}
|
|
const appended = `${current}${line}\n`
|
|
return appended.length <= maxCodeUnits ? appended : appended.slice(-maxCodeUnits)
|
|
}
|
|
|
|
export function attachBoundedProcessLineReader(
|
|
stream,
|
|
onLine,
|
|
maxLineBytes = PROCESS_LINE_MAX_BYTES
|
|
) {
|
|
if (!Number.isSafeInteger(maxLineBytes) || maxLineBytes < 0) {
|
|
throw new RangeError('Process line limit must be a non-negative safe integer')
|
|
}
|
|
|
|
const retained = Buffer.allocUnsafe(maxLineBytes)
|
|
let retainedBytes = 0
|
|
let truncated = false
|
|
let swallowLineFeed = false
|
|
let closed = false
|
|
|
|
const append = (bytes, start, end) => {
|
|
if (start >= end) {
|
|
return
|
|
}
|
|
const available = maxLineBytes - retainedBytes
|
|
const copied = Math.min(available, end - start)
|
|
if (copied > 0) {
|
|
bytes.copy(retained, retainedBytes, start, start + copied)
|
|
retainedBytes += copied
|
|
}
|
|
truncated ||= copied < end - start
|
|
}
|
|
|
|
const emit = () => {
|
|
const line = retained.subarray(0, retainedBytes).toString('utf8')
|
|
onLine(truncated ? `${line}${TRUNCATED_LINE_SUFFIX}` : line)
|
|
retainedBytes = 0
|
|
truncated = false
|
|
}
|
|
|
|
const onData = (chunk) => {
|
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
|
let start = 0
|
|
if (swallowLineFeed) {
|
|
swallowLineFeed = false
|
|
if (bytes[0] === 0x0a) {
|
|
start = 1
|
|
}
|
|
}
|
|
|
|
for (let index = start; index < bytes.length; index += 1) {
|
|
const value = bytes[index]
|
|
if (value !== 0x0a && value !== 0x0d) {
|
|
continue
|
|
}
|
|
append(bytes, start, index)
|
|
emit()
|
|
if (value === 0x0d && index + 1 < bytes.length && bytes[index + 1] === 0x0a) {
|
|
index += 1
|
|
} else if (value === 0x0d && index + 1 === bytes.length) {
|
|
swallowLineFeed = true
|
|
}
|
|
start = index + 1
|
|
}
|
|
append(bytes, start, bytes.length)
|
|
}
|
|
|
|
const detach = () => {
|
|
if (closed) {
|
|
return
|
|
}
|
|
closed = true
|
|
stream.off('data', onData)
|
|
stream.off('end', onEnd)
|
|
}
|
|
|
|
const onEnd = () => {
|
|
if (retainedBytes > 0 || truncated) {
|
|
emit()
|
|
}
|
|
detach()
|
|
}
|
|
|
|
stream.on('data', onData)
|
|
stream.on('end', onEnd)
|
|
return detach
|
|
}
|