From 8bea7a9751c026bcda95a3070c28f97de2de6d8c Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 23 Jul 2026 15:33:12 +0100 Subject: [PATCH] fix(core): preserve binary output in ProcessOutput.buffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buffer() built its Buffer from `this.stdall`, which is a UTF-8 decoded string, so any non-UTF-8 byte in the command output was already replaced with U+FFFD and the returned bytes were corrupted — even though the raw chunks are still retained in the store. blob() and text() both delegate to buffer(), so they were affected too: await $`cat image.png`.buffer() // mangled bytes Rebuild the Buffer from the lossless store chunks instead. --- src/core.ts | 9 ++++++++- test/core.test.js | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/core.ts b/src/core.ts index 39c320149f..401e5da18b 100644 --- a/src/core.ts +++ b/src/core.ts @@ -938,7 +938,14 @@ export class ProcessOutput extends Error { } buffer(): Buffer { - return Buffer.from(this.stdall) + // Reconstruct from the raw store chunks. `this.stdall` is a UTF-8 decoded + // string, so building a Buffer from it corrupts any non-UTF-8 output + // (each invalid byte becomes U+FFFD). The original chunks are lossless. + return Buffer.concat( + [...this._dto.store.stdall].map((chunk) => + isString(chunk) ? Buffer.from(chunk) : chunk + ) + ) } blob(type = 'text/plain'): Blob { diff --git a/test/core.test.js b/test/core.test.js index b80a2eda91..0ffcca45b0 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -1526,6 +1526,15 @@ describe('core', () => { assert.equal(o.buffer().compare(Buffer.from('foo\n', 'utf-8')), 0) }) + test('buffer() preserves non-utf8 bytes', async () => { + const bytes = Buffer.from([0xff, 0xfe, 0x81, 0x82]) // invalid utf-8 + const o = new ProcessOutput({ + store: { stdall: [bytes], stdout: [bytes], stderr: [] }, + }) + assert.equal(o.buffer().toString('hex'), 'fffe8182') + assert.equal(o.text('hex'), 'fffe8182') + }) + test('blob()', async () => { const o = new ProcessOutput(null, null, '', '', 'foo\n') assert.equal(await o.blob().text(), 'foo\n')