diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 48ee960..0000000 --- a/.flowconfig +++ /dev/null @@ -1,6 +0,0 @@ -[ignore] -.*/node_modules/.* - -[options] -suppress_comment= \\(.\\|\n\\)*\\$ExpectError -include_warnings=true diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..baa12cf --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,5 @@ +github: sindresorhus +open_collective: sindresorhus +patreon: 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/.travis.yml b/.travis.yml index 5e3bcf3..1492647 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - '12' - '10' - '8' after_success: diff --git a/benchmark.js b/benchmark.js index f4e9cf3..dc24696 100644 --- a/benchmark.js +++ b/benchmark.js @@ -3,22 +3,48 @@ const chalk = require('.'); suite('chalk', () => { - set('iterations', 100000); + set('iterations', 1000000); - bench('single style', () => { + const chalkRed = chalk.red; + const chalkBgRed = chalk.bgRed; + const chalkBlueBgRed = chalk.blue.bgRed; + const chalkBlueBgRedBold = chalk.blue.bgRed.bold; + + 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); }); }); diff --git a/examples/screenshot.js b/examples/screenshot.js index 7d195a6..37f5850 100644 --- a/examples/screenshot.js +++ b/examples/screenshot.js @@ -1,6 +1,6 @@ 'use strict'; -const chalk = require('..'); const styles = require('ansi-styles'); +const chalk = require('..'); // Generates screenshot for (const key of Object.keys(styles)) { diff --git a/index.d.ts b/index.d.ts index 6256ce3..455ba9a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,276 +1,325 @@ -export const enum Level { +declare const enum LevelEnum { /** - * All colors disabled. - */ + All colors disabled. + */ None = 0, /** - * Basic 16 colors support. - */ + Basic 16 colors support. + */ Basic = 1, /** - * ANSI 256 colors support. - */ + ANSI 256 colors support. + */ Ansi256 = 2, /** - * Truecolor 16 million colors support. - */ + Truecolor 16 million colors support. + */ TrueColor = 3 } -export interface Options { - /** - * Enable or disable Chalk. - * - * @default true - */ - enabled?: boolean; +declare namespace chalk { + type Level = LevelEnum; + + interface Options { + /** + Enable or disable Chalk. + + @default true + */ + enabled?: boolean; + + /** + Specify the color support for Chalk. + By default, color support is automatically detected based on the environment. + */ + level?: Level; + } + + interface Instance { + /** + Return a new Chalk instance. + */ + new (options?: Options): Chalk; + } /** - * Specify the color support for Chalk. - * By default, color support is automatically detected based on the environment. - */ - level?: Level; -} + Detect whether the terminal supports color. + */ + interface ColorSupport { + /** + The color level used by Chalk. + */ + level: Level; -export interface Instance { - /** - * Return a new Chalk instance. - */ - new (options?: Options): Chalk; + /** + Return whether Chalk supports basic 16 colors. + */ + hasBasic: boolean; + + /** + Return whether Chalk supports ANSI 256 colors. + */ + has256: boolean; + + /** + Return whether Chalk supports Truecolor 16 million colors. + */ + has16m: boolean; + } + + interface ChalkFunction { + /** + Use a template string. + + @remarks Template literals are unsupported for nested calls (see [issue #341](https://github.com/chalk/chalk/issues/341)) + + @example + ``` + import chalk = require('chalk'); + + log(chalk` + CPU: {red ${cpu.totalPercent}%} + RAM: {green ${ram.used / ram.total * 100}%} + DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} + `); + ``` + */ + (text: TemplateStringsArray, ...placeholders: unknown[]): string; + + (...text: unknown[]): string; + } + + interface Chalk extends ChalkFunction { + /** + Return a new Chalk instance. + */ + Instance: Instance; + + /** + Enable or disable Chalk. + + @default true + */ + enabled: boolean; + + /** + The color support for Chalk. + By default, color support is automatically detected based on the environment. + */ + level: Level; + + /** + Use HEX value to set text color. + + @param color - Hexadecimal value representing the desired color. + + @example + ``` + import chalk = require('chalk'); + + chalk.hex('#DEADED'); + ``` + */ + hex(color: string): Chalk; + + /** + Use keyword color value to set text color. + + @param color - Keyword value representing the desired color. + + @example + ``` + import chalk = require('chalk'); + + chalk.keyword('orange'); + ``` + */ + keyword(color: string): Chalk; + + /** + Use RGB values to set text color. + */ + rgb(red: number, green: number, blue: number): Chalk; + + /** + Use HSL values to set text color. + */ + hsl(hue: number, saturation: number, lightness: number): Chalk; + + /** + Use HSV values to set text color. + */ + hsv(hue: number, saturation: number, value: number): Chalk; + + /** + Use HWB values to set text color. + */ + hwb(hue: number, whiteness: number, blackness: number): Chalk; + + /** + Use HEX value to set background color. + + @param color - Hexadecimal value representing the desired color. + + @example + ``` + import chalk = require('chalk'); + + chalk.bgHex('#DEADED'); + ``` + */ + bgHex(color: string): Chalk; + + /** + Use keyword color value to set background color. + + @param color - Keyword value representing the desired color. + + @example + ``` + import chalk = require('chalk'); + + chalk.bgKeyword('orange'); + ``` + */ + bgKeyword(color: string): Chalk; + + /** + Use RGB values to set background color. + */ + bgRgb(red: number, green: number, blue: number): Chalk; + + /** + Use HSL values to set background color. + */ + bgHsl(hue: number, saturation: number, lightness: number): Chalk; + + /** + Use HSV values to set background color. + */ + bgHsv(hue: number, saturation: number, value: number): Chalk; + + /** + Use HWB values to set background color. + */ + bgHwb(hue: number, whiteness: number, blackness: number): Chalk; + + /** + Modifier: Resets the current color chain. + */ + readonly reset: Chalk; + + /** + Modifier: Make text bold. + */ + readonly bold: Chalk; + + /** + Modifier: Emitting only a small amount of light. + */ + readonly dim: Chalk; + + /** + Modifier: Make text italic. (Not widely supported) + */ + readonly italic: Chalk; + + /** + Modifier: Make text underline. (Not widely supported) + */ + readonly underline: Chalk; + + /** + Modifier: Inverse background and foreground colors. + */ + readonly inverse: Chalk; + + /** + Modifier: Prints the text, but makes it invisible. + */ + readonly hidden: Chalk; + + /** + Modifier: Puts a horizontal line through the center of the text. (Not widely supported) + */ + readonly strikethrough: Chalk; + + /** + Modifier: Prints the text only when Chalk is enabled. + Can be useful for things that are purely cosmetic. + */ + readonly visible: Chalk; + + readonly black: Chalk; + readonly red: Chalk; + readonly green: Chalk; + readonly yellow: Chalk; + readonly blue: Chalk; + readonly magenta: Chalk; + readonly cyan: Chalk; + readonly white: Chalk; + + /* + Alias for `blackBright`. + */ + readonly gray: Chalk; + + /* + Alias for `blackBright`. + */ + readonly grey: Chalk; + + readonly blackBright: Chalk; + readonly redBright: Chalk; + readonly greenBright: Chalk; + readonly yellowBright: Chalk; + readonly blueBright: Chalk; + readonly magentaBright: Chalk; + readonly cyanBright: Chalk; + readonly whiteBright: Chalk; + + readonly bgBlack: Chalk; + readonly bgRed: Chalk; + readonly bgGreen: Chalk; + readonly bgYellow: Chalk; + readonly bgBlue: Chalk; + readonly bgMagenta: Chalk; + readonly bgCyan: Chalk; + readonly bgWhite: Chalk; + + /* + Alias for `bgBlackBright`. + */ + readonly bgGray: Chalk; + + /* + Alias for `bgBlackBright`. + */ + readonly bgGrey: Chalk; + + readonly bgBlackBright: Chalk; + readonly bgRedBright: Chalk; + readonly bgGreenBright: Chalk; + readonly bgYellowBright: Chalk; + readonly bgBlueBright: Chalk; + readonly bgMagentaBright: Chalk; + readonly bgCyanBright: Chalk; + readonly bgWhiteBright: Chalk; + } } /** - * Detect whether the terminal supports color. - */ -export interface ColorSupport { - /** - * The color level used by Chalk. - */ - level: Level; +Main Chalk object that allows to chain styles together. +Call the last one as a method with a string argument. +Order doesn't matter, and later styles take precedent in case of a conflict. +This simply means that `chalk.red.yellow.green` is equivalent to `chalk.green`. +*/ +declare const chalk: chalk.Chalk & chalk.ChalkFunction & { + supportsColor: chalk.ColorSupport | false; + Level: typeof LevelEnum; +}; - /** - * Return whether Chalk supports basic 16 colors. - */ - hasBasic: boolean; - - /** - * Return whether Chalk supports ANSI 256 colors. - */ - has256: boolean; - - /** - * Return whether Chalk supports Truecolor 16 million colors. - */ - has16m: boolean; -} - -export interface Chalk { - (...text: unknown[]): string; - - (text: TemplateStringsArray, ...placeholders: unknown[]): string; - - /** - * Return a new Chalk instance. - */ - Instance: Instance; - - /** - * Enable or disable Chalk. - * - * @default true - */ - enabled: boolean; - - /** - * The color support for Chalk. - * By default, color support is automatically detected based on the environment. - */ - level: Level; - - /** - * Use HEX value to set text color. - * - * @param color - Hexadecimal value representing the desired color. - * - * @example - * - * import chalk from 'chalk'; - * - * chalk.hex('#DEADED'); - */ - hex(color: string): this; - - /** - * Use keyword color value to set text color. - * - * @param color - Keyword value representing the desired color. - * - * @example - * - * import chalk from 'chalk'; - * - * chalk.keyword('orange'); - */ - keyword(color: string): this; - - /** - * Use RGB values to set text color. - */ - rgb(red: number, green: number, blue: number): this; - - /** - * Use HSL values to set text color. - */ - hsl(hue: number, saturation: number, lightness: number): this; - - /** - * Use HSV values to set text color. - */ - hsv(hue: number, saturation: number, value: number): this; - - /** - * Use HWB values to set text color. - */ - hwb(hue: number, whiteness: number, blackness: number): this; - - /** - * Use HEX value to set background color. - * - * @param color - Hexadecimal value representing the desired color. - * - * @example - * - * import chalk from 'chalk'; - * - * chalk.bgHex('#DEADED'); - */ - bgHex(color: string): this; - - /** - * Use keyword color value to set background color. - * - * @param color - Keyword value representing the desired color. - * - * @example - * - * import chalk from 'chalk'; - * - * chalk.bgKeyword('orange'); - */ - bgKeyword(color: string): this; - - /** - * Use RGB values to set background color. - */ - bgRgb(red: number, green: number, blue: number): this; - - /** - * Use HSL values to set background color. - */ - bgHsl(hue: number, saturation: number, lightness: number): this; - - /** - * Use HSV values to set background color. - */ - bgHsv(hue: number, saturation: number, value: number): this; - - /** - * Use HWB values to set background color. - */ - bgHwb(hue: number, whiteness: number, blackness: number): this; - - /** - * Modifier: Resets the current color chain. - */ - readonly reset: this; - - /** - * Modifier: Make text bold. - */ - readonly bold: this; - - /** - * Modifier: Emitting only a small amount of light. - */ - readonly dim: this; - - /** - * Modifier: Make text italic. (Not widely supported) - */ - readonly italic: this; - - /** - * Modifier: Make text underline. (Not widely supported) - */ - readonly underline: this; - - /** - * Modifier: Inverse background and foreground colors. - */ - readonly inverse: this; - - /** - * Modifier: Prints the text, but makes it invisible. - */ - readonly hidden: this; - - /** - * Modifier: Puts a horizontal line through the center of the text. (Not widely supported) - */ - readonly strikethrough: this; - - /** - * Modifier: Prints the text only when Chalk is enabled. - * Can be useful for things that are purely cosmetic. - */ - readonly visible: this; - - readonly black: this; - readonly red: this; - readonly green: this; - readonly yellow: this; - readonly blue: this; - readonly magenta: this; - readonly cyan: this; - readonly white: this; - readonly gray: this; - readonly grey: this; - readonly blackBright: this; - readonly redBright: this; - readonly greenBright: this; - readonly yellowBright: this; - readonly blueBright: this; - readonly magentaBright: this; - readonly cyanBright: this; - readonly whiteBright: this; - - readonly bgBlack: this; - readonly bgRed: this; - readonly bgGreen: this; - readonly bgYellow: this; - readonly bgBlue: this; - readonly bgMagenta: this; - readonly bgCyan: this; - readonly bgWhite: this; - readonly bgBlackBright: this; - readonly bgRedBright: this; - readonly bgGreenBright: this; - readonly bgYellowBright: this; - readonly bgBlueBright: this; - readonly bgMagentaBright: this; - readonly bgCyanBright: this; - readonly bgWhiteBright: this; -} - -/** - * Main Chalk object that allows to chain styles together. - * Call the last one as a method with a string argument. - * Order doesn't matter, and later styles take precedent in case of a conflict. - * This simply means that `chalk.red.yellow.green` is equivalent to `chalk.green`. - */ -declare const chalk: Chalk & {supportsColor: ColorSupport}; - -export default chalk; +export = chalk; diff --git a/index.js.flow b/index.js.flow deleted file mode 100644 index f7e4abc..0000000 --- a/index.js.flow +++ /dev/null @@ -1,93 +0,0 @@ -// @flow strict - -type TemplateStringsArray = $ReadOnlyArray; - -export type Level = $Values<{ - None: 0, - Basic: 1, - Ansi256: 2, - TrueColor: 3 -}>; - -export type Options = {| - enabled?: boolean, - level?: Level -|}; - -export type ColorSupport = {| - level: Level, - hasBasic: boolean, - has256: boolean, - has16m: boolean -|}; - -export interface Chalk { - (...text: string[]): string, - (text: TemplateStringsArray, ...placeholders: mixed[]): string, - Instance(options?: Options): Chalk, - enabled: boolean, - level: Level, - rgb(red: number, green: number, blue: number): Chalk, - hsl(hue: number, saturation: number, lightness: number): Chalk, - hsv(hue: number, saturation: number, value: number): Chalk, - hwb(hue: number, whiteness: number, blackness: number): Chalk, - bgHex(color: string): Chalk, - bgKeyword(color: string): Chalk, - bgRgb(red: number, green: number, blue: number): Chalk, - bgHsl(hue: number, saturation: number, lightness: number): Chalk, - bgHsv(hue: number, saturation: number, value: number): Chalk, - bgHwb(hue: number, whiteness: number, blackness: number): Chalk, - hex(color: string): Chalk, - keyword(color: string): Chalk, - - +reset: Chalk, - +bold: Chalk, - +dim: Chalk, - +italic: Chalk, - +underline: Chalk, - +inverse: Chalk, - +hidden: Chalk, - +strikethrough: Chalk, - - +visible: Chalk, - - +black: Chalk, - +red: Chalk, - +green: Chalk, - +yellow: Chalk, - +blue: Chalk, - +magenta: Chalk, - +cyan: Chalk, - +white: Chalk, - +gray: Chalk, - +grey: Chalk, - +blackBright: Chalk, - +redBright: Chalk, - +greenBright: Chalk, - +yellowBright: Chalk, - +blueBright: Chalk, - +magentaBright: Chalk, - +cyanBright: Chalk, - +whiteBright: Chalk, - - +bgBlack: Chalk, - +bgRed: Chalk, - +bgGreen: Chalk, - +bgYellow: Chalk, - +bgBlue: Chalk, - +bgMagenta: Chalk, - +bgCyan: Chalk, - +bgWhite: Chalk, - +bgBlackBright: Chalk, - +bgRedBright: Chalk, - +bgGreenBright: Chalk, - +bgYellowBright: Chalk, - +bgBlueBright: Chalk, - +bgMagentaBright: Chalk, - +bgCyanBright: Chalk, - +bgWhiteBright: Chalk, - - supportsColor: ColorSupport -}; - -declare module.exports: Chalk; diff --git a/index.test-d.ts b/index.test-d.ts index 0b26e9c..fd19d04 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,27 +1,31 @@ -import {expectType} from 'tsd-check'; -import chalk, {Level, Chalk, ColorSupport} from '.'; +import {expectType, expectError} from 'tsd'; +import chalk = require('.'); // - Helpers - -type colorReturn = Chalk & {supportsColor: ColorSupport}; +type colorReturn = chalk.Chalk & {supportsColor?: never}; // - Level - -expectType(Level.None); -expectType(Level.Basic); -expectType(Level.Ansi256); -expectType(Level.TrueColor); +expectType(chalk.Level.None); +expectType(chalk.Level.Basic); +expectType(chalk.Level.Ansi256); +expectType(chalk.Level.TrueColor); // - supportsColor - -expectType(chalk.supportsColor.hasBasic); -expectType(chalk.supportsColor.has256); -expectType(chalk.supportsColor.has16m); +expectType(chalk.supportsColor); +expectType((chalk.supportsColor as chalk.ColorSupport).hasBasic); +expectType((chalk.supportsColor as chalk.ColorSupport).has256); +expectType((chalk.supportsColor as chalk.ColorSupport).has16m); + +// -- `supportsColor` is not a member of the Chalk interface -- +expectError(chalk.reset.supportsColor); // - Chalk - // -- Instance -- -expectType(new chalk.Instance({level: 1})); +expectType(new chalk.Instance({level: 1})); // -- Properties -- expectType(chalk.enabled); -expectType(chalk.level); +expectType(chalk.level); // -- Template literal -- expectType(chalk``); diff --git a/package.json b/package.json index bbc1634..b6cbdd4 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,17 @@ "description": "Terminal string styling done right", "license": "MIT", "repository": "chalk/chalk", + "main": "source", "engines": { "node": ">=8" }, "scripts": { - "test": "xo && nyc ava && tsd-check && flow", + "test": "xo && nyc ava && tsd", "bench": "matcha benchmark.js" }, "files": [ - "index.js", - "templates.js", - "index.d.ts", - "index.js.flow" + "source", + "index.d.ts" ], "keywords": [ "color", @@ -41,27 +40,18 @@ "text" ], "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^6.1.0" + "ansi-styles": "^4.0.0", + "supports-color": "^7.0.0" }, "devDependencies": { - "@sindresorhus/tsconfig": "^0.2.1", - "ava": "^1.3.1", - "coveralls": "^3.0.3", - "execa": "^1.0.0", - "flow-bin": "^0.94.0", - "import-fresh": "^3.0.0", + "ava": "^2.2.0", + "coveralls": "^3.0.5", + "execa": "^2.0.3", + "import-fresh": "^3.1.0", "matcha": "^0.7.0", - "nyc": "^13.3.0", - "resolve-from": "^4.0.0", - "tsd-check": "^0.3.0", + "nyc": "^14.1.1", + "resolve-from": "^5.0.0", + "tsd": "^0.7.4", "xo": "^0.24.0" - }, - "types": "index.d.ts", - "xo": { - "ignores": [ - "test/_flow.js" - ] } } diff --git a/readme.md b/readme.md index 1e3b9bb..ede4ee1 100644 --- a/readme.md +++ b/readme.md @@ -9,23 +9,11 @@ > 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) [![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) [![](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/xojs/xo) ![TypeScript- and Flow-ready](https://img.shields.io/npm/types/chalk.svg) +[![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) [![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) [![](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/xojs/xo) ![TypeScript-ready](https://img.shields.io/npm/types/chalk.svg) ---- - -
- - Get professional support for Chalk with a Tidelift subscription - -
- - Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. -
-
- ---- +**This readme reflects the next major version that is currently in development. You probably want [the v2 readme](https://www.npmjs.com/package/chalk).** ## Highlights @@ -47,10 +35,6 @@ $ npm install chalk ``` - - - - ## Usage @@ -169,7 +153,7 @@ Levels are as follows: Detect whether the terminal [supports color](https://github.com/chalk/supports-color). Used internally and handled for you, but exposed for convenience. -Can be overridden by the user with the flags `--color` and `--no-color`. For situations where using `--color` is not possible, add the environment variable `FORCE_COLOR=1` to forcefully enable color or `FORCE_COLOR=0` to forcefully disable. The use of `FORCE_COLOR` overrides all other color support checks. +Can be overridden by the user with the flags `--color` and `--no-color`. For situations where using `--color` is not possible, use the environment variable `FORCE_COLOR=1` (level 1), `FORCE_COLOR=2` (level 2), or `FORCE_COLOR=3` (level 3) to forcefully enable color, or `FORCE_COLOR=0` to forcefully disable. The use of `FORCE_COLOR` overrides all other color support checks. Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color=16m` flags, respectively. @@ -194,11 +178,11 @@ Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color= - `red` - `green` - `yellow` -- `blue` *(On Windows the bright version is used since normal blue is illegible)* +- `blue` - `magenta` - `cyan` - `white` -- `gray` ("bright black") +- `blackBright` (alias: `gray`, `grey`) - `redBright` - `greenBright` - `yellowBright` @@ -217,7 +201,7 @@ Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color= - `bgMagenta` - `bgCyan` - `bgWhite` -- `bgBlackBright` +- `bgBlackBright` (alias: `bgGray`, `bgGrey`) - `bgRedBright` - `bgGreenBright` - `bgYellowBright` @@ -238,8 +222,8 @@ const miles = 18; const calculateFeet = miles => miles * 5280; console.log(chalk` - There are {bold 5280 feet} in a mile. - In {bold ${miles} miles}, there are {green.bold ${calculateFeet(miles)} feet}. + There are {bold 5280 feet} in a mile. + In {bold ${miles} miles}, there are {green.bold ${calculateFeet(miles)} feet}. `); ``` @@ -297,11 +281,6 @@ If you're on Windows, do yourself a favor and use [`cmder`](http://cmder.net/) i [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) and the package is unmaintained. Although there are other packages, they either do too much or not enough. Chalk is a clean and focused alternative. -## Security - -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. - - ## Related - [chalk-cli](https://github.com/chalk/chalk-cli) - CLI for this module @@ -326,6 +305,14 @@ To report a security vulnerability, please use the [Tidelift security contact](h - [Josh Junon](https://github.com/qix-) -## License +--- -MIT +
+ + Get professional support for Chalk with a Tidelift subscription + +
+ + Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. +
+
diff --git a/index.js b/source/index.js similarity index 54% rename from index.js rename to source/index.js index 18a4a5c..e8d47c2 100644 --- a/index.js +++ b/source/index.js @@ -1,8 +1,11 @@ 'use strict'; -const escapeStringRegexp = require('escape-string-regexp'); const ansiStyles = require('ansi-styles'); const {stdout: stdoutColor} = require('supports-color'); -const template = require('./templates.js'); +const template = require('./templates'); +const { + stringReplaceAll, + stringEncaseCRLFWithFirstIndex +} = require('./util'); // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ @@ -57,22 +60,23 @@ function Chalk(options) { } for (const [styleName, style] of Object.entries(ansiStyles)) { - style.closeRe = new RegExp(escapeStringRegexp(style.close), 'g'); - styles[styleName] = { get() { - return createBuilder(this, [...(this._styles || []), style], this._isEmpty); + const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); + Object.defineProperty(this, styleName, {value: builder}); + return builder; } }; } styles.visible = { get() { - return createBuilder(this, this._styles || [], true); + const builder = createBuilder(this, this._styler, true); + Object.defineProperty(this, 'visible', {value: builder}); + return builder; } }; -ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); for (const model of Object.keys(ansiStyles.color.ansi)) { if (skipModels.has(model)) { continue; @@ -82,19 +86,13 @@ for (const model of Object.keys(ansiStyles.color.ansi)) { get() { const {level} = this; return function (...arguments_) { - const open = ansiStyles.color[levelMapping[level]][model](...arguments_); - const codes = { - open, - close: ansiStyles.color.close, - closeRe: ansiStyles.color.closeRe - }; - return createBuilder(this, [...(this._styles || []), codes], this._isEmpty); + const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); + return createBuilder(this, styler, this._isEmpty); }; } }; } -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); for (const model of Object.keys(ansiStyles.bgColor.ansi)) { if (skipModels.has(model)) { continue; @@ -105,72 +103,105 @@ for (const model of Object.keys(ansiStyles.bgColor.ansi)) { get() { const {level} = this; return function (...arguments_) { - const open = ansiStyles.bgColor[levelMapping[level]][model](...arguments_); - const codes = { - open, - close: ansiStyles.bgColor.close, - closeRe: ansiStyles.bgColor.closeRe - }; - return createBuilder(this, [...(this._styles || []), codes], this._isEmpty); + const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler); + return createBuilder(this, styler, this._isEmpty); }; } }; } -const proto = Object.defineProperties(() => {}, styles); - -const createBuilder = (self, _styles, _isEmpty) => { - const builder = (...arguments_) => applyStyle(builder, ...arguments_); - builder._styles = _styles; - builder._isEmpty = _isEmpty; - - Object.defineProperty(builder, 'level', { +const proto = Object.defineProperties(() => {}, { + ...styles, + level: { enumerable: true, get() { - return self.level; + return this._generator.level; }, set(level) { - self.level = level; + this._generator.level = level; } - }); - - Object.defineProperty(builder, 'enabled', { + }, + enabled: { enumerable: true, get() { - return self.enabled; + return this._generator.enabled; }, set(enabled) { - self.enabled = enabled; + this._generator.enabled = enabled; } - }); + } +}); + +const createStyler = (open, close, parent) => { + let openAll; + let closeAll; + if (parent === undefined) { + openAll = open; + closeAll = close; + } else { + openAll = parent.openAll + open; + closeAll = close + parent.closeAll; + } + + return { + open, + close, + openAll, + closeAll, + parent + }; +}; + +const createBuilder = (self, _styler, _isEmpty) => { + const builder = (...arguments_) => { + // Single argument is hot path, implicit coercion is faster than anything + // eslint-disable-next-line no-implicit-coercion + return applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' ')); + }; // `__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 + builder._generator = self; + builder._styler = _styler; + builder._isEmpty = _isEmpty; + return builder; }; -const applyStyle = (self, ...arguments_) => { - let string = arguments_.join(' '); - +const applyStyle = (self, string) => { if (!self.enabled || self.level <= 0 || !string) { return self._isEmpty ? '' : string; } - for (const code of self._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'. - string = code.open + string.replace(code.closeRe, code.close + code.open) + code.close; + let styler = self._styler; - // 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 - string = string.replace(/\r?\n/g, `${code.close}$&${code.open}`); + if (styler === undefined) { + return string; } - return string; + const {openAll, closeAll} = styler; + if (string.indexOf('\u001B') !== -1) { + while (styler !== undefined) { + // 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'. + string = stringReplaceAll(string, styler.close, styler.open); + + styler = styler.parent; + } + } + + // We can move both next actions out of loop, because remaining actions in loop won't have + // any/visible effect on parts we add here. 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 + const lfIndex = string.indexOf('\n'); + if (lfIndex !== -1) { + string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); + } + + return openAll + string + closeAll; }; const chalkTag = (chalk, ...strings) => { @@ -199,4 +230,3 @@ Object.defineProperties(Chalk.prototype, styles); module.exports = Chalk(); // eslint-disable-line new-cap module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript diff --git a/templates.js b/source/templates.js similarity index 85% rename from templates.js rename to source/templates.js index 6cdddea..fe94642 100644 --- a/templates.js +++ b/source/templates.js @@ -1,8 +1,8 @@ 'use strict'; -const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; +const TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; -const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; +const ESCAPE_REGEX = /\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.)|([^\\])/gi; const ESCAPES = new Map([ ['n', '\n'], @@ -18,10 +18,17 @@ const ESCAPES = new Map([ ]); function unescape(c) { - if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { + const u = c[0] === 'u'; + const bracket = c[1] === '{'; + + if ((u && !bracket && c.length === 5) || (c[0] === 'x' && c.length === 3)) { return String.fromCharCode(parseInt(c.slice(1), 16)); } + if (u && bracket) { + return String.fromCodePoint(parseInt(c.slice(2, -1), 16)); + } + return ESCAPES.get(c) || c; } diff --git a/source/util.js b/source/util.js new file mode 100644 index 0000000..ca466fd --- /dev/null +++ b/source/util.js @@ -0,0 +1,39 @@ +'use strict'; + +const stringReplaceAll = (string, substring, replacer) => { + let index = string.indexOf(substring); + if (index === -1) { + return string; + } + + const substringLength = substring.length; + let endIndex = 0; + let returnValue = ''; + do { + returnValue += string.substr(endIndex, index - endIndex) + substring + replacer; + endIndex = index + substringLength; + index = string.indexOf(substring, endIndex); + } while (index !== -1); + + returnValue += string.substr(endIndex); + return returnValue; +}; + +const stringEncaseCRLFWithFirstIndex = (string, prefix, postfix, index) => { + let endIndex = 0; + let returnValue = ''; + do { + const gotCR = string[index - 1] === '\r'; + returnValue += string.substr(endIndex, (gotCR ? index - 1 : index) - endIndex) + prefix + (gotCR ? '\r\n' : '\n') + postfix; + endIndex = index + 1; + index = string.indexOf('\n', endIndex); + } while (index !== -1); + + returnValue += string.substr(endIndex); + return returnValue; +}; + +module.exports = { + stringReplaceAll, + stringEncaseCRLFWithFirstIndex +}; diff --git a/test/_fixture.js b/test/_fixture.js index 5f1771d..214a86c 100644 --- a/test/_fixture.js +++ b/test/_fixture.js @@ -1,4 +1,4 @@ 'use strict'; -const chalk = require('..'); +const chalk = require('../source'); console.log(chalk.hex('#ff6159')('test')); diff --git a/test/_flow.js b/test/_flow.js deleted file mode 100644 index dbb6dd0..0000000 --- a/test/_flow.js +++ /dev/null @@ -1,86 +0,0 @@ -// @flow -import chalk from '..'; - -// $ExpectError (Can't have typo in option name) -new chalk.Instance({levl: 1}); -new chalk.Instance({level: 1}); - -// $ExpectError (Option must have proper type) -new chalk.Instance({enabled: 'true'}); -new chalk.Instance({enabled: true}); - -// $ExpectError (Can't have typo in chalk method) -chalk.rd('foo'); -chalk.red('foo'); - -// $ExpectError (Can't have typo in chalk method) -chalk.gren`foo`; -chalk.green`foo`; - -// $ExpectError (Can't have typo in chalk method) -chalk.red.bgBlu.underline('foo'); -chalk.red.bgBlue.underline('foo'); - -// $ExpectError (Level must be 0, 1, 2, or 3) -const badCtx = chalk.Instance({level: 4}); -const ctx = chalk.Instance({level: 3}); - -// $ExpectError (Can't have typo in method name) -ctx.gry('foo'); -ctx.grey('foo'); - -// $ExpectError (Can't have typo in method name) -ctx`foo`.value(); -ctx`foo`.valueOf(); - -// $ExpectError (Can't have typo in property name) -chalk.abled = true; -chalk.enabled = true; - -// $ExpectError (Can't use invalid Level for property setter) -chalk.level = 10; -chalk.level = 1; - -const chalkInstance = new chalk.Instance(); - -// $ExpectError (Can't have typo in method name) -chalkInstance.blu('foo'); -chalkInstance.blue('foo'); -chalkInstance`foo`; - -// $ExpectError (Can't have typo in method name) -chalk.keywrd('orange').bgBlue('foo'); -chalk.keyword('orange').bgBlue('foo'); - -// $ExpectError (rgb should take in 3 numbers) -chalk.rgb(1, 14).bgBlue('foo'); -chalk.rgb(1, 14, 9).bgBlue('foo'); - -// $ExpectError (hsl should take in 3 numbers) -chalk.hsl(1, 14, '9').bgBlue('foo'); -chalk.hsl(1, 14, 9).bgBlue('foo'); - -// $ExpectError (hsv should take in 3 numbers) -chalk.hsv(1, 14).bgBlue('foo'); -chalk.hsv(1, 14, 9).bgBlue('foo'); - -// $ExpectError (hwb should take in 3 numbers) -chalk.hwb(1, 14).bgBlue('foo'); -chalk.hwb(1, 14, 9).bgBlue('foo'); - -// $ExpectError (Can't have typo in method name) -chalk.visibl('foo'); -chalk.visible('foo'); - -// $ExpectError (Can't have typo in method name) -chalk.red.visibl('foo'); -chalk.red.visible('foo'); -chalk.visible.red('foo'); - -// $ExpectError (Can't write to readonly property) -chalk.black = 'foo'; -chalk.black; - -// $ExpectError (Can't write to readonly property) -chalk.reset = 'foo'; -console.log(chalk.reset); diff --git a/test/chalk.js b/test/chalk.js index 518be6a..2992a09 100644 --- a/test/chalk.js +++ b/test/chalk.js @@ -3,7 +3,7 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const chalk = require('..'); +const chalk = require('../source'); console.log('TERM:', process.env.TERM || '[none]'); console.log('platform:', process.platform || '[unknown]'); @@ -82,6 +82,10 @@ test('line breaks should open and close colors', t => { t.is(chalk.grey('hello\nworld'), '\u001B[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m'); }); +test('line breaks should open and close colors with CRLF', t => { + t.is(chalk.grey('hello\r\nworld'), '\u001B[90mhello\u001B[39m\r\n\u001B[90mworld\u001B[39m'); +}); + test('properly convert RGB to 16 colors on basic color terminals', t => { t.is(new chalk.Instance({level: 1}).hex('#FF0000')('hello'), '\u001B[91mhello\u001B[39m'); t.is(new chalk.Instance({level: 1}).bgHex('#FF0000')('hello'), '\u001B[101mhello\u001B[49m'); @@ -97,3 +101,7 @@ test('don\'t emit RGB codes if level is 0', t => { t.is(new chalk.Instance({level: 0}).hex('#FF0000')('hello'), 'hello'); t.is(new chalk.Instance({level: 0}).bgHex('#FF0000')('hello'), 'hello'); }); + +test('supports blackBright color', t => { + t.is(chalk.blackBright('foo'), '\u001B[90mfoo\u001B[39m'); +}); diff --git a/test/constructor.js b/test/constructor.js index 188001a..f4e9ca1 100644 --- a/test/constructor.js +++ b/test/constructor.js @@ -1,6 +1,6 @@ import test from 'ava'; -const chalk = require('..'); +const chalk = require('../source'); test('Chalk.constructor should throw an expected error', t => { const expectedError = t.throws(() => { diff --git a/test/enabled.js b/test/enabled.js index e596621..3d9c55f 100644 --- a/test/enabled.js +++ b/test/enabled.js @@ -3,7 +3,7 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const chalk = require('..'); +const chalk = require('../source'); test('don\'t output colors when manually disabled', t => { chalk.enabled = false; diff --git a/test/instance.js b/test/instance.js index e34bea7..8d2c4e1 100644 --- a/test/instance.js +++ b/test/instance.js @@ -3,7 +3,7 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const chalk = require('..'); +const chalk = require('../source'); test('create an isolated context where colors can be disabled (by level)', t => { const instance = new chalk.Instance({level: 0, enabled: true}); diff --git a/test/level.js b/test/level.js index 06fdb59..bb6df47 100644 --- a/test/level.js +++ b/test/level.js @@ -5,7 +5,7 @@ import execa from 'execa'; // Spoof supports-color require('./_supports-color')(__dirname); -const chalk = require('..'); +const chalk = require('../source'); test('don\'t output colors when manually disabled', t => { const oldLevel = chalk.level; @@ -40,5 +40,6 @@ test('propagate enable/disable changes from child colors', t => { }); test('disable colors if they are not supported', async t => { - t.is(await execa.stdout('node', [path.join(__dirname, '_fixture')]), 'test'); + const {stdout} = await execa.node(path.join(__dirname, '_fixture')); + t.is(stdout, 'test'); }); diff --git a/test/no-color-support.js b/test/no-color-support.js index 90d934d..dae9bda 100644 --- a/test/no-color-support.js +++ b/test/no-color-support.js @@ -8,7 +8,7 @@ require('./_supports-color')(__dirname, { has16m: false }); -const chalk = require('..'); +const chalk = require('../source'); test.failing('colors can be forced by using chalk.enabled', t => { chalk.enabled = true; diff --git a/test/template-literal.js b/test/template-literal.js index 1f388c8..ece1428 100644 --- a/test/template-literal.js +++ b/test/template-literal.js @@ -4,7 +4,7 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const chalk = require('..'); +const chalk = require('../source'); test('return an empty string for an empty literal', t => { const instance = new chalk.Instance(); @@ -168,3 +168,10 @@ test('should properly handle undefined template interpolated values', t => { t.is(instance`hello ${undefined}`, 'hello undefined'); t.is(instance`hello ${null}`, 'hello null'); }); + +test('should allow bracketed Unicode escapes', t => { + const instance = new chalk.Instance({level: 3}); + t.is(instance`\u{AB}`, '\u{AB}'); + t.is(instance`This is a {bold \u{AB681}} test`, 'This is a \u001B[1m\u{AB681}\u001B[22m test'); + t.is(instance`This is a {bold \u{10FFFF}} test`, 'This is a \u001B[1m\u{10FFFF}\u001B[22m test'); +}); diff --git a/test/visible.js b/test/visible.js index 01eb087..14ecc3c 100644 --- a/test/visible.js +++ b/test/visible.js @@ -3,7 +3,7 @@ import test from 'ava'; // Spoof supports-color require('./_supports-color')(__dirname); -const chalk = require('..'); +const chalk = require('../source'); test('visible: normal output when enabled', t => { const instance = new chalk.Instance({level: 3, enabled: true}); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 3d73ee9..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "@sindresorhus/tsconfig", - "compilerOptions": { - "noEmit": true, - "allowJs": true - } -}