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