diff --git a/.gitattributes b/.gitattributes index 6313b56..391f0a4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -* text=auto eol=lf +* text=auto +*.js text eol=lf diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 1cd069f..0000000 --- a/.github/funding.yml +++ /dev/null @@ -1,4 +0,0 @@ -github: [sindresorhus, Qix-] -open_collective: sindresorhus -tidelift: npm/chalk -custom: https://sindresorhus.com/donate diff --git a/.github/security.md b/.github/security.md deleted file mode 100644 index 5358dc5..0000000 --- a/.github/security.md +++ /dev/null @@ -1,3 +0,0 @@ -# Security Policy - -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index d588995..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: CI -on: - - push - - pull_request -jobs: - test: - name: Node.js ${{ matrix.node-version }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - node-version: - - 18 - - 16 - - 14 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm test - - uses: codecov/codecov-action@v2 - if: matrix.node-version == 16 - with: - fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index 6bff314..1fd04da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ node_modules -yarn.lock coverage .nyc_output diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e7..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ea5900d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - '8' + - '6' + - '4' +after_success: npm run coveralls diff --git a/benchmark.js b/benchmark.js index c5c6e27..af90c06 100644 --- a/benchmark.js +++ b/benchmark.js @@ -1,57 +1,24 @@ -/* globals suite, bench */ -import chalk from './index.js'; +/* globals set bench */ +'use strict'; +const chalk = require('.'); suite('chalk', () => { - const chalkRed = chalk.red; - const chalkBgRed = chalk.bgRed; - const chalkBlueBgRed = chalk.blue.bgRed; - const chalkBlueBgRedBold = chalk.blue.bgRed.bold; + set('iterations', 100000); - const blueStyledString = 'the fox jumps' + chalk.blue('over the lazy dog') + '!'; - - bench('1 style', () => { + bench('single style', () => { chalk.red('the fox jumps over the lazy dog'); }); - bench('2 styles', () => { - chalk.blue.bgRed('the fox jumps over the lazy dog'); - }); - - bench('3 styles', () => { + bench('several styles', () => { chalk.blue.bgRed.bold('the fox jumps over the lazy dog'); }); - bench('cached: 1 style', () => { - chalkRed('the fox jumps over the lazy dog'); + const cached = chalk.blue.bgRed.bold; + bench('cached styles', () => { + cached('the fox jumps over the lazy dog'); }); - bench('cached: 2 styles', () => { - chalkBlueBgRed('the fox jumps over the lazy dog'); - }); - - bench('cached: 3 styles', () => { - chalkBlueBgRedBold('the fox jumps over the lazy dog'); - }); - - bench('cached: 1 style with newline', () => { - chalkRed('the fox jumps\nover the lazy dog'); - }); - - bench('cached: 1 style nested intersecting', () => { - chalkRed(blueStyledString); - }); - - bench('cached: 1 style nested non-intersecting', () => { - chalkBgRed(blueStyledString); - }); - - bench('cached: 1 style template literal', () => { - // eslint-disable-next-line no-unused-expressions - chalkRed`the fox jumps over the lazy dog`; - }); - - bench('cached: nested styles template literal', () => { - // eslint-disable-next-line no-unused-expressions - chalkRed`the fox {bold jumps} over the {underline lazy} dog`; + bench('nested styles', () => { + chalk.red('the fox jumps', chalk.underline.bgBlue('over the lazy dog') + '!'); }); }); diff --git a/examples/rainbow.js b/examples/rainbow.js index 5b2b1d2..3da1e11 100644 --- a/examples/rainbow.js +++ b/examples/rainbow.js @@ -1,38 +1,39 @@ -import {setTimeout as delay} from 'node:timers/promises'; -import convertColor from 'color-convert'; -import updateLog from 'log-update'; -import chalk from '../source/index.js'; +const chalk = require('..'); -const ignoreChars = /[^!-~]/g; +const ignoreChars = /[^!-~]/; -function rainbow(string, offset) { - if (!string || string.length === 0) { - return string; +function rainbow(str, offset) { + if (!str || str.length === 0) { + return str; } - const hueStep = 360 / string.replaceAll(ignoreChars, '').length; + const hueStep = 360 / str.replace(ignoreChars, '').length; let hue = offset % 360; - const characters = []; - for (const character of string) { - if (ignoreChars.test(character)) { - characters.push(character); + const chars = []; + for (const c of str) { + if (c.match(ignoreChars)) { + chars.push(c); } else { - characters.push(chalk.hex(convertColor.hsl.hex(hue, 100, 50))(character)); + chars.push(chalk.hsl(hue, 100, 50)(c)); hue = (hue + hueStep) % 360; } } - return characters.join(''); + return chars.join(''); } -async function animateString(string) { - for (let index = 0; index < 360 * 5; index++) { - updateLog(rainbow(string, index)); - await delay(2); // eslint-disable-line no-await-in-loop +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function animateString(str) { + console.log(); + for (let i = 0; i < 360 * 5; i++) { + console.log('\x1b[1F\x1b[G ', rainbow(str, i)); + await sleep(50); // eslint-disable-line no-await-in-loop } } console.log(); -await animateString('We hope you enjoy Chalk! <3'); -console.log(); +animateString('We hope you enjoy the new version of Chalk 2! <3').then(() => console.log()); diff --git a/examples/screenshot.js b/examples/screenshot.js index 9ad164b..7d195a6 100644 --- a/examples/screenshot.js +++ b/examples/screenshot.js @@ -1,27 +1,18 @@ -import process from 'node:process'; -import styles from 'ansi-styles'; -import chalk from '../source/index.js'; +'use strict'; +const chalk = require('..'); +const styles = require('ansi-styles'); // Generates screenshot for (const key of Object.keys(styles)) { - let returnValue = key; + let ret = key; - // We skip `overline` as almost no terminal supports it so we cannot show it off. - if ( - key === 'reset' - || key === 'hidden' - || key === 'grey' - || key === 'bgGray' - || key === 'bgGrey' - || key === 'overline' - || key.endsWith('Bright') - ) { + if (key === 'reset' || key === 'hidden' || key === 'grey') { continue; } if (/^bg[^B]/.test(key)) { - returnValue = chalk.black(returnValue); + ret = chalk.black(ret); } - process.stdout.write(chalk[key](returnValue) + ' '); + process.stdout.write(chalk[key](ret) + ' '); } diff --git a/index.js b/index.js new file mode 100644 index 0000000..e6e0d6e --- /dev/null +++ b/index.js @@ -0,0 +1,214 @@ +'use strict'; +const escapeStringRegexp = require('escape-string-regexp'); +const ansiStyles = require('ansi-styles'); +const supportsColor = require('supports-color'); + +const template = require('./templates.js'); + +const isSimpleWindowsTerm = process.platform === 'win32' && !process.env.TERM.toLowerCase().startsWith('xterm'); + +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; + +// `color-convert` models to exclude from the Chalk API due to conflicts and such +const skipModels = new Set(['gray']); + +const styles = Object.create(null); + +function applyOptions(obj, options) { + options = options || {}; + + // Detect level if not set manually + obj.level = options.level === undefined ? supportsColor.level : options.level; + obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; +} + +function Chalk(options) { + // We check for this.template here since calling chalk.constructor() + // by itself will have a `this` of a previously constructed chalk object. + if (!this || !(this instanceof Chalk) || this.template) { + const chalk = {}; + applyOptions(chalk, options); + + chalk.template = function () { + const args = [].slice.call(arguments); + return chalkTag.apply(null, [chalk.template].concat(args)); + }; + + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); + + chalk.template.constructor = Chalk; + + return chalk.template; + } + + applyOptions(this, options); +} + +// Use bright blue on Windows as the normal blue color is illegible +if (isSimpleWindowsTerm) { + ansiStyles.blue.open = '\u001B[94m'; +} + +for (const key of Object.keys(ansiStyles)) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); + + styles[key] = { + get() { + const codes = ansiStyles[key]; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], key); + } + }; +} + +ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); +for (const model of Object.keys(ansiStyles.color.ansi)) { + if (skipModels.has(model)) { + continue; + } + + styles[model] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.color.close, + closeRe: ansiStyles.color.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], model); + }; + } + }; +} + +ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); +for (const model of Object.keys(ansiStyles.bgColor.ansi)) { + if (skipModels.has(model)) { + continue; + } + + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.bgColor.close, + closeRe: ansiStyles.bgColor.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], model); + }; + } + }; +} + +const proto = Object.defineProperties(() => {}, styles); + +function build(_styles, key) { + const builder = function () { + return applyStyle.apply(builder, arguments); + }; + + builder._styles = _styles; + + const self = this; + + Object.defineProperty(builder, 'level', { + enumerable: true, + get() { + return self.level; + }, + set(level) { + self.level = level; + } + }); + + Object.defineProperty(builder, 'enabled', { + enumerable: true, + get() { + return self.enabled; + }, + set(enabled) { + self.enabled = enabled; + } + }); + + // See below for fix regarding invisible grey/dim combination on Windows + builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; + + // `__proto__` is used because we must return a function, but there is + // no way to create a function with a different prototype. + builder.__proto__ = proto; // eslint-disable-line no-proto + + return builder; +} + +function applyStyle() { + // Support varags, but simply cast to string in case there's only one arg + const args = arguments; + const argsLen = args.length; + let str = argsLen !== 0 && String(arguments[0]); + + if (argsLen > 1) { + // Don't slice `arguments`, it prevents V8 optimizations + for (let a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } + + if (!this.enabled || this.level <= 0 || !str) { + return str; + } + + // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, + // see https://github.com/chalk/chalk/issues/58 + // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. + const originalDim = ansiStyles.dim.open; + if (isSimpleWindowsTerm && this.hasGrey) { + ansiStyles.dim.open = ''; + } + + for (const code of this._styles.slice().reverse()) { + // Replace any instances already present with a re-opening code + // otherwise only the part of the string until said closing code + // will be colored, and the rest will simply be 'plain'. + str = code.open + str.replace(code.closeRe, code.open) + code.close; + + // Close the styling before a linebreak and reopen + // after next line to fix a bleed issue on macOS + // https://github.com/chalk/chalk/pull/92 + str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); + } + + // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue + ansiStyles.dim.open = originalDim; + + return str; +} + +function chalkTag(chalk, strings) { + const args = [].slice.call(arguments, 2); + + if (!Array.isArray(strings)) { + return strings.toString(); + } + + const parts = [strings.raw[0]]; + + for (let i = 1; i < strings.length; i++) { + parts.push(args[i - 1].toString().replace(/[{}]/g, '\\$&')); + parts.push(strings.raw[i]); + } + + return template(chalk, parts.join('')); +} + +Object.defineProperties(Chalk.prototype, styles); + +module.exports = Chalk(); // eslint-disable-line new-cap +module.exports.supportsColor = supportsColor; diff --git a/license b/license index fa7ceba..e7af2f7 100644 --- a/license +++ b/license @@ -1,6 +1,6 @@ MIT License -Copyright (c) Sindre Sorhus (https://sindresorhus.com) +Copyright (c) Sindre Sorhus (sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/media/screenshot.png b/media/screenshot.png deleted file mode 100644 index da9d89b..0000000 Binary files a/media/screenshot.png and /dev/null differ diff --git a/package.json b/package.json index c9e0dc5..b99d34f 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,20 @@ { "name": "chalk", - "version": "5.6.2", - "description": "Terminal string styling done right", + "version": "2.0.0", + "description": "Terminal string styling done right. Much color", "license": "MIT", "repository": "chalk/chalk", - "funding": "https://github.com/chalk/chalk?sponsor=1", - "type": "module", - "main": "./source/index.js", - "exports": "./source/index.js", - "imports": { - "#ansi-styles": "./source/vendor/ansi-styles/index.js", - "#supports-color": { - "node": "./source/vendor/supports-color/index.js", - "default": "./source/vendor/supports-color/browser.js" - } - }, - "types": "./source/index.d.ts", - "sideEffects": false, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=4" }, "scripts": { - "test": "xo && c8 ava && tsd", - "bench": "matcha benchmark.js" + "test": "xo && nyc mocha", + "bench": "matcha benchmark.js", + "coveralls": "nyc report --reporter=text-lcov | coveralls" }, "files": [ - "source", - "!source/index.test-d.ts" + "index.js", + "templates.js" ], "keywords": [ "color", @@ -36,6 +24,7 @@ "console", "cli", "string", + "str", "ansi", "style", "styles", @@ -50,34 +39,24 @@ "command-line", "text" ], + "dependencies": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + }, "devDependencies": { - "@types/node": "^16.11.10", - "ava": "^3.15.0", - "c8": "^7.10.0", - "color-convert": "^2.0.1", - "execa": "^6.0.0", - "log-update": "^5.0.0", + "coveralls": "^2.11.2", + "import-fresh": "^2.0.0", "matcha": "^0.7.0", - "tsd": "^0.19.0", - "xo": "^0.57.0", - "yoctodelay": "^2.0.0" + "mocha": "*", + "nyc": "^11.0.2", + "resolve-from": "^3.0.0", + "xo": "*" }, "xo": { - "rules": { - "unicorn/prefer-string-slice": "off", - "@typescript-eslint/consistent-type-imports": "off", - "@typescript-eslint/consistent-type-exports": "off", - "@typescript-eslint/consistent-type-definitions": "off", - "unicorn/expiring-todo-comments": "off" - } - }, - "c8": { - "reporter": [ - "text", - "lcov" - ], - "exclude": [ - "source/vendor" + "envs": [ + "node", + "mocha" ] } } diff --git a/readme.md b/readme.md index ce1f3f3..18590bd 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@



