bktide
Version:
Command-line interface for Buildkite CI/CD workflows with rich shell completions (Fish, Bash, Zsh) and Alfred workflow integration for macOS power users
115 lines • 4.42 kB
JavaScript
import { COLORS, SYMBOLS, shouldDecorate, formatTips, TipStyle } from './theme.js';
import { termWidth, calculateColumnWidths, formatTableRow } from './width.js';
function isMachine(format) {
const f = (format || '').toLowerCase();
return f === 'json' || f === 'alfred';
}
function isInteractive() {
// Check for forced interactive mode first (for testing)
if (process.env.BKTIDE_FORCE_INTERACTIVE === '1') {
return true;
}
// Only show decorative messages when stdout is a TTY (not piped/redirected)
return Boolean(process.stdout.isTTY);
}
export class Reporter {
format;
quiet;
showTips;
constructor(format = 'plain', quiet = false, tips) {
this.format = format;
this.quiet = quiet;
// Tips logic:
// - If tips is explicitly set (true or false), use that
// - Otherwise, tips are off if quiet mode is enabled
// - Default to true (show tips) if not quiet and not explicitly set
this.showTips = tips !== undefined ? tips : !quiet;
}
info(message) {
if (this.shouldSuppress())
return;
this.writeStdout(this.decorate(COLORS.info, `${SYMBOLS.info} ${message}`));
}
success(message) {
if (this.shouldSuppress())
return;
this.writeStdout(this.decorate(COLORS.success, `${SYMBOLS.success} ${message}`));
}
tip(message) {
// Tips have their own suppression logic
if (!this.shouldShowTips())
return;
// Use individual style for single tips from reporter
const formatted = formatTips([message], TipStyle.INDIVIDUAL);
this.writeStdout(formatted);
}
/**
* Display multiple tips in a grouped format
*/
tips(messages, style) {
if (!this.shouldShowTips())
return;
const formatted = formatTips(messages, style || TipStyle.GROUPED);
this.writeStdout(formatted);
}
warn(message) {
// Warnings go to stderr, check stderr TTY status
if (isMachine(this.format) || !process.stderr.isTTY)
return;
this.writeStderr(this.decorate(COLORS.warn, `${SYMBOLS.warn} ${message}`));
}
error(message) {
// Errors go to stderr, check stderr TTY status
if (isMachine(this.format) || !process.stderr.isTTY)
return;
this.writeStderr(this.decorate(COLORS.error, `${SYMBOLS.error} ${message}`));
}
table(rows, options) {
// Tables are data output, not decorative - always show them
if (!rows.length || isMachine(this.format))
return;
// Get terminal width for responsive tables
const width = termWidth();
const numColumns = rows[0].length;
if (options?.preserveWidths) {
// Legacy behavior: preserve exact widths (may overflow)
const widths = rows[0].map((_, i) => Math.max(...rows.map(r => (r[i] ?? '').length)));
const lines = rows.map(r => r.map((c, i) => (c ?? '').padEnd(widths[i])).join(' ')).join('\n');
this.writeStdout(lines);
}
else {
// New behavior: responsive width-aware tables
const columnWidths = calculateColumnWidths(numColumns, width);
const lines = rows.map(row => formatTableRow(row, columnWidths));
this.writeStdout(lines.join('\n'));
}
}
shouldSuppress() {
// Suppress decorative messages when:
// - quiet mode is enabled
// - machine format (json/alfred)
// - stdout is not a TTY (piped/redirected)
return this.quiet || isMachine(this.format) || !isInteractive();
}
shouldShowTips() {
// Tips are shown when:
// - Not in machine format (json/alfred)
// - stdout is a TTY (interactive)
// - showTips flag is true (controlled by --tips/--no-tips/--quiet)
return !isMachine(this.format) && isInteractive() && this.showTips;
}
decorate(fn, s) {
return shouldDecorate(this.format) ? fn(s) : s;
}
writeStdout(s) {
if (isMachine(this.format))
return; // keep machine outputs pristine
process.stdout.write(s + '\n');
}
writeStderr(s) {
if (isMachine(this.format))
return; // keep machine outputs pristine
process.stderr.write(s + '\n');
}
}
//# sourceMappingURL=reporter.js.map