@travetto/terminal 3.0.2 → 3.1.0-rc.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,6 +1,7 @@
1
1
  <!-- This file was generated by @travetto/doc and should not be modified directly -->
2
- <!-- Please modify https://github.com/travetto/travetto/tree/main/module/terminal/DOC.ts and execute "npx trv doc" to rebuild -->
2
+ <!-- Please modify https://github.com/travetto/travetto/tree/main/module/terminal/DOC.tsx and execute "npx trv doc" to rebuild -->
3
3
  # Terminal
4
+
4
5
  ## General terminal support
5
6
 
6
7
  **Install: @travetto/terminal**
@@ -12,21 +13,16 @@ npm install @travetto/terminal
12
13
  yarn add @travetto/terminal
13
14
  ```
14
15
 
15
- This module provides basic support for interacting with the terminal, and provides the basis for output colorization and the basic command line interactions. The functionality can be broken down into:
16
-
17
-
16
+ This module provides basic support for interacting with the terminal, and provides the basis for output colorization and the basic command line interactions. The functionality can be broken down into:
18
17
  * Output Colorization
19
18
  * Terminal Interactions
20
19
 
21
20
  ## Output Colorization
22
-
23
21
  Oddly enough, colorizing output in a terminal is a fairly complex process. The standards are somewhat inconsistent and detection can be a tricky process. For terminals, [Node](https://nodejs.org) supports 4 different levels of coloring:
24
-
25
22
  * 0 - One color, essentially uncolored output
26
23
  * 1 - Basic color support, 16 colors
27
24
  * 2 - Enhanced color support, 225 colors, providing a fair representation of most colors
28
25
  * 3 - True color, 24bit color with R, G, B each getting 8-bits. Can represent any color needed
29
-
30
26
  This module provides the ability to define color palettes using RGB or [named colors](https://github.com/travetto/travetto/tree/main/module/terminal/src/named-colors.ts#L1) modeled after the standard HTML color names. The module also provides the ability to specify palettes based on a dark or light background for a given terminal. Support for this is widespread, but when it fails, it will gracefully assume a dark background.
31
27
 
32
28
  These palettes then are usable at runtime, with the module determine light or dark palettes, as well as falling back to the closest color value based on what the existing terminal supports. This means a color like 'olivegreen', will get the proper output in 24bit color support, a close approximation in enhanced color support, fall back to green in basic color support, and will be color less at level 0.
@@ -42,12 +38,12 @@ const tplFn = GlobalTerminal.templateFunction({
42
38
  path: 'teal',
43
39
  success: 'green',
44
40
  failure: 'red',
45
- param: 'yellow',
41
+ param: ['yellow', 'goldenrod'],
46
42
  type: 'cyan',
47
- description: 'white',
48
- title: 'brightWhite',
43
+ description: ['white', 'gray'],
44
+ title: ['brightWhite', 'black'],
49
45
  identifier: 'dodgerBlue',
50
- subtitle: 'lightGray',
46
+ subtitle: ['lightGray', 'darkGray'],
51
47
  subsubtitle: 'darkGray'
52
48
  });
53
49
 
@@ -61,7 +57,7 @@ When the color palette is combined with [Base](https://github.com/travetto/trave
61
57
  cliTpl`Build finished: status=${{success: "complete"}}, output=${{path: "/build.zip"}}`
62
58
  ```
63
59
 
64
- This would then produce colorized output based on the palette, and the terminal capabilities.
60
+ This would then produce colorized output based on the palette, and the terminal capabilities.
65
61
 
