aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-07-17 21:21:32 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-07-17 21:21:43 -0700
commit728c8fdcdb4e9276a7bd0721bad3b5d0cbed67fa (patch)
tree83084ab477feef9b58e91f999cbf0b0bc67ca493
parent13b54fbdb8cc36bbe027238654360f159ecaefbb (diff)
downloadbun-728c8fdcdb4e9276a7bd0721bad3b5d0cbed67fa.tar.gz
bun-728c8fdcdb4e9276a7bd0721bad3b5d0cbed67fa.tar.zst
bun-728c8fdcdb4e9276a7bd0721bad3b5d0cbed67fa.zip
Implement `process.{stdout, stderr}.{columns, rows, getWindowSize}`
-rw-r--r--src/bun.js/bindings/Process.cpp55
-rw-r--r--src/js/builtins/ProcessObjectInternals.ts79
-rw-r--r--src/js/out/WebCoreJSBuiltins.cpp4
-rw-r--r--src/js/out/WebCoreJSBuiltins.h2
4 files changed, 108 insertions, 32 deletions
diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp
index dd5c41c44..a7798bf9f 100644
--- a/src/bun.js/bindings/Process.cpp
+++ b/src/bun.js/bindings/Process.cpp
@@ -15,6 +15,9 @@
#include <JavaScriptCore/LazyProperty.h>
#include <JavaScriptCore/LazyPropertyInlines.h>
#include <JavaScriptCore/VMTrapsInlines.h>
+#include <termios.h>
+#include <errno.h>
+#include <sys/ioctl.h>
#pragma mark - Node.js Process
@@ -109,7 +112,53 @@ JSC_DEFINE_CUSTOM_SETTER(Process_defaultSetter,
return true;
}
-JSC_DECLARE_HOST_FUNCTION(Process_functionNextTick);
+static bool getWindowSize(int fd, size_t* width, size_t* height)
+{
+ struct winsize ws;
+ int err;
+ do
+ err = ioctl(fd, TIOCGWINSZ, &ws);
+ while (err == -1 && errno == EINTR);
+
+ if (err == -1)
+ return false;
+
+ *width = ws.ws_col;
+ *height = ws.ws_row;
+
+ return true;
+}
+
+JSC_DEFINE_HOST_FUNCTION(Process_functionInternalGetWindowSize,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+ auto argCount = callFrame->argumentCount();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ if (argCount == 0) {
+ JSC::throwTypeError(globalObject, throwScope, "getWindowSize requires 2 argument (a file descriptor)"_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ int fd = callFrame->uncheckedArgument(0).toInt32(globalObject);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ JSC::JSArray* array = jsDynamicCast<JSC::JSArray*>(callFrame->uncheckedArgument(1));
+ if (!array || array->length() < 2) {
+ JSC::throwTypeError(globalObject, throwScope, "getWindowSize requires 2 argument (an array)"_s);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ size_t width, height;
+ if (!getWindowSize(fd, &width, &height)) {
+ return JSC::JSValue::encode(jsBoolean(false));
+ }
+
+ array->putDirectIndex(globalObject, 0, jsNumber(width));
+ array->putDirectIndex(globalObject, 1, jsNumber(height));
+
+ return JSC::JSValue::encode(jsBoolean(true));
+}
+
JSC_DEFINE_HOST_FUNCTION(Process_functionNextTick,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
@@ -855,9 +904,13 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
+ JSC::JSFunction* getWindowSizeFunction = JSC::JSFunction::create(vm, globalObject, 2,
+ String("getWindowSize"_s), Process_functionInternalGetWindowSize, ImplementationVisibility::Public);
+
JSC::JSFunction* getStdioWriteStream = JSC::JSFunction::create(vm, processObjectInternalsGetStdioWriteStreamCodeGenerator(vm), globalObject);
JSC::MarkedArgumentBuffer args;
args.append(JSC::jsNumber(fd));
+ args.append(getWindowSizeFunction);
auto clientData = WebCore::clientData(vm);
JSC::CallData callData = JSC::getCallData(getStdioWriteStream);
diff --git a/src/js/builtins/ProcessObjectInternals.ts b/src/js/builtins/ProcessObjectInternals.ts
index 9670886bd..f6990dc5e 100644
--- a/src/js/builtins/ProcessObjectInternals.ts
+++ b/src/js/builtins/ProcessObjectInternals.ts
@@ -47,7 +47,7 @@ export function binding(bindingName) {
return constants;
}
-export function getStdioWriteStream(fd_) {
+export function getStdioWriteStream(fd_, getWindowSize) {
var require = path => {
var existing = $requireMap.get(path);
if (existing) return existing.exports;
@@ -223,8 +223,9 @@ export function getStdioWriteStream(fd_) {
}
var readline;
+ var windowSizeArray = [0, 0];
- var FastStdioWriteStream = class StdioWriteStream extends EventEmitter {
+ var FastStdioWriteStreamInternal = class StdioWriteStream extends EventEmitter {
#fd;
#innerStream;
#writer;
@@ -271,31 +272,6 @@ export function getStdioWriteStream(fd_) {
return this.#fd;
}
- get isTTY() {
- return (this.#isTTY ??= require("node:tty").isatty(this.#fd));
- }
-
- cursorTo(x, y, callback) {
- return (readline ??= require("node:readline")).cursorTo(this, x, y, callback);
- }
-
- moveCursor(dx, dy, callback) {
- return (readline ??= require("node:readline")).moveCursor(this, dx, dy, callback);
- }
-
- clearLine(dir, callback) {
- return (readline ??= require("node:readline")).clearLine(this, dir, callback);
- }
-
- clearScreenDown(callback) {
- return (readline ??= require("node:readline")).clearScreenDown(this, callback);
- }
-
- // TODO: once implemented this.columns and this.rows should be uncommented
- // getWindowSize() {
- // return [this.columns, this.rows];
- // }
-
ref() {
this.#getWriter().ref();
}
@@ -408,6 +384,10 @@ export function getStdioWriteStream(fd_) {
return !!(writeResult || flushResult);
}
+ get isTTY() {
+ return false;
+ }
+
write(chunk, encoding, callback) {
const result = this._write(chunk, encoding, callback);
@@ -471,8 +451,51 @@ export function getStdioWriteStream(fd_) {
return this;
}
};
+ if (getWindowSize(fd_, windowSizeArray)) {
+ var WriteStream = class WriteStream extends FastStdioWriteStreamInternal {
+ get isTTY() {
+ return true;
+ }
+
+ cursorTo(x, y, callback) {
+ return (readline ??= require("node:readline")).cursorTo(this, x, y, callback);
+ }
+
+ moveCursor(dx, dy, callback) {
+ return (readline ??= require("node:readline")).moveCursor(this, dx, dy, callback);
+ }
+
+ clearLine(dir, callback) {
+ return (readline ??= require("node:readline")).clearLine(this, dir, callback);
+ }
+
+ clearScreenDown(callback) {
+ return (readline ??= require("node:readline")).clearScreenDown(this, callback);
+ }
+
+ getWindowSize() {
+ if (getWindowSize(fd_, windowSizeArray) === true) {
+ return [windowSizeArray[0], windowSizeArray[1]];
+ }
+ }
+
+ get columns() {
+ if (getWindowSize(fd_, windowSizeArray) === true) {
+ return windowSizeArray[0];
+ }
+ }
+
+ get rows() {
+ if (getWindowSize(fd_, windowSizeArray) === true) {
+ return windowSizeArray[1];
+ }
+ }
+ };
+
+ return new WriteStream(fd_);
+ }
- return new FastStdioWriteStream(fd_);
+ return new FastStdioWriteStreamInternal(fd_);
}
export function getStdinStream(fd_) {
diff --git a/src/js/out/WebCoreJSBuiltins.cpp b/src/js/out/WebCoreJSBuiltins.cpp
index 141898930..310d23505 100644
--- a/src/js/out/WebCoreJSBuiltins.cpp
+++ b/src/js/out/WebCoreJSBuiltins.cpp
@@ -646,9 +646,9 @@ const char* const s_processObjectInternalsBindingCode = "(function (u){\"use str
const JSC::ConstructAbility s_processObjectInternalsGetStdioWriteStreamCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
const JSC::ConstructorKind s_processObjectInternalsGetStdioWriteStreamCodeConstructorKind = JSC::ConstructorKind::None;
const JSC::ImplementationVisibility s_processObjectInternalsGetStdioWriteStreamCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
-const int s_processObjectInternalsGetStdioWriteStreamCodeLength = 4331;
+const int s_processObjectInternalsGetStdioWriteStreamCodeLength = 4505;
static const JSC::Intrinsic s_processObjectInternalsGetStdioWriteStreamCodeIntrinsic = JSC::NoIntrinsic;
-const char* const s_processObjectInternalsGetStdioWriteStreamCode = "(function (Z){\"use strict\";var L=(M)=>{var j=@requireMap.get(M);if(j)return j.exports;return @internalRequire(M)},D={path:\"node:process\",require:L};function Y(M){var{Duplex:j,eos:z,destroy:B}=L(\"node:stream\"),J=class O extends j{#z;#$;#M=!0;#N=!0;#J;#B;#j;#G;#H;#K;get isTTY(){return this.#K\?\?=L(\"node:tty\").isatty(M)}get fd(){return M}constructor(G){super({readable:!0,writable:!0});this.#J=`/dev/fd/${G}`}#L(G){const H=this.#B;if(this.#B=null,H)H(G);else if(G)this.destroy(G);else if(!this.#M&&!this.#N)this.destroy()}_destroy(G,H){if(!G&&this.#B!==null){var N=class X extends Error{code;name;constructor(Q=\"The operation was aborted\",K=void 0){if(K!==void 0&&typeof K!==\"object\")throw new Error(`Invalid AbortError options:\\n\\n${JSON.stringify(K,null,2)}`);super(Q,K);this.code=\"ABORT_ERR\",this.name=\"AbortError\"}};G=new N}if(this.#j=null,this.#G=null,this.#B===null)H(G);else{if(this.#B=H,this.#z)B(this.#z,G);if(this.#$)B(this.#$,G)}}_write(G,H,N){if(!this.#z){var{createWriteStream:X}=L(\"node:fs\"),Q=this.#z=X(this.#J);Q.on(\"finish\",()=>{if(this.#G){const K=this.#G;this.#G=null,K()}}),Q.on(\"drain\",()=>{if(this.#j){const K=this.#j;this.#j=null,K()}}),z(Q,(K)=>{if(this.#N=!1,K)B(Q,K);this.#L(K)})}if(Q.write(G,H))N();else this.#j=N}_final(G){this.#z&&this.#z.end(),this.#G=G}#O(){var{createReadStream:G}=L(\"node:fs\"),H=this.#$=G(this.#J);return H.on(\"readable\",()=>{if(this.#H){const N=this.#H;this.#H=null,N()}else this.read()}),H.on(\"end\",()=>{this.push(null)}),z(H,(N)=>{if(this.#M=!1,N)B(H,N);this.#L(N)}),H}_read(){var G=this.#$;if(!G)G=this.#O();while(!0){const H=G.read();if(H===null||!this.push(H))return}}};return new J(M)}var{EventEmitter:P}=L(\"node:events\");function V(M){if(!M)return!0;var j=M.toLowerCase();return j===\"utf8\"||j===\"utf-8\"||j===\"buffer\"||j===\"binary\"}var U,A=class M extends P{#z;#$;#M;#N;bytesWritten=0;setDefaultEncoding(j){if(this.#$||!V(j))return this.#j(),this.#$.setDefaultEncoding(j)}#J(){switch(this.#z){case 1:{var j=@Bun.stdout.writer({highWaterMark:0});return j.unref(),j}case 2:{var j=@Bun.stderr.writer({highWaterMark:0});return j.unref(),j}default:throw new Error(\"Unsupported writer\")}}#B(){return this.#M\?\?=this.#J()}constructor(j){super();this.#z=j}get fd(){return this.#z}get isTTY(){return this.#N\?\?=L(\"node:tty\").isatty(this.#z)}cursorTo(j,z,B){return(U\?\?=L(\"node:readline\")).cursorTo(this,j,z,B)}moveCursor(j,z,B){return(U\?\?=L(\"node:readline\")).moveCursor(this,j,z,B)}clearLine(j,z){return(U\?\?=L(\"node:readline\")).clearLine(this,j,z)}clearScreenDown(j){return(U\?\?=L(\"node:readline\")).clearScreenDown(this,j)}ref(){this.#B().ref()}unref(){this.#B().unref()}on(j,z){if(j===\"close\"||j===\"finish\")return this.#j(),this.#$.on(j,z);if(j===\"drain\")return super.on(\"drain\",z);if(j===\"error\")return super.on(\"error\",z);return super.on(j,z)}get _writableState(){return this.#j(),this.#$._writableState}get _readableState(){return this.#j(),this.#$._readableState}pipe(j){return this.#j(),this.#$.pipe(j)}unpipe(j){return this.#j(),this.#$.unpipe(j)}#j(){if(this.#$)return;this.#$=Y(this.#z);const j=this.eventNames();for(let z of j)this.#$.on(z,(...B)=>{this.emit(z,...B)})}#G(j){var z=this.#B();const B=z.write(j);this.bytesWritten+=B;const J=z.flush(!1);return!!(B||J)}#H(j,z){if(!V(z))return this.#j(),this.#$.write(j,z);return this.#G(j)}#K(j,z){if(z)this.emit(\"error\",z);try{j(z\?z:null)}catch(B){this.emit(\"error\",B)}}#L(j,z,B){if(!V(z))return this.#j(),this.#$.write(j,z,B);var J=this.#B();const O=J.write(j),G=J.flush(!0);if(G\?.then)return G.then(()=>{this.#K(B),this.emit(\"drain\")},(H)=>this.#K(B,H)),!1;return queueMicrotask(()=>{this.#K(B)}),!!(O||G)}write(j,z,B){const J=this._write(j,z,B);if(J)this.emit(\"drain\");return J}get hasColors(){return @Bun.tty[this.#z].hasColors}_write(j,z,B){var J=this.#$;if(J)return J.write(j,z,B);switch(arguments.length){case 0:{var O=new Error(\"Invalid arguments\");throw O.code=\"ERR_INVALID_ARG_TYPE\",O}case 1:return this.#G(j);case 2:if(typeof z===\"function\")return this.#L(j,\"\",z);else if(typeof z===\"string\")return this.#H(j,z);default:{if(typeof z!==\"undefined\"&&typeof z!==\"string\"||typeof B!==\"undefined\"&&typeof B!==\"function\"){var O=new Error(\"Invalid arguments\");throw O.code=\"ERR_INVALID_ARG_TYPE\",O}if(typeof B===\"undefined\")return this.#H(j,z);return this.#L(j,z,B)}}}destroy(){return this}end(){return this}};return new A(Z)})\n";
+const char* const s_processObjectInternalsGetStdioWriteStreamCode = "(function (X,Z){\"use strict\";var N=(J)=>{var L=@requireMap.get(J);if(L)return L.exports;return @internalRequire(J)},I={path:\"node:process\",require:N};function q(J){var{Duplex:L,eos:j,destroy:B}=N(\"node:stream\"),K=class Q extends L{#j;#$;#N=!0;#O=!0;#J;#B;#L;#G;#H;#K;get isTTY(){return this.#K\?\?=N(\"node:tty\").isatty(J)}get fd(){return J}constructor(G){super({readable:!0,writable:!0});this.#J=`/dev/fd/${G}`}#M(G){const H=this.#B;if(this.#B=null,H)H(G);else if(G)this.destroy(G);else if(!this.#N&&!this.#O)this.destroy()}_destroy(G,H){if(!G&&this.#B!==null){var O=class P extends Error{code;name;constructor(V=\"The operation was aborted\",M=void 0){if(M!==void 0&&typeof M!==\"object\")throw new Error(`Invalid AbortError options:\\n\\n${JSON.stringify(M,null,2)}`);super(V,M);this.code=\"ABORT_ERR\",this.name=\"AbortError\"}};G=new O}if(this.#L=null,this.#G=null,this.#B===null)H(G);else{if(this.#B=H,this.#j)B(this.#j,G);if(this.#$)B(this.#$,G)}}_write(G,H,O){if(!this.#j){var{createWriteStream:P}=N(\"node:fs\"),V=this.#j=P(this.#J);V.on(\"finish\",()=>{if(this.#G){const M=this.#G;this.#G=null,M()}}),V.on(\"drain\",()=>{if(this.#L){const M=this.#L;this.#L=null,M()}}),j(V,(M)=>{if(this.#O=!1,M)B(V,M);this.#M(M)})}if(V.write(G,H))O();else this.#L=O}_final(G){this.#j&&this.#j.end(),this.#G=G}#Q(){var{createReadStream:G}=N(\"node:fs\"),H=this.#$=G(this.#J);return H.on(\"readable\",()=>{if(this.#H){const O=this.#H;this.#H=null,O()}else this.read()}),H.on(\"end\",()=>{this.push(null)}),j(H,(O)=>{if(this.#N=!1,O)B(H,O);this.#M(O)}),H}_read(){var G=this.#$;if(!G)G=this.#Q();while(!0){const H=G.read();if(H===null||!this.push(H))return}}};return new K(J)}var{EventEmitter:x}=N(\"node:events\");function Y(J){if(!J)return!0;var L=J.toLowerCase();return L===\"utf8\"||L===\"utf-8\"||L===\"buffer\"||L===\"binary\"}var T,U=[0,0],D=class J extends x{#j;#$;#N;#O;bytesWritten=0;setDefaultEncoding(L){if(this.#$||!Y(L))return this.#L(),this.#$.setDefaultEncoding(L)}#J(){switch(this.#j){case 1:{var L=@Bun.stdout.writer({highWaterMark:0});return L.unref(),L}case 2:{var L=@Bun.stderr.writer({highWaterMark:0});return L.unref(),L}default:throw new Error(\"Unsupported writer\")}}#B(){return this.#N\?\?=this.#J()}constructor(L){super();this.#j=L}get fd(){return this.#j}ref(){this.#B().ref()}unref(){this.#B().unref()}on(L,j){if(L===\"close\"||L===\"finish\")return this.#L(),this.#$.on(L,j);if(L===\"drain\")return super.on(\"drain\",j);if(L===\"error\")return super.on(\"error\",j);return super.on(L,j)}get _writableState(){return this.#L(),this.#$._writableState}get _readableState(){return this.#L(),this.#$._readableState}pipe(L){return this.#L(),this.#$.pipe(L)}unpipe(L){return this.#L(),this.#$.unpipe(L)}#L(){if(this.#$)return;this.#$=q(this.#j);const L=this.eventNames();for(let j of L)this.#$.on(j,(...B)=>{this.emit(j,...B)})}#G(L){var j=this.#B();const B=j.write(L);this.bytesWritten+=B;const K=j.flush(!1);return!!(B||K)}#H(L,j){if(!Y(j))return this.#L(),this.#$.write(L,j);return this.#G(L)}#K(L,j){if(j)this.emit(\"error\",j);try{L(j\?j:null)}catch(B){this.emit(\"error\",B)}}#M(L,j,B){if(!Y(j))return this.#L(),this.#$.write(L,j,B);var K=this.#B();const Q=K.write(L),G=K.flush(!0);if(G\?.then)return G.then(()=>{this.#K(B),this.emit(\"drain\")},(H)=>this.#K(B,H)),!1;return queueMicrotask(()=>{this.#K(B)}),!!(Q||G)}get isTTY(){return!1}write(L,j,B){const K=this._write(L,j,B);if(K)this.emit(\"drain\");return K}get hasColors(){return @Bun.tty[this.#j].hasColors}_write(L,j,B){var K=this.#$;if(K)return K.write(L,j,B);switch(arguments.length){case 0:{var Q=new Error(\"Invalid arguments\");throw Q.code=\"ERR_INVALID_ARG_TYPE\",Q}case 1:return this.#G(L);case 2:if(typeof j===\"function\")return this.#M(L,\"\",j);else if(typeof j===\"string\")return this.#H(L,j);default:{if(typeof j!==\"undefined\"&&typeof j!==\"string\"||typeof B!==\"undefined\"&&typeof B!==\"function\"){var Q=new Error(\"Invalid arguments\");throw Q.code=\"ERR_INVALID_ARG_TYPE\",Q}if(typeof B===\"undefined\")return this.#H(L,j);return this.#M(L,j,B)}}}destroy(){return this}end(){return this}};if(Z(X,U)){var C=class J extends D{get isTTY(){return!0}cursorTo(L,j,B){return(T\?\?=N(\"node:readline\")).cursorTo(this,L,j,B)}moveCursor(L,j,B){return(T\?\?=N(\"node:readline\")).moveCursor(this,L,j,B)}clearLine(L,j){return(T\?\?=N(\"node:readline\")).clearLine(this,L,j)}clearScreenDown(L){return(T\?\?=N(\"node:readline\")).clearScreenDown(this,L)}getWindowSize(){if(Z(X,U)===!0)return[U[0],U[1]]}get columns(){if(Z(X,U)===!0)return U[0]}get rows(){if(Z(X,U)===!0)return U[1]}};return new C(X)}return new D(X)})\n";
// getStdinStream
const JSC::ConstructAbility s_processObjectInternalsGetStdinStreamCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
diff --git a/src/js/out/WebCoreJSBuiltins.h b/src/js/out/WebCoreJSBuiltins.h
index 42d14b154..26c2fdee7 100644
--- a/src/js/out/WebCoreJSBuiltins.h
+++ b/src/js/out/WebCoreJSBuiltins.h
@@ -1181,7 +1181,7 @@ extern const JSC::ImplementationVisibility s_processObjectInternalsGetStdinStrea
#define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_DATA(macro) \
macro(binding, processObjectInternalsBinding, 1) \
- macro(getStdioWriteStream, processObjectInternalsGetStdioWriteStream, 1) \
+ macro(getStdioWriteStream, processObjectInternalsGetStdioWriteStream, 2) \
macro(getStdinStream, processObjectInternalsGetStdinStream, 1) \
#define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_CODE(macro) \