Files
bare-operating-system/packages/bare-os-booter/lib/debug-repl.js
T
Raven Scott eca209b663
CI / test (push) Failing after 4m58s
Update
2026-04-03 01:37:34 -04:00

119 lines
3.5 KiB
JavaScript

/**
* Verbose REPL/fish diagnostics when `DEBUG=1`.
* Writes to the **session TTY** when bound (see `bindReplDebugStream`) so Pear/bare-stdio
* logs show in the same terminal pane as the shell. Falls back to `process.stdout`, then
* `stderr`, then `console.log`. Lines are prefixed with reset + newline so they stay
* readable if the prompt left SGR/cursor state; expect the screen to scroll while debugging.
*/
/** @type {import('stream').Writable | null} */
let _debugStream = null
/** Bind REPL/fish stdout (e.g. bare-stdio `out`) so DEBUG lines appear in the visible terminal. */
export function bindReplDebugStream(stream) {
_debugStream =
stream && typeof stream.write === 'function' ? stream : null
}
export function unbindReplDebugStream() {
_debugStream = null
}
/** @returns {boolean} */
export function isReplDebug() {
const p = globalThis.process?.env
return p?.DEBUG === '1'
}
function ts() {
if (typeof globalThis.performance?.now === 'function') {
return `${globalThis.performance.now().toFixed(2)}ms`
}
return `${Date.now()}`
}
function serialize(part) {
if (typeof part === 'string') return part
if (part === undefined) return 'undefined'
if (part === null) return 'null'
if (typeof part === 'number' || typeof part === 'boolean') return String(part)
try {
return JSON.stringify(part)
} catch {
return String(part)
}
}
/**
* @param {string} scope Short tag (e.g. fish-render, repl, booter)
* @param {...unknown} parts Message pieces
*/
function writeDebugLine(text) {
const payload = `\x1b[0m\n${text}\n`
try {
if (_debugStream && typeof _debugStream.write === 'function') {
_debugStream.write(payload)
return
}
const p = globalThis.process
if (p?.stdout && typeof p.stdout.write === 'function') {
p.stdout.write(payload)
return
}
if (p?.stderr && typeof p.stderr.write === 'function') {
p.stderr.write(payload)
return
}
globalThis.console?.log?.(text)
} catch {
/* ignore */
}
}
export function replDbg(scope, ...parts) {
if (!isReplDebug()) return
const line = `[${ts()}] [bare-os DEBUG:${scope}] ${parts.map(serialize).join(' ')}`
writeDebugLine(line)
}
/**
* Human-readable key / escape sequence for logs.
* @param {string} key
*/
export function replDbgKeyRepr(key) {
if (key === '\n') return '\\n'
if (key === '\r') return '\\r'
if (key === '\t') return '\\t'
if (key === '\u007f') return 'DEL'
if (key === '\u0003') return '^C'
if (key === '\u0004') return '^D'
if (key === '\u000c') return '^L'
if (key === '\u0012' || (key.length === 1 && key.charCodeAt(0) === 18)) return '^R'
if (key.startsWith('\u001b')) {
return `CSI(${key.length}ch)=${JSON.stringify(key)}`
}
if (key.length === 1) {
const c = key.charCodeAt(0)
if (c < 32) return `^${String.fromCharCode(c + 64)}(u${c})`
return key
}
return JSON.stringify(key.length > 120 ? key.slice(0, 120) + '…' : key)
}
/**
* @param {string} label
* @param {string} chunk
* @param {number} [maxLen]
*/
export function replDbgChunk(label, chunk, maxLen = 256) {
if (!isReplDebug()) return
const s = typeof chunk === 'string' ? chunk : String(chunk)
const t = s.length > maxLen ? s.slice(0, maxLen) + `…(+${s.length - maxLen})` : s
const codes = [...t].slice(0, 80).map((ch) => {
const u = ch.charCodeAt(0)
if (u < 32 || u === 127) return `\\x${u.toString(16).padStart(2, '0')}`
return ch
})
replDbg('fish-stdin', label, `len=${s.length}`, `head=${JSON.stringify(codes.join(''))}`)
}