Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122// Derived from Node.js v24.16.0 (MIT; Copyright Node.js contributors, and Joyent, Inc. and// other Node contributors). See LICENSE-node.// util.styleText, after Node v24's lib/util.js: SGR open/close pairs from inspect.colors// (aliases included), hex colours as 24-bit foregrounds, nested closes re-opened so an// inner style does not end an outer one, and no styling at all when the stream would not// show colours (Node's shouldColorize, through tty.ts).import { inspect } from "./inspect";import { shouldColorize } from "./tty";
interface Style { openSeq: string; closeSeq: string; keepClose: boolean }
const sgr = (n: number | string): string => `\u001b[${n}m`;
/** Built from inspect.colors on first use, as Node caches it. Bold and dim share their * close (22), so closing one must not drop the other. */let styles: Record<string, Style> | undefined;function styleCache(): Record<string, Style> { if (styles) return styles; styles = Object.create(null) as Record<string, Style>; const colors = inspect.colors as Record<string, [number, number] | undefined>; for (const key of Object.getOwnPropertyNames(colors)) { const codes = colors[key]; if (codes) styles[key] = { openSeq: sgr(codes[0]), closeSeq: sgr(codes[1]), keepClose: codes[0] === 2 || codes[0] === 1 }; } return styles;}
const hexColorRegExp = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
function replaceCloseCode(str: string, closeSeq: string, openSeq: string, keepClose: boolean): string { const closeLen = closeSeq.length; let index = str.indexOf(closeSeq); if (index === -1) return str; let result = ""; let lastIndex = 0; const replacement = keepClose ? closeSeq + openSeq : openSeq; do { const afterClose = index + closeLen; // A close at the very end is the inner style's own and stays. if (afterClose >= str.length) break; result += str.slice(lastIndex, index) + replacement; lastIndex = afterClose; index = str.indexOf(closeSeq, lastIndex); } while (index !== -1); return result + str.slice(lastIndex);}
function hexToRgb(hex: string): [number, number, number] { const six = hex.length === 4 ? hex[1]! + hex[1] + hex[2] + hex[2] + hex[3] + hex[3] : hex.slice(1); return [0, 2, 4].map((at) => parseInt(six.slice(at, at + 2), 16)) as [number, number, number];}
function argType(name: string, expected: string, value: unknown): TypeError { const got = value === null || value === undefined ? `Received ${value}` : typeof value === "object" ? `Received an instance of ${(value as object).constructor?.name ?? "Object"}` : `Received type ${typeof value} (${typeof value === "string" ? `'${value}'` : String(value)})`; return Object.assign(new TypeError(`The "${name}" argument must be ${expected}. ${got}`), { code: "ERR_INVALID_ARG_TYPE" });}
function isStreamLike(s: unknown): boolean { if (typeof s !== "object" || s === null) return false; const o = s as Record<string, unknown>; if (typeof ReadableStream !== "undefined" && (s instanceof ReadableStream || s instanceof WritableStream)) return true; return Boolean(o._readableState || o._writableState || (typeof o.write === "function" && typeof o.on === "function") || (typeof o.pipe === "function" && typeof o.on === "function"));}
export function makeStyleText(stdout: () => unknown) { return function styleText( format: string | string[], text: string, options?: { validateStream?: boolean; stream?: unknown }, ): string { const validateStream = options?.validateStream ?? true; if (typeof text !== "string") throw argType("text", "of type string", text); if (options !== undefined && (typeof options !== "object" || options === null || Array.isArray(options))) { throw argType("options", "of type object", options); } if (typeof validateStream !== "boolean") throw argType("options.validateStream", "of type boolean", validateStream);
let skipColorize = false; if (validateStream) { const stream = options?.stream ?? stdout(); if (!isStreamLike(stream)) throw argType("stream", "an instance of ReadableStream, WritableStream, or Stream", stream); skipColorize = !shouldColorize(stream); }
let openCodes = ""; let closeCodes = ""; let processedText = text; for (const key of Array.isArray(format) ? format : [format]) { if (key === "none") continue; if (typeof key === "string" && hexColorRegExp.test(key)) { if (skipColorize) continue; const [r, g, b] = hexToRgb(key); const openSeq = sgr(`38;2;${r};${g};${b}`); const closeSeq = sgr(39); openCodes += openSeq; closeCodes = closeSeq + closeCodes; processedText = replaceCloseCode(processedText, closeSeq, openSeq, false); continue; } const style = typeof key === "string" ? styleCache()[key] : undefined; if (style === undefined) { if (typeof key === "string" && key[0] === "#") { throw Object.assign(new TypeError(`The argument 'format' must be a valid hex color (#RGB or #RRGGBB). Received ${inspect(key)}`), { code: "ERR_INVALID_ARG_VALUE", }); } const allowed = Object.getOwnPropertyNames(inspect.colors).map((k) => `'${k}'`).join(", "); throw Object.assign(new TypeError(`The argument 'format' must be one of: ${allowed}. Received ${inspect(key)}`), { code: "ERR_INVALID_ARG_VALUE", }); } openCodes += style.openSeq; closeCodes = style.closeSeq + closeCodes; processedText = replaceCloseCode(processedText, style.closeSeq, style.openSeq, style.keepClose); } if (skipColorize) return text; return `${openCodes}${processedText}${closeCodes}`; };}