Files
bun.sh/src/js/builtins/ProcessObjectInternals.ts
Dylan Conway db09ed15fd tty ReadStream, WriteStream, and readline rawmode (#4179)
* tty `WriteStream`, `ReadStream`, and rawmode

* tests

* refactor prototypes

* fix failing test

* fix test and library usage

* more merge

* fix child_process test

* create pseudo terminal for tty tests

* match node logic

* handle invalid tty

* close descriptors

* move tests to another process

* fix test again

* fix test on linux
2023-08-19 00:11:24 -07:00

196 lines
5.2 KiB
TypeScript

/*
* Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// TODO: move this to native code?
export function binding(bindingName) {
if (bindingName === "constants") {
return $processBindingConstants;
}
const issue = {
fs: 3546,
buffer: 2020,
natives: 2254,
uv: 2891,
}[bindingName];
if (issue) {
throw new Error(
`process.binding("${bindingName}") is not implemented in Bun. Track the status & thumbs up the issue: https://github.com/oven-sh/bun/issues/${issue}`,
);
}
throw new TypeError(
`process.binding("${bindingName}") is not implemented in Bun. If that breaks something, please file an issue and include a reproducible code sample.`,
);
}
export function getStdioWriteStream(fd) {
const tty = require("node:tty");
const stream = tty.WriteStream(fd);
process.on("SIGWINCH", () => {
stream._refreshSize();
});
if (fd === 1) {
stream.destroySoon = stream.destroy;
stream._destroy = function (err, cb) {
cb(err);
this._undestroy();
if (!this._writableState.emitClose) {
process.nextTick(() => {
this.emit("close");
});
}
};
} else if (fd === 2) {
stream.destroySoon = stream.destroy;
stream._destroy = function (err, cb) {
cb(err);
this._undestroy();
if (!this._writableState.emitClose) {
process.nextTick(() => {
this.emit("close");
});
}
};
}
stream._type = "tty";
stream._isStdio = true;
stream.fd = fd;
return stream;
}
export function getStdinStream(fd) {
var { destroy } = require("node:stream");
var reader: ReadableStreamDefaultReader | undefined;
var readerRef;
var unrefOnRead = false;
function ref() {
reader ??= Bun.stdin.stream().getReader();
// TODO: remove this. likely we are dereferencing the stream
// when there is still more data to be read.
readerRef ??= setInterval(() => {}, 1 << 30);
}
function unref() {
if (readerRef) {
clearInterval(readerRef);
readerRef = undefined;
}
}
const tty = require("node:tty");
const stream = new tty.ReadStream(fd);
const originalOn = stream.on;
stream.on = function (event, listener) {
// Streams don't generally required to present any data when only
// `readable` events are present, i.e. `readableFlowing === false`
//
// However, Node.js has a this quirk whereby `process.stdin.read()`
// blocks under TTY mode, thus looping `.read()` in this particular
// case would not result in truncation.
//
// Therefore the following hack is only specific to `process.stdin`
// and does not apply to the underlying Stream implementation.
if (event === "readable") {
ref();
unrefOnRead = true;
}
return originalOn.call(this, event, listener);
};
stream.fd = fd;
const originalPause = stream.pause;
stream.pause = function () {
unref();
return originalPause.call(this);
};
const originalResume = stream.resume;
stream.resume = function () {
ref();
return originalResume.call(this);
};
async function internalRead(stream) {
try {
var done: any, value: any;
const read = reader?.readMany();
if ($isPromise(read)) {
({ done, value } = await read);
} else {
// @ts-expect-error
({ done, value } = read);
}
if (!done) {
stream.push(value[0]);
// shouldn't actually happen, but just in case
const length = value.length;
for (let i = 1; i < length; i++) {
stream.push(value[i]);
}
} else {
stream.push(null);
stream.pause();
}
} catch (err) {
stream.destroy(err);
}
}
stream._read = function (size) {
if (unrefOnRead) {
unref();
unrefOnRead = false;
}
internalRead(this);
};
stream.on("pause", () => {
process.nextTick(() => {
destroy(stream);
});
});
stream.on("close", () => {
process.nextTick(() => {
reader?.cancel();
});
});
return stream;
}