Skip to content

spec: ansi-colors-to-styletext #480

Description

@AugustinMauroy

Description

This issue tracks the migration from the ansi-colors package to Node.js's built-in util.styleText API. The goal is to reduce external dependencies by leveraging Node.js's native terminal styling capabilities.

Unlike other color libraries (like kleur), ansi-colors applies its multi-style stacks in reverse order, which exactly matches how util.styleText evaluates arrays of styles. As a result, all compatible chains produce a byte-for-byte exact ANSI escape code match.

Migration Scope

Compatible Features:

  • Basic colors (black, red, green, yellow, blue, magenta, cyan, white)
  • Gray / Grey Aliases (gray and grey map seamlessly to blackBright)
  • Bright variants (redBright, greenBright, yellowBright, etc.)
  • Background colors (bgRed, bgGreen, etc.) and Bright Backgrounds (bgRedBright, etc.)
  • Text modifiers (reset, bold, dim, italic, underline, inverse, hidden, strikethrough)
  • Property Chaining via nested property access (e.g., ac.bold.red('text') converts to an ordered array styleText(['bold', 'red'], 'text'))
  • Inline String Concatenation & Template Literals

Non-Compatible Features (Requires Skipping + Manual Intervention Warnings):

  • Runtime Toggles: ac.enabled = false and conditional flag disabling.
  • Output Suppression: ac.visible = false (which silently returns an empty string '').
  • Utility Methods: ac.unstyle(), ac.stripColor(), ac.hasAnsi(), and ac.hasColor().
  • Theme & Alias Registry: ac.alias() and ac.theme().
  • Factories & Custom Codes: ac.create() and ac.define().
  • Passthroughs & Symbols: ac.noop, ac.none, ac.clear, and the cross-platform unicode symbol map ac.symbols.

Benefits

  1. Zero Dependencies: Eliminates the need for the external ansi-colors package.
  2. No Performance Trade-offs: Retains identical byte-for-byte output since both tools stack styles in reverse sequence.
  3. Native Support: Standardizes terminal behavior using built-in Node.js platform APIs (v20.12+ / v21.7+).

Examples

The following suite of examples demonstrates how the codemod must transform code patterns based on the verified validation rules.

Case 1: Basic Color & Alias Usage

Before:

const ac = require('ansi-colors');

console.log(ac.red('Error message'));
console.log(ac.gray('Hint text'));

After:

const { styleText } = require('node:util');

console.log(styleText('red', 'Error message'));
console.log(styleText('gray', 'Hint text'));

Case 2: Multi-Style Property Chaining (Reverse Order Resolution)

Before:

import ac from 'ansi-colors';

// Property access chaining (not method execution)
console.log(ac.bold.red('Critical failure'));
console.log(ac.bgBlue.white.bold('HEADER TEXT'));

After:

import { styleText } from 'node:util';

console.log(styleText(['bold', 'red'], 'Critical failure'));
console.log(styleText(['bgBlue', 'white', 'bold'], 'HEADER TEXT'));

Case 3: Template Literals with Deeply Chained Badges

Before:

const ac = require('ansi-colors');

const file = 'server.js';
const line = '42';

console.log(`${ac.bold.red("[ERR]")} ${ac.dim(file)}:${ac.dim(line)}`);
console.log(`Multi-badge: ${ac.bgRed.white(" ERR ")} ${ac.bgGreen.black(" OK ")}`);

After:

const { styleText } = require('node:util');

const file = 'server.js';
const line = '42';

console.log(`${styleText(['bold', 'red'], '[ERR]')} ${styleText('dim', file)}:${styleText('dim', line)}`);
console.log(`Multi-badge: ${styleText(['bgRed', 'white'], ' ERR ')} ${styleText(['bgGreen', 'black'], ' OK ')}`);

Case 4: String Concatenation Contexts

Before:

const ac = require('ansi-colors');

console.log('Hello, ' + ac.green('World') + '!');
console.log(ac.bgRedBright.white(' ERR ') + ' ' + ac.red('File not found'));

After:

