@nomicfoundation/hardhat-utils 3.0.2 → 3.0.4
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/CHANGELOG.md +13 -0
- package/dist/src/format.d.ts +103 -0
- package/dist/src/format.d.ts.map +1 -0
- package/dist/src/format.js +193 -0
- package/dist/src/format.js.map +1 -0
- package/dist/src/internal/format.d.ts +89 -0
- package/dist/src/internal/format.d.ts.map +1 -0
- package/dist/src/internal/format.js +199 -0
- package/dist/src/internal/format.js.map +1 -0
- package/dist/src/internal/panic-errors.d.ts +2 -0
- package/dist/src/internal/panic-errors.d.ts.map +1 -0
- package/dist/src/internal/panic-errors.js +24 -0
- package/dist/src/internal/panic-errors.js.map +1 -0
- package/dist/src/panic-errors.d.ts +13 -0
- package/dist/src/panic-errors.d.ts.map +1 -1
- package/dist/src/panic-errors.js +14 -23
- package/dist/src/panic-errors.js.map +1 -1
- package/dist/src/spinner.d.ts +46 -0
- package/dist/src/spinner.d.ts.map +1 -0
- package/dist/src/spinner.js +91 -0
- package/dist/src/spinner.js.map +1 -0
- package/package.json +4 -2
- package/src/format.ts +267 -0
- package/src/internal/format.ts +260 -0
- package/src/internal/panic-errors.ts +23 -0
- package/src/panic-errors.ts +14 -24
- package/src/spinner.ts +130 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { TableItemV2 } from "../format.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculate the display width of a string by removing ANSI escape codes.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: This implementation only removes basic ANSI color/style codes and may
|
|
7
|
+
* not handle all escape sequences (e.g., cursor movement, complex control
|
|
8
|
+
* sequences).
|
|
9
|
+
*/
|
|
10
|
+
export function getStringWidth(str: string): number {
|
|
11
|
+
// Remove ANSI escape codes if present
|
|
12
|
+
const stripped = str.replace(/\u001b\[[0-9;]*m/g, "");
|
|
13
|
+
return stripped.length;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Calculates the minimum width needed by each column in the table
|
|
18
|
+
* to fit its content (accounting for ANSI color codes).
|
|
19
|
+
*/
|
|
20
|
+
export function getColumnWidths(items: TableItemV2[]): number[] {
|
|
21
|
+
const columnWidths: number[] = [];
|
|
22
|
+
|
|
23
|
+
for (const item of items) {
|
|
24
|
+
if (item.type === "row" || item.type === "header") {
|
|
25
|
+
item.cells.forEach((cell, i) => {
|
|
26
|
+
columnWidths[i] = Math.max(columnWidths[i] ?? 0, getStringWidth(cell));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return columnWidths;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Calculates the inner width needed to fit the rows and headers
|
|
36
|
+
* (excludes borders, which are added during rendering).
|
|
37
|
+
*
|
|
38
|
+
* Each column is padded by 1 space on each side, and columns are
|
|
39
|
+
* separated by " │ " (3 spaces).
|
|
40
|
+
*/
|
|
41
|
+
export function getContentWidth(columnWidths: number[]): number {
|
|
42
|
+
return (
|
|
43
|
+
columnWidths.reduce((sum, w) => sum + w, 0) +
|
|
44
|
+
(columnWidths.length - 1) * 3 +
|
|
45
|
+
2
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Calculates the inner width needed to fit titles and section headers
|
|
51
|
+
* (excludes borders, which are added during rendering).
|
|
52
|
+
*
|
|
53
|
+
* Each title/header is padded by 1 space on each side.
|
|
54
|
+
* Accounts for ANSI color codes.
|
|
55
|
+
*/
|
|
56
|
+
export function getHeadingWidth(items: TableItemV2[]): number {
|
|
57
|
+
let headingWidth = 0;
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
if (item.type === "section-header" || item.type === "title") {
|
|
60
|
+
headingWidth = Math.max(headingWidth, getStringWidth(item.text) + 2);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return headingWidth;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Calculates the width needed for unused columns when a row/header has fewer
|
|
68
|
+
* cells than the total column count (e.g., if table has 6 columns but row
|
|
69
|
+
* only has 2 cells, calculates space for the remaining 4 columns).
|
|
70
|
+
*/
|
|
71
|
+
export function getUnusedColumnsWidth(
|
|
72
|
+
columnWidths: number[],
|
|
73
|
+
previousCellCount: number,
|
|
74
|
+
): number {
|
|
75
|
+
const remainingWidths = columnWidths.slice(previousCellCount);
|
|
76
|
+
return remainingWidths.reduce((sum, w) => sum + w + 3, 0) - 3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Renders a horizontal rule segment by repeating a character for each column
|
|
81
|
+
* with padding, joined by a separator (e.g., "─────┼─────┼─────").
|
|
82
|
+
*/
|
|
83
|
+
export function renderRuleSegment(
|
|
84
|
+
columnWidths: number[],
|
|
85
|
+
char: string,
|
|
86
|
+
joiner: string,
|
|
87
|
+
): string {
|
|
88
|
+
return columnWidths.map((w) => char.repeat(w + 2)).join(joiner);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Renders a complete horizontal rule with left and right borders
|
|
93
|
+
* (e.g., "╟─────┼─────┼─────╢").
|
|
94
|
+
*/
|
|
95
|
+
export function renderHorizontalRule(
|
|
96
|
+
leftBorder: string,
|
|
97
|
+
columnWidths: number[],
|
|
98
|
+
char: string,
|
|
99
|
+
joiner: string,
|
|
100
|
+
rightBorder: string,
|
|
101
|
+
): string {
|
|
102
|
+
return (
|
|
103
|
+
leftBorder + renderRuleSegment(columnWidths, char, joiner) + rightBorder
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Renders a content line containing cells from either a header or row.
|
|
109
|
+
*
|
|
110
|
+
* Handles two cases:
|
|
111
|
+
* - Full width: When all columns are used, cells are separated by " │ " and
|
|
112
|
+
* line ends with " ║" (e.g., "║ cell1 │ cell2 │ cell3 ║")
|
|
113
|
+
* - Short line: When fewer columns are used, active cells are followed by
|
|
114
|
+
* " │ " and empty space, ending with "║" (e.g., "║ cell1 │ cell2 │ ║")
|
|
115
|
+
*
|
|
116
|
+
* Accounts for ANSI color codes when padding cells.
|
|
117
|
+
*/
|
|
118
|
+
export function renderContentLine(
|
|
119
|
+
cells: string[],
|
|
120
|
+
columnWidths: number[],
|
|
121
|
+
currentCellCount: number,
|
|
122
|
+
): string {
|
|
123
|
+
if (currentCellCount === columnWidths.length) {
|
|
124
|
+
return (
|
|
125
|
+
"║ " +
|
|
126
|
+
cells
|
|
127
|
+
.map((cell, j) => {
|
|
128
|
+
const displayWidth = getStringWidth(cell);
|
|
129
|
+
const actualLength = cell.length;
|
|
130
|
+
// Adjust padding to account for ANSI escape codes
|
|
131
|
+
return cell.padEnd(columnWidths[j] + actualLength - displayWidth);
|
|
132
|
+
})
|
|
133
|
+
.join(" │ ") +
|
|
134
|
+
" ║"
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
const usedWidths = columnWidths.slice(0, currentCellCount);
|
|
138
|
+
const remainingWidth = getUnusedColumnsWidth(
|
|
139
|
+
columnWidths,
|
|
140
|
+
currentCellCount,
|
|
141
|
+
);
|
|
142
|
+
return (
|
|
143
|
+
"║ " +
|
|
144
|
+
cells
|
|
145
|
+
.map((cell, j) => {
|
|
146
|
+
const displayWidth = getStringWidth(cell);
|
|
147
|
+
const actualLength = cell.length;
|
|
148
|
+
// Adjust padding to account for ANSI escape codes
|
|
149
|
+
return cell.padEnd(usedWidths[j] + actualLength - displayWidth);
|
|
150
|
+
})
|
|
151
|
+
.join(" │ ") +
|
|
152
|
+
" │ " +
|
|
153
|
+
" ".repeat(remainingWidth + 1) +
|
|
154
|
+
"║"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Renders the horizontal rule that appears above a header row.
|
|
161
|
+
*
|
|
162
|
+
* Handles three cases:
|
|
163
|
+
* - Transition rule: When going from more columns to fewer, shows ┴ marks
|
|
164
|
+
* where columns collapse (e.g., "╟───┼───┼───┴───┴───╢")
|
|
165
|
+
* - Full width: When header uses all columns (e.g., "╟───┬───┬───╢" or "╟───┼───┼───╢")
|
|
166
|
+
* - Short header: When header uses fewer columns than max (e.g., "╟───┬─────────╢")
|
|
167
|
+
*
|
|
168
|
+
* The innerJoiner determines the separator character: ┬ after section-header, ┼ otherwise.
|
|
169
|
+
*/
|
|
170
|
+
export function renderHeaderOpen(
|
|
171
|
+
columnWidths: number[],
|
|
172
|
+
currentCellCount: number,
|
|
173
|
+
innerJoiner: string,
|
|
174
|
+
needsTransition: boolean,
|
|
175
|
+
): string {
|
|
176
|
+
if (needsTransition) {
|
|
177
|
+
const usedWidths = columnWidths.slice(0, currentCellCount);
|
|
178
|
+
const collapsingWidths = columnWidths.slice(currentCellCount);
|
|
179
|
+
return (
|
|
180
|
+
"╟" +
|
|
181
|
+
renderRuleSegment(usedWidths, "─", "┼") +
|
|
182
|
+
"┼" +
|
|
183
|
+
renderRuleSegment(collapsingWidths, "─", "┴") +
|
|
184
|
+
"╢"
|
|
185
|
+
);
|
|
186
|
+
} else if (currentCellCount === columnWidths.length) {
|
|
187
|
+
return renderHorizontalRule("╟", columnWidths, "─", innerJoiner, "╢");
|
|
188
|
+
} else {
|
|
189
|
+
const usedWidths = columnWidths.slice(0, currentCellCount);
|
|
190
|
+
const remainingWidth = getUnusedColumnsWidth(
|
|
191
|
+
columnWidths,
|
|
192
|
+
currentCellCount,
|
|
193
|
+
);
|
|
194
|
+
return (
|
|
195
|
+
"╟" +
|
|
196
|
+
renderRuleSegment(usedWidths, "─", innerJoiner) +
|
|
197
|
+
innerJoiner +
|
|
198
|
+
"─".repeat(remainingWidth + 2) +
|
|
199
|
+
"╢"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Renders the horizontal rule that appears above a row.
|
|
206
|
+
*
|
|
207
|
+
* Handles two cases:
|
|
208
|
+
* - Full width: When row uses all columns, renders with ┼ joiners and
|
|
209
|
+
* ends with ╢ (e.g., "╟───┼───┼───╢")
|
|
210
|
+
* - Short row: When row uses fewer columns, renders active columns with
|
|
211
|
+
* ┼ joiners, ends with ┤, then fills remaining space and ends with ║
|
|
212
|
+
* (e.g., "╟───┼───┤ ║")
|
|
213
|
+
*/
|
|
214
|
+
export function renderRowSeparator(
|
|
215
|
+
columnWidths: number[],
|
|
216
|
+
currentCellCount: number,
|
|
217
|
+
): string {
|
|
218
|
+
if (currentCellCount === columnWidths.length) {
|
|
219
|
+
return renderHorizontalRule("╟", columnWidths, "─", "┼", "╢");
|
|
220
|
+
} else {
|
|
221
|
+
// Short row - ends with ┤ instead of ╢
|
|
222
|
+
const usedWidths = columnWidths.slice(0, currentCellCount);
|
|
223
|
+
const remainingWidth = getUnusedColumnsWidth(
|
|
224
|
+
columnWidths,
|
|
225
|
+
currentCellCount,
|
|
226
|
+
);
|
|
227
|
+
return (
|
|
228
|
+
"╟" +
|
|
229
|
+
renderRuleSegment(usedWidths, "─", "┼") +
|
|
230
|
+
"┤" +
|
|
231
|
+
" ".repeat(remainingWidth + 2) +
|
|
232
|
+
"║"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Renders the section's bottom border, placing ╧ marks under column
|
|
239
|
+
* separators where the last row/header had cells (e.g., if the last row
|
|
240
|
+
* looked like "║ a │ b │ ║", the bottom border would be
|
|
241
|
+
* "╚═══╧═══╧═══════╝").
|
|
242
|
+
*/
|
|
243
|
+
export function renderSectionClose(
|
|
244
|
+
columnWidths: number[],
|
|
245
|
+
previousCellCount: number,
|
|
246
|
+
): string {
|
|
247
|
+
if (previousCellCount === columnWidths.length) {
|
|
248
|
+
return renderHorizontalRule("╚", columnWidths, "═", "╧", "╝");
|
|
249
|
+
} else {
|
|
250
|
+
const usedWidths = columnWidths.slice(0, previousCellCount);
|
|
251
|
+
const unusedWidth = getUnusedColumnsWidth(columnWidths, previousCellCount);
|
|
252
|
+
return (
|
|
253
|
+
"╚" +
|
|
254
|
+
renderRuleSegment(usedWidths, "═", "╧") +
|
|
255
|
+
"╧" +
|
|
256
|
+
renderRuleSegment([unusedWidth], "═", "") +
|
|
257
|
+
"╝"
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function panicErrorCodeToReason(errorCode: bigint): string | undefined {
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- we are only covering some of the integer range
|
|
3
|
+
switch (errorCode) {
|
|
4
|
+
case 0x1n:
|
|
5
|
+
return "Assertion error";
|
|
6
|
+
case 0x11n:
|
|
7
|
+
return "Arithmetic operation overflowed outside of an unchecked block";
|
|
8
|
+
case 0x12n:
|
|
9
|
+
return "Division or modulo division by zero";
|
|
10
|
+
case 0x21n:
|
|
11
|
+
return "Tried to convert a value into an enum, but the value was too big or negative";
|
|
12
|
+
case 0x22n:
|
|
13
|
+
return "Incorrectly encoded storage byte array";
|
|
14
|
+
case 0x31n:
|
|
15
|
+
return ".pop() was called on an empty array";
|
|
16
|
+
case 0x32n:
|
|
17
|
+
return "Array accessed at an out-of-bounds or negative index";
|
|
18
|
+
case 0x41n:
|
|
19
|
+
return "Too much memory was allocated, or an array was created that is too large";
|
|
20
|
+
case 0x51n:
|
|
21
|
+
return "Called a zero-initialized variable of internal function type";
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/panic-errors.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { numberToHexString } from "./hex.js";
|
|
2
|
+
import { panicErrorCodeToReason } from "./internal/panic-errors.js";
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Converts a Solidity panic error code into a human-readable revert message.
|
|
6
|
+
*
|
|
7
|
+
* Solidity defines a set of standardized panic codes (0x01, 0x11, etc.)
|
|
8
|
+
* that represent specific runtime errors (e.g. arithmetic overflow).
|
|
9
|
+
* This function looks up the corresponding reason string and formats it
|
|
10
|
+
* into a message similar to what clients like Hardhat or ethers.js display.
|
|
11
|
+
*
|
|
12
|
+
* @param errorCode The panic error code returned by the EVM as a bigint.
|
|
13
|
+
* @returns A formatted message string:
|
|
14
|
+
* - `"reverted with panic code <hex> (<reason>)"` if the code is recognized.
|
|
15
|
+
* - `"reverted with unknown panic code <hex>"` if the code is not recognized.
|
|
16
|
+
*/
|
|
3
17
|
export function panicErrorCodeToMessage(errorCode: bigint): string {
|
|
4
18
|
const reason = panicErrorCodeToReason(errorCode);
|
|
5
19
|
|
|
@@ -9,27 +23,3 @@ export function panicErrorCodeToMessage(errorCode: bigint): string {
|
|
|
9
23
|
|
|
10
24
|
return `reverted with unknown panic code ${numberToHexString(errorCode)}`;
|
|
11
25
|
}
|
|
12
|
-
|
|
13
|
-
function panicErrorCodeToReason(errorCode: bigint): string | undefined {
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- we are only covering some of the integer range
|
|
15
|
-
switch (errorCode) {
|
|
16
|
-
case 0x1n:
|
|
17
|
-
return "Assertion error";
|
|
18
|
-
case 0x11n:
|
|
19
|
-
return "Arithmetic operation overflowed outside of an unchecked block";
|
|
20
|
-
case 0x12n:
|
|
21
|
-
return "Division or modulo division by zero";
|
|
22
|
-
case 0x21n:
|
|
23
|
-
return "Tried to convert a value into an enum, but the value was too big or negative";
|
|
24
|
-
case 0x22n:
|
|
25
|
-
return "Incorrectly encoded storage byte array";
|
|
26
|
-
case 0x31n:
|
|
27
|
-
return ".pop() was called on an empty array";
|
|
28
|
-
case 0x32n:
|
|
29
|
-
return "Array accessed at an out-of-bounds or negative index";
|
|
30
|
-
case 0x41n:
|
|
31
|
-
return "Too much memory was allocated, or an array was created that is too large";
|
|
32
|
-
case 0x51n:
|
|
33
|
-
return "Called a zero-initialized variable of internal function type";
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/spinner.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
2
|
+
export const FRAME_INTERVAL_MS = 80;
|
|
3
|
+
|
|
4
|
+
export interface ISpinner {
|
|
5
|
+
readonly isEnabled: boolean;
|
|
6
|
+
start(): void;
|
|
7
|
+
stop(): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Optional settings when creating a spinner.
|
|
12
|
+
*/
|
|
13
|
+
export interface SpinnerOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Text shown next to the spinner.
|
|
16
|
+
*/
|
|
17
|
+
text?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Stream used to write frames.
|
|
21
|
+
*/
|
|
22
|
+
stream?: NodeJS.WriteStream;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether the spinner is enabled.
|
|
26
|
+
*/
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Spinner that writes frames to a stream.
|
|
32
|
+
*/
|
|
33
|
+
class Spinner implements ISpinner {
|
|
34
|
+
public readonly isEnabled: boolean;
|
|
35
|
+
readonly #text: string;
|
|
36
|
+
#interval: NodeJS.Timeout | null = null;
|
|
37
|
+
readonly #stream: NodeJS.WriteStream;
|
|
38
|
+
|
|
39
|
+
constructor(options: Required<SpinnerOptions>) {
|
|
40
|
+
this.isEnabled = options.enabled;
|
|
41
|
+
this.#stream = options.stream;
|
|
42
|
+
this.#text = options.text;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Begin rendering frames when enabled.
|
|
46
|
+
*/
|
|
47
|
+
public start(): void {
|
|
48
|
+
if (!this.isEnabled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.#stopAnimation();
|
|
53
|
+
let frameIndex = 0;
|
|
54
|
+
|
|
55
|
+
this.#interval = setInterval(() => {
|
|
56
|
+
this.#render(FRAMES[frameIndex]);
|
|
57
|
+
frameIndex = (frameIndex + 1) % FRAMES.length;
|
|
58
|
+
}, FRAME_INTERVAL_MS);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Stop the spinner without printing a final line.
|
|
63
|
+
*/
|
|
64
|
+
public stop(): void {
|
|
65
|
+
this.#stopAnimation();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#clearLine(): void {
|
|
69
|
+
this.#stream.clearLine(0);
|
|
70
|
+
this.#stream.cursorTo(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#render(frame: string): void {
|
|
74
|
+
if (!this.isEnabled) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.#clearLine();
|
|
78
|
+
this.#stream.write(`${frame} ${this.#text}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#stopAnimation(): void {
|
|
82
|
+
if (this.#interval === null) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
clearInterval(this.#interval);
|
|
87
|
+
this.#interval = null;
|
|
88
|
+
|
|
89
|
+
if (this.isEnabled) {
|
|
90
|
+
this.#clearLine();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a spinner instance.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const spinner = createSpinner({ text: "Compiling…" });
|
|
101
|
+
* spinner.start();
|
|
102
|
+
*
|
|
103
|
+
* try {
|
|
104
|
+
* await compileContracts();
|
|
105
|
+
* spinner.stop();
|
|
106
|
+
* console.log("Compiled 12 contracts");
|
|
107
|
+
* } catch (error) {
|
|
108
|
+
* spinner.stop();
|
|
109
|
+
* console.error("Compilation failed");
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @param options Optional spinner configuration.
|
|
114
|
+
* @returns {Spinner} A spinner instance.
|
|
115
|
+
*/
|
|
116
|
+
export function createSpinner(options: SpinnerOptions = {}): ISpinner {
|
|
117
|
+
const stream = options.stream ?? process.stdout;
|
|
118
|
+
|
|
119
|
+
const enabled =
|
|
120
|
+
stream.isTTY === true &&
|
|
121
|
+
process.env.TERM !== "dumb" &&
|
|
122
|
+
(options.enabled ?? true);
|
|
123
|
+
|
|
124
|
+
const text = options.text ?? "";
|
|
125
|
+
return new Spinner({
|
|
126
|
+
enabled,
|
|
127
|
+
stream,
|
|
128
|
+
text,
|
|
129
|
+
});
|
|
130
|
+
}
|