/**
* @file colors.js
* @description Zero-dependency ANSI styling helpers for the Avenx CLI.
*
* Styles are applied only when the active terminal can render them. Detection
* follows the widely adopted conventions so that piped output, CI logs, and
* `--json` consumers keep receiving clean, parseable text:
*
* - `--no-color` / `--no-colors` argument → always disabled
* - `NO_COLOR` environment variable → always disabled (https://no-color.org)
* - `FORCE_COLOR` environment variable → enabled unless set to `0`/`false`
* - `TERM=dumb` → disabled
* - non-TTY stdout (pipes, files, CI) → disabled
*
* When styling is disabled every helper returns the plain string unchanged,
* so call sites never need to branch on color support themselves.
*/
/**
* Determines whether ANSI escape codes should be emitted for this process.
* @returns {boolean} True when the current stdout stream can render colors.
*/
export function detectColorSupport() {
const argv = process.argv || [];
if (argv.includes('--no-color') || argv.includes('--no-colors')) {
return false;
}
const env = process.env || {};
if (typeof env.NO_COLOR === 'string' && env.NO_COLOR !== '') {
return false;
}
if (typeof env.FORCE_COLOR === 'string' && env.FORCE_COLOR !== '') {
return env.FORCE_COLOR !== '0' && env.FORCE_COLOR !== 'false';
}
if (env.TERM === 'dumb') {
return false;
}
const stream = process.stdout;
if (!stream || !stream.isTTY) {
return false;
}
if (typeof stream.hasColors === 'function') {
return stream.hasColors();
}
return true;
}
let colorEnabled = detectColorSupport();
/**
* Reports whether styling helpers currently emit ANSI escape codes.
* @returns {boolean}
*/
export function isColorEnabled() {
return colorEnabled;
}
/**
* Overrides color support, mainly for tests and explicit CLI flags.
* Call without arguments to re-run the automatic detection.
* @param {boolean} [value] - Force enable (true) or disable (false).
* @returns {boolean} The resolved state.
*/
export function setColorEnabled(value) {
colorEnabled = value === undefined ? detectColorSupport() : Boolean(value);
return colorEnabled;
}
/**
* Builds a styling function for an ANSI open/close code pair.
* Closing with the attribute-specific reset (instead of a full reset) keeps
* nested styles such as `bold(cyan('text'))` intact.
* @param {number} open - The ANSI code that enables the style.
* @param {number} close - The ANSI code that disables just that style.
* @returns {function(string): string} The styling function.
*/
function style(open, close) {
const openCode = `\x1b[${open}m`;
const closeCode = `\x1b[${close}m`;
return (text) => (colorEnabled ? `${openCode}${text}${closeCode}` : String(text));
}
/** Bold text, used for section headings. */
export const bold = style(1, 22);
/** Dimmed text, used for secondary details. */
export const dim = style(2, 22);
/** Red text, reserved for errors and failed checks. */
export const red = style(31, 39);
/** Green text, reserved for successful actions. */
export const green = style(32, 39);
/** Yellow text, reserved for warnings. */
export const yellow = style(33, 39);
/** Blue text, used for informational notices. */
export const blue = style(34, 39);
/** Cyan text, used for headings and highlighted values. */
export const cyan = style(36, 39);
/** Gray text, used for descriptions and hints. */
export const gray = style(90, 39);
/**
* Creates an AvenxLogger formatter that tints diagnostics by severity:
* warnings yellow and errors red, leaving informational build output untouched.
*
* The formatter returns the original argument list (no `[Avenx level]` prefix),
* matching the compiler's existing CLI output format, and only styles string
* arguments so Error objects and structured context keep their shape.
* @returns {function(string, any[]): any[]} A formatter for `logger.configure`.
*/
export function createSeverityFormatter() {
return (level, args) => {
const tint = level === 'warn' ? yellow : level === 'error' || level === 'fatal' ? red : null;
if (!tint || !Array.isArray(args)) {
return args;
}
return args.map((arg) => (typeof arg === 'string' ? tint(arg) : arg));
};
}