/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node/polyfills/internal/tty.js
465 строк
12 KB
em
fix(ext/node): expose internal/tty as requireable module (#34105)
17 май 2026, 00:02
Не верифицирован
17 май 2026, 00:02
9732c5f
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. // Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) // deno-lint-ignore-file no-process-global import { core, primordials } from "ext:core/mod.js"; const { ArrayPrototypeSome, FunctionPrototypeCall, ObjectEntries, ObjectPrototypeHasOwnProperty, ObjectPrototypeIsPrototypeOf, ObjectSetPrototypeOf, RegExpPrototypeExec, SafeMap, SafeMapIterator, SafeRegExp, SafeSet, SafeSetIterator, SetPrototypeAdd, SetPrototypeDelete, SetPrototypeGetSize, StringPrototypeSplit, StringPrototypeToLowerCase, } = primordials; const { ERR_INVALID_FD, ERR_TTY_INIT_FAILED, errnoException, } = core.loadExtScript("ext:deno_node/internal/errors.ts"); const { validateInteger } = core.loadExtScript( "ext:deno_node/internal/validators.mjs", ); import { op_tty_check_fd_permission, TTY } from "ext:core/ops"; const { isatty } = core.loadExtScript("ext:deno_node/tty.js"); const lazyNet = core.createLazyLoader("node:net"); const { clearLine, clearScreenDown, cursorTo, moveCursor, } = core.loadExtScript("ext:deno_node/internal/readline/callbacks.mjs"); const { release } = core.loadExtScript("ext:deno_node/os.ts"); // Color depth constants const COLORS_2 = 1; const COLORS_16 = 4; const COLORS_256 = 8; const COLORS_16M = 24; // Terminal environments supporting specific color depths const TERM_ENVS = { "eterm": COLORS_16, "cons25": COLORS_16, "console": COLORS_16, "cygwin": COLORS_16, "dtterm": COLORS_16, "gnome": COLORS_16, "hurd": COLORS_16, "jfbterm": COLORS_16, "konsole": COLORS_16, "kterm": COLORS_16, "mlterm": COLORS_16, "mosh": COLORS_16M, "putty": COLORS_16, "st": COLORS_16, "rxvt-unicode-24bit": COLORS_16M, "terminator": COLORS_16M, "xterm-kitty": COLORS_16M, }; // CI environments and their color support const CI_ENVS_MAP = new SafeMap(ObjectEntries({ APPVEYOR: COLORS_256, BUILDKITE: COLORS_256, CIRCLECI: COLORS_16M, DRONE: COLORS_256, GITEA_ACTIONS: COLORS_16M, GITHUB_ACTIONS: COLORS_16M, GITLAB_CI: COLORS_256, TRAVIS: COLORS_256, })); // Regular expressions for terminal types const TERM_ENVS_REG_EXP = [ new SafeRegExp("ansi"), new SafeRegExp("color"), new SafeRegExp("linux"), new SafeRegExp("direct"), new SafeRegExp("^con[0-9]*x[0-9]"), new SafeRegExp("^rxvt"), new SafeRegExp("^screen"), new SafeRegExp("^xterm"), new SafeRegExp("^vt100"), new SafeRegExp("^vt220"), ]; let warned = false; function warnOnDeactivatedColors(env) { if (warned) { return; } let name = ""; if (env.NODE_DISABLE_COLORS !== undefined && env.NODE_DISABLE_COLORS !== "") { name = "NODE_DISABLE_COLORS"; } if (env.NO_COLOR !== undefined && env.NO_COLOR !== "") { if (name !== "") { name += "' and '"; } name += "NO_COLOR"; } if (name !== "") { process.emitWarning( "The '" + name + "' env is ignored due to the 'FORCE_COLOR' env being set.", "Warning", ); warned = true; } } let OSRelease; /** * @param {Record<string, string>} [env] * @returns {1 | 4 | 8 | 24} */ export function getColorDepth(env = process.env) { // Use level 0-3 to support the same levels as `chalk` does. if (env.FORCE_COLOR !== undefined) { switch (env.FORCE_COLOR) { case "": case "1": case "true": warnOnDeactivatedColors(env); return COLORS_16; case "2": warnOnDeactivatedColors(env); return COLORS_256; case "3": warnOnDeactivatedColors(env); return COLORS_16M; default: return COLORS_2; } } if ( (env.NODE_DISABLE_COLORS !== undefined && env.NODE_DISABLE_COLORS !== "") || (env.NO_COLOR !== undefined && env.NO_COLOR !== "") || env.TERM === "dumb" ) { return COLORS_2; } if (process.platform === "win32") { if (OSRelease === undefined) { OSRelease = StringPrototypeSplit(release(), ".", 3); } if (+OSRelease[0] >= 10) { const build = +OSRelease[2]; if (build >= 14931) { return COLORS_16M; } if (build >= 10586) { return COLORS_256; } } return COLORS_16; } if (env.TMUX) { return COLORS_16M; } // Azure DevOps if ( ObjectPrototypeHasOwnProperty(env, "TF_BUILD") && ObjectPrototypeHasOwnProperty(env, "AGENT_NAME") ) { return COLORS_16; } if (ObjectPrototypeHasOwnProperty(env, "CI")) { for (const { 0: envName, 1: colors } of new SafeMapIterator(CI_ENVS_MAP)) { if (ObjectPrototypeHasOwnProperty(env, envName)) { return colors; } } if (env.CI_NAME === "codeship") { return COLORS_256; } return COLORS_2; } if (ObjectPrototypeHasOwnProperty(env, "TEAMCITY_VERSION")) { return RegExpPrototypeExec( new SafeRegExp("^(9\\.(0*[1-9]\\d*)\\.|\\d{2,}\\.)"), env.TEAMCITY_VERSION, ) !== null ? COLORS_16 : COLORS_2; } switch (env.TERM_PROGRAM) { case "iTerm.app": if ( !env.TERM_PROGRAM_VERSION || RegExpPrototypeExec( new SafeRegExp("^[0-2]\\."), env.TERM_PROGRAM_VERSION, ) !== null ) { return COLORS_256; } return COLORS_16M; case "HyperTerm": case "MacTerm": return COLORS_16M; case "Apple_Terminal": return COLORS_256; } if (env.COLORTERM === "truecolor" || env.COLORTERM === "24bit") { return COLORS_16M; } if (env.TERM) { if (RegExpPrototypeExec(new SafeRegExp("truecolor"), env.TERM) !== null) { return COLORS_16M; } if (RegExpPrototypeExec(new SafeRegExp("^xterm-256"), env.TERM) !== null) { return COLORS_256; } const termEnv = StringPrototypeToLowerCase(env.TERM); if (TERM_ENVS[termEnv]) { return TERM_ENVS[termEnv]; } if ( ArrayPrototypeSome( TERM_ENVS_REG_EXP, (term) => RegExpPrototypeExec(term, termEnv) !== null, ) ) { return COLORS_16; } } // Move 16 color COLORTERM below 16m and 256 if (env.COLORTERM) { return COLORS_16; } return COLORS_2; } // Lazy SIGWINCH handling: only register the signal listener when at least one // WriteStream has a "resize" listener, and unregister when none do. This avoids // creating a persistent pending op that interferes with event loop exit / TLA // stall detection. Uses Deno.addSignalListener directly to avoid circular // dependency with node:process. const sigwinchStreams = new SafeSet(); let sigwinchRegistered = false; function onSigwinch() { for (const stream of new SafeSetIterator(sigwinchStreams)) { stream._refreshSize(); } } function addSigwinchListener(stream) { SetPrototypeAdd(sigwinchStreams, stream); if (!sigwinchRegistered) { sigwinchRegistered = true; Deno.addSignalListener("SIGWINCH", onSigwinch); } } function removeSigwinchListener(stream) { SetPrototypeDelete(sigwinchStreams, stream); if (SetPrototypeGetSize(sigwinchStreams) === 0 && sigwinchRegistered) { sigwinchRegistered = false; Deno.removeSignalListener("SIGWINCH", onSigwinch); } } // WriteStream needs to be callable without `new` to match Node.js behavior. function WriteStream(fd) { ensureWriteStreamPrototype(); if (!ObjectPrototypeIsPrototypeOf(WriteStream.prototype, this)) { return new WriteStream(fd); } if (fd >> 0 !== fd || fd < 0) { throw new ERR_INVALID_FD(fd); } // Non-stdio fds require --allow-all op_tty_check_fd_permission(fd); const ctx = {}; const tty = new TTY(fd, ctx); if (ctx.code !== undefined) { throw new ERR_TTY_INIT_FAILED(ctx); } FunctionPrototypeCall(lazyNet().Socket, this, { readableHighWaterMark: 0, handle: tty, manualStart: true, }); // Prevents interleaved or dropped stdout/stderr output for terminals. // As noted in the following reference, local TTYs tend to be quite fast and // this behavior has become expected due historical functionality on OS X, // even though it was originally intended to change in v1.0.2 (Libuv 1.2.1). // Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671 this._handle.setBlocking(true); const winSize = [0, 0]; const err = tty.getWindowSize(winSize); if (!err) { this.columns = winSize[0]; this.rows = winSize[1]; } } // `lazyNet().Socket` may be in TDZ at module-eval time when net.ts is // lazy-loaded JS, since this module's body can run before net_esm.ts // finishes evaluating. Defer the prototype hookup until the first // WriteStream is constructed. let writeStreamPrototypeReady = false; function ensureWriteStreamPrototype() { if (writeStreamPrototypeReady) return; writeStreamPrototypeReady = true; const NetSocket = lazyNet().Socket; ObjectSetPrototypeOf(WriteStream.prototype, NetSocket.prototype); ObjectSetPrototypeOf(WriteStream, NetSocket); } WriteStream.prototype.isTTY = true; WriteStream.prototype.on = function on(event, listener) { FunctionPrototypeCall(lazyNet().Socket.prototype.on, this, event, listener); if (event === "resize" && this.listenerCount("resize") === 1) { addSigwinchListener(this); } return this; }; WriteStream.prototype.addListener = function addListener(event, listener) { return this.on(event, listener); }; WriteStream.prototype.removeListener = function removeListener( event, listener, ) { FunctionPrototypeCall( lazyNet().Socket.prototype.removeListener, this, event, listener, ); if (event === "resize" && this.listenerCount("resize") === 0) { removeSigwinchListener(this); } return this; }; WriteStream.prototype.off = function off(event, listener) { return this.removeListener(event, listener); }; WriteStream.prototype.removeAllListeners = function removeAllListeners(event) { FunctionPrototypeCall( lazyNet().Socket.prototype.removeAllListeners, this, event, ); if (!event || event === "resize") { removeSigwinchListener(this); } return this; }; WriteStream.prototype._refreshSize = function _refreshSize() { const oldCols = this.columns; const oldRows = this.rows; const winSize = [0, 0]; const err = this._handle.getWindowSize(winSize); if (err) { this.emit("error", errnoException(err, "getWindowSize")); return; } const { 0: newCols, 1: newRows } = winSize; if (oldCols !== newCols || oldRows !== newRows) { this.columns = newCols; this.rows = newRows; this.emit("resize"); } }; WriteStream.prototype.cursorTo = function cursorTo_(x, y, callback) { return cursorTo(this, x, y, callback); }; WriteStream.prototype.moveCursor = function moveCursor_(dx, dy, callback) { return moveCursor(this, dx, dy, callback); }; WriteStream.prototype.clearLine = function clearLine_(dir, callback) { return clearLine(this, dir, callback); }; WriteStream.prototype.clearScreenDown = function clearScreenDown_(callback) { return clearScreenDown(this, callback); }; WriteStream.prototype.getWindowSize = function getWindowSize() { return [this.columns, this.rows]; }; /** * @param {number | Record<string, string>} [count] * @param {Record<string, string>} [env] * @returns {boolean} */ export function hasColors(count, env) { if ( env === undefined && (count === undefined || typeof count === "object" && count !== null) ) { env = count; count = 16; } else { validateInteger(count, "count", 2); } const depth = getColorDepth(env); return count <= 2 ** depth; } WriteStream.prototype.hasColors = function hasColors_(count, env) { return hasColors(count, env); }; /** * @param {Record<string, string>} [env] * @returns {1 | 4 | 8 | 24} */ WriteStream.prototype.getColorDepth = function getColorDepth_(env) { return getColorDepth(env); }; export { addSigwinchListener, isatty, WriteStream }; export default { getColorDepth, hasColors, isatty, WriteStream };