import { encode } from "@toon-format/toon"; export type OutputFormat = "json" | "text"; export interface FormatResult { full: string; preview: string; warning?: string; format: OutputFormat; } function numericSafeJson(raw: string): unknown { let lost = false; const reviver = (_key: string, value: unknown, context?: { source?: string }) => { if (typeof value === "number") { const source = context?.source; if (!source || !Number.isFinite(value) || (Number.isInteger(value) && !Number.isSafeInteger(value)) || String(value) !== source) lost = true; } return value; }; const value = JSON.parse(raw, reviver as (key: string, value: unknown) => unknown); if (lost) throw new Error("JSON number changed during parsing"); return value; } function encodeJson(raw: string): { text: string; warning?: string } { try { return { text: encode(numericSafeJson(raw)) }; } catch (error) { return { text: raw, warning: `TOON formatting skipped: ${error instanceof Error ? error.message : String(error)}`, }; } } export function truncateUtf8(text: string, maxBytes: number, maxLines = 200, notice = "preview truncated"): string { const source = Buffer.from(text, "utf8"); if (source.length <= maxBytes && (text.match(/\n/g)?.length ?? 0) + 1 <= maxLines) return text; const suffix = `\n[${notice}]`; const suffixBytes = Buffer.byteLength(suffix); let end = Math.max(0, maxBytes - suffixBytes); end = Math.min(end, source.length); while (end > 0) { try { const candidate = new TextDecoder("utf-8", { fatal: true }).decode(source.subarray(0, end)); const lines = candidate.split("\n"); if (lines.length > maxLines) { end = Buffer.byteLength(lines.slice(0, maxLines).join("\n")); continue; } return candidate + suffix; } catch { end--; } } return suffix.slice(0, maxBytes); } export function formatCapture(stdout: string, stderr: string, format: OutputFormat, options: { previewBytes?: number; previewLines?: number } = {}): FormatResult { let full = stdout; let warning: string | undefined; if (format === "json") { if (!stdout.trim()) { full = encode({ stdout: "" }); } else { const encoded = encodeJson(stdout); full = encoded.text; warning = encoded.warning; } } if (stderr) { const label = `\n\n[stderr]\n${stderr}`; full += label; } if (warning) full = `${full}\n\n[warning] ${warning}`; const preview = truncateUtf8(full, options.previewBytes ?? 12 * 1024, options.previewLines ?? 200); return { full, preview, warning, format }; }