()
| 157 | } |
| 158 | |
| 159 | private render(): void { |
| 160 | if (this.totalItems === 0 && this.processedItems === 0 && !this.startTime) |
| 161 | return // Avoid rendering if not started for 0 items |
| 162 | |
| 163 | const percent = |
| 164 | this.totalItems > 0 ? |
| 165 | Math.min(100, (this.processedItems / this.totalItems) * 100) |
| 166 | : 100 |
| 167 | const filledLength = Math.floor((this.barWidth * percent) / 100) |
| 168 | const emptyLength = this.barWidth - filledLength |
| 169 | |
| 170 | const bar = `[${"=".repeat(filledLength)}${" ".repeat(emptyLength)}]` |
| 171 | |
| 172 | const elapsedMs = Date.now() - this.startTime |
| 173 | const elapsedSec = elapsedMs / 1000 |
| 174 | const formattedElapsed = formatSeconds(elapsedSec) |
| 175 | |
| 176 | let etaStr = "" |
| 177 | if (this.processedItems > 0 && this.processedItems < this.totalItems) { |
| 178 | const avgTimePerItemMs = elapsedMs / this.processedItems |
| 179 | const remainingItems = this.totalItems - this.processedItems |
| 180 | const etaMs = remainingItems * avgTimePerItemMs |
| 181 | etaStr = ` (ETA: ${formatSeconds(etaMs / 1000)})` |
| 182 | } else if (this.processedItems >= this.totalItems && this.totalItems > 0) |
| 183 | etaStr = " (Done)" |
| 184 | |
| 185 | const summary = `${this.processedItems}/${this.totalItems} entries (${percent.toFixed(0)}%)` |
| 186 | const timeInfo = `${formattedElapsed} elapsed${etaStr}` |
| 187 | |
| 188 | process.stdout.clearLine(0) // Clear the current line |
| 189 | process.stdout.cursorTo(0) // Move cursor to the beginning of the line |
| 190 | process.stdout.write(`Processing: ${bar} ${summary} | ${timeInfo}`) |
| 191 | } |
| 192 | |
| 193 | stop(): void { |
| 194 | if (this.timerId) { |
no test coverage detected