@vendure/core
Version:
A modern, headless ecommerce framework
117 lines • 5.07 kB
JavaScript
"use strict";
/**
* Minimal vendored replacement for the `progress` npm package. Trimmed to the
* exact surface used by the Importer (and by the dev-server load-testing
* benchmark scripts).
*
* Supports the format tokens Vendure templates use:
*
* `:bar` width-aware progress bar (capped to terminal columns)
* `:current` current tick count
* `:total` total tick count
* `:percent` completion percentage (e.g. "42%")
* `:etas` estimated seconds remaining (e.g. "12s")
*
* Plus arbitrary custom tokens supplied via `tick({ tokenName: value })`.
*
* Renders are throttled (default 16ms between draws) to avoid hammering the
* terminal. The bar redraws in-place using `\r` and clears the trailing line
* with `\x1B[2K`. Output goes to `process.stderr` by default to match the
* upstream `progress` package's behaviour.
*
* Deliberately omitted from the upstream surface (none of which the Importer
* uses): the `head` character on the leading edge of the bar, the `clear`
* option that wipes the bar on completion, the `:elapsed` and `:rate` tokens,
* and the `update()` method.
*
* Non-TTY behaviour (CI logs, piped output): like upstream `progress`, output
* is suppressed entirely so log files don't get peppered with `\r\x1B[2K`
* escape sequences.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProgressBar = void 0;
const ANSI_CLEAR_LINE = '\x1B[2K';
class ProgressBar {
constructor(fmt, options) {
var _a, _b, _c, _d, _e;
this.current = 0;
this.startTime = 0;
this.lastRender = 0;
this.finished = false;
this.fmt = fmt;
this.total = options.total;
this.width = (_a = options.width) !== null && _a !== void 0 ? _a : 40;
this.complete = (_b = options.complete) !== null && _b !== void 0 ? _b : '=';
this.incomplete = (_c = options.incomplete) !== null && _c !== void 0 ? _c : '-';
this.renderThrottle = (_d = options.renderThrottle) !== null && _d !== void 0 ? _d : 16;
this.stream = (_e = options.stream) !== null && _e !== void 0 ? _e : process.stderr;
}
/**
* Advance by 1 (or by the supplied delta if a number is passed). When an
* object is passed, advance by 1 and substitute each entry as a custom
* format token (e.g. `:prodName`).
*/
tick(deltaOrTokens) {
if (this.finished)
return;
let delta = 1;
let tokens;
if (typeof deltaOrTokens === 'number') {
delta = deltaOrTokens;
}
else if (deltaOrTokens && typeof deltaOrTokens === 'object') {
tokens = deltaOrTokens;
}
this.current += delta;
if (this.startTime === 0) {
this.startTime = Date.now();
}
const now = Date.now();
const isComplete = this.current >= this.total;
if (!isComplete && now - this.lastRender < this.renderThrottle) {
return;
}
this.lastRender = now;
this.render(tokens);
if (isComplete) {
this.finished = true;
this.stream.write('\n');
}
}
render(tokens) {
var _a;
// Match the original `progress` package: silent in non-TTY environments
// (CI logs, file redirects) so we don't spam escape sequences into logs.
const tty = this.stream;
if (!tty.isTTY)
return;
const ratio = Math.min(Math.max(this.current / this.total, 0), 1);
const percent = `${Math.floor(ratio * 100)}%`;
const elapsedMs = Date.now() - this.startTime;
const etaSeconds = this.current === 0 ? 0 : (elapsedMs / this.current) * (this.total - this.current) / 1000;
const etas = this.current >= this.total ? '0s' : `${Math.round(etaSeconds)}s`;
let output = this.fmt
.replace(':current', String(this.current))
.replace(':total', String(this.total))
.replace(':percent', percent)
.replace(':etas', etas);
if (output.includes(':bar')) {
// Cap bar width to whatever space the terminal can show after the
// non-bar prefix/suffix. Without this, narrow terminals overflow.
const nonBarLen = output.replace(':bar', '').length;
const available = Math.max(0, ((_a = tty.columns) !== null && _a !== void 0 ? _a : this.width + nonBarLen) - nonBarLen);
const barWidth = Math.min(this.width, available);
const completeLength = Math.round(barWidth * ratio);
const bar = this.complete.repeat(completeLength) + this.incomplete.repeat(barWidth - completeLength);
output = output.replace(':bar', bar);
}
if (tokens) {
for (const [name, value] of Object.entries(tokens)) {
output = output.replace(`:${name}`, String(value));
}
}
this.stream.write(`\r${ANSI_CLEAR_LINE}${output}`);
}
}
exports.ProgressBar = ProgressBar;
//# sourceMappingURL=progress-bar.js.map