kodevu 0.1.18 → 0.1.20
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/package.json +1 -1
- package/src/progress-ui.js +18 -255
package/package.json
CHANGED
package/src/progress-ui.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import readline from "node:readline";
|
|
2
|
-
|
|
3
|
-
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
4
|
-
const ELLIPSIS = "...";
|
|
5
|
-
|
|
6
1
|
function clampProgress(value) {
|
|
7
2
|
if (!Number.isFinite(value)) {
|
|
8
3
|
return 0;
|
|
@@ -11,78 +6,6 @@ function clampProgress(value) {
|
|
|
11
6
|
return Math.max(0, Math.min(1, value));
|
|
12
7
|
}
|
|
13
8
|
|
|
14
|
-
function getCharacterWidth(character) {
|
|
15
|
-
const codePoint = character.codePointAt(0);
|
|
16
|
-
|
|
17
|
-
if (!codePoint || codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
18
|
-
return 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
codePoint >= 0x1100 &&
|
|
23
|
-
(
|
|
24
|
-
codePoint <= 0x115f ||
|
|
25
|
-
codePoint === 0x2329 ||
|
|
26
|
-
codePoint === 0x232a ||
|
|
27
|
-
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
28
|
-
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
29
|
-
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
30
|
-
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
31
|
-
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
32
|
-
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
33
|
-
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
34
|
-
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
35
|
-
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
36
|
-
(codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
|
37
|
-
)
|
|
38
|
-
) {
|
|
39
|
-
return 2;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return 1;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getDisplayWidth(text) {
|
|
46
|
-
let width = 0;
|
|
47
|
-
|
|
48
|
-
for (const character of text) {
|
|
49
|
-
width += getCharacterWidth(character);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return width;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function truncateLine(line, maxWidth) {
|
|
56
|
-
if (!Number.isFinite(maxWidth) || maxWidth <= 0) {
|
|
57
|
-
return "";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (getDisplayWidth(line) <= maxWidth) {
|
|
61
|
-
return line;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (maxWidth <= 3) {
|
|
65
|
-
return ".".repeat(maxWidth);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const targetWidth = maxWidth - getDisplayWidth(ELLIPSIS);
|
|
69
|
-
let result = "";
|
|
70
|
-
let width = 0;
|
|
71
|
-
|
|
72
|
-
for (const character of line) {
|
|
73
|
-
const nextWidth = width + getCharacterWidth(character);
|
|
74
|
-
|
|
75
|
-
if (nextWidth > targetWidth) {
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
result += character;
|
|
80
|
-
width = nextWidth;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return `${result}${ELLIPSIS}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
9
|
class ProgressItem {
|
|
87
10
|
constructor(display, label) {
|
|
88
11
|
this.display = display;
|
|
@@ -90,20 +13,13 @@ class ProgressItem {
|
|
|
90
13
|
this.progress = 0;
|
|
91
14
|
this.stage = "";
|
|
92
15
|
this.active = false;
|
|
93
|
-
this.
|
|
16
|
+
this.lastStatusLine = "";
|
|
94
17
|
}
|
|
95
18
|
|
|
96
19
|
start(stage = "starting") {
|
|
97
20
|
this.active = true;
|
|
98
21
|
this.stage = stage;
|
|
99
|
-
|
|
100
|
-
if (!this.display.enabled) {
|
|
101
|
-
this.writeFallback(`... ${this.label}: ${stage}`);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
this.display.start();
|
|
106
|
-
this.display.activate(this);
|
|
22
|
+
this.writeStatus();
|
|
107
23
|
}
|
|
108
24
|
|
|
109
25
|
update(progress, stage) {
|
|
@@ -113,16 +29,11 @@ class ProgressItem {
|
|
|
113
29
|
this.stage = stage;
|
|
114
30
|
}
|
|
115
31
|
|
|
116
|
-
|
|
117
|
-
this.writeFallback(`... ${this.label}: ${this.stage}`);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
this.display.activate(this);
|
|
32
|
+
this.writeStatus();
|
|
122
33
|
}
|
|
123
34
|
|
|
124
35
|
log(message) {
|
|
125
|
-
this.display.
|
|
36
|
+
this.display.writeLine(message);
|
|
126
37
|
}
|
|
127
38
|
|
|
128
39
|
succeed(message) {
|
|
@@ -136,184 +47,36 @@ class ProgressItem {
|
|
|
136
47
|
finish(prefix, progress, message) {
|
|
137
48
|
this.progress = clampProgress(progress);
|
|
138
49
|
this.active = false;
|
|
139
|
-
this.display.
|
|
140
|
-
this.display.writeStaticLine(this.buildFinalLine(prefix, message));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
renderLine(frameIndex) {
|
|
144
|
-
return this.buildStatusLine(SPINNER_FRAMES[frameIndex], this.stage);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
buildFinalLine(prefix, message) {
|
|
148
|
-
return this.buildStatusLine(prefix, message);
|
|
50
|
+
this.display.writeLine(`${prefix} ${message}`);
|
|
149
51
|
}
|
|
150
52
|
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
|
|
154
|
-
const suffixText = suffix ? ` | ${suffix}` : "";
|
|
155
|
-
return truncateLine(`${prefix} ${pct}% ${this.label}${suffixText}`, availableWidth);
|
|
156
|
-
}
|
|
53
|
+
writeStatus() {
|
|
54
|
+
const line = this.buildStatusLine();
|
|
157
55
|
|
|
158
|
-
|
|
159
|
-
if (line === this.lastFallbackLine) {
|
|
56
|
+
if (line === this.lastStatusLine) {
|
|
160
57
|
return;
|
|
161
58
|
}
|
|
162
59
|
|
|
163
|
-
this.
|
|
164
|
-
this.display.
|
|
60
|
+
this.lastStatusLine = line;
|
|
61
|
+
this.display.writeLine(line);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
buildStatusLine() {
|
|
65
|
+
const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
|
|
66
|
+
return `[progress] ${pct}% ${this.label}${this.stage ? ` | ${this.stage}` : ""}`;
|
|
165
67
|
}
|
|
166
68
|
}
|
|
167
69
|
|
|
168
70
|
export class ProgressDisplay {
|
|
169
71
|
constructor(options = {}) {
|
|
170
72
|
this.stream = options.stream || process.stdout;
|
|
171
|
-
this.enabled = Boolean(this.stream.isTTY);
|
|
172
|
-
this.frameIndex = 0;
|
|
173
|
-
this.timer = null;
|
|
174
|
-
this.items = [];
|
|
175
|
-
this.currentItem = null;
|
|
176
|
-
this.statusVisible = false;
|
|
177
|
-
this.lastStatusWidth = 0;
|
|
178
|
-
this.resizeAttached = false;
|
|
179
|
-
this.handleResize = this.handleResize.bind(this);
|
|
180
73
|
}
|
|
181
74
|
|
|
182
75
|
createItem(label) {
|
|
183
|
-
|
|
184
|
-
this.items.push(item);
|
|
185
|
-
return item;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
start() {
|
|
189
|
-
if (!this.enabled || this.timer) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
this.attachResizeHandler();
|
|
194
|
-
this.timer = setInterval(() => {
|
|
195
|
-
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
196
|
-
this.render();
|
|
197
|
-
}, 120);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
activate(item) {
|
|
201
|
-
this.currentItem = item;
|
|
202
|
-
this.render();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
deactivate(item) {
|
|
206
|
-
if (this.currentItem === item) {
|
|
207
|
-
this.currentItem = this.items.findLast((candidate) => candidate.active) || null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
this.stopIfIdle();
|
|
211
|
-
this.clearStatusLine();
|
|
212
|
-
|
|
213
|
-
if (this.currentItem) {
|
|
214
|
-
this.render();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
log(message) {
|
|
219
|
-
if (!this.enabled) {
|
|
220
|
-
this.stream.write(`${message}\n`);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
this.clearStatusLine();
|
|
225
|
-
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
226
|
-
this.render();
|
|
76
|
+
return new ProgressItem(this, label);
|
|
227
77
|
}
|
|
228
78
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.stream.write(`${message}\n`);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
this.clearStatusLine();
|
|
236
|
-
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
237
|
-
this.render();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
render() {
|
|
241
|
-
if (!this.enabled || !this.currentItem?.active) {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
this.clearStatusLine();
|
|
246
|
-
const line = this.currentItem.renderLine(this.frameIndex);
|
|
247
|
-
this.stream.write(line);
|
|
248
|
-
this.statusVisible = true;
|
|
249
|
-
this.lastStatusWidth = getDisplayWidth(line);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
clearStatusLine() {
|
|
253
|
-
if (!this.enabled || !this.statusVisible) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const rows = Math.max(1, Math.ceil(this.lastStatusWidth / Math.max(this.stream.columns || 1, 1)));
|
|
258
|
-
|
|
259
|
-
readline.moveCursor(this.stream, 0, -Math.max(rows - 1, 0));
|
|
260
|
-
|
|
261
|
-
for (let index = 0; index < rows; index += 1) {
|
|
262
|
-
readline.clearLine(this.stream, 0);
|
|
263
|
-
readline.cursorTo(this.stream, 0);
|
|
264
|
-
|
|
265
|
-
if (index < rows - 1) {
|
|
266
|
-
readline.moveCursor(this.stream, 0, 1);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
readline.moveCursor(this.stream, 0, -Math.max(rows - 1, 0));
|
|
271
|
-
readline.cursorTo(this.stream, 0);
|
|
272
|
-
this.statusVisible = false;
|
|
273
|
-
this.lastStatusWidth = 0;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
stopIfIdle() {
|
|
277
|
-
if (this.items.some((item) => item.active)) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.timer) {
|
|
282
|
-
clearInterval(this.timer);
|
|
283
|
-
this.timer = null;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
this.detachResizeHandler();
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
getAvailableWidth() {
|
|
290
|
-
const columns = this.stream.columns || 80;
|
|
291
|
-
return Math.max(columns - 1, 1);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
handleResize() {
|
|
295
|
-
if (!this.enabled) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
this.render();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
attachResizeHandler() {
|
|
303
|
-
if (typeof this.stream.on !== "function" || this.resizeAttached) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
this.stream.on("resize", this.handleResize);
|
|
308
|
-
this.resizeAttached = true;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
detachResizeHandler() {
|
|
312
|
-
if (typeof this.stream.off !== "function" || !this.resizeAttached) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
this.stream.off("resize", this.handleResize);
|
|
317
|
-
this.resizeAttached = false;
|
|
79
|
+
writeLine(message) {
|
|
80
|
+
this.stream.write(`${message}\n`);
|
|
318
81
|
}
|
|
319
82
|
}
|