@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
JavaScript
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;
}