kodevu 0.1.16 → 0.1.18
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 +109 -75
- package/src/review-runner.js +8 -17
package/package.json
CHANGED
package/src/progress-ui.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
|
|
3
3
|
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
4
|
-
const
|
|
4
|
+
const ELLIPSIS = "...";
|
|
5
5
|
|
|
6
6
|
function clampProgress(value) {
|
|
7
7
|
if (!Number.isFinite(value)) {
|
|
@@ -11,19 +11,45 @@ function clampProgress(value) {
|
|
|
11
11
|
return Math.max(0, Math.min(1, value));
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
const filled = Math.max(Math.round(safeProgress * width), safeProgress > 0 ? 1 : 0);
|
|
14
|
+
function getCharacterWidth(character) {
|
|
15
|
+
const codePoint = character.codePointAt(0);
|
|
17
16
|
|
|
18
|
-
if (
|
|
19
|
-
return
|
|
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;
|
|
20
40
|
}
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getDisplayWidth(text) {
|
|
46
|
+
let width = 0;
|
|
47
|
+
|
|
48
|
+
for (const character of text) {
|
|
49
|
+
width += getCharacterWidth(character);
|
|
24
50
|
}
|
|
25
51
|
|
|
26
|
-
return
|
|
52
|
+
return width;
|
|
27
53
|
}
|
|
28
54
|
|
|
29
55
|
function truncateLine(line, maxWidth) {
|
|
@@ -31,7 +57,7 @@ function truncateLine(line, maxWidth) {
|
|
|
31
57
|
return "";
|
|
32
58
|
}
|
|
33
59
|
|
|
34
|
-
if (line
|
|
60
|
+
if (getDisplayWidth(line) <= maxWidth) {
|
|
35
61
|
return line;
|
|
36
62
|
}
|
|
37
63
|
|
|
@@ -39,14 +65,28 @@ function truncateLine(line, maxWidth) {
|
|
|
39
65
|
return ".".repeat(maxWidth);
|
|
40
66
|
}
|
|
41
67
|
|
|
42
|
-
|
|
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}`;
|
|
43
84
|
}
|
|
44
85
|
|
|
45
86
|
class ProgressItem {
|
|
46
|
-
constructor(display, label
|
|
87
|
+
constructor(display, label) {
|
|
47
88
|
this.display = display;
|
|
48
89
|
this.label = label;
|
|
49
|
-
this.barWidth = options.barWidth || DEFAULT_BAR_WIDTH;
|
|
50
90
|
this.progress = 0;
|
|
51
91
|
this.stage = "";
|
|
52
92
|
this.active = false;
|
|
@@ -54,8 +94,8 @@ class ProgressItem {
|
|
|
54
94
|
}
|
|
55
95
|
|
|
56
96
|
start(stage = "starting") {
|
|
57
|
-
this.stage = stage;
|
|
58
97
|
this.active = true;
|
|
98
|
+
this.stage = stage;
|
|
59
99
|
|
|
60
100
|
if (!this.display.enabled) {
|
|
61
101
|
this.writeFallback(`... ${this.label}: ${stage}`);
|
|
@@ -63,7 +103,7 @@ class ProgressItem {
|
|
|
63
103
|
}
|
|
64
104
|
|
|
65
105
|
this.display.start();
|
|
66
|
-
this.display.
|
|
106
|
+
this.display.activate(this);
|
|
67
107
|
}
|
|
68
108
|
|
|
69
109
|
update(progress, stage) {
|
|
@@ -78,7 +118,7 @@ class ProgressItem {
|
|
|
78
118
|
return;
|
|
79
119
|
}
|
|
80
120
|
|
|
81
|
-
this.display.
|
|
121
|
+
this.display.activate(this);
|
|
82
122
|
}
|
|
83
123
|
|
|
84
124
|
log(message) {
|
|
@@ -96,46 +136,23 @@ class ProgressItem {
|
|
|
96
136
|
finish(prefix, progress, message) {
|
|
97
137
|
this.progress = clampProgress(progress);
|
|
98
138
|
this.active = false;
|
|
99
|
-
this.display.
|
|
100
|
-
this.display.writeStaticLine(this.
|
|
139
|
+
this.display.deactivate(this);
|
|
140
|
+
this.display.writeStaticLine(this.buildFinalLine(prefix, message));
|
|
101
141
|
}
|
|
102
142
|
|
|
103
143
|
renderLine(frameIndex) {
|
|
104
|
-
|
|
105
|
-
return this.buildLine(spinner, this.stage);
|
|
144
|
+
return this.buildStatusLine(SPINNER_FRAMES[frameIndex], this.stage);
|
|
106
145
|
}
|
|
107
146
|
|
|
108
|
-
|
|
147
|
+
buildFinalLine(prefix, message) {
|
|
148
|
+
return this.buildStatusLine(prefix, message);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
buildStatusLine(prefix, suffix) {
|
|
109
152
|
const availableWidth = this.display.getAvailableWidth();
|
|
110
153
|
const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (availableWidth < 10) {
|
|
115
|
-
return truncateLine(`${prefix} ${pct}%`, availableWidth);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const fullReservedWidth = prefix.length + this.label.length + pct.length + suffixText.length + 4;
|
|
119
|
-
const fullBarWidth = availableWidth - fullReservedWidth;
|
|
120
|
-
|
|
121
|
-
if (fullBarWidth >= 4) {
|
|
122
|
-
return truncateLine(
|
|
123
|
-
`${parts.join(" ")} ${buildBar(this.progress, Math.min(this.barWidth, fullBarWidth))} ${pct}%${suffixText}`,
|
|
124
|
-
availableWidth
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const compactReservedWidth = prefix.length + pct.length + suffixText.length + 4;
|
|
129
|
-
const compactBarWidth = availableWidth - compactReservedWidth;
|
|
130
|
-
|
|
131
|
-
if (compactBarWidth >= 4) {
|
|
132
|
-
return truncateLine(
|
|
133
|
-
`${prefix} ${buildBar(this.progress, compactBarWidth)} ${pct}%${suffixText}`,
|
|
134
|
-
availableWidth
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return truncateLine(`${prefix} ${pct}%${suffixText}`, availableWidth);
|
|
154
|
+
const suffixText = suffix ? ` | ${suffix}` : "";
|
|
155
|
+
return truncateLine(`${prefix} ${pct}% ${this.label}${suffixText}`, availableWidth);
|
|
139
156
|
}
|
|
140
157
|
|
|
141
158
|
writeFallback(line) {
|
|
@@ -155,12 +172,15 @@ export class ProgressDisplay {
|
|
|
155
172
|
this.frameIndex = 0;
|
|
156
173
|
this.timer = null;
|
|
157
174
|
this.items = [];
|
|
158
|
-
this.
|
|
175
|
+
this.currentItem = null;
|
|
176
|
+
this.statusVisible = false;
|
|
177
|
+
this.lastStatusWidth = 0;
|
|
178
|
+
this.resizeAttached = false;
|
|
159
179
|
this.handleResize = this.handleResize.bind(this);
|
|
160
180
|
}
|
|
161
181
|
|
|
162
|
-
createItem(label
|
|
163
|
-
const item = new ProgressItem(this, label
|
|
182
|
+
createItem(label) {
|
|
183
|
+
const item = new ProgressItem(this, label);
|
|
164
184
|
this.items.push(item);
|
|
165
185
|
return item;
|
|
166
186
|
}
|
|
@@ -171,21 +191,38 @@ export class ProgressDisplay {
|
|
|
171
191
|
}
|
|
172
192
|
|
|
173
193
|
this.attachResizeHandler();
|
|
174
|
-
|
|
175
194
|
this.timer = setInterval(() => {
|
|
176
195
|
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
177
196
|
this.render();
|
|
178
197
|
}, 120);
|
|
179
198
|
}
|
|
180
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
|
+
|
|
181
218
|
log(message) {
|
|
182
219
|
if (!this.enabled) {
|
|
183
220
|
this.stream.write(`${message}\n`);
|
|
184
221
|
return;
|
|
185
222
|
}
|
|
186
223
|
|
|
187
|
-
this.
|
|
188
|
-
this.stream.write(`${message}\n`);
|
|
224
|
+
this.clearStatusLine();
|
|
225
|
+
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
189
226
|
this.render();
|
|
190
227
|
}
|
|
191
228
|
|
|
@@ -195,48 +232,45 @@ export class ProgressDisplay {
|
|
|
195
232
|
return;
|
|
196
233
|
}
|
|
197
234
|
|
|
198
|
-
this.
|
|
235
|
+
this.clearStatusLine();
|
|
199
236
|
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
200
237
|
this.render();
|
|
201
238
|
}
|
|
202
239
|
|
|
203
240
|
render() {
|
|
204
|
-
if (!this.enabled) {
|
|
241
|
+
if (!this.enabled || !this.currentItem?.active) {
|
|
205
242
|
return;
|
|
206
243
|
}
|
|
207
244
|
|
|
208
|
-
|
|
209
|
-
this.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const lines = activeItems.map((item) => item.renderLine(this.frameIndex));
|
|
217
|
-
this.stream.write(lines.join("\n"));
|
|
218
|
-
this.renderedLineCount = lines.length;
|
|
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);
|
|
219
250
|
}
|
|
220
251
|
|
|
221
|
-
|
|
222
|
-
if (!this.enabled || this.
|
|
252
|
+
clearStatusLine() {
|
|
253
|
+
if (!this.enabled || !this.statusVisible) {
|
|
223
254
|
return;
|
|
224
255
|
}
|
|
225
256
|
|
|
226
|
-
|
|
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));
|
|
227
260
|
|
|
228
|
-
for (let index = 0; index <
|
|
261
|
+
for (let index = 0; index < rows; index += 1) {
|
|
229
262
|
readline.clearLine(this.stream, 0);
|
|
230
263
|
readline.cursorTo(this.stream, 0);
|
|
231
264
|
|
|
232
|
-
if (index <
|
|
265
|
+
if (index < rows - 1) {
|
|
233
266
|
readline.moveCursor(this.stream, 0, 1);
|
|
234
267
|
}
|
|
235
268
|
}
|
|
236
269
|
|
|
237
|
-
readline.moveCursor(this.stream, 0, -Math.max(
|
|
270
|
+
readline.moveCursor(this.stream, 0, -Math.max(rows - 1, 0));
|
|
238
271
|
readline.cursorTo(this.stream, 0);
|
|
239
|
-
this.
|
|
272
|
+
this.statusVisible = false;
|
|
273
|
+
this.lastStatusWidth = 0;
|
|
240
274
|
}
|
|
241
275
|
|
|
242
276
|
stopIfIdle() {
|
package/src/review-runner.js
CHANGED
|
@@ -592,24 +592,16 @@ export async function runReviewCycle(config) {
|
|
|
592
592
|
|
|
593
593
|
console.log(`Reviewing ${backend.displayName} ${backend.changeName}s ${formatChangeList(backend, changeIdsToReview)}`);
|
|
594
594
|
const progressDisplay = new ProgressDisplay();
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
{ barWidth: 30 }
|
|
598
|
-
);
|
|
599
|
-
overallProgress.start("0/" + changeIdsToReview.length + " completed");
|
|
595
|
+
const progress = progressDisplay.createItem(`${backend.displayName} ${backend.changeName} batch`);
|
|
596
|
+
progress.start("0/" + changeIdsToReview.length + " completed");
|
|
600
597
|
|
|
601
598
|
for (const [index, changeId] of changeIdsToReview.entries()) {
|
|
602
599
|
debugLog(config, `Starting review for ${backend.formatChangeId(changeId)}.`);
|
|
603
600
|
const displayId = backend.formatChangeId(changeId);
|
|
604
|
-
|
|
605
|
-
`${backend.displayName} ${backend.changeName} ${displayId} (${index + 1}/${changeIdsToReview.length})`
|
|
606
|
-
);
|
|
607
|
-
progress.start("queued");
|
|
608
|
-
updateOverallProgress(overallProgress, index, changeIdsToReview.length, 0, `starting ${displayId}`);
|
|
601
|
+
updateOverallProgress(progress, index, changeIdsToReview.length, 0, `starting ${displayId}`);
|
|
609
602
|
|
|
610
603
|
const syncOverallProgress = (fraction, stage) => {
|
|
611
|
-
progress.
|
|
612
|
-
updateOverallProgress(overallProgress, index, changeIdsToReview.length, fraction, `${displayId} | ${stage}`);
|
|
604
|
+
updateOverallProgress(progress, index, changeIdsToReview.length, fraction, `${displayId} | ${stage}`);
|
|
613
605
|
};
|
|
614
606
|
|
|
615
607
|
let result;
|
|
@@ -618,8 +610,7 @@ export async function runReviewCycle(config) {
|
|
|
618
610
|
result = await reviewChange(config, backend, targetInfo, changeId, { update: syncOverallProgress, log: (message) => progress.log(message) });
|
|
619
611
|
syncOverallProgress(0.94, "saving checkpoint");
|
|
620
612
|
} catch (error) {
|
|
621
|
-
progress.fail(`failed at ${displayId}`);
|
|
622
|
-
overallProgress.fail(`failed at ${displayId} (${index}/${changeIdsToReview.length} completed)`);
|
|
613
|
+
progress.fail(`failed at ${displayId} (${index}/${changeIdsToReview.length} completed)`);
|
|
623
614
|
throw error;
|
|
624
615
|
}
|
|
625
616
|
|
|
@@ -631,11 +622,11 @@ export async function runReviewCycle(config) {
|
|
|
631
622
|
await saveState(config.stateFilePath, updateProjectState(stateFile, targetInfo, nextProjectState));
|
|
632
623
|
stateFile.projects[targetInfo.stateKey] = nextProjectState;
|
|
633
624
|
debugLog(config, `Saved checkpoint for ${backend.formatChangeId(changeId)} to ${config.stateFilePath}.`);
|
|
634
|
-
progress.
|
|
635
|
-
updateOverallProgress(
|
|
625
|
+
progress.log(`[done] reviewed ${displayId}: ${outputLabels.join(" | ") || "(no report file generated)"}`);
|
|
626
|
+
updateOverallProgress(progress, index + 1, changeIdsToReview.length, 0, `finished ${displayId}`);
|
|
636
627
|
}
|
|
637
628
|
|
|
638
|
-
|
|
629
|
+
progress.succeed(`completed ${changeIdsToReview.length}/${changeIdsToReview.length}`);
|
|
639
630
|
|
|
640
631
|
const remainingChanges = await backend.getPendingChangeIds(
|
|
641
632
|
config,
|