66
62
  This module follows the pattern [Node](https://nodejs.org) follows with respect to the environment variables: `NO_COLOR`, `FORCE_COLOR` and `NODE_DISABLE_COLORS`
67
63
 
@@ -78,12 +74,9 @@ NODE_DISABLE_COLORS set to 1 to disable colors in the REPL
78
74
 
79
75
  ## Terminal Interactions
80
76
  Within the [Travetto](https://travetto.dev) framework, there are plenty of command line interactions that are enhanced with additional interactivity. This mainly revolves around indicating progress while a program is executing. The module provides support for:
81
-
82
-
83
77
  * Progress Bars
84
78
  * Waiting Indicators
85
79
  * Streaming Content
86
-
87
- This is generally meant for use within the framework, and so is highly tailored to the specific needs and scenarios. You can see this pattern play out in the [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework") progress output, or in [Pack](https://github.com/travetto/travetto/tree/main/module/pack#readme "Code packing utilities")'s output.
80
+ This is generally meant for use within the framework, and so is highly tailored to the specific needs and scenarios. You can see this pattern play out in the [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework") progress output, or in [Pack](https://github.com/travetto/travetto/tree/main/module/pack#readme "Code packing utilities")'s output.
88
81
 
89
82
  In these scenarios, the dynamic behaviors are dependent on having an interactive TTY. When running without access to a proper stdin, the output will default to basic line printing. This dynamic behavior can also be disabled using the environment variable `TRV_QUIET`. When set to `1` will provide a minimal text-based experience.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/terminal",
3
- "version": "3.0.2",
3
+ "version": "3.1.0-rc.0",
4
4
  "description": "General terminal support",
5
5
  "keywords": [
6
6
  "terminal",
@@ -9,7 +9,7 @@ export type TermStyle =
9
9
 
10
10
  export type TermStyleInput = TermStyle | RGBInput;
11
11
  export type Prim = string | number | boolean | Date | RegExp;
12
- export type TermColorPaletteInput = Record<string, TermStyleInput | [TermStyleInput, TermStyleInput]>;
12
+ export type TermColorPaletteInput = Record<string, TermStyleInput | [dark: TermStyleInput, light: TermStyleInput]>;
13
13
  export type TermColorFn = (text: Prim) => string;
14
14
  export type TermColorPalette<T> = Record<keyof T, TermColorFn>;
15
15
 
@@ -92,7 +92,7 @@ export class ColorOutputUtil {
92
92
  /**
93
93
  * Make a simple primitive colorer
94
94
  */
95
- static colorer(term: TermState, style: TermStyleInput | [light: TermStyleInput, dark: TermStyleInput]): TermColorFn {
95
+ static colorer(term: TermState, style: TermStyleInput | [dark: TermStyleInput, light: TermStyleInput]): TermColorFn {
96
96
  const schemes = {
97
97
  light: this.getStyledLevels(Array.isArray(style) ? style[1] ?? style[0] : style),
98
98
  dark: this.getStyledLevels(Array.isArray(style) ? style[0] : style),
package/src/operation.ts CHANGED
@@ -32,9 +32,23 @@ export class TerminalOperation {
32
32
  }
33
33
  await batch.commit();
34
34
 
35
+ let start = Date.now();
36
+ const minDelay = config.minDelay ?? 0;
37
+
38
+ let line: string = '';
35
39
  for await (const text of source) {
36
- await TerminalWriter.for(term).setPosition(writePos).write(text).clearLine(1).commit(true);
40
+ line = text;
41
+ if ((Date.now() - start) >= minDelay) {
42
+ start = Date.now();
43
+ await TerminalWriter.for(term).setPosition(writePos).write(line).clearLine(1).commit(true);
44
+ line = '';
45
+ }
46
+ }
47
+
48
+ if (line) {
49
+ await TerminalWriter.for(term).setPosition(writePos).write(line).clearLine(1).commit(true);
37
50
  }
51
+
38
52
  if (config.clearOnFinish ?? true) {
39
53
  await TerminalWriter.for(term).setPosition(writePos).clearLine().commit(true);
40
54
  }
package/src/query.ts CHANGED
@@ -1,4 +1,5 @@
1
- import tty from 'tty';
1
+ import type tty from 'tty';
2
+ import { spawn } from 'child_process';
2
3
 
3
4
  import { ANSICodes } from './codes';
4
5
  import { IterableUtil } from './iterable';
@@ -7,6 +8,31 @@ import { RGB, TermCoord, TermQuery } from './types';
7
8
  const to256 = (x: string): number => Math.trunc(parseInt(x, 16) / (16 ** (x.length - 2)));
8
9
  const COLOR_RESPONSE = /(?<r>][0-9a-f]+)[/](?<g>[0-9a-f]+)[/](?<b>[0-9a-f]+)[/]?(?<a>[0-9a-f]+)?/i;
9
10
 
11
+ // @ts-expect-error
12
+ const queryScript = (function (...bytes) {
13
+ const i = process.stdin;
14
+ i.setRawMode(true);
15
+ i.resume();
16
+ i.once('readable', function () {
17
+ const inp = i.read();
18
+ /* @ts-expect-error */
19
+ process.send(Buffer.isBuffer(inp) ? inp.toString('utf8') : inp);
20
+ i.setRawMode(false);
21
+ });
22
+ process.stdout.write(String.fromCharCode(...bytes));
23
+ });
24
+
25
+ const runQuery = async (input: tty.ReadStream, output: tty.WriteStream, code: string): Promise<Buffer> => {
26
+ const script = queryScript.toString().replaceAll('\'', '"').replaceAll('\n', '');
27
+ const fullScript = `(${script})(${code.split('').map(x => x.charCodeAt(0))})`
28
+ const proc = spawn(process.argv0, ['-e', fullScript], { stdio: [input, output, 2, 'ipc'], detached: true });
29
+ const text = await new Promise<string>((res, rej) => {
30
+ proc.once('message', res);
31
+ proc.on('error', rej);
32
+ });
33
+ return Buffer.from(text, 'utf8');
34
+ };
35
+
10
36
  const ANSIQueries = {
11
37
  /** Parse xterm color response */
12
38
  color: (field: 'background' | 'foreground'): TermQuery<RGB | undefined> => ({
@@ -43,43 +69,15 @@ export class TerminalQuerier {
43
69
  #queue = IterableUtil.simpleQueue();
44
70
  #output: tty.WriteStream;
45
71
  #input: tty.ReadStream;
72
+ #restore?: () => void;
46
73
 
47
74
  constructor(input: tty.ReadStream, output: tty.WriteStream) {
48
75
  this.#input = input;
49
76
  this.#output = output;
50
77
  }
51
78
 
52
- async #readInput(query: string): Promise<Buffer> {
53
- const isRaw = this.#input.isRaw;
54
- const isPaused = this.#input.isPaused();
55
- const data = this.#input.listeners('data');
56
- try {
57
- this.#input.removeAllListeners('data');
58
-
59
- if (isPaused) {
60
- this.#input.resume();
61
- }
62
-
63
- this.#input.setRawMode(true);
64
- // Send data, but do not wait on it
65
- this.#output.write(query);
66
- await new Promise(res => this.#input.once('readable', res));
67
- const val: Buffer | string = this.#input.read();
68
- return typeof val === 'string' ? Buffer.from(val, 'utf8') : val;
69
- } finally {
70
- if (isPaused) {
71
- this.#input.pause();
72
- }
73
- this.#input.setRawMode(isRaw);
74
- for (const fn of data) {
75
- // @ts-ignore
76
- this.#input.on('data', fn);
77
- }
78
- }
79
- }
80
-
81
79
  query<T>(q: TermQuery<T>): Promise<T> {
82
- return this.#queue.add(() => this.#readInput(q.query()).then(q.response));
80
+ return this.#queue.add(() => runQuery(this.#input, this.#output, q.query()).then(q.response));
83
81
  }
84
82
 
85
83
  cursorPosition(): Promise<TermCoord> {
@@ -90,7 +88,8 @@ export class TerminalQuerier {
90
88
  return this.query(ANSIQueries.color('background'));
91
89
  }
92
90
 
93
- close(): Promise<void> {
94
- return this.#queue.close();
91
+ close(): void {
92
+ this.#restore?.();
93
+ this.#queue.close();
95
94
  }
96
95
  }
package/src/terminal.ts CHANGED
@@ -13,6 +13,7 @@ import { ColorOutputUtil, Prim, TermColorFn, TermColorPalette, TermColorPaletteI
13
13
  type TerminalStreamPositionConfig = {
14
14
  position?: TermLinePosition;
15
15
  staticMessage?: string;
16
+ minDelay?: number;
16
17
  };
17
18
 
18
19
  type TerminalProgressConfig = TerminalStreamPositionConfig & {
@@ -98,12 +99,11 @@ export class Terminal implements TermState {
98
99
  return this.#init;
99
100
  }
100
101
 
101
- async reset(): Promise<void> {
102
- await this.#query.close();
102
+ reset(): void {
103
+ this.#query.close();
103
104
  if (this.interactive) {
104
- await this.writer().reset().commit();
105
+ this.#output.write(this.writer().resetCommands());
105
106
  }
106
- return;
107
107
  }
108
108
 
109
109
  getCursorPosition(): Promise<TermCoord> {
package/src/types.ts CHANGED
@@ -13,7 +13,7 @@ export type TerminalTableEvent = { idx: number, text: string, done?: boolean };
13
13
  export type TerminalTableConfig = { header?: string[], forceNonInteractiveOrder?: boolean };
14
14
  export type TerminalProgressEvent = { idx: number, total?: number, text?: string };
15
15
  export type TerminalProgressRender = (ev: TerminalProgressEvent) => string;
16
- export type TerminalStreamingConfig = { position?: TermLinePosition, clearOnFinish?: boolean, at?: TermCoord };
16
+ export type TerminalStreamingConfig = { position?: TermLinePosition, clearOnFinish?: boolean, at?: TermCoord, minDelay?: number };
17
17
  export type TerminalWaitingConfig = { end?: boolean, committedPrefix?: string } & TerminalStreamingConfig & DelayedConfig;
18
18
 
19
19
  export type TermColorLevel = 0 | 1 | 2 | 3;
package/src/writer.ts CHANGED
@@ -137,10 +137,16 @@ export class TerminalWriter {
137
137
 
138
138
  /** Reset */
139
139
  reset(): this {
140
- return this
141
- .write(ANSICodes.POSITION_SAVE())
142
- .write(ANSICodes.SHOW_CURSOR())
143
- .write(ANSICodes.SCROLL_RANGE_CLEAR())
144
- .write(ANSICodes.POSITION_RESTORE());
140
+ return this.write(this.resetCommands());
141
+ }
142
+
143
+ /** Reset Commands */
144
+ resetCommands(): string {
145
+ return [
146
+ ANSICodes.POSITION_SAVE(),
147
+ ANSICodes.SHOW_CURSOR(),
148
+ ANSICodes.SCROLL_RANGE_CLEAR(),
149
+ ANSICodes.POSITION_RESTORE()
150
+ ].join('');
145
151
  }
146
152
  }