2023-02-14 11:38:45 -05:00
|
|
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
|
|
|
|
|
2023-03-08 06:44:54 -05:00
|
|
|
import { Buffer } from "ext:deno_node/buffer.ts";
|
2023-02-14 11:38:45 -05:00
|
|
|
import {
|
|
|
|
clearLine,
|
|
|
|
clearScreenDown,
|
|
|
|
cursorTo,
|
|
|
|
moveCursor,
|
2023-03-08 06:44:54 -05:00
|
|
|
} from "ext:deno_node/internal/readline/callbacks.mjs";
|
|
|
|
import { Duplex, Readable, Writable } from "ext:deno_node/stream.ts";
|
|
|
|
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";
|
2023-02-14 11:38:45 -05:00
|
|
|
|
|
|
|
// https://github.com/nodejs/node/blob/00738314828074243c9a52a228ab4c68b04259ef/lib/internal/bootstrap/switches/is_main_thread.js#L41
|
2023-02-16 08:30:14 -05:00
|
|
|
export function createWritableStdioStream(writer, name) {
|
2023-02-14 11:38:45 -05:00
|
|
|
const stream = new Writable({
|
|
|
|
write(buf, enc, cb) {
|
|
|
|
if (!writer) {
|
|
|
|
this.destroy(
|
|
|
|
new Error(`Deno.${name} is not available in this environment`),
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
writer.writeSync(buf instanceof Uint8Array ? buf : Buffer.from(buf, enc));
|
|
|
|
cb();
|
|
|
|
},
|
|
|
|
destroy(err, cb) {
|
|
|
|
cb(err);
|
|
|
|
this._undestroy();
|
|
|
|
if (!this._writableState.emitClose) {
|
|
|
|
nextTick(() => this.emit("close"));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
stream.fd = writer?.rid ?? -1;
|
|
|
|
stream.destroySoon = stream.destroy;
|
|
|
|
stream._isStdio = true;
|
|
|
|
stream.once("close", () => writer?.close());
|
|
|
|
Object.defineProperties(stream, {
|
|
|
|
columns: {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
get: () =>
|
|
|
|
Deno.isatty?.(writer?.rid) ? Deno.consoleSize?.().columns : undefined,
|
|
|
|
},
|
|
|
|
rows: {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
get: () =>
|
|
|
|
Deno.isatty?.(writer?.rid) ? Deno.consoleSize?.().rows : undefined,
|
|
|
|
},
|
|
|
|
isTTY: {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
get: () => Deno.isatty?.(writer?.rid),
|
|
|
|
},
|
|
|
|
getWindowSize: {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
value: () =>
|
|
|
|
Deno.isatty?.(writer?.rid)
|
|
|
|
? Object.values(Deno.consoleSize?.())
|
|
|
|
: undefined,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (Deno.isatty?.(writer?.rid)) {
|
|
|
|
// These belong on tty.WriteStream(), but the TTY streams currently have
|
|
|
|
// following problems:
|
|
|
|
// 1. Using them here introduces a circular dependency.
|
|
|
|
// 2. Creating a net.Socket() from a fd is not currently supported.
|
|
|
|
stream.cursorTo = function (x, y, callback) {
|
|
|
|
return cursorTo(this, x, y, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
stream.moveCursor = function (dx, dy, callback) {
|
|
|
|
return moveCursor(this, dx, dy, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
stream.clearLine = function (dir, callback) {
|
|
|
|
return clearLine(this, dir, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
stream.clearScreenDown = function (callback) {
|
|
|
|
return clearScreenDown(this, callback);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2023-02-15 13:44:52 -05:00
|
|
|
if (!isWindows) {
|
2023-02-14 11:38:45 -05:00
|
|
|
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.
|
2023-02-15 13:44:52 -05:00
|
|
|
if (isWindows && e.code === "EISDIR") return "FILE";
|
2023-02-14 11:38:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return "UNKNOWN";
|
|
|
|
}
|
|
|
|
|
|
|
|
const _read = function (size) {
|
|
|
|
const p = Buffer.alloc(size || 16 * 1024);
|
2023-03-04 22:37:37 -05:00
|
|
|
io.stdin?.read(p).then((length) => {
|
2023-02-14 11:38:45 -05:00
|
|
|
this.push(length === null ? null : p.slice(0, length));
|
|
|
|
}, (error) => {
|
|
|
|
this.destroy(error);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/** https://nodejs.org/api/process.html#process_process_stdin */
|
|
|
|
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L189
|
2023-02-16 08:30:14 -05:00
|
|
|
/** Create process.stdin */
|
|
|
|
export const initStdin = () => {
|
2023-03-04 22:37:37 -05:00
|
|
|
const fd = io.stdin?.rid;
|
2023-02-16 08:30:14 -05:00
|
|
|
let stdin;
|
2023-02-14 11:38:45 -05:00
|
|
|
const stdinType = _guessStdinType(fd);
|
|
|
|
|
|
|
|
switch (stdinType) {
|
|
|
|
case "FILE": {
|
|
|
|
// Since `fs.ReadStream` cannot be imported before process initialization,
|
|
|
|
// use `Readable` instead.
|
|
|
|
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L200
|
|
|
|
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/fs/streams.js#L148
|
2023-02-16 08:30:14 -05:00
|
|
|
stdin = new Readable({
|
2023-02-14 11:38:45 -05:00
|
|
|
highWaterMark: 64 * 1024,
|
|
|
|
autoDestroy: false,
|
|
|
|
read: _read,
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "TTY":
|
|
|
|
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.
|
|
|
|
// 2. Creating a net.Socket() from a fd is not currently supported.
|
|
|
|
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L206
|
|
|
|
// https://github.com/nodejs/node/blob/v18.12.1/lib/net.js#L329
|
2023-02-16 08:30:14 -05:00
|
|
|
stdin = new Duplex({
|
2023-02-14 11:38:45 -05:00
|
|
|
readable: stdinType === "TTY" ? undefined : true,
|
|
|
|
writable: stdinType === "TTY" ? undefined : false,
|
|
|
|
readableHighWaterMark: stdinType === "TTY" ? 0 : undefined,
|
|
|
|
allowHalfOpen: false,
|
|
|
|
emitClose: false,
|
|
|
|
autoDestroy: true,
|
|
|
|
decodeStrings: false,
|
|
|
|
read: _read,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (stdinType !== "TTY") {
|
|
|
|
// Make sure the stdin can't be `.end()`-ed
|
2023-02-16 08:30:14 -05:00
|
|
|
stdin._writableState.ended = true;
|
2023-02-14 11:38:45 -05:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
// Provide a dummy contentless input for e.g. non-console
|
|
|
|
// Windows applications.
|
2023-02-16 08:30:14 -05:00
|
|
|
stdin = new Readable({ read() {} });
|
|
|
|
stdin.push(null);
|
2023-02-14 11:38:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-04 22:37:37 -05:00
|
|
|
stdin.on("close", () => io.stdin?.close());
|
|
|
|
stdin.fd = io.stdin?.rid ?? -1;
|
2023-02-16 08:30:14 -05:00
|
|
|
Object.defineProperty(stdin, "isTTY", {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
get() {
|
|
|
|
return Deno.isatty?.(Deno.stdin.rid);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
stdin._isRawMode = false;
|
|
|
|
stdin.setRawMode = (enable) => {
|
2023-03-04 22:37:37 -05:00
|
|
|
io.stdin?.setRaw?.(enable);
|
2023-02-16 08:30:14 -05:00
|
|
|
stdin._isRawMode = enable;
|
|
|
|
return stdin;
|
|
|
|
};
|
|
|
|
Object.defineProperty(stdin, "isRaw", {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
get() {
|
|
|
|
return stdin._isRawMode;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-02-14 11:38:45 -05:00
|
|
|
return stdin;
|
|
|
|
};
|
2023-02-16 08:30:14 -05:00
|
|
|
|