diff options
author | 2023-07-17 21:21:32 -0700 | |
---|---|---|
committer | 2023-07-17 21:21:43 -0700 | |
commit | 728c8fdcdb4e9276a7bd0721bad3b5d0cbed67fa (patch) | |
tree | 83084ab477feef9b58e91f999cbf0b0bc67ca493 | |
parent | 13b54fbdb8cc36bbe027238654360f159ecaefbb (diff) | |
download | bun-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.cpp | 55 | ||||
-rw-r--r-- | src/js/builtins/ProcessObjectInternals.ts | 79 | ||||
-rw-r--r-- | src/js/out/WebCoreJSBuiltins.cpp | 4 | ||||
-rw-r--r-- | src/js/out/WebCoreJSBuiltins.h | 2 |
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) \ |