diff --git a/src/js/internal/fs/streams.ts b/src/js/internal/fs/streams.ts index 939ce1153ea..bfce25ce7eb 100644 --- a/src/js/internal/fs/streams.ts +++ b/src/js/internal/fs/streams.ts @@ -560,6 +560,9 @@ function _write(data, encoding, cb) { const fileSink = this[kWriteStreamFastPath]; if (fileSink && fileSink !== true) { + if (typeof data === "string" && encoding && encoding !== "utf8" && encoding !== "utf-8" && encoding !== "buffer") { + data = Buffer.from(data, encoding); + } const maybePromise = fileSink.write(data); if ($isPromise(maybePromise)) { maybePromise @@ -603,6 +606,9 @@ function underscoreWriteFast(this: FSStream, data: any, encoding: any, cb: any) this.fd = fileSink._getFd(); } + if (typeof data === "string" && encoding && encoding !== "utf8" && encoding !== "utf-8" && encoding !== "buffer") { + data = Buffer.from(data, encoding); + } const maybePromise = fileSink.write(data); if ($isPromise(maybePromise)) { maybePromise.then( @@ -652,6 +658,9 @@ function writeFast(this: FSStream, data: any, encoding: any, cb: any) { const fileSink = this[kWriteStreamFastPath]; if (fileSink && fileSink !== true) { + if (typeof data === "string" && encoding && encoding !== "utf8" && encoding !== "utf-8" && encoding !== "buffer") { + data = Buffer.from(data, encoding); + } const maybePromise = fileSink.write(data); if ($isPromise(maybePromise)) { maybePromise diff --git a/test/js/node/process/process-stdio.test.ts b/test/js/node/process/process-stdio.test.ts index ba35bc4f048..c271d5687de 100644 --- a/test/js/node/process/process-stdio.test.ts +++ b/test/js/node/process/process-stdio.test.ts @@ -158,4 +158,48 @@ describe.concurrent("process-stdio", () => { `hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`.repeat(9999), ); }); + + // Regression: process.stdout.write(string, encoding) was ignoring the encoding + // parameter on the fast path and UTF-8 encoding the string instead. + test.each(["binary", "latin1"] as const)("process.stdout.write(string, '%s') writes raw bytes", encoding => { + const { stdout } = spawnSync({ + cmd: [ + bunExe(), + "-e", + `for (let i = 0; i <= 0xff; i++) process.stdout.write(String.fromCharCode(i), ${JSON.stringify(encoding)});`, + ], + stdout: "pipe", + stdin: null, + stderr: "inherit", + env: bunEnv, + }); + expect(stdout).toBeInstanceOf(Buffer); + expect(stdout.length).toBe(256); + const expected = Buffer.alloc(256); + for (let i = 0; i < 256; i++) expected[i] = i; + expect(Buffer.compare(stdout, expected)).toBe(0); + }); + + test("process.stdout.write(string, 'hex') decodes hex", () => { + const { stdout } = spawnSync({ + cmd: [bunExe(), "-e", `process.stdout.write("deadbeef", "hex");`], + stdout: "pipe", + stdin: null, + stderr: "inherit", + env: bunEnv, + }); + expect(Buffer.compare(stdout, Buffer.from([0xde, 0xad, 0xbe, 0xef]))).toBe(0); + }); + + test("process.stdout.write(string) defaults to UTF-8", () => { + const { stdout } = spawnSync({ + cmd: [bunExe(), "-e", `process.stdout.write("héllo");`], + stdout: "pipe", + stdin: null, + stderr: "inherit", + env: bunEnv, + }); + // é = U+00E9 = UTF-8 c3 a9 + expect(Buffer.compare(stdout, Buffer.from([0x68, 0xc3, 0xa9, 0x6c, 0x6c, 0x6f]))).toBe(0); + }); });