🖥️ Playground (CLI primitives)
Qué es
El subpath ./playground expone los renderers visuales que usa internamente TerminalBridge para dar a los scripts Node.js la misma sensación que un CLI moderno: boxes con borde Unicode, tablas ASCII alineadas, headers con subtítulo dimmed, divisores horizontales, contadores de step [n/total] y spinners braille animados.
Estos primitives son terminal-only (Node, TTY real). En browser o entornos non-TTY el TerminalBridge los degrada automáticamente a llamadas logger.info vía ServerFallback — ver ServerFallback.
🔑 API pública recomendada: usa los wrappers del logger (
logger.box(),logger.cliTable(),logger.header(),logger.divider(),logger.step(),logger.spinner()). Resuelven el color capability del terminal, enrutan aServerFallbackcuando hace falta, y respetancliLevel/showPrimitives.Los renderers raw (
renderBox,renderTable,renderStep, …) viven en el subpath para casos avanzados donde quieres componer tu propio output sin pasar por el Logger.
📥 Imports
// Wrappers (recomendados)
import logger from '@mks2508/better-logger';
logger.box(...);
logger.cliTable(...);
// Renderers raw (avanzado — para componer output custom)
import {
renderBox,
renderTable,
renderStep,
renderHeader,
renderDivider,
SpinnerManager,
NoopSpinner
} from '@mks2508/better-logger/playground';
// Tipos
import type { IBoxOptions, ITableOptions, ISpinnerHandle } from '@mks2508/better-logger';
📦 Renderers raw vs wrappers
Renderers raw (renderX) |
Logger wrappers (logger.x) |
|
|---|---|---|
| Devuelven | string (líneas formateadas) |
void (escriben a stderr o degradan) |
| Color capability | Manual (param colorCap) |
Auto-detect |
| Enrutamiento non-TTY | No (emiten ANSI igual) | Sí (vía ServerFallback) |
Respeta cliLevel |
No | Sí (silent/quiet suprimen) |
| Cuándo usar | Tests, composición custom, output controlado | Scripts, CLIs, pipelines de deploy |
🧱 renderBox / logger.box()
Caja ASCII con borde Unicode configurable. Soporta 4 estilos de borde (single, rounded, double, bold), título embebido en el borde superior, color del borde (hex o ANSI), y padding interior. El ancho se acota a min(terminalWidth - 4, 80).
import logger from '@mks2508/better-logger';
logger.box('Service: auth-svc\nStatus: healthy', {
title: 'Health Check',
borderColor: '#00bcd4',
borderStyle: 'double',
padding: 1
});
Output (TTI real):
╔═ Health Check ═══════════════════════════════╗
║ ║
║ Service: auth-svc ║
║ Status: healthy ║
║ ║
╚═══════════════════════════════════════════════╝
Opción (IBoxOptions) |
Default | Descripción |
|---|---|---|
title |
— | Texto embebido en el borde superior |
borderColor |
— | Hex (#00bcd4) o nombre ANSI |
borderStyle |
'rounded' |
'single' | 'rounded' | 'double' | 'bold' |
padding |
0 |
Líneas vacías simétricas arriba/abajo |
Forma raw (componer output custom):
import { renderBox } from '@mks2508/better-logger/playground';
process.stdout.write(renderBox('Build complete', { borderStyle: 'bold' }) + '\n');
Referencia: renderBox · IBoxOptions
📊 renderTable / logger.cliTable()
Tabla ASCII alineada con headers en cyan bold, separador ─ bajo la cabecera, y padding horizontal por columna. Las columnas se autodetectan de las keys del primer row; opcionalmente se imponen vía options.columns y se renombran vía options.head.
import logger from '@mks2508/better-logger';
logger.cliTable([
{ service: 'auth', status: 'healthy', latency: 12 },
{ service: 'api', status: 'degraded', latency: 245 },
{ service: 'worker', status: 'healthy', latency: 8 }
]);
Output:
service status latency
───────────────────────────────────────
auth healthy 12
api degraded 245
worker healthy 8
Con columnas forzadas y headers custom:
logger.cliTable(rows, {
columns: ['service', 'latency'],
head: ['Service', 'Latency (ms)']
});
Un array rows vacío devuelve string vacío (no se imprime nada).
Referencia: renderTable · ITableOptions
🔢 renderStep / logger.step()
Indicador de progreso secuencial [n/total] con el contador en cyan bold. Pensado para output tipo steps de un build, fases de deploy, o tareas de un script.
import logger from '@mks2508/better-logger';
const steps = ['Resolving deps', 'Bundling', 'Writing dist'];
steps.forEach((step, i) => {
logger.step(i + 1, steps.length, step);
});
Output:
[1/3] Resolving deps
[2/3] Bundling
[3/3] Writing dist
renderStep no chequea el rango de current/total — el caller es responsable de pasar valores coherentes.
Referencia: renderStep
🏷️ renderHeader / logger.header() y renderDivider / logger.divider()
renderHeader emite un header de sección: title en bold, subtitle opcional en dim. renderDivider emite una línea ─ dimmed del ancho del terminal (capped a 60).
import logger from '@mks2508/better-logger';
logger.header('Deploy', 'production · us-east-1');
logger.divider();
logger.header('Build summary');
Output:
**Deploy** production · us-east-1
────────────────────────────────────────────
**Build summary**
| Wrapper | Renderer raw | Notas |
|---|---|---|
logger.header(t, s?) |
renderHeader(t, s?) |
Subtitle dimmed, separado por espacio |
logger.divider() |
renderDivider(width?) |
Auto-ancho capped a 60; override explícito |
logger.blank() |
(n/a) | Línea vacía a stderr |
Referencia: renderHeader · renderDivider
🔄 SpinnerManager / logger.spinner()
Spinner braille animado a ~12 FPS (frames a U+2800-U+28FF, interval de 80 ms). Cicla reescribiendo la línea actual con \r\x1b[K — no emite líneas nuevas hasta que se llama a stop/succeed/fail.
Devuelve un ISpinnerHandle con lifecycle explícito:
import logger from '@mks2508/better-logger';
const s = logger.spinner('Compilando...');
s.start();
// ...trabajo async...
s.succeed('Build OK'); // → " ✓ Build OK"
// o
s.fail(`Build failed: ${err.message}`); // → " ✗ Build failed: ..."
// o
s.stop(); // desmonta sin mensaje final
Mutación in-place del texto sin reiniciar la animación:
const s = logger.spinner('Procesando 0/100');
s.start();
for (let i = 1; i <= 100; i++) {
s.text(`Procesando ${i}/100`);
await processItem(i);
}
s.succeed('Done');
| Método | Efecto |
|---|---|
start() |
Arranca animación. Mutea el logger (outputMode: 'silent') |
stop() |
Limpia línea, restaura output. Sin mensaje final |
succeed(msg?) |
Línea verde con ✓ msg |
fail(msg?) |
Línea roja con ✗ msg |
text(msg) |
Cambia el texto in-place (frame no se reinicia) |
⚠️ Importante:
start()cambia eloutputModedel logger a'silent'para que los logs del usuario no se impriman encima del frame animado. Cualquierstop/succeed/faillo restaura a'console'. Por eso es crítico cerrar el spinner (succeed/fail/stop) — si no, el logger queda en silencio.El spinner escribe siempre a
process.stderr(no poluistdoutcuando va por pipe).
NoopSpinner (entornos non-TTY)
El TerminalBridge entrega automáticamente un NoopSpinner en lugar de SpinnerManager cuando no detecta TTY (CI logs, pipes) o cuando outputMode === 'silent'. Mantiene la misma interfaz ISpinnerHandle para que el caller no ramifique:
Método NoopSpinner |
Efecto |
|---|---|
start() |
logger.info(message) |
succeed(msg?) |
logger.success(msg ?? message) |
fail(msg?) |
logger.error(msg ?? message) |
stop() |
No-op |
text(msg) |
Cambia el mensaje interno (sin output) |
// En CI sin TTY: TerminalBridge entrega NoopSpinner transparentemente.
const s = logger.spinner('Running tests...');
s.start(); // → logger.info('Running tests...')
await runTests();
s.succeed('All green'); // → logger.success('All green')
A diferencia de SpinnerManager, NoopSpinner no mutea el logger — no hay animación que proteger.
Referencia: SpinnerManager · NoopSpinner · ISpinnerHandle
🧩 Ejemplo conjunto
Pipeline de deploy con header, steps, box final y tabla resumen:
import logger from '@mks2508/better-logger';
logger.header('Deploy', 'production · us-east-1');
logger.divider();
const phases = ['Compiling', 'Running tests', 'Building image', 'Pushing'];
for (let i = 0; i < phases.length; i++) {
logger.step(i + 1, phases.length, phases[i]);
}
const spinner = logger.spinner('Pushing image to registry...');
spinner.start();
try {
await pushImage('myapp:v1.2.3');
spinner.succeed('Image pushed');
} catch (err) {
spinner.fail(`Push failed: ${err.message}`);
process.exit(1);
}
logger.divider();
logger.box('Deploy complete\nService: orders-api\nStatus: healthy', {
title: 'Done',
borderColor: '#00ff00',
borderStyle: 'double',
padding: 1
});
logger.cliTable([
{ service: 'auth', status: 'healthy', latency: 12 },
{ service: 'api', status: 'healthy', latency: 23 },
{ service: 'worker', status: 'healthy', latency: 8 }
]);
Output (TTY real, abreviado):
**Deploy** production · us-east-1
────────────────────────────────────────────
[1/4] Compiling
[2/4] Running tests
[3/4] Building image
[4/4] Pushing
⠋ Pushing image to registry...
✓ Image pushed
╔═ Done ═════════════════════════════════════╗
║ Deploy complete ║
║ Service: orders-api ║
║ Status: healthy ║
╚═════════════════════════════════════════════╝
service status latency
───────────────────────────────────
auth healthy 12
api healthy 23
worker healthy 8
⚠️ Fallback automático non-TTY
El TerminalBridge enruta los primitives a ServerFallback cuando isRunningInTerminal() retorna false o cuando outputMode === 'json'. Cada renderer se degrada a una llamada logger.info:
| Wrapper TTY | ServerFallback (CI / json) |
|---|---|
logger.step(2, 5, 'Compilando') |
logger.info('[2/5] Compilando') |
logger.header('Build', 'v1.2.3') |
logger.info('Build v1.2.3') |
logger.divider() |
(no-op) |
logger.box(...) |
logger.info(...) (texto plano) |
logger.cliTable([...]) |
logger.info(...) (texto plano) |
logger.spinner(msg) |
NoopSpinner (start/succeed/fail → info/success/error) |
El caller mantiene una sola API — el routing es interno.
🛠️ Control de visibilidad
cliLevel controla a la vez la verbosidad de los logs y la visibilidad de los primitives. Útil para silent (sólo errors) o quiet (CLI primitivo suprimido):
logger.setCLILevel('silent'); // nada de primitives, suprime la mayoría de logs
logger.setCLILevel('quiet'); // sin primitives, sólo logs importantes
logger.setCLILevel('normal'); // default — primitives visibles
logger.setCLILevel('verbose'); // primitives + debug logs
Puntualmente, setShowPrimitives(false) apaga solo el output de primitives sin tocar la verbosidad del logger.
🌐 Compatibilidad
| Entorno | Comportamiento |
|---|---|
| Node + TTY | Renderers TTY activos, spinner animado |
Node + pipe / redirect (> file) |
ServerFallback automáticamente |
Node + outputMode: 'json' |
ServerFallback (cada primitive → logger.info con texto plano) |
| CI (sin TTY) | ServerFallback automáticamente |
| Browser | No hay equivalentes (los primitives son terminal-only) |
Los renderers raw no hacen fallback — emiten su output sea cual sea el entorno. Usa los wrappers logger.x() si quieres routing automático.
🔗 Referencia API
- Renderers:
renderBox·renderTable·renderStep·renderHeader·renderDivider - Spinners:
SpinnerManager·NoopSpinner·ISpinnerHandle - Fallback:
ServerFallback - Interfaces:
IBoxOptions·ITableOptions - Wrappers del Logger:
Logger.box·Logger.cliTable·Logger.step·Logger.spinner·Logger.setCLILevel - Subpath:
@mks2508/better-logger/playground