diff --git a/.gitattributes b/.gitattributes index 391f0a4..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..1cd069f --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,4 @@ +github: [sindresorhus, Qix-] +open_collective: sindresorhus +tidelift: npm/chalk +custom: https://sindresorhus.com/donate diff --git a/.github/security.md b/.github/security.md new file mode 100644 index 0000000..5358dc5 --- /dev/null +++ b/.github/security.md @@ -0,0 +1,3 @@ +# 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 new file mode 100644 index 0000000..d588995 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,26 @@ +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/.travis.yml b/.travis.yml deleted file mode 100644 index ea5900d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: node_js -node_js: - - '8' - - '6' - - '4' -after_success: npm run coveralls diff --git a/benchmark.js b/benchmark.js index af90c06..c5c6e27 100644 --- a/benchmark.js +++ b/benchmark.js @@ -1,24 +1,57 @@ -/* globals set bench */ -'use strict'; -const chalk = require('.'); +/* globals suite, bench */ +import chalk from './index.js'; suite('chalk', () => { - set('iterations', 100000); + const chalkRed = chalk.red; + const chalkBgRed = chalk.bgRed; + const chalkBlueBgRed = chalk.blue.bgRed; + const chalkBlueBgRedBold = chalk.blue.bgRed.bold; - bench('single style', () => { + const blueStyledString = 'the fox jumps' + chalk.blue('over the lazy dog') + '!'; + + bench('1 style', () => { chalk.red('the fox jumps over the lazy dog'); }); - bench('several styles', () => { + bench('2 styles', () => { + chalk.blue.bgRed('the fox jumps over the lazy dog'); + }); + + bench('3 styles', () => { chalk.blue.bgRed.bold('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: 1 style', () => { + chalkRed('the fox jumps over the lazy dog'); }); - bench('nested styles', () => { - chalk.red('the fox jumps', chalk.underline.bgBlue('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`; }); }); diff --git a/examples/rainbow.js b/examples/rainbow.js index a4a2e0a..5b2b1d2 100644 --- a/examples/rainbow.js +++ b/examples/rainbow.js @@ -1,38 +1,38 @@ -'use strict'; -const chalk = require('..'); +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 ignoreChars = /[^!-~]/; +const ignoreChars = /[^!-~]/g; -function rainbow(str, offset) { - if (!str || str.length === 0) { - return str; +function rainbow(string, offset) { + if (!string || string.length === 0) { + return string; } - const hueStep = 360 / str.replace(ignoreChars, '').length; + const hueStep = 360 / string.replaceAll(ignoreChars, '').length; let hue = offset % 360; - const chars = []; - for (const c of str) { - if (c.match(ignoreChars)) { - chars.push(c); + const characters = []; + for (const character of string) { + if (ignoreChars.test(character)) { + characters.push(character); } else { - chars.push(chalk.hsl(hue, 100, 50)(c)); + characters.push(chalk.hex(convertColor.hsl.hex(hue, 100, 50))(character)); hue = (hue + hueStep) % 360; } } - return chars.join(''); + return characters.join(''); } -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); - -async function animateString(str) { - console.log(); - for (let i = 0; i < 360 * 5; i++) { - console.log('\u001B[1F\u001B[G ', rainbow(str, i)); - await sleep(2); // eslint-disable-line no-await-in-loop +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 } } console.log(); -animateString('We hope you enjoy the new version of Chalk 2! <3').then(() => console.log()); +await animateString('We hope you enjoy Chalk! <3'); +console.log(); diff --git a/examples/screenshot.js b/examples/screenshot.js index 7d195a6..9ad164b 100644 --- a/examples/screenshot.js +++ b/examples/screenshot.js @@ -1,18 +1,27 @@ -'use strict'; -const chalk = require('..'); -const styles = require('ansi-styles'); +import process from 'node:process'; +import styles from 'ansi-styles'; +import chalk from '../source/index.js'; // Generates screenshot for (const key of Object.keys(styles)) { - let ret = key; + let returnValue = key; - if (key === 'reset' || key === 'hidden' || key === 'grey') { + // 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') + ) { continue; } if (/^bg[^B]/.test(key)) { - ret = chalk.black(ret); + returnValue = chalk.black(returnValue); } - process.stdout.write(chalk[key](ret) + ' '); + process.stdout.write(chalk[key](returnValue) + ' '); } diff --git a/index.js b/index.js deleted file mode 100644 index de65bd4..0000000 --- a/index.js +++ /dev/null @@ -1,221 +0,0 @@ -'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 - const scLevel = supportsColor ? supportsColor.level : 0; - obj.level = options.level === undefined ? scLevel : 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 = String(arguments[0]); - - if (argsLen === 0) { - return ''; - } - - 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) { - if (!Array.isArray(strings)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); - } - - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; - - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(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; -module.exports.default = module.exports; // For TypeScript diff --git a/license b/license index e7af2f7..fa7ceba 100644 --- a/license +++ b/license @@ -1,6 +1,6 @@ MIT License -Copyright (c) Sindre Sorhus (sindresorhus.com) +Copyright (c) Sindre Sorhus (https://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 new file mode 100644 index 0000000..da9d89b Binary files /dev/null and b/media/screenshot.png differ diff --git a/package.json b/package.json index 426092f..c9e0dc5 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,32 @@ { "name": "chalk", - "version": "2.2.0", + "version": "5.6.2", "description": "Terminal string styling done right", "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": ">=4" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "scripts": { - "test": "xo && tsc --project types && nyc ava", - "bench": "matcha benchmark.js", - "coveralls": "nyc report --reporter=text-lcov | coveralls" + "test": "xo && c8 ava && tsd", + "bench": "matcha benchmark.js" }, "files": [ - "index.js", - "templates.js", - "types/index.d.ts" + "source", + "!source/index.test-d.ts" ], "keywords": [ "color", @@ -25,7 +36,6 @@ "console", "cli", "string", - "str", "ansi", "style", "styles", @@ -40,27 +50,34 @@ "command-line", "text" ], - "dependencies": { - "ansi-styles": "^3.1.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^4.0.0" - }, "devDependencies": { - "ava": "*", - "coveralls": "^3.0.0", - "execa": "^0.8.0", - "import-fresh": "^2.0.0", + "@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", "matcha": "^0.7.0", - "nyc": "^11.0.2", - "resolve-from": "^4.0.0", - "typescript": "^2.5.3", - "xo": "*" + "tsd": "^0.19.0", + "xo": "^0.57.0", + "yoctodelay": "^2.0.0" }, - "types": "types/index.d.ts", "xo": { - "envs": [ - "node", - "mocha" + "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" ] } } diff --git a/readme.md b/readme.md index 7158d5a..ce1f3f3 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@



