diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2023-10-30 08:53:08 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-30 15:53:08 +0000 |
commit | 09204107d85351dae07a45f6a9684b5b6e573652 (patch) | |
tree | 5a04f3a877e677e382e34684784ddf832838bd77 /ext/node/polyfills | |
parent | 1acef755ca8a0a0433a98e4a66433c63ee0a3b09 (diff) |
fix: implement node:tty (#20892)
Fixes #21012
Closes https://github.com/denoland/deno/issues/20855
Fixes https://github.com/denoland/deno/issues/20890
Fixes https://github.com/denoland/deno/issues/20611
Fixes https://github.com/denoland/deno/issues/20336
Fixes `create-svelte` from https://github.com/denoland/deno/issues/17248
Fixes more reports here:
- https://github.com/denoland/deno/issues/6529#issuecomment-1432690559
- https://github.com/denoland/deno/issues/6529#issuecomment-1522059006
- https://github.com/denoland/deno/issues/6529#issuecomment-1695803570
Diffstat (limited to 'ext/node/polyfills')
-rw-r--r-- | ext/node/polyfills/_process/streams.mjs | 74 | ||||
-rw-r--r-- | ext/node/polyfills/internal_binding/util.ts | 9 | ||||
-rw-r--r-- | ext/node/polyfills/process.ts | 106 | ||||
-rw-r--r-- | ext/node/polyfills/tty.js | 83 | ||||
-rw-r--r-- | ext/node/polyfills/tty.ts | 25 |
5 files changed, 123 insertions, 174 deletions
diff --git a/ext/node/polyfills/_process/streams.mjs b/ext/node/polyfills/_process/streams.mjs index b6efef65e..39ee89a82 100644 --- a/ext/node/polyfills/_process/streams.mjs +++ b/ext/node/polyfills/_process/streams.mjs @@ -12,9 +12,9 @@ import { moveCursor, } from "ext:deno_node/internal/readline/callbacks.mjs"; import { Duplex, Readable, Writable } from "node:stream"; -import { isWindows } from "ext:deno_node/_util/os.ts"; -import { fs as fsConstants } from "ext:deno_node/internal_binding/constants.ts"; import * as io from "ext:deno_io/12_io.js"; +import * as tty from "node:tty"; +import { guessHandleType } from "ext:deno_node/internal_binding/util.ts"; // https://github.com/nodejs/node/blob/00738314828074243c9a52a228ab4c68b04259ef/lib/internal/bootstrap/switches/is_main_thread.js#L41 export function createWritableStdioStream(writer, name) { @@ -95,60 +95,21 @@ export function createWritableStdioStream(writer, name) { return stream; } -// TODO(PolarETech): This function should be replaced by -// `guessHandleType()` in "../internal_binding/util.ts". -// https://github.com/nodejs/node/blob/v18.12.1/src/node_util.cc#L257 function _guessStdinType(fd) { if (typeof fd !== "number" || fd < 0) return "UNKNOWN"; - if (Deno.isatty?.(fd)) return "TTY"; - - try { - const fileInfo = Deno.fstatSync?.(fd); - - // https://github.com/nodejs/node/blob/v18.12.1/deps/uv/src/unix/tty.c#L333 - if (!isWindows) { - switch (fileInfo.mode & fsConstants.S_IFMT) { - case fsConstants.S_IFREG: - case fsConstants.S_IFCHR: - return "FILE"; - case fsConstants.S_IFIFO: - return "PIPE"; - case fsConstants.S_IFSOCK: - // TODO(PolarETech): Need a better way to identify "TCP". - // Currently, unable to exclude UDP. - return "TCP"; - default: - return "UNKNOWN"; - } - } - - // https://github.com/nodejs/node/blob/v18.12.1/deps/uv/src/win/handle.c#L31 - if (fileInfo.isFile) { - // TODO(PolarETech): Need a better way to identify a piped stdin on Windows. - // On Windows, `Deno.fstatSync(rid).isFile` returns true even for a piped stdin. - // Therefore, a piped stdin cannot be distinguished from a file by this property. - // The mtime, atime, and birthtime of the file are "2339-01-01T00:00:00.000Z", - // so use the property as a workaround. - if (fileInfo.birthtime.valueOf() === 11644473600000) return "PIPE"; - return "FILE"; - } - } catch (e) { - // TODO(PolarETech): Need a better way to identify a character file on Windows. - // "EISDIR" error occurs when stdin is "null" on Windows, - // so use the error as a workaround. - if (isWindows && e.code === "EISDIR") return "FILE"; - } - - return "UNKNOWN"; + return guessHandleType(fd); } const _read = function (size) { const p = Buffer.alloc(size || 16 * 1024); - io.stdin?.read(p).then((length) => { - this.push(length === null ? null : p.slice(0, length)); - }, (error) => { - this.destroy(error); - }); + io.stdin?.read(p).then( + (length) => { + this.push(length === null ? null : p.slice(0, length)); + }, + (error) => { + this.destroy(error); + }, + ); }; /** https://nodejs.org/api/process.html#process_process_stdin */ @@ -172,17 +133,12 @@ export const initStdin = () => { }); break; } - case "TTY": + case "TTY": { + stdin = new tty.ReadStream(fd); + break; + } case "PIPE": case "TCP": { - // TODO(PolarETech): - // For TTY, `new Duplex()` should be replaced `new tty.ReadStream()` if possible. - // There are two problems that need to be resolved. - // 1. Using them here introduces a circular dependency. - // 2. Creating a tty.ReadStream() is not currently supported. - // https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L194 - // https://github.com/nodejs/node/blob/v18.12.1/lib/tty.js#L47 - // For PIPE and TCP, `new Duplex()` should be replaced `new net.Socket()` if possible. // There are two problems that need to be resolved. // 1. Using them here introduces a circular dependency. diff --git a/ext/node/polyfills/internal_binding/util.ts b/ext/node/polyfills/internal_binding/util.ts index a2d355c1e..38eeebee0 100644 --- a/ext/node/polyfills/internal_binding/util.ts +++ b/ext/node/polyfills/internal_binding/util.ts @@ -28,10 +28,13 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials -import { notImplemented } from "ext:deno_node/_utils.ts"; +const core = globalThis.Deno.core; +const ops = core.ops; -export function guessHandleType(_fd: number): string { - notImplemented("util.guessHandleType"); +const handleTypes = ["TCP", "TTY", "UDP", "FILE", "PIPE", "UNKNOWN"]; +export function guessHandleType(fd: number): string { + const type = ops.op_node_guess_handle_type(fd); + return handleTypes[type]; } export const ALL_PROPERTIES = 0; diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts index 618f92d3f..a4fc3317d 100644 --- a/ext/node/polyfills/process.ts +++ b/ext/node/polyfills/process.ts @@ -33,8 +33,6 @@ export { _nextTick as nextTick, chdir, cwd, env, version, versions }; import { createWritableStdioStream, initStdin, - Readable, - Writable, } from "ext:deno_node/_process/streams.mjs"; import { enableNextTick, @@ -57,41 +55,9 @@ export let platform = ""; // TODO(kt3k): This should be set at start up time export let pid = 0; -// We want streams to be as lazy as possible, but we cannot export a getter in a module. To -// work around this we make these proxies that eagerly instantiate the underlying object on -// first access of any property/method. -function makeLazyStream<T>(objectFactory: () => T): T { - return new Proxy({}, { - get: function (_, prop, receiver) { - // deno-lint-ignore no-explicit-any - return Reflect.get(objectFactory() as any, prop, receiver); - }, - has: function (_, prop) { - // deno-lint-ignore no-explicit-any - return Reflect.has(objectFactory() as any, prop); - }, - ownKeys: function (_) { - // deno-lint-ignore no-explicit-any - return Reflect.ownKeys(objectFactory() as any); - }, - set: function (_, prop, value, receiver) { - // deno-lint-ignore no-explicit-any - return Reflect.set(objectFactory() as any, prop, value, receiver); - }, - getPrototypeOf: function (_) { - // deno-lint-ignore no-explicit-any - return Reflect.getPrototypeOf(objectFactory() as any); - }, - getOwnPropertyDescriptor(_, prop) { - // deno-lint-ignore no-explicit-any - return Reflect.getOwnPropertyDescriptor(objectFactory() as any, prop); - }, - }) as T; -} +let stdin, stdout, stderr; -export let stderr = makeLazyStream(getStderr); -export let stdin = makeLazyStream(getStdin); -export let stdout = makeLazyStream(getStdout); +export { stderr, stdin, stdout }; import { getBinding } from "ext:deno_node/internal_binding/mod.ts"; import * as constants from "ext:deno_node/internal_binding/constants.ts"; @@ -646,19 +612,13 @@ class Process extends EventEmitter { memoryUsage = memoryUsage; /** https://nodejs.org/api/process.html#process_process_stderr */ - get stderr(): Writable { - return getStderr(); - } + stderr = stderr; /** https://nodejs.org/api/process.html#process_process_stdin */ - get stdin(): Readable { - return getStdin(); - } + stdin = stdin; /** https://nodejs.org/api/process.html#process_process_stdout */ - get stdout(): Writable { - return getStdout(); - } + stdout = stdout; /** https://nodejs.org/api/process.html#process_process_version */ version = version; @@ -906,52 +866,24 @@ internals.__bootstrapNodeProcess = function ( core.setMacrotaskCallback(runNextTicks); enableNextTick(); + stdin = process.stdin = initStdin(); + /** https://nodejs.org/api/process.html#process_process_stdout */ + stdout = process.stdout = createWritableStdioStream( + io.stdout, + "stdout", + ); + + /** https://nodejs.org/api/process.html#process_process_stderr */ + stderr = process.stderr = createWritableStdioStream( + io.stderr, + "stderr", + ); + process.setStartTime(Date.now()); + // @ts-ignore Remove setStartTime and #startTime is not modifiable delete process.setStartTime; delete internals.__bootstrapNodeProcess; }; -// deno-lint-ignore no-explicit-any -let stderr_ = null as any; -// deno-lint-ignore no-explicit-any -let stdin_ = null as any; -// deno-lint-ignore no-explicit-any -let stdout_ = null as any; - -function getStdin(): Readable { - if (!stdin_) { - stdin_ = initStdin(); - stdin = stdin_; - Object.defineProperty(process, "stdin", { get: () => stdin_ }); - } - return stdin_; -} - -/** https://nodejs.org/api/process.html#process_process_stdout */ -function getStdout(): Writable { - if (!stdout_) { - stdout_ = createWritableStdioStream( - io.stdout, - "stdout", - ); - stdout = stdout_; - Object.defineProperty(process, "stdout", { get: () => stdout_ }); - } - return stdout_; -} - -/** https://nodejs.org/api/process.html#process_process_stderr */ -function getStderr(): Writable { - if (!stderr_) { - stderr_ = createWritableStdioStream( - io.stderr, - "stderr", - ); - stderr = stderr_; - Object.defineProperty(process, "stderr", { get: () => stderr_ }); - } - return stderr_; -} - export default process; diff --git a/ext/node/polyfills/tty.js b/ext/node/polyfills/tty.js new file mode 100644 index 000000000..54f8f6eae --- /dev/null +++ b/ext/node/polyfills/tty.js @@ -0,0 +1,83 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { ERR_INVALID_FD } from "ext:deno_node/internal/errors.ts"; +import { LibuvStreamWrap } from "ext:deno_node/internal_binding/stream_wrap.ts"; +import { providerType } from "ext:deno_node/internal_binding/async_wrap.ts"; +import { Duplex } from "node:stream"; +const { Error } = globalThis.__bootstrap.primordials; + +// Returns true when the given numeric fd is associated with a TTY and false otherwise. +function isatty(fd) { + if (typeof fd !== "number") { + return false; + } + try { + return Deno.isatty(fd); + } catch (_) { + return false; + } +} + +class TTY extends LibuvStreamWrap { + constructor(handle) { + super(providerType.TTYWRAP, handle); + } +} + +export class ReadStream extends Duplex { + constructor(fd, options) { + if (fd >> 0 !== fd || fd < 0) { + throw new ERR_INVALID_FD(fd); + } + + // We only support `stdin`. + if (fd != 0) throw new Error("Only fd 0 is supported."); + + const tty = new TTY(Deno.stdin); + super({ + readableHighWaterMark: 0, + handle: tty, + manualStart: true, + ...options, + }); + + this.isRaw = false; + this.isTTY = true; + } + + setRawMode(flag) { + flag = !!flag; + this._handle.setRaw(flag); + + this.isRaw = flag; + return this; + } +} + +export class WriteStream extends Duplex { + constructor(fd) { + if (fd >> 0 !== fd || fd < 0) { + throw new ERR_INVALID_FD(fd); + } + + // We only support `stdin`, `stdout` and `stderr`. + if (fd > 2) throw new Error("Only fd 0, 1 and 2 are supported."); + + const tty = new TTY( + fd === 0 ? Deno.stdin : fd === 1 ? Deno.stdout : Deno.stderr, + ); + + super({ + readableHighWaterMark: 0, + handle: tty, + manualStart: true, + }); + + const { columns, rows } = Deno.consoleSize(); + this.columns = columns; + this.rows = rows; + } +} + +export { isatty }; +export default { isatty, WriteStream, ReadStream }; diff --git a/ext/node/polyfills/tty.ts b/ext/node/polyfills/tty.ts deleted file mode 100644 index d33f779ca..000000000 --- a/ext/node/polyfills/tty.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { Socket } from "node:net"; - -// Returns true when the given numeric fd is associated with a TTY and false otherwise. -function isatty(fd: number) { - if (typeof fd !== "number") { - return false; - } - try { - return Deno.isatty(fd); - } catch (_) { - return false; - } -} - -// TODO(kt3k): Implement tty.ReadStream class -export class ReadStream extends Socket { -} -// TODO(kt3k): Implement tty.WriteStream class -export class WriteStream extends Socket { -} - -export { isatty }; -export default { isatty, WriteStream, ReadStream }; |