console-toolkit 1.2.8 → 1.2.10

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.
Files changed (156) hide show
  1. package/README.md +70 -25
  2. package/package.json +26 -6
  3. package/src/alphanumeric/arrows.d.ts +48 -0
  4. package/src/alphanumeric/arrows.js +23 -0
  5. package/src/alphanumeric/fractions.d.ts +65 -0
  6. package/src/alphanumeric/fractions.js +49 -0
  7. package/src/alphanumeric/number-formatters.d.ts +91 -0
  8. package/src/alphanumeric/number-formatters.js +45 -1
  9. package/src/alphanumeric/roman.d.ts +15 -0
  10. package/src/alphanumeric/roman.js +12 -0
  11. package/src/alphanumeric/unicode-cultural-numbers.d.ts +65 -0
  12. package/src/alphanumeric/unicode-cultural-numbers.js +1 -0
  13. package/src/alphanumeric/unicode-letters.d.ts +32 -0
  14. package/src/alphanumeric/unicode-letters.js +8 -0
  15. package/src/alphanumeric/unicode-numbers.d.ts +44 -0
  16. package/src/alphanumeric/unicode-numbers.js +21 -0
  17. package/src/alphanumeric/utils.d.ts +45 -0
  18. package/src/alphanumeric/utils.js +26 -0
  19. package/src/ansi/csi.d.ts +141 -0
  20. package/src/ansi/csi.js +51 -2
  21. package/src/ansi/index.d.ts +26 -0
  22. package/src/ansi/sgr-constants.d.ts +173 -0
  23. package/src/ansi/sgr-state.d.ts +91 -0
  24. package/src/ansi/sgr-state.js +45 -0
  25. package/src/ansi/sgr.d.ts +587 -0
  26. package/src/ansi/sgr.js +426 -6
  27. package/src/box.d.ts +160 -0
  28. package/src/box.js +113 -12
  29. package/src/charts/bars/block-frac-grouped.d.ts +12 -0
  30. package/src/charts/bars/block-frac-grouped.js +6 -0
  31. package/src/charts/bars/block-frac.d.ts +34 -0
  32. package/src/charts/bars/block-frac.js +13 -0
  33. package/src/charts/bars/block-grouped.d.ts +12 -0
  34. package/src/charts/bars/block-grouped.js +6 -0
  35. package/src/charts/bars/block.d.ts +43 -0
  36. package/src/charts/bars/block.js +13 -0
  37. package/src/charts/bars/draw-grouped.d.ts +41 -0
  38. package/src/charts/bars/draw-grouped.js +4 -0
  39. package/src/charts/bars/draw-stacked.d.ts +47 -0
  40. package/src/charts/bars/draw-stacked.js +4 -0
  41. package/src/charts/bars/frac-grouped.d.ts +32 -0
  42. package/src/charts/bars/frac-grouped.js +13 -0
  43. package/src/charts/bars/plain-grouped.d.ts +12 -0
  44. package/src/charts/bars/plain-grouped.js +6 -0
  45. package/src/charts/bars/plain.d.ts +75 -0
  46. package/src/charts/bars/plain.js +27 -0
  47. package/src/charts/columns/block-frac-grouped.d.ts +12 -0
  48. package/src/charts/columns/block-frac-grouped.js +6 -0
  49. package/src/charts/columns/block-frac.d.ts +39 -0
  50. package/src/charts/columns/block-frac.js +13 -0
  51. package/src/charts/columns/block-grouped.d.ts +12 -0
  52. package/src/charts/columns/block-grouped.js +6 -0
  53. package/src/charts/columns/block.d.ts +43 -0
  54. package/src/charts/columns/block.js +13 -0
  55. package/src/charts/columns/draw-grouped.d.ts +41 -0
  56. package/src/charts/columns/draw-grouped.js +4 -0
  57. package/src/charts/columns/draw-stacked.d.ts +39 -0
  58. package/src/charts/columns/draw-stacked.js +4 -0
  59. package/src/charts/columns/frac-grouped.d.ts +37 -0
  60. package/src/charts/columns/frac-grouped.js +13 -0
  61. package/src/charts/columns/plain-grouped.d.ts +12 -0
  62. package/src/charts/columns/plain-grouped.js +6 -0
  63. package/src/charts/columns/plain.d.ts +32 -0
  64. package/src/charts/columns/plain.js +13 -0
  65. package/src/charts/themes/default.d.ts +6 -0
  66. package/src/charts/themes/default.js +1 -0
  67. package/src/charts/themes/rainbow-reversed.d.ts +6 -0
  68. package/src/charts/themes/rainbow-reversed.js +2 -1
  69. package/src/charts/themes/rainbow.d.ts +6 -0
  70. package/src/charts/themes/rainbow.js +1 -0
  71. package/src/charts/utils.d.ts +79 -0
  72. package/src/charts/utils.js +32 -4
  73. package/src/draw-block-frac.d.ts +16 -0
  74. package/src/draw-block-frac.js +14 -0
  75. package/src/draw-block.d.ts +53 -0
  76. package/src/draw-block.js +25 -1
  77. package/src/meta.d.ts +84 -0
  78. package/src/meta.js +64 -0
  79. package/src/output/show.d.ts +55 -0
  80. package/src/output/show.js +28 -0
  81. package/src/output/updater.d.ts +114 -0
  82. package/src/output/updater.js +58 -4
  83. package/src/output/writer.d.ts +87 -0
  84. package/src/output/writer.js +57 -5
  85. package/src/panel.d.ts +402 -0
  86. package/src/panel.js +219 -5
  87. package/src/plot/bitmap.d.ts +80 -0
  88. package/src/plot/bitmap.js +33 -4
  89. package/src/plot/draw-line.d.ts +13 -0
  90. package/src/plot/draw-line.js +8 -0
  91. package/src/plot/draw-rect.d.ts +13 -0
  92. package/src/plot/draw-rect.js +38 -30
  93. package/src/plot/index.d.ts +39 -0
  94. package/src/plot/index.js +22 -0
  95. package/src/plot/to-quads.d.ts +10 -0
  96. package/src/plot/to-quads.js +5 -0
  97. package/src/spinner/index.d.ts +4 -0
  98. package/src/spinner/index.js +0 -2
  99. package/src/spinner/spin.d.ts +13 -0
  100. package/src/spinner/spin.js +13 -2
  101. package/src/spinner/spinner.d.ts +69 -0
  102. package/src/spinner/spinner.js +30 -2
  103. package/src/spinner/spinners.d.ts +34 -0
  104. package/src/spinner/spinners.js +23 -9
  105. package/src/strings/clip.d.ts +21 -0
  106. package/src/strings/clip.js +10 -0
  107. package/src/strings/parse.d.ts +23 -0
  108. package/src/strings/parse.js +7 -0
  109. package/src/strings/split.d.ts +38 -0
  110. package/src/strings/split.js +15 -0
  111. package/src/strings.d.ts +44 -0
  112. package/src/strings.js +34 -4
  113. package/src/style.d.ts +462 -0
  114. package/src/style.js +58 -4
  115. package/src/symbols.d.ts +167 -0
  116. package/src/symbols.js +91 -7
  117. package/src/table/draw-borders.d.ts +38 -0
  118. package/src/table/draw-borders.js +10 -2
  119. package/src/table/index.d.ts +8 -0
  120. package/src/table/index.js +1 -0
  121. package/src/table/table.d.ts +234 -0
  122. package/src/table/table.js +59 -1
  123. package/src/themes/blocks/unicode-half.d.ts +6 -0
  124. package/src/themes/blocks/unicode-half.js +1 -0
  125. package/src/themes/blocks/unicode-thin.d.ts +6 -0
  126. package/src/themes/blocks/unicode-thin.js +1 -0
  127. package/src/themes/lines/ascii-compact.d.ts +6 -0
  128. package/src/themes/lines/ascii-compact.js +1 -0
  129. package/src/themes/lines/ascii-dots.d.ts +6 -0
  130. package/src/themes/lines/ascii-dots.js +1 -0
  131. package/src/themes/lines/ascii-girder.d.ts +6 -0
  132. package/src/themes/lines/ascii-girder.js +1 -0
  133. package/src/themes/lines/ascii-github.d.ts +6 -0
  134. package/src/themes/lines/ascii-github.js +1 -0
  135. package/src/themes/lines/ascii-reddit.d.ts +6 -0
  136. package/src/themes/lines/ascii-reddit.js +1 -0
  137. package/src/themes/lines/ascii-rounded.d.ts +6 -0
  138. package/src/themes/lines/ascii-rounded.js +1 -0
  139. package/src/themes/lines/ascii.d.ts +6 -0
  140. package/src/themes/lines/ascii.js +1 -0
  141. package/src/themes/lines/unicode-bold.d.ts +6 -0
  142. package/src/themes/lines/unicode-bold.js +1 -0
  143. package/src/themes/lines/unicode-rounded.d.ts +6 -0
  144. package/src/themes/lines/unicode-rounded.js +1 -0
  145. package/src/themes/lines/unicode.d.ts +6 -0
  146. package/src/themes/lines/unicode.js +1 -0
  147. package/src/themes/utils.d.ts +33 -0
  148. package/src/themes/utils.js +7 -0
  149. package/src/turtle/draw-line-art.d.ts +19 -0
  150. package/src/turtle/draw-line-art.js +7 -0
  151. package/src/turtle/draw-unicode.d.ts +19 -0
  152. package/src/turtle/draw-unicode.js +8 -0
  153. package/src/turtle/index.d.ts +21 -0
  154. package/src/turtle/index.js +8 -0
  155. package/src/turtle/turtle.d.ts +269 -0
  156. package/src/turtle/turtle.js +124 -4
