@gjsify/tty 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/lib/esm/index.js +126 -33
- package/lib/types/index.d.ts +62 -0
- package/package.json +15 -17
- package/src/index.spec.ts +293 -6
- package/src/index.ts +166 -59
- package/src/test.mts +1 -1
- package/tsconfig.json +23 -6
- package/tsconfig.tsbuildinfo +1 -0
- package/lib/cjs/index.js +0 -104
- package/test.gjs.mjs +0 -34685
- package/test.gjs.mjs.meta.json +0 -1
- package/test.node.mjs +0 -307
- package/tsconfig.types.json +0 -8
package/README.md
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
# @gjsify/tty
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
GJS implementation of the Node.js `tty` module. Provides ReadStream, WriteStream, and ANSI escape support.
|
|
4
|
+
|
|
5
|
+
Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gjsify/tty
|
|
11
|
+
# or
|
|
12
|
+
yarn add @gjsify/tty
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { isatty, ReadStream, WriteStream } from '@gjsify/tty';
|
|
19
|
+
|
|
20
|
+
console.log(isatty(0)); // true if stdin is a TTY
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Inspirations and credits
|
|
24
|
+
|
|
4
25
|
- https://github.com/geut/brode/blob/main/packages/browser-node-core/src/tty.js
|
|
5
26
|
- https://github.com/browserify/tty-browserify
|
|
6
27
|
- https://github.com/denoland/deno_std/blob/main/node/tty.ts
|
|
7
28
|
- https://github.com/jvilk/bfs-process/blob/master/ts/tty.ts
|
|
8
|
-
- https://nodejs.org/api/tty.html
|
|
29
|
+
- https://nodejs.org/api/tty.html
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
MIT
|
package/lib/esm/index.js
CHANGED
|
@@ -1,66 +1,152 @@
|
|
|
1
|
-
import { Writable, Readable } from "stream";
|
|
2
|
-
import
|
|
1
|
+
import { Writable, Readable } from "node:stream";
|
|
2
|
+
import GLib from "@girs/glib-2.0";
|
|
3
3
|
class ReadStream extends Readable {
|
|
4
4
|
isRaw = false;
|
|
5
|
+
fd;
|
|
6
|
+
constructor(fd = 0) {
|
|
7
|
+
super();
|
|
8
|
+
this.fd = fd;
|
|
9
|
+
}
|
|
5
10
|
get isTTY() {
|
|
6
|
-
return
|
|
11
|
+
return isatty(this.fd);
|
|
7
12
|
}
|
|
8
13
|
setRawMode(mode) {
|
|
9
14
|
if (this.isRaw !== mode) {
|
|
10
15
|
this.isRaw = mode;
|
|
11
16
|
this.emit("modeChange");
|
|
12
17
|
}
|
|
18
|
+
return this;
|
|
13
19
|
}
|
|
14
20
|
}
|
|
15
21
|
class WriteStream extends Writable {
|
|
16
22
|
isRaw = false;
|
|
17
23
|
columns = 80;
|
|
18
|
-
rows =
|
|
19
|
-
|
|
24
|
+
rows = 24;
|
|
25
|
+
fd;
|
|
20
26
|
_print = console.log;
|
|
21
|
-
// TODO fd
|
|
22
27
|
constructor(fd) {
|
|
23
28
|
super();
|
|
29
|
+
this.fd = fd;
|
|
30
|
+
this._detectSize();
|
|
24
31
|
}
|
|
25
32
|
get isTTY() {
|
|
26
|
-
return
|
|
33
|
+
return isatty(this.fd);
|
|
27
34
|
}
|
|
35
|
+
/** Detect terminal size from environment or defaults. */
|
|
36
|
+
_detectSize() {
|
|
37
|
+
const cols = parseInt(
|
|
38
|
+
globalThis.process?.env?.COLUMNS || (typeof GLib !== "undefined" ? GLib.getenv("COLUMNS") : "") || "0",
|
|
39
|
+
10
|
|
40
|
+
);
|
|
41
|
+
const rows = parseInt(
|
|
42
|
+
globalThis.process?.env?.LINES || (typeof GLib !== "undefined" ? GLib.getenv("LINES") : "") || "0",
|
|
43
|
+
10
|
|
44
|
+
);
|
|
45
|
+
if (cols > 0) this.columns = cols;
|
|
46
|
+
if (rows > 0) this.rows = rows;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Clear the current line.
|
|
50
|
+
* dir: -1 = left of cursor, 0 = entire line, 1 = right of cursor
|
|
51
|
+
*/
|
|
28
52
|
clearLine(dir, callback) {
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
let seq;
|
|
54
|
+
if (dir === -1) {
|
|
55
|
+
seq = "\x1B[1K";
|
|
56
|
+
} else if (dir === 1) {
|
|
57
|
+
seq = "\x1B[0K";
|
|
58
|
+
} else {
|
|
59
|
+
seq = "\x1B[2K";
|
|
60
|
+
}
|
|
61
|
+
return this.write(seq, callback);
|
|
33
62
|
}
|
|
63
|
+
/** Clear the screen from the cursor down. */
|
|
34
64
|
clearScreenDown(callback) {
|
|
35
|
-
|
|
36
|
-
if (callback)
|
|
37
|
-
callback();
|
|
38
|
-
return true;
|
|
65
|
+
return this.write("\x1B[0J", callback);
|
|
39
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Move cursor to absolute position (x, y).
|
|
69
|
+
* If y is omitted, only move horizontally.
|
|
70
|
+
*/
|
|
40
71
|
cursorTo(x, y, callback) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
if (typeof y === "function") {
|
|
73
|
+
callback = y;
|
|
74
|
+
y = void 0;
|
|
75
|
+
}
|
|
76
|
+
let seq;
|
|
77
|
+
if (y == null) {
|
|
78
|
+
seq = `\x1B[${x + 1}G`;
|
|
79
|
+
} else {
|
|
80
|
+
seq = `\x1B[${y + 1};${x + 1}H`;
|
|
81
|
+
}
|
|
82
|
+
return this.write(seq, callback);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Move cursor relative to its current position.
|
|
86
|
+
*/
|
|
87
|
+
moveCursor(dx, dy, callback) {
|
|
88
|
+
let seq = "";
|
|
89
|
+
if (dx > 0) seq += `\x1B[${dx}C`;
|
|
90
|
+
else if (dx < 0) seq += `\x1B[${-dx}D`;
|
|
91
|
+
if (dy > 0) seq += `\x1B[${dy}B`;
|
|
92
|
+
else if (dy < 0) seq += `\x1B[${-dy}A`;
|
|
93
|
+
if (seq.length === 0) {
|
|
94
|
+
if (callback) callback();
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return this.write(seq, callback);
|
|
45
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the color depth of the terminal.
|
|
101
|
+
* Returns 1 (no color), 4 (16 colors), 8 (256 colors), or 24 (true color).
|
|
102
|
+
*/
|
|
46
103
|
getColorDepth(env) {
|
|
47
|
-
|
|
48
|
-
|
|
104
|
+
const e = env || globalThis.process?.env || {};
|
|
105
|
+
if ("FORCE_COLOR" in e) {
|
|
106
|
+
const force = e.FORCE_COLOR;
|
|
107
|
+
if (force === "0" || force === "false") return 1;
|
|
108
|
+
if (force === "1") return 4;
|
|
109
|
+
if (force === "2") return 8;
|
|
110
|
+
if (force === "3") return 24;
|
|
111
|
+
return 4;
|
|
112
|
+
}
|
|
113
|
+
if ("NO_COLOR" in e) return 1;
|
|
114
|
+
const term = e.TERM || "";
|
|
115
|
+
const colorterm = e.COLORTERM || "";
|
|
116
|
+
if (colorterm === "truecolor" || colorterm === "24bit") return 24;
|
|
117
|
+
if (term === "xterm-256color" || term.endsWith("-256color")) return 8;
|
|
118
|
+
if (colorterm) return 4;
|
|
119
|
+
if (term === "dumb") return 1;
|
|
120
|
+
if (term.startsWith("xterm") || term.startsWith("screen") || term.startsWith("vt100") || term.startsWith("linux")) return 4;
|
|
121
|
+
return 1;
|
|
49
122
|
}
|
|
123
|
+
/** Get the terminal window size as [columns, rows]. */
|
|
50
124
|
getWindowSize() {
|
|
51
|
-
warnNotImplemented("WriteStream.getWindowSize");
|
|
52
125
|
return [this.columns, this.rows];
|
|
53
126
|
}
|
|
54
|
-
|
|
55
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Check if the terminal supports the given number of colors.
|
|
129
|
+
*/
|
|
130
|
+
hasColors(count, env) {
|
|
131
|
+
let _count;
|
|
132
|
+
let _env;
|
|
133
|
+
if (typeof count === "object") {
|
|
134
|
+
_env = count;
|
|
135
|
+
_count = 16;
|
|
136
|
+
} else {
|
|
137
|
+
_count = count ?? 16;
|
|
138
|
+
_env = env;
|
|
139
|
+
}
|
|
140
|
+
const depth = this.getColorDepth(_env);
|
|
141
|
+
switch (depth) {
|
|
56
142
|
case 1:
|
|
57
|
-
return
|
|
143
|
+
return _count >= 2;
|
|
58
144
|
case 4:
|
|
59
|
-
return
|
|
145
|
+
return _count >= 16;
|
|
60
146
|
case 8:
|
|
61
|
-
return
|
|
147
|
+
return _count >= 256;
|
|
62
148
|
case 24:
|
|
63
|
-
return
|
|
149
|
+
return _count >= 16777216;
|
|
64
150
|
default:
|
|
65
151
|
return false;
|
|
66
152
|
}
|
|
@@ -70,6 +156,7 @@ class WriteStream extends Writable {
|
|
|
70
156
|
this.isRaw = mode;
|
|
71
157
|
this.emit("modeChange");
|
|
72
158
|
}
|
|
159
|
+
return this;
|
|
73
160
|
}
|
|
74
161
|
_write(chunk, enc, cb) {
|
|
75
162
|
this._print(enc === "buffer" ? chunk.toString("utf-8") : chunk);
|
|
@@ -88,10 +175,16 @@ class WriteStream extends Writable {
|
|
|
88
175
|
}
|
|
89
176
|
}
|
|
90
177
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
178
|
+
function isatty(fd) {
|
|
179
|
+
if (fd instanceof ReadStream || fd instanceof WriteStream) {
|
|
180
|
+
return isatty(fd.fd);
|
|
181
|
+
}
|
|
182
|
+
if (typeof fd === "number") {
|
|
183
|
+
return GLib.log_writer_supports_color(fd);
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
var index_default = {
|
|
95
188
|
isatty,
|
|
96
189
|
WriteStream,
|
|
97
190
|
ReadStream
|
|
@@ -99,6 +192,6 @@ var src_default = {
|
|
|
99
192
|
export {
|
|
100
193
|
ReadStream,
|
|
101
194
|
WriteStream,
|
|
102
|
-
|
|
195
|
+
index_default as default,
|
|
103
196
|
isatty
|
|
104
197
|
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Writable, Readable } from 'node:stream';
|
|
2
|
+
export declare class ReadStream extends Readable {
|
|
3
|
+
isRaw: boolean;
|
|
4
|
+
readonly fd: number;
|
|
5
|
+
constructor(fd?: number);
|
|
6
|
+
get isTTY(): boolean;
|
|
7
|
+
setRawMode(mode: boolean): this;
|
|
8
|
+
}
|
|
9
|
+
export declare class WriteStream extends Writable {
|
|
10
|
+
isRaw: boolean;
|
|
11
|
+
columns: number;
|
|
12
|
+
rows: number;
|
|
13
|
+
readonly fd: number;
|
|
14
|
+
protected _print: (...data: any[]) => void;
|
|
15
|
+
constructor(fd: number);
|
|
16
|
+
get isTTY(): boolean;
|
|
17
|
+
/** Detect terminal size from environment or defaults. */
|
|
18
|
+
private _detectSize;
|
|
19
|
+
/**
|
|
20
|
+
* Clear the current line.
|
|
21
|
+
* dir: -1 = left of cursor, 0 = entire line, 1 = right of cursor
|
|
22
|
+
*/
|
|
23
|
+
clearLine(dir: number, callback?: () => void): boolean;
|
|
24
|
+
/** Clear the screen from the cursor down. */
|
|
25
|
+
clearScreenDown(callback?: () => void): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Move cursor to absolute position (x, y).
|
|
28
|
+
* If y is omitted, only move horizontally.
|
|
29
|
+
*/
|
|
30
|
+
cursorTo(x: number, y?: number | (() => void), callback?: () => void): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Move cursor relative to its current position.
|
|
33
|
+
*/
|
|
34
|
+
moveCursor(dx: number, dy: number, callback?: () => void): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Get the color depth of the terminal.
|
|
37
|
+
* Returns 1 (no color), 4 (16 colors), 8 (256 colors), or 24 (true color).
|
|
38
|
+
*/
|
|
39
|
+
getColorDepth(env?: Record<string, string>): number;
|
|
40
|
+
/** Get the terminal window size as [columns, rows]. */
|
|
41
|
+
getWindowSize(): [number, number];
|
|
42
|
+
/**
|
|
43
|
+
* Check if the terminal supports the given number of colors.
|
|
44
|
+
*/
|
|
45
|
+
hasColors(count?: number | Record<string, string>, env?: Record<string, string>): boolean;
|
|
46
|
+
setRawMode(mode: boolean): this;
|
|
47
|
+
_write(chunk: any, enc: string, cb: Function): void;
|
|
48
|
+
_changeColumns(columns: number): void;
|
|
49
|
+
_changeRows(rows: number): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if a file descriptor refers to a TTY.
|
|
53
|
+
* Uses GLib.log_writer_supports_color() as a reliable TTY proxy: if a fd supports
|
|
54
|
+
* ANSI colors it is connected to an interactive terminal.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isatty(fd: number | ReadStream | WriteStream): boolean;
|
|
57
|
+
declare const _default: {
|
|
58
|
+
isatty: typeof isatty;
|
|
59
|
+
WriteStream: typeof WriteStream;
|
|
60
|
+
ReadStream: typeof ReadStream;
|
|
61
|
+
};
|
|
62
|
+
export default _default;
|
package/package.json
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/tty",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Node.js tty module for Gjs",
|
|
5
|
-
"main": "lib/cjs/index.js",
|
|
6
5
|
"module": "lib/esm/index.js",
|
|
6
|
+
"types": "lib/types/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"default": "./lib/esm/index.js"
|
|
13
|
-
},
|
|
14
|
-
"require": {
|
|
15
|
-
"types": "./lib/types/index.d.ts",
|
|
16
|
-
"default": "./lib/cjs/index.js"
|
|
17
|
-
}
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/esm/index.js"
|
|
18
12
|
}
|
|
19
13
|
},
|
|
20
14
|
"scripts": {
|
|
21
|
-
"clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo || exit 0",
|
|
22
|
-
"
|
|
23
|
-
"build": "yarn
|
|
15
|
+
"clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
|
|
16
|
+
"check": "tsc --noEmit",
|
|
17
|
+
"build": "yarn build:gjsify && yarn build:types",
|
|
24
18
|
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
19
|
+
"build:types": "tsc",
|
|
25
20
|
"build:test": "yarn build:test:gjs && yarn build:test:node",
|
|
26
21
|
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
27
22
|
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
28
|
-
"test": "yarn
|
|
23
|
+
"test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
|
|
29
24
|
"test:gjs": "gjs -m test.gjs.mjs",
|
|
30
25
|
"test:node": "node test.node.mjs"
|
|
31
26
|
},
|
|
@@ -35,10 +30,13 @@
|
|
|
35
30
|
"tty"
|
|
36
31
|
],
|
|
37
32
|
"devDependencies": {
|
|
38
|
-
"@gjsify/cli": "^0.
|
|
39
|
-
"@
|
|
33
|
+
"@gjsify/cli": "^0.1.1",
|
|
34
|
+
"@gjsify/unit": "^0.1.1",
|
|
35
|
+
"@types/node": "^25.5.0",
|
|
36
|
+
"typescript": "^6.0.2"
|
|
40
37
|
},
|
|
41
38
|
"dependencies": {
|
|
42
|
-
"@
|
|
39
|
+
"@girs/glib-2.0": "^2.88.0-4.0.0-beta.43",
|
|
40
|
+
"@gjsify/stream": "^0.1.1"
|
|
43
41
|
}
|
|
44
42
|
}
|
package/src/index.spec.ts
CHANGED
|
@@ -1,9 +1,296 @@
|
|
|
1
|
+
// Ported from refs/node-test/parallel/test-tty-{isatty,get-color-depth,has-colors,window-size,wrap,stream-constructors}.js
|
|
2
|
+
// Original: MIT license, Node.js contributors
|
|
3
|
+
|
|
1
4
|
import { describe, it, expect } from '@gjsify/unit';
|
|
5
|
+
import tty, { isatty, ReadStream, WriteStream } from 'node:tty';
|
|
6
|
+
import process from 'node:process';
|
|
2
7
|
|
|
3
8
|
export default async () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
await describe('tty exports', async () => {
|
|
10
|
+
await it('should export isatty as a function', async () => {
|
|
11
|
+
expect(typeof isatty).toBe('function');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await it('should export ReadStream as a function', async () => {
|
|
15
|
+
expect(typeof ReadStream).toBe('function');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await it('should export WriteStream as a function', async () => {
|
|
19
|
+
expect(typeof WriteStream).toBe('function');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await it('should have all exports on default export', async () => {
|
|
23
|
+
expect(typeof tty.isatty).toBe('function');
|
|
24
|
+
expect(typeof tty.ReadStream).toBe('function');
|
|
25
|
+
expect(typeof tty.WriteStream).toBe('function');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await it('isatty should be same reference on default', async () => {
|
|
29
|
+
expect(tty.isatty).toBe(isatty);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await describe('tty.isatty', async () => {
|
|
34
|
+
await it('should return a boolean for fd 0 (stdin)', async () => {
|
|
35
|
+
expect(typeof isatty(0)).toBe('boolean');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await it('should return a boolean for fd 1 (stdout)', async () => {
|
|
39
|
+
expect(typeof isatty(1)).toBe('boolean');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await it('should return a boolean for fd 2 (stderr)', async () => {
|
|
43
|
+
expect(typeof isatty(2)).toBe('boolean');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await it('should return false for invalid fd -1', async () => {
|
|
47
|
+
expect(isatty(-1)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await it('should return false for very large fd', async () => {
|
|
51
|
+
expect(isatty(999999)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await it('should return false for fd 100', async () => {
|
|
55
|
+
expect(isatty(100)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await it('should return false for fd 255', async () => {
|
|
59
|
+
expect(isatty(255)).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await it('should return false for fd 42', async () => {
|
|
63
|
+
expect(isatty(42)).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await describe('tty.ReadStream', async () => {
|
|
68
|
+
await it('should be a constructor', async () => {
|
|
69
|
+
expect(typeof ReadStream).toBe('function');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await it('should have setRawMode on prototype', async () => {
|
|
73
|
+
expect(typeof ReadStream.prototype.setRawMode).toBe('function');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await it('should have isTTY property', async () => {
|
|
77
|
+
const desc = Object.getOwnPropertyDescriptor(ReadStream.prototype, 'isTTY');
|
|
78
|
+
if (desc) {
|
|
79
|
+
expect(desc.get !== undefined || desc.value !== undefined).toBe(true);
|
|
80
|
+
} else {
|
|
81
|
+
expect('setRawMode' in ReadStream.prototype).toBe(true);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await describe('tty.WriteStream prototype', async () => {
|
|
87
|
+
await it('should be a constructor', async () => {
|
|
88
|
+
expect(typeof WriteStream).toBe('function');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await it('should have clearLine method', async () => {
|
|
92
|
+
expect(typeof WriteStream.prototype.clearLine).toBe('function');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await it('should have clearScreenDown method', async () => {
|
|
96
|
+
expect(typeof WriteStream.prototype.clearScreenDown).toBe('function');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await it('should have cursorTo method', async () => {
|
|
100
|
+
expect(typeof WriteStream.prototype.cursorTo).toBe('function');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await it('should have moveCursor method', async () => {
|
|
104
|
+
expect(typeof WriteStream.prototype.moveCursor).toBe('function');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await it('should have getColorDepth method', async () => {
|
|
108
|
+
expect(typeof WriteStream.prototype.getColorDepth).toBe('function');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await it('should have hasColors method', async () => {
|
|
112
|
+
expect(typeof WriteStream.prototype.hasColors).toBe('function');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await it('should have getWindowSize method', async () => {
|
|
116
|
+
expect(typeof WriteStream.prototype.getWindowSize).toBe('function');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await it('should have isTTY getter or property', async () => {
|
|
120
|
+
const desc = Object.getOwnPropertyDescriptor(WriteStream.prototype, 'isTTY');
|
|
121
|
+
if (desc) {
|
|
122
|
+
expect(desc.get !== undefined || desc.value !== undefined).toBe(true);
|
|
123
|
+
} else {
|
|
124
|
+
// Some implementations set isTTY as a direct property in constructor
|
|
125
|
+
expect(typeof WriteStream.prototype.getColorDepth).toBe('function');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await describe('WriteStream.getColorDepth (via process.stdout)', async () => {
|
|
131
|
+
// process.stdout may or may not be a tty.WriteStream
|
|
132
|
+
const ws = process.stdout as unknown as { getColorDepth?: (env?: Record<string, string>) => number };
|
|
133
|
+
|
|
134
|
+
if (typeof ws.getColorDepth === 'function') {
|
|
135
|
+
await it('should return a number', async () => {
|
|
136
|
+
expect(typeof ws.getColorDepth!()).toBe('number');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await it('should return 1, 4, 8, or 24', async () => {
|
|
140
|
+
expect([1, 4, 8, 24].includes(ws.getColorDepth!())).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await it('NO_COLOR should return 1', async () => {
|
|
144
|
+
expect(ws.getColorDepth!({ NO_COLOR: '1' })).toBe(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await it('COLORTERM=truecolor should return 24', async () => {
|
|
148
|
+
expect(ws.getColorDepth!({ COLORTERM: 'truecolor' })).toBe(24);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await it('COLORTERM=24bit should return 24', async () => {
|
|
152
|
+
expect(ws.getColorDepth!({ COLORTERM: '24bit' })).toBe(24);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await it('TERM=xterm-256color should return 8', async () => {
|
|
156
|
+
expect(ws.getColorDepth!({ TERM: 'xterm-256color' })).toBe(8);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await it('TERM=xterm should return 4', async () => {
|
|
160
|
+
expect(ws.getColorDepth!({ TERM: 'xterm' })).toBe(4);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await it('TERM=dumb should return 1', async () => {
|
|
164
|
+
expect(ws.getColorDepth!({ TERM: 'dumb' })).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await it('FORCE_COLOR=0 should return 1', async () => {
|
|
168
|
+
expect(ws.getColorDepth!({ FORCE_COLOR: '0' })).toBe(1);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await it('FORCE_COLOR=1 should return 4', async () => {
|
|
172
|
+
expect(ws.getColorDepth!({ FORCE_COLOR: '1' })).toBe(4);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await it('FORCE_COLOR=2 should return 8', async () => {
|
|
176
|
+
expect(ws.getColorDepth!({ FORCE_COLOR: '2' })).toBe(8);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await it('FORCE_COLOR=3 should return 24', async () => {
|
|
180
|
+
expect(ws.getColorDepth!({ FORCE_COLOR: '3' })).toBe(24);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await it('FORCE_COLOR overrides NO_COLOR', async () => {
|
|
184
|
+
expect(ws.getColorDepth!({ FORCE_COLOR: '1', NO_COLOR: '1' })).toBe(4);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await it('TERM=screen should return 4', async () => {
|
|
188
|
+
expect(ws.getColorDepth!({ TERM: 'screen' })).toBe(4);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await it('TERM=linux should return 4', async () => {
|
|
192
|
+
expect(ws.getColorDepth!({ TERM: 'linux' })).toBe(4);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await it('empty env should return 1', async () => {
|
|
196
|
+
expect(ws.getColorDepth!({})).toBe(1);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await describe('WriteStream.hasColors (via process.stdout)', async () => {
|
|
202
|
+
const ws = process.stdout as unknown as { hasColors?: (count?: number | Record<string, string>, env?: Record<string, string>) => boolean };
|
|
203
|
+
|
|
204
|
+
if (typeof ws.hasColors === 'function') {
|
|
205
|
+
await it('should return a boolean', async () => {
|
|
206
|
+
expect(typeof ws.hasColors!()).toBe('boolean');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await it('should accept count argument', async () => {
|
|
210
|
+
expect(typeof ws.hasColors!(256)).toBe('boolean');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await it('should accept env as first argument', async () => {
|
|
214
|
+
expect(typeof ws.hasColors!({ TERM: 'xterm-256color' })).toBe('boolean');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await it('truecolor env should support 256 colors', async () => {
|
|
218
|
+
expect(ws.hasColors!(256, { COLORTERM: 'truecolor' })).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await it('dumb terminal should not support 256 colors', async () => {
|
|
222
|
+
expect(ws.hasColors!(256, { TERM: 'dumb' })).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await it('xterm-256color should support 256 colors', async () => {
|
|
226
|
+
expect(ws.hasColors!(256, { TERM: 'xterm-256color' })).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await describe('WriteStream.getWindowSize (via process.stdout)', async () => {
|
|
232
|
+
const ws = process.stdout as unknown as { getWindowSize?: () => [number, number]; columns?: number; rows?: number };
|
|
233
|
+
|
|
234
|
+
if (typeof ws.getWindowSize === 'function') {
|
|
235
|
+
await it('should return array of two numbers', async () => {
|
|
236
|
+
const size = ws.getWindowSize!();
|
|
237
|
+
expect(Array.isArray(size)).toBe(true);
|
|
238
|
+
expect(size.length).toBe(2);
|
|
239
|
+
expect(typeof size[0]).toBe('number');
|
|
240
|
+
expect(typeof size[1]).toBe('number');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await it('columns should be positive', async () => {
|
|
244
|
+
expect(ws.getWindowSize!()[0]).toBeGreaterThan(0);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await it('rows should be positive', async () => {
|
|
248
|
+
expect(ws.getWindowSize!()[1]).toBeGreaterThan(0);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (typeof ws.columns === 'number') {
|
|
253
|
+
await it('columns property should be positive', async () => {
|
|
254
|
+
expect(ws.columns!).toBeGreaterThan(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
await it('rows property should be positive', async () => {
|
|
258
|
+
expect(ws.rows!).toBeGreaterThan(0);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await it('columns should match getWindowSize()[0]', async () => {
|
|
262
|
+
if (typeof ws.getWindowSize === 'function') {
|
|
263
|
+
expect(ws.columns).toBe(ws.getWindowSize!()[0]);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await describe('process.stdout/stderr', async () => {
|
|
270
|
+
await it('process.stdout should have write method', async () => {
|
|
271
|
+
expect(typeof process.stdout.write).toBe('function');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await it('process.stderr should have write method', async () => {
|
|
275
|
+
expect(typeof process.stderr.write).toBe('function');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await it('process.stdout.isTTY should be boolean if defined', async () => {
|
|
279
|
+
if (process.stdout.isTTY !== undefined) {
|
|
280
|
+
expect(typeof process.stdout.isTTY).toBe('boolean');
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await it('process.stderr.isTTY should be boolean if defined', async () => {
|
|
285
|
+
if (process.stderr.isTTY !== undefined) {
|
|
286
|
+
expect(typeof process.stderr.isTTY).toBe('boolean');
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
await it('process.stdin should have isTTY property if available', async () => {
|
|
291
|
+
if ((process.stdin as any).isTTY !== undefined) {
|
|
292
|
+
expect(typeof (process.stdin as any).isTTY).toBe('boolean');
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
};
|