UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

153 lines (132 loc) 4.97 kB
import picocolors from 'picocolors'; const { createColors, isColorSupported } = picocolors; const colors = createColors(isColorSupported); /** @type {null | (() => void)} */ let transientLogLineCleaner = null; /** * Tracks the last plugin name that called needleLog. * Used to suppress repeated header lines for consecutive logs from the same plugin. * @type {string | null} */ let lastLoggedPlugin = null; /** * Resets the sticky-header state so the next needleLog call always emits its * [plugin-name] header. Call this at the start of a new build or any logical * output section where you want a guaranteed fresh header. */ export function resetLastLoggedPlugin() { lastLoggedPlugin = null; } function getConsoleMethod(level) { if (level === 'error') return console.error; if (level === 'warn') return console.warn; return console.log; } function colorBodyByLevel(level, text) { if (!isColorSupported) return text; if (level === 'error') return colors.red(text); if (level === 'warn') return colors.yellow(text); return text; } function normalizeMessage(message) { if (Array.isArray(message)) return message.join("\n"); if (message === undefined || message === null) return ""; return String(message); } function formatHeader(pluginName) { if (!isColorSupported) return `[${pluginName}]`; const name = String(pluginName ?? ""); let prefix = ""; let suffix = name; if (name.startsWith("needle-")) { prefix = "needle-"; suffix = name.substring("needle-".length); } else if (name.startsWith("needle:")) { prefix = "needle:"; suffix = name.substring("needle:".length); } const open = colors.green("["); const close = colors.green("]"); if (!prefix.length) { return `${open}${colors.bold(colors.green(name))}${close}`; } const prefixStyled = colors.green(prefix); const suffixStyled = colors.bold(colors.green(suffix)); return `${open}${prefixStyled}${suffixStyled}${close}`; } /** * @param {string} pluginName * @param {string | string[]} message * @param {'log' | 'warn' | 'error'} [level='log'] * @param {{ dimBody?: boolean, leadingNewline?: boolean, showHeader?: boolean }} [options] */ export function needleLog(pluginName, message, level = 'log', options = undefined) { if (typeof transientLogLineCleaner === "function") { transientLogLineCleaner(); } const log = getConsoleMethod(level); const headerText = formatHeader(pluginName); const bodyText = normalizeMessage(message); const dimBody = options?.dimBody ?? (level === 'log'); const leadingNewline = options?.leadingNewline === true; const showHeader = options?.showHeader !== false; // Sticky header: only re-emit [plugin-name] when the plugin changes. // Always re-emit for warn/error (for clarity), header-only calls (no body), // or section breaks (leadingNewline). const pluginChanged = pluginName !== lastLoggedPlugin; const emitHeader = showHeader && (pluginChanged || level !== 'log' || bodyText.length === 0 || leadingNewline); lastLoggedPlugin = pluginName; if (isColorSupported) { const header = `\x1b[0m${headerText}`; const leveledBody = colorBodyByLevel(level, bodyText); const body = bodyText.length > 0 ? (dimBody ? leveledBody.split('\n').map(l => colors.dim(l)).join('\n') : leveledBody) : ""; const payloadCore = emitHeader ? (body.length > 0 ? `${header}\n${body}\n` : `${header}\n`) : (body.length > 0 ? `${body}\n` : ""); const payload = leadingNewline ? `\n${payloadCore}` : payloadCore; log(payload); return; } if (bodyText.length > 0) { const out = emitHeader ? `${headerText} ${bodyText}` : bodyText; log(leadingNewline ? `\n${out}` : out); } else { const out = emitHeader ? headerText : ""; if (out.length > 0) { log(leadingNewline ? `\n${out}` : out); } } } /** * @param {string} pluginName * @param {string[]} lines * @param {'log' | 'warn' | 'error'} [level='log'] */ export function needleLogLines(pluginName, lines, level = 'log') { needleLog(pluginName, lines.join("\n"), level); } export function needleSupportsColor() { return isColorSupported; } export function needleBlue(text) { const value = String(text ?? ""); return isColorSupported ? colors.blue(value) : value; } export function needleDim(text) { const value = String(text ?? ""); return isColorSupported ? colors.dim(value) : value; } export function needleGreenBold(text) { const value = String(text ?? ""); return isColorSupported ? colors.bold(colors.green(value)) : value; } /** * @param {(() => void) | null} cleaner */ export function setTransientLogLineCleaner(cleaner) { transientLogLineCleaner = typeof cleaner === "function" ? cleaner : null; }