nodejs / nodejs/userland-migrations

spec: `ansi-colors-to-styletext`

Open
#480 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

good first issue human let's do it
Dominant language
TypeScript
Stars
85
Forks
55
Avg merge
3d 11h
Merged PRs (30d)
9

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading utils/src/ast-grep/package-json.ts and the Node.js util.styleText documentation linked in the issue. Use the listed ansi-colors examples and non-migratable APIs as the migration requirements; done means compatible patterns are transformed, unsupported cases are skipped with warnings, and dependency cleanup is addressed where applicable.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
developer-experience, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.