- Chalk + chalk


@@ -9,42 +9,39 @@ > Terminal string styling done right -[![Coverage Status](https://codecov.io/gh/chalk/chalk/branch/main/graph/badge.svg)](https://codecov.io/gh/chalk/chalk) -[![npm dependents](https://badgen.net/npm/dependents/chalk)](https://www.npmjs.com/package/chalk?activeTab=dependents) -[![Downloads](https://badgen.net/npm/dt/chalk)](https://www.npmjs.com/package/chalk) +[![Build Status](https://travis-ci.org/chalk/chalk.svg?branch=master)](https://travis-ci.org/chalk/chalk) [![Coverage Status](https://coveralls.io/repos/github/chalk/chalk/badge.svg?branch=master)](https://coveralls.io/github/chalk/chalk?branch=master) [![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) -![](media/screenshot.png) +[colors.js](https://github.com/Marak/colors.js) used to be the most popular string styling module, but it has serious deficiencies like extending `String.prototype` which causes all kinds of [problems](https://github.com/yeoman/yo/issues/68). Although there are other ones, they either do too much or not enough. -## Info +**Chalk is a clean and focused alternative.** + +![](https://github.com/chalk/ansi-styles/raw/master/screenshot.png) -- [Why not switch to a smaller coloring package?](https://github.com/chalk/chalk?tab=readme-ov-file#why-not-switch-to-a-smaller-coloring-package) -- See [yoctocolors](https://github.com/sindresorhus/yoctocolors) for a smaller alternative ## Highlights - Expressive API - Highly performant -- No dependencies - Ability to nest styles - [256/Truecolor color support](#256-and-truecolor-color-support) - Auto-detects color support - Doesn't extend `String.prototype` - Clean and focused - Actively maintained -- [Used by ~115,000 packages](https://www.npmjs.com/browse/depended/chalk) as of July 4, 2024 +- [Used by ~17,000 packages](https://www.npmjs.com/browse/depended/chalk) as of June 20th, 2017 + ## Install -```sh -npm install chalk +```console +$ npm install chalk ``` -**IMPORTANT:** Chalk 5 is ESM. If you want to use Chalk with TypeScript or a build tool, you will probably want to use Chalk 4 for now. [Read more.](https://github.com/chalk/chalk/releases/tag/v5.0.0) ## Usage ```js -import chalk from 'chalk'; +const chalk = require('chalk'); console.log(chalk.blue('Hello world!')); ``` @@ -52,12 +49,11 @@ console.log(chalk.blue('Hello world!')); Chalk comes with an easy to use composable API where you just chain and nest the styles you want. ```js -import chalk from 'chalk'; - +const chalk = require('chalk'); const log = console.log; // Combine styled and normal strings -log(chalk.blue('Hello') + ' World' + chalk.red('!')); +log(chalk.blue('Hello') + 'World' + chalk.red('!')); // Compose multiple styles using the chainable API log(chalk.blue.bgRed.bold('Hello world!')); @@ -82,7 +78,15 @@ RAM: ${chalk.green('40%')} DISK: ${chalk.yellow('70%')} `); +// ES2015 tagged template literal +log(chalk` +CPU: {red ${cpu.totalPercent}%} +RAM: {green ${ram.used / ram.total * 100}%} +DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} +`); + // Use RGB colors in terminal emulators that support it. +log(chalk.keyword('orange')('Yay for orange colored text!')); log(chalk.rgb(123, 45, 67).underline('Underlined reddish color')); log(chalk.hex('#DEADED').bold('Bold gray!')); ``` @@ -90,10 +94,10 @@ log(chalk.hex('#DEADED').bold('Bold gray!')); Easily define your own themes: ```js -import chalk from 'chalk'; +const chalk = require('chalk'); const error = chalk.bold.red; -const warning = chalk.hex('#FFA500'); // Orange color +const warning = chalk.keyword('orange'); console.log(error('Error!')); console.log(warning('Warning!')); @@ -102,13 +106,12 @@ console.log(warning('Warning!')); Take advantage of console.log [string substitution](https://nodejs.org/docs/latest/api/console.html#console_console_log_data_args): ```js -import chalk from 'chalk'; - const name = 'Sindre'; console.log(chalk.green('Hello %s'), name); //=> 'Hello Sindre' ``` + ## API ### chalk.`