fix(utils): normalized ptree.bytes() return type to Uint8Array
Bun's `Response(stream).bytes()` returns the raw `ArrayBuffer` once the body arrives in more than one chunk, which happens for subprocess stdout past ~128 KB. The public contract of `ptree.ChildProcess.bytes()` is `Promise<Uint8Array>`, and callers — most visibly the `ssh://` read path's `decodeUtf8Text` — rely on `Uint8Array` methods such as `.indexOf` and `.subarray`. On larger remote text files this surfaced as: TypeError: bytes.indexOf is not a function Normalize the result at the boundary: when `Response.bytes()` hands back an `ArrayBuffer`, wrap it in a zero-copy `Uint8Array` view before returning. Adds a regression test that drives a 256 KB stdout payload through `ptree.spawn(...).bytes()` and asserts the contract. Fixes #3712
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Normalized `ptree.ChildProcess.bytes()` to always return a `Uint8Array`. Bun's `Response(stream).bytes()` returns the raw `ArrayBuffer` once the body arrives in more than one chunk (subprocess stdout past ~128 KB), which broke callers relying on `Uint8Array` methods like `.indexOf` and `.subarray` — including the `ssh://` read path's `decodeUtf8Text`, which crashed with `indexOf is not a function` on larger remote text files. ([#3712](https://github.com/can1357/oh-my-pi/issues/3712))
|
||||
|
||||
## [16.2.0] - 2026-06-27
|
||||
|
||||
### Added
|
||||
|
||||
@@ -247,7 +247,12 @@ export class ChildProcess<In extends InMask = InMask> {
|
||||
}
|
||||
|
||||
async bytes(): Promise<Uint8Array> {
|
||||
return new Response(this.stdout).bytes();
|
||||
// Bun's `Response(stream).bytes()` returns the raw `ArrayBuffer` once the
|
||||
// stream emits more than one chunk (subprocess stdout chunks past ~128 KB).
|
||||
// Normalize at the contract boundary so every caller — SSH read,
|
||||
// `decodeUtf8Text`, callers slicing with `.subarray` — sees a `Uint8Array`.
|
||||
const body = (await new Response(this.stdout).bytes()) as Uint8Array | ArrayBuffer;
|
||||
return body instanceof Uint8Array ? body : new Uint8Array(body);
|
||||
}
|
||||
|
||||
// ── Wait ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { spawn } from "@oh-my-pi/pi-utils/ptree";
|
||||
|
||||
describe("ptree.ChildProcess.bytes()", () => {
|
||||
// Regression for https://github.com/can1357/oh-my-pi/issues/3712:
|
||||
// `Response(stream).bytes()` returns the raw `ArrayBuffer` once the body
|
||||
// arrives in more than one chunk (which happens for subprocess stdout past
|
||||
// ~128 KB). Downstream code — e.g. the SSH read path's `decodeUtf8Text` —
|
||||
// relied on `Uint8Array` methods (`.indexOf`, `.subarray`) and crashed.
|
||||
it("returns a Uint8Array regardless of stdout size", async () => {
|
||||
// 256 KB is comfortably past the multi-chunk boundary observed on Bun
|
||||
// 1.3.x; the test then asserts only on the contract, not on the exact
|
||||
// chunk threshold, so it stays robust to future Bun runtime changes.
|
||||
const size = 256 * 1024;
|
||||
const child = spawn(["bun", "-e", `process.stdout.write("a".repeat(${size}))`]);
|
||||
const bytes = await child.bytes();
|
||||
await child.exitedCleanly;
|
||||
|
||||
expect(bytes).toBeInstanceOf(Uint8Array);
|
||||
expect(bytes.length).toBe(size);
|
||||
// The two methods the SSH read path depends on.
|
||||
expect(typeof bytes.indexOf).toBe("function");
|
||||
expect(typeof bytes.subarray).toBe("function");
|
||||
expect(bytes.indexOf(0)).toBe(-1);
|
||||
expect(bytes.subarray(0, 4)).toEqual(new Uint8Array([0x61, 0x61, 0x61, 0x61]));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user