@vigilbase/cli
Version:
Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.
89 lines (88 loc) • 3.51 kB
JavaScript
/**
* Terminal formatting for local checkups. Progress shows counts only, never
* account or zone names, and is written to stderr. Every other line goes
* through the CLI writer, which strips terminal controls from untrusted text.
*/
import { sanitizeTerminal } from '../../lib/format.js';
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
/**
* Untrusted single-line text (account names from cf): controls and line
* breaks collapsed, length bounded. The terminal UI passes its `ellipsis`
* (`...` on an ASCII-only console).
*/
export function displayText(value, max, ellipsis = '…') {
const flat = sanitizeTerminal(value.replace(/[\s\u0085\u2028\u2029]+/g, ' ')).trim();
return flat.length > max ? `${flat.slice(0, Math.max(0, max - ellipsis.length))}${ellipsis}` : flat;
}
/** `0123…cdef` (`0123...cdef` with the ASCII `ellipsis` of the terminal UI). */
export function maskId(id, ellipsis = '…') {
return id.length > 8 ? `${id.slice(0, 4)}${ellipsis}${id.slice(-4)}` : id;
}
/** `9f2c…a71e` */
export function shortSha(sha256) {
return `${sha256.slice(0, 4)}…${sha256.slice(-4)}`;
}
export function formatBytes(bytes) {
if (bytes < 1024)
return `${bytes} B`;
if (bytes < 1024 * 1024)
return `${Math.max(1, Math.round(bytes / 1024))} KB`;
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}
/** Local `HH:MM` for an ISO timestamp. */
export function formatClock(iso) {
const date = new Date(iso);
if (Number.isNaN(date.getTime()))
return iso;
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}
export function formatDuration(seconds) {
if (seconds < 90)
return `~${Math.max(1, Math.round(seconds))} s`;
return `~${Math.round(seconds / 60)} min`;
}
/** `3 reads denied (HTTP 403) · 60 reads not collectable with cf` */
export function readsSummary(counts, denied) {
const parts = [];
if (denied > 0)
parts.push(`${denied} ${denied === 1 ? 'read' : 'reads'} denied (HTTP 403)`);
const otherHttp = counts.httpError - denied;
if (otherHttp > 0)
parts.push(`${otherHttp} ${otherHttp === 1 ? 'read' : 'reads'} failed with another HTTP error`);
if (counts.collectionFailed > 0)
parts.push(`${counts.collectionFailed} ${counts.collectionFailed === 1 ? 'read' : 'reads'} could not be collected`);
if (counts.notCollected > 0)
parts.push(`${counts.notCollected} reads not collectable with cf`);
return parts.join(' · ');
}
/** A single updating stderr line, or nothing when progress is disabled. */
export function startCountsProgress(enabled, stream = process.stderr) {
if (!enabled)
return { update: () => undefined, stop: () => undefined };
let text = '';
let frame = 0;
let closed = false;
let width = 0;
const draw = () => {
if (closed)
return;
const line = `${SPINNER[frame++ % SPINNER.length]} ${sanitizeTerminal(text).replace(/[\n\t]/g, ' ')}`;
width = Math.max(width, line.length);
stream.write(`\r${line.padEnd(width)}`);
};
const timer = setInterval(draw, 80);
timer.unref?.();
return {
update(next) {
text = next;
draw();
},
stop() {
if (closed)
return;
closed = true;
clearInterval(timer);
stream.write(`\r${' '.repeat(width)}\r`);
},
};
}