@@ -2,6 +2,12 @@ import process from 'node:process';
2
2
  import {matchCsiNoGroups} from '../strings.js';
3
3
  import Box from '../box.js';
4
4
 
5
+ /** Logs a text container to the console via `console.log()`. Strips ANSI codes if colorDepth < 4.
6
+ * @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
7
+ * @param {object} [options] - Options.
8
+ * @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
9
+ * @param {number} [options.colorDepth=24] - Color depth (1 = no color, 4/8/24 = color).
10
+ */
5
11
  export const log = (s, {endOfLineCommand = '\x1B[m', colorDepth = 24} = {}) => {
6
12
  s = Box.make(s);
7
13
  if (colorDepth < 4) {
@@ -12,6 +18,13 @@ export const log = (s, {endOfLineCommand = '\x1B[m', colorDepth = 24} = {}) => {
12
18
  s.box.forEach(row => console.log(row + endOfLineCommand));
13
19
  };
14
20
 
21
+ /** Writes a text container to a stream. Strips ANSI codes if colorDepth < 4.
22
+ * @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
23
+ * @param {object} [options] - Options.
24
+ * @param {import('node:stream').Writable} [options.stream=process.stdout] - The output stream.
25
+ * @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
26
+ * @param {number} [options.colorDepth] - Color depth. Auto-detected from stream if not specified.
27
+ */
15
28
  export const out = (s, {stream = process.stdout, endOfLineCommand = '\x1B[m', colorDepth} = {}) => {
16
29
  s = Box.make(s);
17
30
  if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
@@ -23,17 +36,32 @@ export const out = (s, {stream = process.stdout, endOfLineCommand = '\x1B[m', co
23
36
  s.box.forEach(row => stream.write(row + endOfLineCommand + '\n'));
24
37
  };
25
38
 
39
+ /** Convenience wrapper around a writable stream for formatted output.
40
+ * Auto-detects color depth from the stream.
41
+ */
26
42
  export class Out {
43
+ /**
44
+ * @param {import('node:stream').Writable} stream - The writable stream to wrap.
45
+ */
27
46
  constructor(stream) {
28
47
  this.stream = stream;
29
48
  this.colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
30
49
  }
50
+ /** Writes a text container to the stream.
51
+ * @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
52
+ * @param {object} [options] - Options.
53
+ * @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
54
+ * @param {number} [options.colorDepth] - Color depth override.
55
+ */
31
56
  out(s, {endOfLineCommand = '\x1B[m', colorDepth} = {}) {
32
57
  if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = this.colorDepth;
33
58
  return out(s, {stream: this.stream, endOfLineCommand, colorDepth});
34
59
  }
35
60
  }
36
61
 
62
+ /** Logs a string with control characters visualized as hex escape sequences.
63
+ * @param {string} string - The string to debug.
64
+ */
37
65
  export const debug = string =>
38
66
  console.log(
39
67
  string.replace(/[\x00-\x1F]/g, m => '\\x' + m[0].charCodeAt(0).toString(16).padStart(2, '0').toUpperCase())
@@ -0,0 +1,114 @@
1
+ import Writer from './writer.js';
2
+ import {StringsInput} from '../strings.js';
3
+
4
+ /** Options for `Updater` constructor. */
5
+ export interface UpdaterOptions {
6
+ /** String written before the first frame. */
7
+ prologue?: string;
8
+ /** String written after the final frame. */
9
+ epilogue?: string;
10
+ /** String written before each frame. */
11
+ beforeFrame?: string;
12
+ /** String written after each frame. */
13
+ afterFrame?: string;
14
+ /** String prepended before each line. */
15
+ beforeLine?: string;
16
+ /** String appended after each line. */
17
+ afterLine?: string;
18
+ /** If true, omit the trailing newline on the last line. */
19
+ noLastNewLine?: boolean;
20
+ }
21
+
22
+ /** An object that can provide frames for the Updater. */
23
+ export interface UpdaterTarget {
24
+ /** Current state string. */
25
+ state?: string;
26
+ /** Returns the current frame content.
27
+ * @returns Frame content (Box, string, or string array).
28
+ */
29
+ getFrame(...args: unknown[]): StringsInput;
30
+ }
31
+
32
+ /** Manages auto-refreshing console output for spinners, progress bars, etc. */
33
+ export class Updater {
34
+ /** The updater function or target object. */
35
+ updater: ((state: string, ...args: unknown[]) => StringsInput) | UpdaterTarget;
36
+ /** The Writer instance used for output. */
37
+ writer: Writer;
38
+ /** String written before the first frame. */
39
+ prologue: string;
40
+ /** String written after the final frame. */
41
+ epilogue: string;
42
+ /** String written before each frame. */
43
+ beforeFrame: string;
44
+ /** String written after each frame. */
45
+ afterFrame: string;
46
+ /** String prepended before each line. */
47
+ beforeLine: string;
48
+ /** String appended after each line. */
49
+ afterLine: string;
50
+ /** If true, omit the trailing newline on the last line. */
51
+ noLastNewLine: boolean | undefined;
52
+ /** Height of the last written frame in rows. */
53
+ lastHeight: number;
54
+ /** Whether the updater has been finalized. */
55
+ isDone: boolean;
56
+ /** Whether this is the first frame. */
57
+ first: boolean;
58
+ /** Handle for the auto-refresh interval, or null. */
59
+ intervalHandle: ReturnType<typeof setInterval> | null;
60
+
61
+ /**
62
+ * @param updater - A function or UpdaterTarget that provides frames.
63
+ * @param options - Updater options.
64
+ * @param writer - Writer instance (default: new Writer).
65
+ */
66
+ constructor(
67
+ updater: ((state: string, ...args: unknown[]) => StringsInput) | UpdaterTarget,
68
+ options?: UpdaterOptions,
69
+ writer?: Writer
70
+ );
71
+
72
+ /** Whether auto-refreshing is active. */
73
+ readonly isRefreshing: boolean;
74
+
75
+ /** Starts auto-refreshing at the given interval.
76
+ * @param ms - Refresh interval in milliseconds (default: 100).
77
+ * @returns This Updater.
78
+ */
79
+ startRefreshing(ms?: number): this;
80
+ /** Stops auto-refreshing.
81
+ * @returns This Updater.
82
+ */
83
+ stopRefreshing(): this;
84
+ /** Resets the updater state.
85
+ * @returns This Updater.
86
+ */
87
+ reset(): this;
88
+
89
+ /** Gets the current frame from the updater.
90
+ * @param state - State string.
91
+ * @returns Frame content.
92
+ */
93
+ getFrame(state: string, ...args: unknown[]): StringsInput;
94
+ /** Writes a frame to the stream.
95
+ * @param state - State string.
96
+ * @returns A promise that resolves when the frame is written.
97
+ */
98
+ writeFrame(state: string, ...args: unknown[]): Promise<void>;
99
+ /** Finishes updating: writes the epilogue and stops refreshing.
100
+ * @returns A promise that resolves when done.
101
+ */
102
+ done(): Promise<void>;
103
+ /** Updates with a new state and writes the frame.
104
+ * @param state - State string.
105
+ * @returns A promise that resolves when the update is written.
106
+ */
107
+ update(state?: string, ...args: unknown[]): Promise<void>;
108
+ /** Writes the final frame with state 'finished' and calls `done()`.
109
+ * @returns A promise that resolves when done.
110
+ */
111
+ final(...args: unknown[]): Promise<void>;
112
+ }
113
+
114
+ export default Updater;
@@ -1,13 +1,30 @@
1
- 'use strict';
2
-
3
1
  import Writer from './writer.js';
4
2
  import {toStrings} from '../strings.js';
5
3
  import {cursorUp, setCommands} from '../ansi/csi.js';
6
4
 
7
5
  const RESET = setCommands([]);
8
6
 
7
+ /** Manages continuously updating console output (spinners, progress bars, etc.).
8
+ * Handles refreshing frames, prologue/epilogue sequences, and interacts with a Writer instance.
9
+ */
9
10
  export class Updater {
10
- constructor(updater, {prologue, epilogue, beforeFrame, afterFrame, beforeLine, afterLine, noLastNewLine} = {}, writer = new Writer()) {
11
+ /**
12
+ * @param {((state: string, ...args: unknown[]) => import('../strings.js').StringsInput)|{state?: string, getFrame: (...args: unknown[]) => import('../strings.js').StringsInput}} updater - A function `(state, ...args) => frame` or an object with `getFrame()`.
13
+ * @param {object} [options] - Options.
14
+ * @param {string} [options.prologue] - String written before the first frame.
15
+ * @param {string} [options.epilogue] - String written after the last frame.
16
+ * @param {string} [options.beforeFrame] - String written before each frame.
17
+ * @param {string} [options.afterFrame] - String written after each frame.
18
+ * @param {string} [options.beforeLine] - String prepended to each line.
19
+ * @param {string} [options.afterLine] - String appended to each line.
20
+ * @param {boolean} [options.noLastNewLine] - If true, omit the trailing newline of each frame.
21
+ * @param {import('./writer.js').default} [writer=new Writer()] - The Writer instance to use.
22
+ */
23
+ constructor(
24
+ updater,
25
+ {prologue, epilogue, beforeFrame, afterFrame, beforeLine, afterLine, noLastNewLine} = {},
26
+ writer = new Writer()
27
+ ) {
11
28
  this.updater = updater;
12
29
  this.writer = writer;
13
30
  this.prologue = prologue || RESET;
@@ -23,16 +40,24 @@ export class Updater {
23
40
  this.intervalHandle = null;
24
41
  }
25
42
 
43
+ /** Whether the updater is currently auto-refreshing. */
26
44
  get isRefreshing() {
27
45
  return this.intervalHandle !== null;
28
46
  }
29
47
 
48
+ /** Starts auto-refreshing at the given interval.
49
+ * @param {number} [ms=100] - Refresh interval in milliseconds.
50
+ * @returns {this}
51
+ */
30
52
  startRefreshing(ms = 100) {
31
53
  if (this.intervalHandle || this.isDone || !this.writer.isTTY) return this;
32
54
  this.intervalHandle = setInterval(this.update.bind(this), ms);
33
55
  return this;
34
56
  }
35
57
 
58
+ /** Stops auto-refreshing.
59
+ * @returns {this}
60
+ */
36
61
  stopRefreshing() {
37
62
  if (!this.intervalHandle) return this;
38
63
  clearInterval(this.intervalHandle);
@@ -40,6 +65,9 @@ export class Updater {
40
65
  return this;
41
66
  }
42
67
 
68
+ /** Resets the updater state, stopping any refresh and clearing the done flag.
69
+ * @returns {this}
70
+ */
43
71
  reset() {
44
72
  this.stopRefreshing();
45
73
  this.isDone = false;
@@ -47,6 +75,11 @@ export class Updater {
47
75
  return this;
48
76
  }
49
77
 
78
+ /** Gets a frame from the updater function or object.
79
+ * @param {string} state - The current state ('active', 'paused', 'finished', etc.).
80
+ * @param {...unknown} args - Additional arguments.
81
+ * @returns {import('../strings.js').StringsInput} The frame content.
82
+ */
50
83
  getFrame(state, ...args) {
51
84
  if (typeof this.updater == 'function') return this.updater(state, ...args);
52
85
  if (typeof this.updater?.getFrame == 'function') {
@@ -56,6 +89,11 @@ export class Updater {
56
89
  throw new TypeError('Updater must be a function or implement getFrame()');
57
90
  }
58
91
 
92
+ /** Writes a single frame to the output, handling cursor repositioning.
93
+ * @param {string} state - The current state.
94
+ * @param {...unknown} args - Additional arguments passed to `getFrame()`.
95
+ * @returns {Promise<void>}
96
+ */
59
97
  async writeFrame(state, ...args) {
60
98
  if (this.first) {
61
99
  this.prologue && (await this.writer.writeString(this.prologue));
@@ -70,10 +108,17 @@ export class Updater {
70
108
  this.lastHeight = frame.length;
71
109
  if (this.noLastNewLine) --this.lastHeight;
72
110
 
73
- await this.writer.write(frame, {noLastNewLine: this.noLastNewLine, beforeLine: this.beforeLine, afterLine: this.afterLine});
111
+ await this.writer.write(frame, {
112
+ noLastNewLine: this.noLastNewLine,
113
+ beforeLine: this.beforeLine,
114
+ afterLine: this.afterLine
115
+ });
74
116
  this.afterFrame && (await this.writer.writeString(this.afterFrame));
75
117
  }
76
118
 
119
+ /** Marks the updater as done, stops refreshing, and writes the epilogue.
120
+ * @returns {Promise<void>}
121
+ */
77
122
  async done() {
78
123
  if (this.isDone) return;
79
124
  this.isDone = true;
@@ -81,11 +126,20 @@ export class Updater {
81
126
  this.epilogue && (await this.writer.writeString(this.epilogue));
82
127
  }
83
128
 
129
+ /** Updates the display with a new frame.
130
+ * @param {string} [state='active'] - The current state.
131
+ * @param {...unknown} args - Additional arguments passed to `getFrame()`.
132
+ * @returns {Promise<void>}
133
+ */
84
134
  async update(state = 'active', ...args) {
85
135
  if (this.isDone || !this.writer.isTTY) return;
86
136
  await this.writeFrame(state, ...args);
87
137
  }
88
138
 
139
+ /** Writes the final frame with state 'finished' and calls `done()`.
140
+ * @param {...unknown} args - Additional arguments passed to `getFrame()`.
141
+ * @returns {Promise<void>}
142
+ */
89
143
  async final(...args) {
90
144
  if (this.isDone) return;
91
145
  await this.writeFrame('finished', ...args);
@@ -0,0 +1,87 @@
1
+ import {Writable} from 'node:stream';
2
+ import {StringsInput} from '../strings.js';
3
+
4
+ /** Options for `Writer.write()`. */
5
+ export interface WriteOptions {
6
+ /** If true or 'save', keep cursor in the same column after writing. */
7
+ sameColumn?: boolean | 'save';
8
+ /** If true, omit the trailing newline on the last line. */
9
+ noLastNewLine?: boolean;
10
+ /** String prepended before each line. */
11
+ beforeLine?: string;
12
+ /** String appended after each line. */
13
+ afterLine?: string;
14
+ }
15
+
16
+ /** Manages writing to a stream with cursor manipulation and color depth awareness. */
17
+ export class Writer {
18
+ /** The underlying writable stream. */
19
+ stream: Writable;
20
+ /** Forced color depth override, or undefined to auto-detect. */
21
+ forceColorDepth: number | undefined;
22
+
23
+ /**
24
+ * @param stream - Writable stream (default: stdout).
25
+ * @param forceColorDepth - Override color depth.
26
+ */
27
+ constructor(stream?: Writable, forceColorDepth?: number);
28
+
29
+ /** Whether the stream is a TTY. */
30
+ readonly isTTY: boolean;
31
+ /** Terminal width in columns. */
32
+ readonly columns: number;
33
+ /** Terminal height in rows. */
34
+ readonly rows: number;
35
+ /** Terminal dimensions. */
36
+ readonly size: {columns: number; rows: number};
37
+
38
+ /** Returns the color depth of the stream.
39
+ * @param env - Environment variables to check (default: `process.env`).
40
+ * @returns Color depth (1, 4, 8, or 24).
41
+ */
42
+ getColorDepth(env?: object): number;
43
+ /** Checks if the stream supports the given number of colors.
44
+ * @param count - Number of colors to check for.
45
+ * @param env - Environment variables to check.
46
+ * @returns True if supported.
47
+ */
48
+ hasColors(count?: number, env?: object): boolean;
49
+ hasColors(env?: object): boolean;
50
+
51
+ /** Clears the current line.
52
+ * @param dir - Direction: -1 left, 0 entire, 1 right.
53
+ * @returns Promise resolving when done.
54
+ */
55
+ clearLine(dir: number): Promise<boolean>;
56
+ /** Clears from cursor to end of screen.
57
+ * @returns Promise resolving when done.
58
+ */
59
+ clearScreenDown(): Promise<boolean>;
60
+
61
+ /** Moves cursor to absolute position.
62
+ * @param x - Column.
63
+ * @param y - Row (optional).
64
+ * @returns Promise resolving when done.
65
+ */
66
+ cursorTo(x: number, y?: number): Promise<boolean>;
67
+ /** Moves cursor relative to current position.
68
+ * @param dx - Columns.
69
+ * @param dy - Rows.
70
+ * @returns Promise resolving when done.
71
+ */
72
+ moveCursor(dx: number, dy: number): Promise<boolean>;
73
+
74
+ /** Writes a raw string to the stream.
75
+ * @param s - The string to write.
76
+ * @returns Promise resolving when done.
77
+ */
78
+ writeString(s: string): Promise<void>;
79
+ /** Writes a Box/string to the stream with optional cursor control.
80
+ * @param s - Input: Box, string, or string array.
81
+ * @param options - Write options.
82
+ * @returns Promise resolving when done.
83
+ */
84
+ write(s: StringsInput, options?: WriteOptions): Promise<void>;
85
+ }
86
+
87
+ export default Writer;
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  import process from 'node:process';
4
2
 
5
3
  import {CURSOR_DOWN1, CURSOR_RESTORE_POS, CURSOR_SAVE_POS} from '../ansi/csi.js';
@@ -8,33 +6,57 @@ import {getLength, matchCsiNoGroups, matchCsiNoSgrNoGroups, toStrings} from '../
8
6
  const write = async (stream, chunk, encoding = 'utf8') =>
9
7
  new Promise((resolve, reject) => stream.write(chunk, encoding, error => (error ? reject(error) : resolve())));
10
8
 
9
+ /** Abstracts writing to a stream (defaulting to `process.stdout`).
10
+ * Handles TTY capabilities, color depth, cursor manipulation, and ANSI stripping for non-TTY streams.
11
+ */
11
12
  export class Writer {
13
+ /**
14
+ * @param {import('node:stream').Writable} [stream=process.stdout] - The output stream.
15
+ * @param {number} [forceColorDepth] - Force a specific color depth instead of auto-detecting.
16
+ */
12
17
  constructor(stream = process.stdout, forceColorDepth) {
13
18
  this.stream = stream;
14
19
  this.forceColorDepth = forceColorDepth;
15
20
  }
16
21
 
22
+ /** Whether the stream is a TTY. */
17
23
  get isTTY() {
18
24
  return this.stream.isTTY;
19
25
  }
26
+ /** The number of columns in the terminal. */
20
27
  get columns() {
21
28
  return this.stream.columns;
22
29
  }
30
+ /** The number of rows in the terminal. */
23
31
  get rows() {
24
32
  return this.stream.rows;
25
33
  }
34
+ /** The terminal size as `{columns, rows}`. */
26
35
  get size() {
27
36
  const [columns, rows] = this.stream.getWindowSize?.() || [];
28
37
  return {columns, rows};
29
38
  }
39
+ /** Returns the color depth of the stream.
40
+ * @param {object} [env] - Environment variables to check (default: `process.env`).
41
+ * @returns {number} The color depth (1, 4, 8, or 24).
42
+ */
30
43
  getColorDepth(...args) {
31
44
  return this.forceColorDepth || this.stream.getColorDepth?.(...args);
32
45
  }
33
46
 
47
+ /** Checks if the stream supports the given number of colors.
48
+ * @param {number} [count] - Number of colors to check for.
49
+ * @param {object} [env] - Environment variables to check.
50
+ * @returns {boolean} True if supported.
51
+ */
34
52
  hasColors(...args) {
35
53
  return this.forceColorDepth ? args[0] <= Math.pow(2, this.forceColorDepth) : this.stream.hasColors?.(...args);
36
54
  }
37
55
 
56
+ /** Clears the current line.
57
+ * @param {number} dir - Direction: -1 = left, 0 = entire line, 1 = right.
58
+ * @returns {Promise<boolean>} True if the operation was supported.
59
+ */
38
60
  clearLine(dir) {
39
61
  return new Promise((resolve, reject) => {
40
62
  if (typeof this.stream.clearLine == 'function') {
@@ -44,6 +66,9 @@ export class Writer {
44
66
  }
45
67
  });
46
68
  }
69
+ /** Clears the screen from the cursor down.
70
+ * @returns {Promise<boolean>} True if the operation was supported.
71
+ */
47
72
  clearScreenDown() {
48
73
  return new Promise((resolve, reject) => {
49
74
  if (typeof this.stream.clearScreenDown == 'function') {
@@ -54,6 +79,11 @@ export class Writer {
54
79
  });
55
80
  }
56
81
 
82
+ /** Moves the cursor to an absolute position.
83
+ * @param {number} x - Column.
84
+ * @param {number} [y] - Row.
85
+ * @returns {Promise<boolean>} True if the operation was supported.
86
+ */
57
87
  cursorTo(x, y) {
58
88
  return new Promise((resolve, reject) => {
59
89
  if (typeof this.stream.cursorTo == 'function') {
@@ -63,6 +93,11 @@ export class Writer {
63
93
  }
64
94
  });
65
95
  }
96
+ /** Moves the cursor relative to its current position.
97
+ * @param {number} dx - Columns to move.
98
+ * @param {number} dy - Rows to move.
99
+ * @returns {Promise<boolean>} True if the operation was supported.
100
+ */
66
101
  moveCursor(dx, dy) {
67
102
  return new Promise((resolve, reject) => {
68
103
  if (typeof this.stream.moveCursor == 'function') {
@@ -73,6 +108,10 @@ export class Writer {
73
108
  });
74
109
  }
75
110
 
111
+ /** Writes a raw string to the stream, stripping ANSI codes for non-TTY streams.
112
+ * @param {string} s - The string to write.
113
+ * @returns {Promise<void>}
114
+ */
76
115
  async writeString(s) {
77
116
  s = String(s);
78
117
 
@@ -93,12 +132,23 @@ export class Writer {
93
132
  await write(this.stream, s.replace(matchCsiNoGroups, ''));
94
133
  }
95
134
 
135
+ /** Writes a text container to the stream.
136
+ * @param {import('../strings.js').StringsInput} s - Input convertible to strings.
137
+ * @param {object} [options] - Options.
138
+ * @param {boolean|'save'} [options.sameColumn] - If true or 'save', keep cursor in the same column between lines.
139
+ * @param {boolean} [options.noLastNewLine] - If true, omit the trailing newline.
140
+ * @param {string} [options.beforeLine=''] - String prepended to each line.
141
+ * @param {string} [options.afterLine=''] - String appended to each line.
142
+ * @returns {Promise<void>}
143
+ */
96
144
  async write(s, {sameColumn, noLastNewLine, beforeLine = '', afterLine = ''} = {}) {
97
145
  s = toStrings(s);
98
146
 
99
147
  if (!this.isTTY) {
100
148
  const matcher = this.forceColorDepth ? matchCsiNoSgrNoGroups : matchCsiNoGroups;
101
- let lines = Array.from(s).map(line => beforeLine + line + afterLine).join('\n');
149
+ let lines = Array.from(s)
150
+ .map(line => beforeLine + line + afterLine)
151
+ .join('\n');
102
152
  if (!noLastNewLine) lines += '\n';
103
153
  matcher.lastIndex = 0;
104
154
  lines = lines.replace(matcher, '');
@@ -108,7 +158,7 @@ export class Writer {
108
158
 
109
159
  if (sameColumn === 'save') {
110
160
  for (const line of s) {
111
- await write(this.stream, CURSOR_SAVE_POS + beforeLine + line + afterline + CURSOR_RESTORE_POS + CURSOR_DOWN1);
161
+ await write(this.stream, CURSOR_SAVE_POS + beforeLine + line + afterLine + CURSOR_RESTORE_POS + CURSOR_DOWN1);
112
162
  }
113
163
  return;
114
164
  }
@@ -123,7 +173,9 @@ export class Writer {
123
173
  return;
124
174
  }
125
175
 
126
- let lines = Array.from(s).map(line => beforeLine + line + afterLine).join('\n');
176
+ let lines = Array.from(s)
177
+ .map(line => beforeLine + line + afterLine)
178
+ .join('\n');
127
179
  if (!noLastNewLine) lines += '\n';
128
180
  await write(this.stream, lines);
129
181
  }