bananass-utils-console 0.8.0 → 0.9.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 +11 -153
- package/build/icons.d.ts +31 -0
- package/{src/icons → build}/icons.js +10 -51
- package/build/{is-interactive/is-interactive.d.ts → is-interactive.d.ts} +5 -4
- package/{src/is-interactive → build}/is-interactive.js +4 -8
- package/build/{is-unicode-supported/is-unicode-supported.d.ts → is-unicode-supported.d.ts} +6 -1
- package/{src/is-unicode-supported → build}/is-unicode-supported.js +16 -24
- package/build/{logger/logger.d.ts → logger.d.ts} +32 -35
- package/build/logger.js +163 -0
- package/build/{spinner/spinner.d.ts → spinner.d.ts} +57 -78
- package/build/spinner.js +277 -0
- package/build/theme.d.ts +64 -0
- package/{src/theme → build}/theme.js +31 -46
- package/package.json +24 -25
- package/build/icons/icons.d.ts +0 -28
- package/build/icons/index.d.ts +0 -1
- package/build/is-interactive/index.d.ts +0 -2
- package/build/is-unicode-supported/index.d.ts +0 -2
- package/build/logger/index.d.ts +0 -2
- package/build/spinner/index.d.ts +0 -2
- package/build/theme/index.d.ts +0 -1
- package/build/theme/theme.d.ts +0 -60
- package/src/icons/index.js +0 -1
- package/src/is-interactive/index.js +0 -3
- package/src/is-unicode-supported/index.js +0 -3
- package/src/logger/index.js +0 -3
- package/src/logger/logger.js +0 -208
- package/src/spinner/index.js +0 -3
- package/src/spinner/spinner.js +0 -388
- package/src/theme/index.js +0 -1
package/build/logger.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Console logger.
|
|
3
|
+
* @module bananass-utils-console/logger
|
|
4
|
+
*/
|
|
5
|
+
// --------------------------------------------------------------------------------
|
|
6
|
+
// Import
|
|
7
|
+
// --------------------------------------------------------------------------------
|
|
8
|
+
import { styleText } from 'node:util';
|
|
9
|
+
// --------------------------------------------------------------------------------
|
|
10
|
+
// Class
|
|
11
|
+
// --------------------------------------------------------------------------------
|
|
12
|
+
class Logger {
|
|
13
|
+
// ------------------------------------------------------------------------------
|
|
14
|
+
// Private Properties
|
|
15
|
+
// ------------------------------------------------------------------------------
|
|
16
|
+
#debug;
|
|
17
|
+
#quiet;
|
|
18
|
+
#textPrefix;
|
|
19
|
+
#textPrefixDefault = '>';
|
|
20
|
+
#lastMethodCalled = 'log';
|
|
21
|
+
#undeclaredValue = Symbol('undeclared-value');
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.#debug = options.debug ?? false;
|
|
24
|
+
this.#quiet = options.quiet ?? false;
|
|
25
|
+
this.#textPrefix =
|
|
26
|
+
typeof options.textPrefix === 'boolean'
|
|
27
|
+
? options.textPrefix
|
|
28
|
+
? this.#textPrefixDefault
|
|
29
|
+
: this.#undeclaredValue
|
|
30
|
+
: (options.textPrefix ?? this.#textPrefixDefault);
|
|
31
|
+
}
|
|
32
|
+
// ------------------------------------------------------------------------------
|
|
33
|
+
// Public Methods
|
|
34
|
+
// ------------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Log a message or executes a callback.
|
|
37
|
+
*
|
|
38
|
+
* The first parameter can be a message or a callback function.
|
|
39
|
+
* - If it's a function, it's invoked with subsequent arguments.
|
|
40
|
+
* - Otherwise, the message (with a configured text prefix) and additional arguments are logged.
|
|
41
|
+
*
|
|
42
|
+
* Behavior based on `quiet` option:
|
|
43
|
+
* - `quiet === true`: output X
|
|
44
|
+
* - `quiet === false`: output O
|
|
45
|
+
*
|
|
46
|
+
* @param params The message or callback function, followed by any additional arguments.
|
|
47
|
+
* @returns The current `Logger` instance for chaining.
|
|
48
|
+
*/
|
|
49
|
+
log(...params) {
|
|
50
|
+
const textOrCallback = typeof params[0] !== 'undefined' // Same with default parameter implemetation using `arguments`
|
|
51
|
+
? params[0]
|
|
52
|
+
: params.length > 0
|
|
53
|
+
? undefined
|
|
54
|
+
: this.#undeclaredValue;
|
|
55
|
+
const args = params.slice(1);
|
|
56
|
+
this.#lastMethodCalled = 'log';
|
|
57
|
+
if (this.#quiet)
|
|
58
|
+
return this;
|
|
59
|
+
if (typeof textOrCallback === 'function') {
|
|
60
|
+
textOrCallback(...args);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// eslint-disable-next-line no-console -- Needed for logging.
|
|
64
|
+
console.log(...[this.#textPrefix, textOrCallback, ...args].filter(arg => arg !== this.#undeclaredValue));
|
|
65
|
+
}
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Log a message or executes a callback in debug mode.
|
|
70
|
+
*
|
|
71
|
+
* The first parameter can be a message or a callback function.
|
|
72
|
+
* - If it's a function, it's invoked with subsequent arguments.
|
|
73
|
+
* - Otherwise, the message (with a configured text prefix) and additional arguments are logged in debug mode.
|
|
74
|
+
*
|
|
75
|
+
* Behavior based on `debug` and `quiet` options:
|
|
76
|
+
* - `quiet === true`: output X
|
|
77
|
+
* - `quiet === false && debug === true`: output O
|
|
78
|
+
* - `quiet === false && debug === false`: output X
|
|
79
|
+
*
|
|
80
|
+
* @param params The message or callback function, followed by any additional arguments.
|
|
81
|
+
* @returns The current `Logger` instance for chaining.
|
|
82
|
+
*/
|
|
83
|
+
debug(...params) {
|
|
84
|
+
const textOrCallback = typeof params[0] !== 'undefined'
|
|
85
|
+
? params[0]
|
|
86
|
+
: params.length > 0
|
|
87
|
+
? undefined
|
|
88
|
+
: this.#undeclaredValue;
|
|
89
|
+
const args = params.slice(1);
|
|
90
|
+
this.#lastMethodCalled = 'debug';
|
|
91
|
+
if (this.#quiet)
|
|
92
|
+
return this;
|
|
93
|
+
if (!this.#debug)
|
|
94
|
+
return this;
|
|
95
|
+
if (typeof textOrCallback === 'function') {
|
|
96
|
+
textOrCallback(...args);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// eslint-disable-next-line no-console -- Needed for logging.
|
|
100
|
+
console.log(...[this.#textPrefix, textOrCallback, ...args]
|
|
101
|
+
.filter(arg => arg !== this.#undeclaredValue)
|
|
102
|
+
.map(arg => (typeof arg === 'string' ? styleText('gray', arg) : arg)));
|
|
103
|
+
}
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Ends the current line by logging an empty line using the last method called.
|
|
108
|
+
*
|
|
109
|
+
* This method temporarily sets the text prefix to an undeclared value,
|
|
110
|
+
* then invokes either `log()` or `debug()` depending on the previously method called.
|
|
111
|
+
* After logging, it restores the original text prefix.
|
|
112
|
+
*
|
|
113
|
+
* @returns The current `Logger` instance for chaining.
|
|
114
|
+
*/
|
|
115
|
+
eol() {
|
|
116
|
+
const prevTextPrefix = this.#textPrefix;
|
|
117
|
+
this.#textPrefix = this.#undeclaredValue;
|
|
118
|
+
if (this.#lastMethodCalled === 'log') {
|
|
119
|
+
this.log();
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
this.debug();
|
|
123
|
+
}
|
|
124
|
+
this.#textPrefix = prevTextPrefix;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
// ------------------------------------------------------------------------------
|
|
128
|
+
// Getters and Setters
|
|
129
|
+
// ------------------------------------------------------------------------------
|
|
130
|
+
/**
|
|
131
|
+
* Get the last method called.
|
|
132
|
+
* @returns {'log' | 'debug'}
|
|
133
|
+
*/
|
|
134
|
+
get lastMethodCalled() {
|
|
135
|
+
return this.#lastMethodCalled;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// --------------------------------------------------------------------------------
|
|
139
|
+
// Export
|
|
140
|
+
// --------------------------------------------------------------------------------
|
|
141
|
+
/**
|
|
142
|
+
* Create a new `Logger` instance.
|
|
143
|
+
*
|
|
144
|
+
* @param options The logger options.
|
|
145
|
+
* @returns A new `Logger` instance.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* import createLogger from 'bananass-utils-console/logger';
|
|
149
|
+
*
|
|
150
|
+
* const logger = createLogger({
|
|
151
|
+
* debug: false,
|
|
152
|
+
* quiet: false,
|
|
153
|
+
* textPrefix: '>',
|
|
154
|
+
* })
|
|
155
|
+
*
|
|
156
|
+
* logger
|
|
157
|
+
* .log('Hello, log!');
|
|
158
|
+
* .debug('Hello, debug!');
|
|
159
|
+
* .eol();
|
|
160
|
+
*/
|
|
161
|
+
export default function createLogger(options) {
|
|
162
|
+
return new Logger(options);
|
|
163
|
+
}
|
|
@@ -1,101 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @
|
|
5
|
-
* @returns {Spinner} A new spinner instance.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* import createSpinner from 'bananass-utils-console/spinner';
|
|
9
|
-
*
|
|
10
|
-
* const spinner = createSpinner({
|
|
11
|
-
* text: 'Loading…'
|
|
12
|
-
* color: 'yellow',
|
|
13
|
-
* stream: process.stderr,
|
|
14
|
-
* spinner: {
|
|
15
|
-
* frames: ['-', '\\', '|', '/'],
|
|
16
|
-
* interval: 100,
|
|
17
|
-
* },
|
|
18
|
-
* }).start();
|
|
19
|
-
*
|
|
20
|
-
* setTimeout(() => {
|
|
21
|
-
* spinner.success('Success!');
|
|
22
|
-
* }, 2000);
|
|
2
|
+
* @fileoverview Console spinner.
|
|
3
|
+
* @module bananass-utils-console/spinner
|
|
4
|
+
* @license MIT Portions of this code were borrowed from [`yocto-spinner`](https://github.com/sindresorhus/yocto-spinner).
|
|
23
5
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
frames: string[];
|
|
28
|
-
interval: number;
|
|
29
|
-
};
|
|
6
|
+
import type { WriteStream } from 'node:tty';
|
|
7
|
+
import { type SpinnerStyle } from './icons.ts';
|
|
8
|
+
type ForegroundColors = 'black' | 'blackBright' | 'blue' | 'blueBright' | 'cyan' | 'cyanBright' | 'gray' | 'green' | 'greenBright' | 'grey' | 'magenta' | 'magentaBright' | 'red' | 'redBright' | 'white' | 'whiteBright' | 'yellow' | 'yellowBright';
|
|
30
9
|
/**
|
|
31
10
|
* Spinner options.
|
|
32
11
|
*/
|
|
33
|
-
|
|
12
|
+
interface Options {
|
|
34
13
|
/**
|
|
35
|
-
* Text to display next to the spinner.
|
|
14
|
+
* Text to display next to the spinner.
|
|
15
|
+
* @default ''
|
|
36
16
|
*/
|
|
37
17
|
text?: string | undefined;
|
|
38
18
|
/**
|
|
39
|
-
* The color of the spinner.
|
|
19
|
+
* The color of the spinner.
|
|
20
|
+
* @default 'yellow'
|
|
40
21
|
*/
|
|
41
22
|
color?: ForegroundColors | undefined;
|
|
42
23
|
/**
|
|
43
|
-
* The stream to which the spinner is written.
|
|
24
|
+
* The stream to which the spinner is written.
|
|
25
|
+
* @default process.stderr
|
|
44
26
|
*/
|
|
45
|
-
stream?:
|
|
27
|
+
stream?: WriteStream | undefined;
|
|
46
28
|
/**
|
|
47
|
-
* Whether the spinner should be interactive.
|
|
29
|
+
* Whether the spinner should be interactive.
|
|
30
|
+
* @default Auto-detected
|
|
48
31
|
*/
|
|
49
32
|
isInteractive?: boolean | undefined;
|
|
50
33
|
/**
|
|
51
34
|
* Customize the spinner animation with a custom set of frames and interval.
|
|
52
35
|
*
|
|
53
|
-
*
|
|
36
|
+
* Pass in any spinner from [`cli-spinners`](https://github.com/sindresorhus/cli-spinners).
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
54
39
|
* {
|
|
55
|
-
*
|
|
56
|
-
*
|
|
40
|
+
* frames: ['-', '\\', '|', '/'],
|
|
41
|
+
* interval: 100,
|
|
57
42
|
* }
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* Pass in any spinner from [`cli-spinners`](https://github.com/sindresorhus/cli-spinners).
|
|
61
43
|
*/
|
|
62
44
|
spinner?: SpinnerStyle | undefined;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* @typedef {"black"|"blackBright"|"blue"|"blueBright"|"cyan"|"cyanBright"|"gray"|"green"|"greenBright"|"grey"|"magenta"|"magentaBright"|"red"|"redBright"|"white"|"whiteBright"|"yellow"|"yellowBright"} ForegroundColors
|
|
66
|
-
*/
|
|
67
|
-
/**
|
|
68
|
-
* @typedef {object} SpinnerStyle
|
|
69
|
-
* @property {string[]} frames
|
|
70
|
-
* @property {number} interval
|
|
71
|
-
*/
|
|
72
|
-
/**
|
|
73
|
-
* @typedef {object} Options Spinner options.
|
|
74
|
-
* @property {string | undefined} [text] Text to display next to the spinner. (default: `''`)
|
|
75
|
-
* @property {ForegroundColors | undefined} [color] The color of the spinner. (default: `'yellow'`)
|
|
76
|
-
* @property {NodeJS.WriteStream | undefined} [stream] The stream to which the spinner is written. (default: `process.stderr`)
|
|
77
|
-
* @property {boolean | undefined} [isInteractive] Whether the spinner should be interactive. (default: Auto-detected)
|
|
78
|
-
* @property {SpinnerStyle | undefined} [spinner]
|
|
79
|
-
* Customize the spinner animation with a custom set of frames and interval.
|
|
80
|
-
*
|
|
81
|
-
* ```
|
|
82
|
-
* {
|
|
83
|
-
* frames: ['-', '\\', '|', '/'],
|
|
84
|
-
* interval: 100,
|
|
85
|
-
* }
|
|
86
|
-
* ```
|
|
87
|
-
*
|
|
88
|
-
* Pass in any spinner from [`cli-spinners`](https://github.com/sindresorhus/cli-spinners).
|
|
89
|
-
*/
|
|
45
|
+
}
|
|
90
46
|
declare class Spinner {
|
|
91
|
-
|
|
47
|
+
#private;
|
|
92
48
|
constructor(options?: Options);
|
|
93
49
|
/**
|
|
94
50
|
* Starts the spinner.
|
|
95
51
|
*
|
|
96
52
|
* Optionally, updates the text.
|
|
97
53
|
*
|
|
98
|
-
* @param
|
|
54
|
+
* @param text The text to display next to the spinner.
|
|
99
55
|
* @returns The spinner instance.
|
|
100
56
|
*/
|
|
101
57
|
start(text?: string): this;
|
|
@@ -104,35 +60,35 @@ declare class Spinner {
|
|
|
104
60
|
*
|
|
105
61
|
* Optionally displays a final message.
|
|
106
62
|
*
|
|
107
|
-
* @param
|
|
63
|
+
* @param finalText The final text to display after stopping the spinner.
|
|
108
64
|
* @returns The spinner instance.
|
|
109
65
|
*/
|
|
110
66
|
stop(finalText?: string): this;
|
|
111
67
|
/**
|
|
112
68
|
* Stops the spinner and displays a success symbol with the message.
|
|
113
69
|
*
|
|
114
|
-
* @param
|
|
70
|
+
* @param text The success message to display.
|
|
115
71
|
* @returns The spinner instance.
|
|
116
72
|
*/
|
|
117
73
|
success(text?: string): this;
|
|
118
74
|
/**
|
|
119
75
|
* Stops the spinner and displays an error symbol with the message.
|
|
120
76
|
*
|
|
121
|
-
* @param
|
|
77
|
+
* @param text The error message to display.
|
|
122
78
|
* @returns The spinner instance.
|
|
123
79
|
*/
|
|
124
80
|
error(text?: string): this;
|
|
125
81
|
/**
|
|
126
82
|
* Stops the spinner and displays a warning symbol with the message.
|
|
127
83
|
*
|
|
128
|
-
* @param
|
|
84
|
+
* @param text The warning message to display.
|
|
129
85
|
* @returns The spinner instance.
|
|
130
86
|
*/
|
|
131
87
|
warning(text?: string): this;
|
|
132
88
|
/**
|
|
133
89
|
* Stops the spinner and displays an info symbol with the message.
|
|
134
90
|
*
|
|
135
|
-
* @param
|
|
91
|
+
* @param text The info message to display.
|
|
136
92
|
* @returns The spinner instance.
|
|
137
93
|
*/
|
|
138
94
|
info(text?: string): this;
|
|
@@ -148,26 +104,49 @@ declare class Spinner {
|
|
|
148
104
|
* @example
|
|
149
105
|
* spinner.text = 'New text';
|
|
150
106
|
*/
|
|
151
|
-
|
|
107
|
+
get text(): string;
|
|
152
108
|
/**
|
|
153
109
|
* Change the text displayed next to the spinner.
|
|
154
110
|
*
|
|
155
111
|
* @example
|
|
156
112
|
* spinner.text = 'New text';
|
|
157
113
|
*/
|
|
158
|
-
|
|
114
|
+
set text(value: string);
|
|
159
115
|
/**
|
|
160
116
|
* Change the spinner color.
|
|
161
117
|
*/
|
|
162
|
-
|
|
118
|
+
get color(): ForegroundColors;
|
|
163
119
|
/**
|
|
164
120
|
* Change the spinner color.
|
|
165
121
|
*/
|
|
166
|
-
|
|
122
|
+
set color(value: ForegroundColors);
|
|
167
123
|
/**
|
|
168
124
|
* Returns whether the spinner is currently spinning.
|
|
169
125
|
*/
|
|
170
126
|
get isSpinning(): boolean;
|
|
171
|
-
#private;
|
|
172
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Create a new spinner instance.
|
|
130
|
+
*
|
|
131
|
+
* @param options The spinner options.
|
|
132
|
+
* @returns A new spinner instance.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* import createSpinner from 'bananass-utils-console/spinner';
|
|
136
|
+
*
|
|
137
|
+
* const spinner = createSpinner({
|
|
138
|
+
* text: 'Loading…'
|
|
139
|
+
* color: 'yellow',
|
|
140
|
+
* stream: process.stderr,
|
|
141
|
+
* spinner: {
|
|
142
|
+
* frames: ['-', '\\', '|', '/'],
|
|
143
|
+
* interval: 100,
|
|
144
|
+
* },
|
|
145
|
+
* }).start();
|
|
146
|
+
*
|
|
147
|
+
* setTimeout(() => {
|
|
148
|
+
* spinner.success('Success!');
|
|
149
|
+
* }, 2000);
|
|
150
|
+
*/
|
|
151
|
+
export default function createSpinner(options?: Options): Spinner;
|
|
173
152
|
export {};
|
package/build/spinner.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Console spinner.
|
|
3
|
+
* @module bananass-utils-console/spinner
|
|
4
|
+
* @license MIT Portions of this code were borrowed from [`yocto-spinner`](https://github.com/sindresorhus/yocto-spinner).
|
|
5
|
+
*/
|
|
6
|
+
// --------------------------------------------------------------------------------
|
|
7
|
+
// Import
|
|
8
|
+
// --------------------------------------------------------------------------------
|
|
9
|
+
import { styleText } from 'node:util';
|
|
10
|
+
import { successIcon, errorIcon, warningIcon, infoIcon, defaultSpinner, } from "./icons.js";
|
|
11
|
+
import isInteractive from "./is-interactive.js";
|
|
12
|
+
// --------------------------------------------------------------------------------
|
|
13
|
+
// Class
|
|
14
|
+
// --------------------------------------------------------------------------------
|
|
15
|
+
class Spinner {
|
|
16
|
+
// ------------------------------------------------------------------------------
|
|
17
|
+
// Private Properties
|
|
18
|
+
// ------------------------------------------------------------------------------
|
|
19
|
+
#frames;
|
|
20
|
+
#interval;
|
|
21
|
+
#currentFrame = -1;
|
|
22
|
+
#timer = undefined;
|
|
23
|
+
#text;
|
|
24
|
+
#stream;
|
|
25
|
+
#color;
|
|
26
|
+
#lines = 0;
|
|
27
|
+
#isInteractive;
|
|
28
|
+
#exitHandlerBound;
|
|
29
|
+
#lastSpinnerFrameTime = 0;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
const spinner = options.spinner ?? defaultSpinner;
|
|
32
|
+
this.#frames = spinner.frames;
|
|
33
|
+
this.#interval = spinner.interval;
|
|
34
|
+
this.#text = options.text ?? '';
|
|
35
|
+
this.#stream = options.stream ?? process.stderr;
|
|
36
|
+
this.#color = options.color ?? 'yellow';
|
|
37
|
+
this.#isInteractive = options.isInteractive ?? isInteractive(this.#stream);
|
|
38
|
+
this.#exitHandlerBound = this.#exitHandler.bind(this);
|
|
39
|
+
}
|
|
40
|
+
// ------------------------------------------------------------------------------
|
|
41
|
+
// Private Methods
|
|
42
|
+
// ------------------------------------------------------------------------------
|
|
43
|
+
#symbolStop(symbol, text = this.#text) {
|
|
44
|
+
return this.stop(`${symbol} ${text}`);
|
|
45
|
+
}
|
|
46
|
+
#render() {
|
|
47
|
+
const currentTime = Date.now();
|
|
48
|
+
// Ensure we only update the spinner frame at the wanted interval, even if the render method is called more often.
|
|
49
|
+
if (this.#currentFrame === -1 ||
|
|
50
|
+
currentTime - this.#lastSpinnerFrameTime >= this.#interval) {
|
|
51
|
+
this.#currentFrame = (this.#currentFrame + 1) % this.#frames.length;
|
|
52
|
+
this.#lastSpinnerFrameTime = currentTime;
|
|
53
|
+
}
|
|
54
|
+
const color = this.#color ?? 'yellow';
|
|
55
|
+
const frame = this.#frames[this.#currentFrame];
|
|
56
|
+
let string = `${styleText(color, frame)} ${this.#text}`;
|
|
57
|
+
if (!this.#isInteractive) {
|
|
58
|
+
string += '\n';
|
|
59
|
+
}
|
|
60
|
+
this.clear();
|
|
61
|
+
this.#write(string);
|
|
62
|
+
if (this.#isInteractive) {
|
|
63
|
+
this.#lines = this.#lineCount(string);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
#write(text) {
|
|
67
|
+
this.#stream.write(text);
|
|
68
|
+
}
|
|
69
|
+
#lineCount(text) {
|
|
70
|
+
const width = this.#stream.columns ?? 80;
|
|
71
|
+
const lines = text.split('\n');
|
|
72
|
+
let lineCount = 0;
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
lineCount += Math.max(1, Math.ceil(line.length / width));
|
|
75
|
+
}
|
|
76
|
+
return lineCount;
|
|
77
|
+
}
|
|
78
|
+
#hideCursor() {
|
|
79
|
+
if (this.#isInteractive) {
|
|
80
|
+
this.#write('\u001B[?25l');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
#showCursor() {
|
|
84
|
+
if (this.#isInteractive) {
|
|
85
|
+
this.#write('\u001B[?25h');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
#subscribeToProcessEvents() {
|
|
89
|
+
process.once('SIGINT', this.#exitHandlerBound);
|
|
90
|
+
process.once('SIGTERM', this.#exitHandlerBound);
|
|
91
|
+
}
|
|
92
|
+
#unsubscribeFromProcessEvents() {
|
|
93
|
+
process.off('SIGINT', this.#exitHandlerBound);
|
|
94
|
+
process.off('SIGTERM', this.#exitHandlerBound);
|
|
95
|
+
}
|
|
96
|
+
#exitHandler(signal) {
|
|
97
|
+
if (this.isSpinning) {
|
|
98
|
+
this.stop();
|
|
99
|
+
}
|
|
100
|
+
// SIGINT: 128 + 2, SIGTERM: 128 + 15
|
|
101
|
+
const exitCode = signal === 'SIGINT' ? 130 : signal === 'SIGTERM' ? 143 : 1;
|
|
102
|
+
process.exit(exitCode);
|
|
103
|
+
}
|
|
104
|
+
// ------------------------------------------------------------------------------
|
|
105
|
+
// Public Methods
|
|
106
|
+
// ------------------------------------------------------------------------------
|
|
107
|
+
/**
|
|
108
|
+
* Starts the spinner.
|
|
109
|
+
*
|
|
110
|
+
* Optionally, updates the text.
|
|
111
|
+
*
|
|
112
|
+
* @param text The text to display next to the spinner.
|
|
113
|
+
* @returns The spinner instance.
|
|
114
|
+
*/
|
|
115
|
+
start(text) {
|
|
116
|
+
if (text) {
|
|
117
|
+
this.#text = text;
|
|
118
|
+
}
|
|
119
|
+
if (this.isSpinning) {
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
this.#hideCursor();
|
|
123
|
+
this.#render();
|
|
124
|
+
this.#subscribeToProcessEvents();
|
|
125
|
+
this.#timer = setInterval(() => {
|
|
126
|
+
this.#render();
|
|
127
|
+
}, this.#interval);
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Stops the spinner.
|
|
132
|
+
*
|
|
133
|
+
* Optionally displays a final message.
|
|
134
|
+
*
|
|
135
|
+
* @param finalText The final text to display after stopping the spinner.
|
|
136
|
+
* @returns The spinner instance.
|
|
137
|
+
*/
|
|
138
|
+
stop(finalText) {
|
|
139
|
+
if (!this.isSpinning) {
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
clearInterval(this.#timer);
|
|
143
|
+
this.#timer = undefined;
|
|
144
|
+
this.#showCursor();
|
|
145
|
+
this.clear();
|
|
146
|
+
this.#unsubscribeFromProcessEvents();
|
|
147
|
+
if (finalText) {
|
|
148
|
+
this.#stream.write(`${finalText}\n`);
|
|
149
|
+
}
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Stops the spinner and displays a success symbol with the message.
|
|
154
|
+
*
|
|
155
|
+
* @param text The success message to display.
|
|
156
|
+
* @returns The spinner instance.
|
|
157
|
+
*/
|
|
158
|
+
success(text) {
|
|
159
|
+
return this.#symbolStop(successIcon, text);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Stops the spinner and displays an error symbol with the message.
|
|
163
|
+
*
|
|
164
|
+
* @param text The error message to display.
|
|
165
|
+
* @returns The spinner instance.
|
|
166
|
+
*/
|
|
167
|
+
error(text) {
|
|
168
|
+
return this.#symbolStop(errorIcon, text);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Stops the spinner and displays a warning symbol with the message.
|
|
172
|
+
*
|
|
173
|
+
* @param text The warning message to display.
|
|
174
|
+
* @returns The spinner instance.
|
|
175
|
+
*/
|
|
176
|
+
warning(text) {
|
|
177
|
+
return this.#symbolStop(warningIcon, text);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Stops the spinner and displays an info symbol with the message.
|
|
181
|
+
*
|
|
182
|
+
* @param text The info message to display.
|
|
183
|
+
* @returns The spinner instance.
|
|
184
|
+
*/
|
|
185
|
+
info(text) {
|
|
186
|
+
return this.#symbolStop(infoIcon, text);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Clears the spinner.
|
|
190
|
+
*
|
|
191
|
+
* @returns The spinner instance.
|
|
192
|
+
*/
|
|
193
|
+
clear() {
|
|
194
|
+
if (!this.#isInteractive) {
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
this.#stream.cursorTo(0);
|
|
198
|
+
for (let index = 0; index < this.#lines; index++) {
|
|
199
|
+
if (index > 0) {
|
|
200
|
+
this.#stream.moveCursor(0, -1);
|
|
201
|
+
}
|
|
202
|
+
this.#stream.clearLine(1);
|
|
203
|
+
}
|
|
204
|
+
this.#lines = 0;
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
// ------------------------------------------------------------------------------
|
|
208
|
+
// Getters and Setters
|
|
209
|
+
// ------------------------------------------------------------------------------
|
|
210
|
+
/**
|
|
211
|
+
* Change the text displayed next to the spinner.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* spinner.text = 'New text';
|
|
215
|
+
*/
|
|
216
|
+
get text() {
|
|
217
|
+
return this.#text;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Change the text displayed next to the spinner.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* spinner.text = 'New text';
|
|
224
|
+
*/
|
|
225
|
+
set text(value) {
|
|
226
|
+
this.#text = value ?? '';
|
|
227
|
+
this.#render();
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Change the spinner color.
|
|
231
|
+
*/
|
|
232
|
+
get color() {
|
|
233
|
+
return this.#color;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Change the spinner color.
|
|
237
|
+
*/
|
|
238
|
+
set color(value) {
|
|
239
|
+
this.#color = value;
|
|
240
|
+
this.#render();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Returns whether the spinner is currently spinning.
|
|
244
|
+
*/
|
|
245
|
+
get isSpinning() {
|
|
246
|
+
return this.#timer !== undefined;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// --------------------------------------------------------------------------------
|
|
250
|
+
// Export
|
|
251
|
+
// --------------------------------------------------------------------------------
|
|
252
|
+
/**
|
|
253
|
+
* Create a new spinner instance.
|
|
254
|
+
*
|
|
255
|
+
* @param options The spinner options.
|
|
256
|
+
* @returns A new spinner instance.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* import createSpinner from 'bananass-utils-console/spinner';
|
|
260
|
+
*
|
|
261
|
+
* const spinner = createSpinner({
|
|
262
|
+
* text: 'Loading…'
|
|
263
|
+
* color: 'yellow',
|
|
264
|
+
* stream: process.stderr,
|
|
265
|
+
* spinner: {
|
|
266
|
+
* frames: ['-', '\\', '|', '/'],
|
|
267
|
+
* interval: 100,
|
|
268
|
+
* },
|
|
269
|
+
* }).start();
|
|
270
|
+
*
|
|
271
|
+
* setTimeout(() => {
|
|
272
|
+
* spinner.success('Success!');
|
|
273
|
+
* }, 2000);
|
|
274
|
+
*/
|
|
275
|
+
export default function createSpinner(options) {
|
|
276
|
+
return new Spinner(options);
|
|
277
|
+
}
|