const { styleText } = require('node:util');

console.log('Hello, ' + styleText('green', 'World') + '!');
console.log(styleText(['bgRedBright', 'white'], ' ERR ') + ' ' + styleText('red', 'File not found'));

Case 5: Reusable Style Functions and Conditionals

Before:

const ac = require('ansi-colors');

const errorStyle = (msg) => ac.bold.red(msg);
const status = level === 'error' ? ac.bold.red('boom') : ac.yellow('slow');

After:

const { styleText } = require('node:util');

const errorStyle = (msg) => styleText(['bold', 'red'], msg);
const status = level === 'error' ? styleText(['bold', 'red'], 'boom') : styleText('yellow', 'slow');

Non-Migratable Warnings & Adjustments

When the codemod detects un-migratable APIs, it must skip transformation for that block and output a structured console notification advising manual remediation:

  • **For ac.enabled = false**:

Warning: util.styleText has no equivalent runtime instance flag. Map this configuration to environment variables instead: set process.env.NO_COLOR='1' or NODE_DISABLE_COLORS='1' before application initialization.

  • **For ac.visible = false**:

Warning: util.styleText lacks a visual toggling mechanism and will always return a string wrapper. Please guard the call site explicitly: const out = visible ? styleText('red', msg) : '';.

  • **For ac.unstyle(str) / ac.stripColor(str)**:

Warning: util.styleText does not expose an ANSI text stripper. Replace with a native regex str.replace(/\x1b\[[0-9;]*m/g, '') or install a zero-dependency package like strip-ansi.

  • For Custom Themes / Aliases (ac.theme, ac.alias, ac.define):

Warning: util.styleText is stateless and does not maintain a style or theme registry. Migrate global configurations to dedicated structural objects mapping keys to arrow functions (e.g., const theme = { error: (m) => styleText(['bold', 'red'], m) }).


Additional Note

https://github.com/nodejs/userland-migrations/blob/main/utils/src/ast-grep/package-json.ts may need to be extended to fully support dependency cleanup post-migration.

REFS


Description

This issue tracks the migration from the ansi-colors package to Node.js's built-in util.styleText API. The goal is to reduce external dependencies by leveraging Node.js's native terminal styling capabilities.

Unlike other color libraries (like kleur), ansi-colors applies its multi-style stacks in reverse order, which exactly matches how util.styleText evaluates arrays of styles. As a result, all compatible chains produce a byte-for-byte exact ANSI escape code match.

Migration Scope

Compatible Features:

  • Basic colors (black, red, green, yellow, blue, magenta, cyan, white)
  • Gray / Grey Aliases (gray and grey map seamlessly to blackBright)
  • Bright variants (redBright, greenBright, yellowBright, etc.)
  • Background colors (bgRed, bgGreen, etc.) and Bright Backgrounds (bgRedBright, etc.)
  • Text modifiers (reset, bold, dim, italic, underline, inverse, hidden, strikethrough)
  • Property Chaining via nested property access (e.g., ac.bold.red('text') converts to an ordered array styleText(['bold', 'red'], 'text'))
  • Inline String Concatenation & Template Literals

Non-Compatible Features (Requires Skipping + Manual Intervention Warnings):

  • Runtime Toggles: ac.enabled = false and conditional flag disabling.
  • Output Suppression: ac.visible = false (which silently returns an empty string '').
  • Utility Methods: ac.unstyle(), ac.stripColor(), ac.hasAnsi(), and ac.hasColor().
  • Theme & Alias Registry: ac.alias() and ac.theme().
  • Factories & Custom Codes: ac.create() and ac.define().
  • Passthroughs & Symbols: ac.noop, ac.none, ac.clear, and the cross-platform unicode symbol map ac.symbols.

Benefits

  1. Zero Dependencies: Eliminates the need for the external ansi-colors package.
  2. No Performance Trade-offs: Retains identical byte-for-byte output since both tools stack styles in reverse sequence.
  3. Native Support: Standardizes terminal behavior using built-in Node.js platform APIs (v20.12+ / v21.7+).

Examples

The following suite of examples demonstrates how the codemod must transform code patterns based on the verified validation rules.

Case 1: Basic Color & Alias Usage

Before:

const ac = require('ansi-colors');

console.log(ac.red('Error message'));
console.log(ac.gray('Hint text'));

After:

const { styleText } = require('node:util');

console.log(styleText('red', 'Error message'));
console.log(styleText('gray', 'Hint text'));

Case 2: Multi-Style Property Chaining (Reverse Order Resolution)

Before:

import ac from 'ansi-colors';

// Property access chaining (not method execution)
console.log(ac.bold.red('Critical failure'));
console.log(ac.bgBlue.white.bold('HEADER TEXT'));

After:

import { styleText } from 'node:util';

console.log(styleText(['bold', 'red'], 'Critical failure'));
console.log(styleText(['bgBlue', 'white', 'bold'], 'HEADER TEXT'));

Case 3: Template Literals with Deeply Chained Badges

Before:

const ac = require('ansi-colors');

const file = 'server.js';
const line = '42';

console.log(`${ac.bold.red("[ERR]")} ${ac.dim(file)}:${ac.dim(line)}`);
console.log(`Multi-badge: ${ac.bgRed.white(" ERR ")} ${ac.bgGreen.black(" OK ")}`);

After:

const { styleText } = require('node:util');

const file = 'server.js';
const line = '42';

console.log(`${styleText(['bold', 'red'], '[ERR]')} ${styleText('dim', file)}:${styleText('dim', line)}`);
console.log(`Multi-badge: ${styleText(['bgRed', 'white'], ' ERR ')} ${styleText(['bgGreen', 'black'], ' OK ')}`);

Case 4: String Concatenation Contexts

Before:

const ac = require('ansi-colors');

console.log('Hello, ' + ac.green('World') + '!');
console.log(ac.bgRedBright.white(' ERR ') + ' ' + ac.red('File not found'));

After:

const { styleText } = require('node:util');

console.log('Hello, ' + styleText('green', 'World') + '!');
console.log(styleText(['bgRedBright', 'white'], ' ERR ') + ' ' + styleText('red', 'File not found'));

Case 5: Reusable Style Functions and Conditionals

Before:

const ac = require('ansi-colors');

const errorStyle = (msg) => ac.bold.red(msg);
const status = level === 'error' ? ac.bold.red('boom') : ac.yellow('slow');

After:

const { styleText } = require('node:util');

const errorStyle = (msg) => styleText(['bold', 'red'], msg);
const status = level === 'error' ? styleText(['bold', 'red'], 'boom') : styleText('yellow', 'slow');

Non-Migratable Warnings & Adjustments

When the codemod detects un-migratable APIs, it must skip transformation for that block and output a structured console notification advising manual remediation:

  • For ac.enabled = false:

Warning: util.styleText has no equivalent runtime instance flag. Map this configuration to environment variables instead: set process.env.NO_COLOR='1' or NODE_DISABLE_COLORS='1' before application initialization.

  • For ac.visible = false:

Warning: util.styleText lacks a visual toggling mechanism and will always return a string wrapper. Please guard the call site explicitly: const out = visible ? styleText('red', msg) : '';.

  • For ac.unstyle(str) / ac.stripColor(str):

Warning: util.styleText does not expose an ANSI text stripper. Replace with a native regex str.replace(/\x1b\[[0-9;]*m/g, '') or install a zero-dependency package like strip-ansi.

  • For Custom Themes / Aliases (ac.theme, ac.alias, ac.define):

Warning: util.styleText is stateless and does not maintain a style or theme registry. Migrate global configurations to dedicated structural objects mapping keys to arrow functions (e.g., const theme = { error: (m) => styleText(['bold', 'red'], m) }).


Additional Note

https://github.com/nodejs/userland-migrations/blob/main/utils/src/ast-grep/package-json.ts may need to be extended to fully support dependency cleanup post-migration.

REFS

Metadata

Metadata

Assignees

No one assigned
    No fields configured for Feature.

    Projects

    Status
    🔖 Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions