2026-05-10 23:12:26 +02:00
|
|
|
import { profileMark } from './startup-profile.js';
|
|
|
|
|
|
|
|
|
|
profileMark('module:viz-fallback');
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
type KtxVizFallbackReason =
|
2026-05-10 23:12:26 +02:00
|
|
|
| 'stdout-not-tty'
|
|
|
|
|
| 'term-dumb'
|
|
|
|
|
| 'stdin-not-tty'
|
|
|
|
|
| 'stdin-raw-mode-unavailable'
|
|
|
|
|
| 'renderer-unavailable';
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
interface KtxVizFallbackIo {
|
2026-05-10 23:12:26 +02:00
|
|
|
stdin?: { isTTY?: boolean; setRawMode?(value: boolean): void };
|
|
|
|
|
stdout: { isTTY?: boolean };
|
|
|
|
|
stderr: { write(chunk: string): void };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
interface KtxVizFallbackOptions {
|
2026-05-10 23:12:26 +02:00
|
|
|
requireInput?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
type KtxVizFallbackDecision =
|
2026-05-10 23:12:26 +02:00
|
|
|
| {
|
|
|
|
|
shouldDegrade: false;
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
shouldDegrade: true;
|
2026-05-10 23:51:24 +02:00
|
|
|
reason: KtxVizFallbackReason;
|
2026-05-10 23:12:26 +02:00
|
|
|
message: string;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
const warnedFallbackReasons = new Set<KtxVizFallbackReason>();
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
export function resolveVizFallback(
|
2026-05-10 23:51:24 +02:00
|
|
|
io: KtxVizFallbackIo,
|
2026-05-10 23:12:26 +02:00
|
|
|
env: NodeJS.ProcessEnv = process.env,
|
2026-05-10 23:51:24 +02:00
|
|
|
options: KtxVizFallbackOptions = {},
|
|
|
|
|
): KtxVizFallbackDecision {
|
2026-05-10 23:12:26 +02:00
|
|
|
if (io.stdout.isTTY !== true) {
|
|
|
|
|
return {
|
|
|
|
|
shouldDegrade: true,
|
|
|
|
|
reason: 'stdout-not-tty',
|
|
|
|
|
message: 'stdout is not an interactive terminal',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((env.TERM ?? '').toLowerCase() === 'dumb') {
|
|
|
|
|
return {
|
|
|
|
|
shouldDegrade: true,
|
|
|
|
|
reason: 'term-dumb',
|
|
|
|
|
message: 'TERM=dumb does not support the visual renderer',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.requireInput === true && io.stdin?.isTTY !== true) {
|
|
|
|
|
return {
|
|
|
|
|
shouldDegrade: true,
|
|
|
|
|
reason: 'stdin-not-tty',
|
|
|
|
|
message: 'stdin is not an interactive terminal',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.requireInput === true && typeof io.stdin?.setRawMode !== 'function') {
|
|
|
|
|
return {
|
|
|
|
|
shouldDegrade: true,
|
|
|
|
|
reason: 'stdin-raw-mode-unavailable',
|
|
|
|
|
message: 'stdin raw mode is unavailable',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { shouldDegrade: false };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export function rendererUnavailableVizFallback(): KtxVizFallbackDecision {
|
2026-05-10 23:12:26 +02:00
|
|
|
return {
|
|
|
|
|
shouldDegrade: true,
|
|
|
|
|
reason: 'renderer-unavailable',
|
|
|
|
|
message: 'the terminal renderer is unavailable',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export function warnVizFallbackOnce(io: KtxVizFallbackIo, decision: KtxVizFallbackDecision): void {
|
2026-05-10 23:12:26 +02:00
|
|
|
if (!decision.shouldDegrade || warnedFallbackReasons.has(decision.reason)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
warnedFallbackReasons.add(decision.reason);
|
|
|
|
|
io.stderr.write(`Visualization requested but ${decision.message}; printing plain output.\n`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function resetVizFallbackWarningsForTest(): void {
|
|
|
|
|
warnedFallbackReasons.clear();
|
|
|
|
|
}
|