Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378// node:console: the Console class, and the global console this runtime installs over// process.stdout/stderr (see main.ts). Methods publish to the same diagnostics_channel// names Node's own console does ('console.log' etc.), so a subscriber sees every call// whether or not anything is listening.import { AsyncResource } from "./async_hooks";import { channel } from "./diagnostics_channel";import { formatWithOptions, inspect, type InspectOptions } from "./inspect";import { clearScreenDown, cursorTo } from "./readline/callbacks";import { getStringWidth } from "./readline/utils";import { shouldColorize } from "./tty";
const onLog = channel("console.log");const onInfo = channel("console.info");const onDebug = channel("console.debug");const onWarn = channel("console.warn");const onError = channel("console.error");
const MAX_GROUP_INDENTATION = 1000;
/** count/countReset/time/timeEnd/timeLog warn through process.emitWarning (not * through the instance's own stderr), so this file needs it the way readline/ * interface.ts needs its deps: installed once by builtins.ts, before any Console * method that could warn runs. */let emitWarning: (msg: string) => void = () => {};export function installConsole(deps: { emitWarning: (msg: string) => void }): void { emitWarning = deps.emitWarning;}
interface Writer { write(data: string, cb?: (err?: Error | null) => void): boolean; isTTY?: boolean; listenerCount(event: string): number; once(event: string, fn: (...args: unknown[]) => void): unknown; removeListener(event: string, fn: (...args: unknown[]) => void): unknown;}
function invalidArgType(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}`; return Object.assign(new TypeError(`The "${name}" argument must be ${expected}.${got}`), { code: "ERR_INVALID_ARG_TYPE" });}
function writableStreamError(name: string): Error { return Object.assign(new TypeError(`Console expects a writable stream instance for ${name}`), { code: "ERR_CONSOLE_WRITABLE_STREAM" });}
const noop = (): void => {};
export interface ConsoleOptions { stdout?: Writer; stderr?: Writer; ignoreErrors?: boolean; colorMode?: "auto" | boolean; inspectOptions?: InspectOptions; groupIndentation?: number;}
export class Console { #stdout: Writer; #stderr: Writer; #ignoreErrors: boolean; #colorMode: "auto" | boolean; #inspectOptions: InspectOptions | undefined; #groupIndentWidth: number; #groupIndent = ""; #times = new Map<string, number>(); #counts = new Map<string, number>();
constructor(options?: Writer | ConsoleOptions, stderrArg?: Writer, ignoreErrorsArg?: boolean) { const opts: ConsoleOptions = options && typeof (options as Writer).write === "function" ? { stdout: options as Writer, stderr: stderrArg, ignoreErrors: ignoreErrorsArg } : (options as ConsoleOptions | undefined) ?? {}; const { stdout, stderr = stdout, ignoreErrors = true, colorMode = "auto", inspectOptions, groupIndentation = 2 } = opts; if (!stdout || typeof stdout.write !== "function") throw writableStreamError("stdout"); if (!stderr || typeof stderr.write !== "function") throw writableStreamError("stderr"); if (colorMode !== "auto" && colorMode !== true && colorMode !== false) { const shown = typeof colorMode === "string" ? `'${colorMode}'` : String(colorMode); throw Object.assign(new TypeError(`The argument 'colorMode' must be one of: 'auto', true, false. Received ${shown}`), { code: "ERR_INVALID_ARG_VALUE", }); } if (groupIndentation < 0 || groupIndentation > MAX_GROUP_INDENTATION || !Number.isInteger(groupIndentation)) { throw Object.assign(new RangeError(`The value of "groupIndentation" is out of range. It must be >= 0 && <= ${MAX_GROUP_INDENTATION}. Received ${groupIndentation}`), { code: "ERR_OUT_OF_RANGE", }); } this.#stdout = stdout; this.#stderr = stderr; this.#ignoreErrors = Boolean(ignoreErrors); this.#colorMode = colorMode; this.#inspectOptions = inspectOptions; this.#groupIndentWidth = groupIndentation;
// Bind every own method so `const { log } = console` keeps working, and so a // subclass's prototype override is still seen through the bound copy. for (const key of Object.getOwnPropertyNames(Console.prototype)) { const value = (this as unknown as Record<string, unknown>)[key]; if (typeof value === "function" && key !== "constructor") { (this as unknown as Record<string, unknown>)[key] = value.bind(this); } } }
#inspectOptionsFor(stream: Writer): InspectOptions { let color = this.#colorMode; if (color === "auto") color = shouldColorize(stream); if (this.#inspectOptions) return { ...this.#inspectOptions, colors: this.#inspectOptions.colors ?? color }; return color ? { colors: true } : {}; }
#format(stream: Writer, args: unknown[]): string { if (args.length === 1 && typeof args[0] === "string") return args[0]; return formatWithOptions(this.#inspectOptionsFor(stream), ...args); }
#write(stream: Writer, useStdout: boolean, text: string): void { if (this.#groupIndent.length) text = this.#groupIndent + text.replace(/\n/g, `\n${this.#groupIndent}`); text += "\n"; if (!this.#ignoreErrors) { stream.write(text); return; } try { if (stream.listenerCount("error") === 0) stream.once("error", noop); stream.write(text); } catch { // A debugging utility swallowing a synchronous write error (e.g. EPIPE) // is Node's own behavior; there is no way to report it further here. } finally { stream.removeListener("error", noop); void useStdout; } }
log(...args: unknown[]): void { if (onLog.hasSubscribers) onLog.publish(args); this.#write(this.#stdout, true, this.#format(this.#stdout, args)); }
info(...args: unknown[]): void { if (onInfo.hasSubscribers) onInfo.publish(args); this.#write(this.#stdout, true, this.#format(this.#stdout, args)); }
debug(...args: unknown[]): void { if (onDebug.hasSubscribers) onDebug.publish(args); this.#write(this.#stdout, true, this.#format(this.#stdout, args)); }
warn(...args: unknown[]): void { if (onWarn.hasSubscribers) onWarn.publish(args); this.#write(this.#stderr, false, this.#format(this.#stderr, args)); }
error(...args: unknown[]): void { if (onError.hasSubscribers) onError.publish(args); this.#write(this.#stderr, false, this.#format(this.#stderr, args)); }
dir(obj: unknown, options?: InspectOptions): void { this.#write(this.#stdout, true, inspect(obj, { customInspect: false, ...this.#inspectOptionsFor(this.#stdout), ...options })); }
dirxml(...args: unknown[]): void { this.log(...args); }
trace(...args: unknown[]): void { const err = new Error(this.#format(this.#stderr, args)); err.name = "Trace"; Error.captureStackTrace(err, this.trace); this.error(err.stack); }
assert(expression: unknown, ...args: unknown[]): void { if (expression) return; if (args.length && typeof args[0] === "string") args[0] = `Assertion failed: ${args[0]}`; else args.unshift("Assertion failed"); this.warn(...args); }
clear(): void { if (this.#stdout.isTTY) { cursorTo(this.#stdout as never, 0, 0); clearScreenDown(this.#stdout as never); } }
count(label = "default"): void { const key = `${label}`; const next = (this.#counts.get(key) ?? 0) + 1; this.#counts.set(key, next); this.log(`${key}: ${next}`); }
countReset(label = "default"): void { const key = `${label}`; if (!this.#counts.has(key)) { emitWarning(`Count for '${key}' does not exist`); return; } this.#counts.delete(key); }
group(...data: unknown[]): void { if (data.length) this.log(...data); this.#groupIndent += " ".repeat(this.#groupIndentWidth); }
groupCollapsed(...data: unknown[]): void { this.group(...data); }
groupEnd(): void { this.#groupIndent = this.#groupIndent.slice(0, -this.#groupIndentWidth || undefined); }
time(label = "default"): void { const key = `${label}`; if (this.#times.has(key)) { emitWarning(`Label '${key}' already exists for console.time()`); return; } this.#times.set(key, performance.now()); }
timeEnd(label = "default"): void { const key = `${label}`; this.#timeLogImpl("console.timeEnd()", key, undefined); this.#times.delete(key); }
timeLog(label = "default", ...data: unknown[]): void { this.#timeLogImpl("console.timeLog()", `${label}`, data); }
#timeLogImpl(implementation: string, label: string, data: unknown[] | undefined): void { const started = this.#times.get(label); if (started === undefined) { emitWarning(`No such label '${label}' for ${implementation}`); return; } const formatted = formatElapsed(performance.now() - started); if (data === undefined) this.log("%s: %s", label, formatted); else this.log("%s: %s", label, formatted, ...data); }
table(tabularData: unknown, properties?: string[]): void { if (properties !== undefined && !Array.isArray(properties)) throw invalidArgType("properties", "an instance of Array", properties); if (tabularData === null || typeof tabularData !== "object") { this.log(tabularData); return; } this.log(renderTable(tabularData, properties, this.#inspectOptionsFor(this.#stdout))); }
}
function formatElapsed(ms: number): string { if (ms >= 1000) return `${(ms / 1000).toFixed(3)}s`; return `${Number(ms.toFixed(3))}ms`;}
// --- console.table: Node's box-drawing renderer (lib/internal/cli_table.js). ---const BOX = { topLeft: "┌", topMid: "┬", topRight: "┐", mid: "─", rowMid: "┼", leftMid: "├", rightMid: "┤", bottomLeft: "└", bottomMid: "┴", bottomRight: "┘", left: "│ ", right: " │", sep: " │ ",};
function renderRow(row: string[], widths: number[]): string { let out = BOX.left; for (let i = 0; i < row.length; i++) { const cell = row[i] ?? ""; out += cell + " ".repeat(Math.ceil(widths[i]! - getStringWidth(cell))); if (i !== row.length - 1) out += BOX.sep; } return out + BOX.right;}
function asciiTable(head: string[], columns: string[][]): string { const widths = head.map((h) => getStringWidth(h)); const longest = Math.max(0, ...columns.map((c) => c.length)); const rows: string[][] = []; for (let i = 0; i < head.length; i++) { const column = columns[i]!; for (let j = 0; j < longest; j++) { rows[j] ??= []; const value = (rows[j]![i] = Object.hasOwn(column, j) ? column[j]! : ""); widths[i] = Math.max(widths[i] ?? 0, getStringWidth(value)); } } const divider = widths.map((w) => BOX.mid.repeat(w + 2)); let out = `${BOX.topLeft}${divider.join(BOX.topMid)}${BOX.topRight}\n${renderRow(head, widths)}\n` + `${BOX.leftMid}${divider.join(BOX.rowMid)}${BOX.rightMid}\n`; for (const row of rows) out += `${renderRow(row, widths)}\n`; return `${out}${BOX.bottomLeft}${divider.join(BOX.bottomMid)}${BOX.bottomRight}`;}
function renderTable(tabularData: object, properties: string[] | undefined, inspectOpts: InspectOptions): string { const show = (v: unknown): string => { const depth = v !== null && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length > 2 ? -1 : 0; return inspect(v, { depth, maxArrayLength: 3, breakLength: Infinity, ...inspectOpts }); }; const indexArray = (length: number): string[] => Array.from({ length }, (_, i) => show(i));
if (tabularData instanceof Map) { const keys: string[] = []; const values: string[] = []; for (const [k, v] of tabularData) { keys.push(show(k)); values.push(show(v)); } return asciiTable(["(iteration index)", "Key", "Values"], [indexArray(keys.length), keys, values]); } if (tabularData instanceof Set) { const values = [...tabularData].map(show); return asciiTable(["(iteration index)", "Values"], [indexArray(values.length), values]); }
const map: Record<string, string[]> = Object.create(null); const valuesCol: string[] = []; let hasPrimitives = false; const indexKeys = Object.keys(tabularData); for (let i = 0; i < indexKeys.length; i++) { const item = (tabularData as Record<string, unknown>)[indexKeys[i]!]; const primitive = item === null || (typeof item !== "function" && typeof item !== "object"); if (properties === undefined && primitive) { hasPrimitives = true; valuesCol[i] = show(item); } else { const keys = properties ?? Object.keys(item as object); for (const key of keys) { map[key] ??= []; map[key]![i] = (primitive && properties) || !Object.hasOwn(item as object, key) ? "" : show((item as Record<string, unknown>)[key]); } } } const keys = Object.keys(map); const values = keys.map((k) => map[k]!); if (hasPrimitives) { keys.push("Values"); values.push(valuesCol); } keys.unshift("(index)"); values.unshift(indexKeys); return asciiTable(keys, values);}
/** The global console this runtime installs: Console bound to its streams, each own * method named and enumerable like Node's (see main.ts, which installs it over * process.stdout/stderr). profile/profileEnd/timeStamp/createTask/context are * singleton-only in Node too — not Console.prototype methods, so `new Console()` * does not have them either (they come from the V8 Inspector binding, which the * global console alone wires up). */export function makeConsole(stdout: Writer, stderr: Writer): Console & { Console: typeof Console; profile(label?: string): void; profileEnd(label?: string): void; timeStamp(label?: string): void; createTask(name: string): { run<R>(fn: () => R): R }; context(): unknown;} { const instance = new Console({ stdout, stderr }); const createTask = (name: string): { run<R>(fn: () => R): R } => { const resource = new AsyncResource(typeof name === "string" ? name : "Task"); return { run: (fn) => resource.runInAsyncScope(fn) }; }; // No V8 CPU profiler/Inspector protocol is reachable from inside a worker; Node // itself also no-ops these without an attached inspector. const extended = Object.assign(instance, { Console, createTask, context: () => extended, profile: () => {}, profileEnd: () => {}, timeStamp: () => {}, }); return extended as unknown as ReturnType<typeof makeConsole>;}