kodevu 0.1.16 → 0.1.17
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 +48 -88
- package/src/review-runner.js +8 -17
package/package.json
CHANGED
package/src/progress-ui.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
|
|
3
3
|
const SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
4
|
-
const DEFAULT_BAR_WIDTH = 24;
|
|
5
4
|
|
|
6
5
|
function clampProgress(value) {
|
|
7
6
|
if (!Number.isFinite(value)) {
|
|
@@ -11,21 +10,6 @@ function clampProgress(value) {
|
|
|
11
10
|
return Math.max(0, Math.min(1, value));
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
function buildBar(progress, width) {
|
|
15
|
-
const safeProgress = clampProgress(progress);
|
|
16
|
-
const filled = Math.max(Math.round(safeProgress * width), safeProgress > 0 ? 1 : 0);
|
|
17
|
-
|
|
18
|
-
if (filled >= width) {
|
|
19
|
-
return `[${"=".repeat(width)}]`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (filled === 0) {
|
|
23
|
-
return `[${" ".repeat(width)}]`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return `[${"=".repeat(Math.max(filled - 1, 0))}>${" ".repeat(width - filled)}]`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
13
|
function truncateLine(line, maxWidth) {
|
|
30
14
|
if (!Number.isFinite(maxWidth) || maxWidth <= 0) {
|
|
31
15
|
return "";
|
|
@@ -43,10 +27,9 @@ function truncateLine(line, maxWidth) {
|
|
|
43
27
|
}
|
|
44
28
|
|
|
45
29
|
class ProgressItem {
|
|
46
|
-
constructor(display, label
|
|
30
|
+
constructor(display, label) {
|
|
47
31
|
this.display = display;
|
|
48
32
|
this.label = label;
|
|
49
|
-
this.barWidth = options.barWidth || DEFAULT_BAR_WIDTH;
|
|
50
33
|
this.progress = 0;
|
|
51
34
|
this.stage = "";
|
|
52
35
|
this.active = false;
|
|
@@ -54,8 +37,8 @@ class ProgressItem {
|
|
|
54
37
|
}
|
|
55
38
|
|
|
56
39
|
start(stage = "starting") {
|
|
57
|
-
this.stage = stage;
|
|
58
40
|
this.active = true;
|
|
41
|
+
this.stage = stage;
|
|
59
42
|
|
|
60
43
|
if (!this.display.enabled) {
|
|
61
44
|
this.writeFallback(`... ${this.label}: ${stage}`);
|
|
@@ -63,7 +46,7 @@ class ProgressItem {
|
|
|
63
46
|
}
|
|
64
47
|
|
|
65
48
|
this.display.start();
|
|
66
|
-
this.display.
|
|
49
|
+
this.display.activate(this);
|
|
67
50
|
}
|
|
68
51
|
|
|
69
52
|
update(progress, stage) {
|
|
@@ -78,7 +61,7 @@ class ProgressItem {
|
|
|
78
61
|
return;
|
|
79
62
|
}
|
|
80
63
|
|
|
81
|
-
this.display.
|
|
64
|
+
this.display.activate(this);
|
|
82
65
|
}
|
|
83
66
|
|
|
84
67
|
log(message) {
|
|
@@ -96,46 +79,23 @@ class ProgressItem {
|
|
|
96
79
|
finish(prefix, progress, message) {
|
|
97
80
|
this.progress = clampProgress(progress);
|
|
98
81
|
this.active = false;
|
|
99
|
-
this.display.
|
|
100
|
-
this.display.writeStaticLine(this.
|
|
82
|
+
this.display.deactivate(this);
|
|
83
|
+
this.display.writeStaticLine(this.buildFinalLine(prefix, message));
|
|
101
84
|
}
|
|
102
85
|
|
|
103
86
|
renderLine(frameIndex) {
|
|
104
|
-
|
|
105
|
-
|
|
87
|
+
return this.buildStatusLine(SPINNER_FRAMES[frameIndex], this.stage);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
buildFinalLine(prefix, message) {
|
|
91
|
+
return this.buildStatusLine(prefix, message);
|
|
106
92
|
}
|
|
107
93
|
|
|
108
|
-
|
|
94
|
+
buildStatusLine(prefix, suffix) {
|
|
109
95
|
const availableWidth = this.display.getAvailableWidth();
|
|
110
96
|
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);
|
|
97
|
+
const suffixText = suffix ? ` | ${suffix}` : "";
|
|
98
|
+
return truncateLine(`${prefix} ${pct}% ${this.label}${suffixText}`, availableWidth);
|
|
139
99
|
}
|
|
140
100
|
|
|
141
101
|
writeFallback(line) {
|
|
@@ -155,12 +115,14 @@ export class ProgressDisplay {
|
|
|
155
115
|
this.frameIndex = 0;
|
|
156
116
|
this.timer = null;
|
|
157
117
|
this.items = [];
|
|
158
|
-
this.
|
|
118
|
+
this.currentItem = null;
|
|
119
|
+
this.statusVisible = false;
|
|
120
|
+
this.resizeAttached = false;
|
|
159
121
|
this.handleResize = this.handleResize.bind(this);
|
|
160
122
|
}
|
|
161
123
|
|
|
162
|
-
createItem(label
|
|
163
|
-
const item = new ProgressItem(this, label
|
|
124
|
+
createItem(label) {
|
|
125
|
+
const item = new ProgressItem(this, label);
|
|
164
126
|
this.items.push(item);
|
|
165
127
|
return item;
|
|
166
128
|
}
|
|
@@ -171,21 +133,38 @@ export class ProgressDisplay {
|
|
|
171
133
|
}
|
|
172
134
|
|
|
173
135
|
this.attachResizeHandler();
|
|
174
|
-
|
|
175
136
|
this.timer = setInterval(() => {
|
|
176
137
|
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
177
138
|
this.render();
|
|
178
139
|
}, 120);
|
|
179
140
|
}
|
|
180
141
|
|
|
142
|
+
activate(item) {
|
|
143
|
+
this.currentItem = item;
|
|
144
|
+
this.render();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
deactivate(item) {
|
|
148
|
+
if (this.currentItem === item) {
|
|
149
|
+
this.currentItem = this.items.findLast((candidate) => candidate.active) || null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.stopIfIdle();
|
|
153
|
+
this.clearStatusLine();
|
|
154
|
+
|
|
155
|
+
if (this.currentItem) {
|
|
156
|
+
this.render();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
181
160
|
log(message) {
|
|
182
161
|
if (!this.enabled) {
|
|
183
162
|
this.stream.write(`${message}\n`);
|
|
184
163
|
return;
|
|
185
164
|
}
|
|
186
165
|
|
|
187
|
-
this.
|
|
188
|
-
this.stream.write(`${message}\n`);
|
|
166
|
+
this.clearStatusLine();
|
|
167
|
+
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
189
168
|
this.render();
|
|
190
169
|
}
|
|
191
170
|
|
|
@@ -195,48 +174,29 @@ export class ProgressDisplay {
|
|
|
195
174
|
return;
|
|
196
175
|
}
|
|
197
176
|
|
|
198
|
-
this.
|
|
177
|
+
this.clearStatusLine();
|
|
199
178
|
this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
|
|
200
179
|
this.render();
|
|
201
180
|
}
|
|
202
181
|
|
|
203
182
|
render() {
|
|
204
|
-
if (!this.enabled) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const activeItems = this.items.filter((item) => item.active);
|
|
209
|
-
this.clearRender();
|
|
210
|
-
|
|
211
|
-
if (activeItems.length === 0) {
|
|
212
|
-
this.renderedLineCount = 0;
|
|
183
|
+
if (!this.enabled || !this.currentItem?.active) {
|
|
213
184
|
return;
|
|
214
185
|
}
|
|
215
186
|
|
|
216
|
-
|
|
217
|
-
this.stream.write(
|
|
218
|
-
this.
|
|
187
|
+
this.clearStatusLine();
|
|
188
|
+
this.stream.write(this.currentItem.renderLine(this.frameIndex));
|
|
189
|
+
this.statusVisible = true;
|
|
219
190
|
}
|
|
220
191
|
|
|
221
|
-
|
|
222
|
-
if (!this.enabled || this.
|
|
192
|
+
clearStatusLine() {
|
|
193
|
+
if (!this.enabled || !this.statusVisible) {
|
|
223
194
|
return;
|
|
224
195
|
}
|
|
225
196
|
|
|
226
|
-
readline.
|
|
227
|
-
|
|
228
|
-
for (let index = 0; index < this.renderedLineCount; index += 1) {
|
|
229
|
-
readline.clearLine(this.stream, 0);
|
|
230
|
-
readline.cursorTo(this.stream, 0);
|
|
231
|
-
|
|
232
|
-
if (index < this.renderedLineCount - 1) {
|
|
233
|
-
readline.moveCursor(this.stream, 0, 1);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
readline.moveCursor(this.stream, 0, -Math.max(this.renderedLineCount - 1, 0));
|
|
197
|
+
readline.clearLine(this.stream, 0);
|
|
238
198
|
readline.cursorTo(this.stream, 0);
|
|
239
|
-
this.
|
|
199
|
+
this.statusVisible = false;
|
|
240
200
|
}
|
|
241
201
|
|
|
242
202
|
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,
|