wireit
Version:
Upgrade your npm scripts to make them smarter and more efficient
197 lines • 6.43 kB
JavaScript
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { DEBUG } from '../logger.js';
import '../../util/dispose.js';
// To prevent using the global console accidentally, we shadow it with
// undefined
const console = undefined;
function markAsUsed(_) { }
markAsUsed(console);
class BaseWriteoverLine {
#updateInterval;
#disposed;
constructor(console) {
this._line = '';
this._targetFps = 60;
/**
* If true, we write over the previous line with a \r carriage return,
* otherwise we write a new line.
*/
this._writeOver = !DEBUG;
this.#disposed = false;
this.#previousLineLength = 0;
this.console = console;
}
clearAndStopRendering() {
// Writeover the previous line and cancel the spinner interval.
if (this.#updateInterval !== undefined) {
clearInterval(this.#updateInterval);
this.#updateInterval = undefined;
}
if (this._line !== '') {
this._line = '';
this._writeLine('');
}
}
#previousLineLength;
_writeLine(line) {
if (!this._writeOver) {
if (line === '') {
return;
}
this.console.stderr.write(line);
this.console.stderr.write('\n');
return;
}
this.console.stderr.write(line);
const overflow = this.#previousLineLength - line.length;
if (overflow > 0) {
this.console.stderr.write(' '.repeat(overflow));
}
this.console.stderr.write('\r');
this.#previousLineLength = line.length;
}
/**
* Clears the line and stops the spinner, and returns a Disposable that, once
* disposed, will restore the line and restart the spinner (if the spinner
* was going when clearUntilDisposed() was called).
*
* Note that we don't expect writeoverLine.writeLine to be called while the
* Disposable is active, so we don't handle that case. We could, it just
* hasn't come up yet. We'd need to have an instance variable to count how
* many active Disposables there are, and only restore the line and restart
* the spinner when the last one is disposed. We'd also need to short circuit
* the logic in writeLine, and set aside the latest line to be written.
*
* Use like:
*
* {
* using _pause = writeoverLine.clearUntilDisposed();
* // console.log, write to stdout and stderr, etc
* }
* // once the block ends, the writeoverLine is restored
*/
clearUntilDisposed() {
// already cleared, nothing to do
if (this.#updateInterval === undefined) {
return undefined;
}
const line = this._line;
this.clearAndStopRendering();
return {
[Symbol.dispose]: () => {
this.updateStatusLine(line);
},
};
}
updateStatusLine(line) {
if (this.#disposed) {
return;
}
if (DEBUG) {
if (this._line !== line) {
// Ensure that every line is written immediately in debug mode
this.console.stderr.write(` ${line}\n`);
}
}
this._line = line;
if (line === '') {
// Writeover the previous line and cancel the spinner interval.
if (this.#updateInterval !== undefined) {
clearInterval(this.#updateInterval);
this.#updateInterval = undefined;
}
this._writeLine('');
return;
}
if (this.#updateInterval !== undefined) {
// will render on next frame
return;
}
// render now, and then schedule future renders.
if (!DEBUG) {
this._update();
}
// schedule future renders so the spinner stays going
this.#updateInterval = setInterval(() => {
if (DEBUG) {
// We want to schedule an interval even in debug mode, so that tests
// will still fail if we don't clean it up properly, but we don't want
// to actually render anything here, since we render any new line
// the moment it comes in.
return;
}
this._update();
}, 1000 / this._targetFps);
}
[Symbol.dispose]() {
this.#disposed = true;
this.clearAndStopRendering();
}
}
/**
* Handles displaying a single line of status text, overwriting the previously
* written line, and displaying a spinner to indicate liveness.
*/
export class WriteoverLine extends BaseWriteoverLine {
#spinner = new Spinner();
#previouslyWrittenLine = undefined;
_update() {
if (this._line === this.#previouslyWrittenLine) {
// just write over the spinner
this.console.stderr.write(this.#spinner.nextFrame);
this.console.stderr.write('\r');
return;
}
this.#previouslyWrittenLine = this._line;
this._writeLine(`${this.#spinner.nextFrame} ${this._line}`);
}
}
/**
* Like WriteoverLine, but it updates much less frequently, just prints lines
* rather doing fancy writeover, doesn't draw a spinner, and stays silent
* if the status line line hasn't changed.
*/
export class CiWriter extends BaseWriteoverLine {
constructor(console) {
super(console);
this.previousLine = '';
// Don't write too much, no need to flood the CI logs.
this._targetFps = 1;
// GitHub seems to handle \r carraige returns the same as \n, but
// we don't want to rely on that. Just print status lines on new lines.
this._writeOver = false;
}
_update() {
if (this._line === this.previousLine) {
// nothing new to log
return;
}
this.previousLine = this._line;
this._writeLine(this._line);
}
}
const spinnerFrames = [
'⠋',
'⠙',
'⠹',
'⠸',
'⠼',
'⠴',
'⠦',
'⠧',
'⠇',
'⠏',
];
class Spinner {
#frame = 0;
get nextFrame() {
const frame = spinnerFrames[this.#frame];
this.#frame = (this.#frame + 1) % spinnerFrames.length;
return frame;
}
}
//# sourceMappingURL=writeover-line.js.map