- chalk + Chalk


@@ -9,37 +9,42 @@ > Terminal string styling done right -[![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) +[![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) -### [See what's new in Chalk 2](https://github.com/chalk/chalk/releases/tag/v2.0.0) +![](media/screenshot.png) -![](https://github.com/chalk/ansi-styles/raw/master/screenshot.png) +## Info +- [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 ~17,000 packages](https://www.npmjs.com/browse/depended/chalk) as of June 20th, 2017 - +- [Used by ~115,000 packages](https://www.npmjs.com/browse/depended/chalk) as of July 4, 2024 ## Install -```console -$ npm install chalk +```sh +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 -const chalk = require('chalk'); +import chalk from 'chalk'; console.log(chalk.blue('Hello world!')); ``` @@ -47,11 +52,12 @@ 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 -const chalk = require('chalk'); +import chalk from '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!')); @@ -76,15 +82,7 @@ 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!')); ``` @@ -92,10 +90,10 @@ log(chalk.hex('#DEADED').bold('Bold gray!')); Easily define your own themes: ```js -const chalk = require('chalk'); +import chalk from 'chalk'; const error = chalk.bold.red; -const warning = chalk.keyword('orange'); +const warning = chalk.hex('#FFA500'); // Orange color console.log(error('Error!')); console.log(warning('Warning!')); @@ -104,12 +102,13 @@ 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.`