Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: implement node:tty #20892

Merged
merged 28 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ fn unwrap_or_exit<T>(result: Result<T, AnyError>) -> T {
pub fn main() {
setup_panic_hook();

util::unix::prepare_stdio();
util::unix::raise_fd_limit();
util::windows::ensure_stdio_open();
#[cfg(windows)]
Expand Down
24 changes: 24 additions & 0 deletions cli/util/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,27 @@ pub fn raise_fd_limit() {
}
}
}

pub fn prepare_stdio() {
#[cfg(unix)]
// SAFETY: Save current state of stdio and restore it when we exit.
unsafe {
use libc::atexit;
use libc::tcgetattr;
use libc::tcsetattr;
use libc::termios;

let mut termios = std::mem::zeroed::<termios>();
if tcgetattr(libc::STDIN_FILENO, &mut termios) == 0 {
static mut ORIG_TERMIOS: Option<termios> = None;
ORIG_TERMIOS = Some(termios);

extern "C" fn reset_stdio() {
// SAFETY: Reset the stdio state.
unsafe { tcsetattr(libc::STDIN_FILENO, 0, &ORIG_TERMIOS.unwrap()) };
}

atexit(reset_stdio);
}
}
bartlomieju marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ deno_core::extension!(deno_node,
"timers.ts" with_specifier "node:timers",
"timers/promises.ts" with_specifier "node:timers/promises",
"tls.ts" with_specifier "node:tls",
"tty.ts" with_specifier "node:tty",
"tty.js" with_specifier "node:tty",
"url.ts" with_specifier "node:url",
"util.ts" with_specifier "node:util",
"util/types.ts" with_specifier "node:util/types",
Expand Down
111 changes: 24 additions & 87 deletions ext/node/polyfills/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -54,41 +52,14 @@ 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);
Comment on lines -63 to -67
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bartlomieju @mmastrac

This breaks code in _streams.mjs because of the use of Proxy:

divy@mini /tmp> ~/gh/deno/target/debug/deno run -A npm:nuxi@latest init my-app

[5:25:30 PM]  ERROR  'get' on proxy: property '_eventsCount' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '2' but got '1')

  at _addListener (ext:deno_node/_stream.mjs:1884:9)
  at Proxy.addListener (ext:deno_node/_stream.mjs:1913:14)
  at Proxy.Readable.on (ext:deno_node/_stream.mjs:3275:39)
  at Proxy.Readable.pipe (ext:deno_node/_stream.mjs:3177:11)

when its trying to update the eventsCount. I reverted it back to not lazy load the stdin/out/err streams which fixed the issue.

},
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;
}
// 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;

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";
Expand Down Expand Up @@ -634,19 +605,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;
Expand Down Expand Up @@ -892,52 +857,24 @@ internals.__bootstrapNodeProcess = function (
core.setMacrotaskCallback(runNextTicks);
enableNextTick();

// Initializes stdin
stdin = process.stdin = initStdin();

/** https://nodejs.org/api/process.html#process_process_stderr */
stderr = process.stderr = createWritableStdioStream(
io.stderr,
"stderr",
);

/** https://nodejs.org/api/process.html#process_process_stdout */
stdout = process.stdout = createWritableStdioStream(
io.stdout,
"stdout",
);
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;
84 changes: 84 additions & 0 deletions ext/node/polyfills/tty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

import { Socket } from "node:net";
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";

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 Socket {
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.");
littledivy marked this conversation as resolved.
Show resolved Hide resolved

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 Socket {
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 };
25 changes: 0 additions & 25 deletions ext/node/polyfills/tty.ts

This file was deleted.

Loading