@gjsify/tty 0.0.3 → 0.1.0

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 CHANGED
@@ -1,8 +1,33 @@
1
1
  # @gjsify/tty
2
2
 
3
- ## Inspirations
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 { warnNotImplemented } from "@gjsify/utils";
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 true;
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 = 120;
19
- // TODO stdout / stderr
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 true;
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
- warnNotImplemented("WriteStream.clearLine");
30
- if (callback)
31
- callback();
32
- return true;
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
- warnNotImplemented("WriteStream.clearScreenDown");
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
- warnNotImplemented("WriteStream.cursorTo");
42
- if (callback)
43
- callback();
44
- return true;
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
- warnNotImplemented("WriteStream.getColorDepth");
48
- return 8;
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
- hasColors(count = 16, env) {
55
- switch (this.getColorDepth(env)) {
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 count >= 2;
143
+ return _count >= 2;
58
144
  case 4:
59
- return count >= 16;
145
+ return _count >= 16;
60
146
  case 8:
61
- return count >= 256;
147
+ return _count >= 256;
62
148
  case 24:
63
- return count >= 16777216;
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
- const isatty = (fd) => {
92
- return fd && (fd instanceof ReadStream || fd instanceof WriteStream);
93
- };
94
- var src_default = {
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
- src_default as default,
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.0.3",
3
+ "version": "0.1.0",
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
- "import": {
11
- "types": "./lib/types/index.d.ts",
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
- "print:name": "echo '@gjsify/tty'",
23
- "build": "yarn print:name && yarn build:gjsify",
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 print:name && yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
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.0.3",
39
- "@types/node": "^20.3.1"
33
+ "@gjsify/cli": "^0.1.0",
34
+ "@gjsify/unit": "^0.1.0",
35
+ "@types/node": "^25.5.0",
36
+ "typescript": "^6.0.2"
40
37
  },
41
38
  "dependencies": {
42
- "@gjsify/utils": "^0.0.3"
39
+ "@girs/glib-2.0": "^2.88.0-4.0.0-beta.42",
40
+ "@gjsify/stream": "^0.1.0"
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
- await describe('true', async () => {
5
- await it('should be true', async () => {
6
- expect(true).toBeTruthy();
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
+ };