From 89bc5daf780d26cd084a076acd771117c5c2175a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 19 Feb 2026 14:33:43 +0100 Subject: [PATCH 01/34] Vendor chalk --- lib/Compile.js | 2 +- lib/Generate.js | 2 +- lib/RunTests.js | 2 +- lib/Supervisor.js | 2 +- lib/chalk.js | 229 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 - 6 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 lib/chalk.js diff --git a/lib/Compile.js b/lib/Compile.js index 67be3723..307335df 100644 --- a/lib/Compile.js +++ b/lib/Compile.js @@ -1,6 +1,6 @@ //@flow -const { supportsColor } = require('chalk'); +const { supportsColor } = require('./chalk'); const spawn = require('cross-spawn'); const ElmCompiler = require('./ElmCompiler'); const Report = require('./Report'); diff --git a/lib/Generate.js b/lib/Generate.js index e6aec134..71e81d13 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -1,6 +1,6 @@ // @flow -const { supportsColor } = require('chalk'); +const { supportsColor } = require('./chalk'); const fs = require('fs'); const path = require('path'); const { DependencyProvider } = require('./DependencyProvider.js'); diff --git a/lib/RunTests.js b/lib/RunTests.js index 42e34320..98aa06c0 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -1,6 +1,6 @@ // @flow -const chalk = require('chalk'); +const chalk = require('./chalk'); const chokidar = require('chokidar'); const path = require('path'); const readline = require('readline'); diff --git a/lib/Supervisor.js b/lib/Supervisor.js index 22ddbf2f..a6cef1d6 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -1,6 +1,6 @@ // @flow -const chalk = require('chalk'); +const chalk = require('./chalk'); const child_process = require('child_process'); const fs = require('fs'); const net = require('net'); diff --git a/lib/chalk.js b/lib/chalk.js new file mode 100644 index 00000000..75ec6635 --- /dev/null +++ b/lib/chalk.js @@ -0,0 +1,229 @@ +'use strict'; +const ansiStyles = require('ansi-styles'); +const {stdout: stdoutColor, stderr: stderrColor} = require('supports-color'); +const { + stringReplaceAll, + stringEncaseCRLFWithFirstIndex +} = require('./util'); + +const {isArray} = Array; + +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = [ + 'ansi', + 'ansi', + 'ansi256', + 'ansi16m' +]; + +const styles = Object.create(null); + +const applyOptions = (object, options = {}) => { + if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) { + throw new Error('The `level` option should be an integer from 0 to 3'); + } + + // Detect level if not set manually + const colorLevel = stdoutColor ? stdoutColor.level : 0; + object.level = options.level === undefined ? colorLevel : options.level; +}; + +class ChalkClass { + constructor(options) { + // eslint-disable-next-line no-constructor-return + return chalkFactory(options); + } +} + +const chalkFactory = options => { + const chalk = {}; + applyOptions(chalk, options); + + chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); + + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); + + chalk.template.constructor = () => { + throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.'); + }; + + chalk.template.Instance = ChalkClass; + + return chalk.template; +}; + +function Chalk(options) { + return chalkFactory(options); +} + +for (const [styleName, style] of Object.entries(ansiStyles)) { + styles[styleName] = { + get() { + const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); + Object.defineProperty(this, styleName, {value: builder}); + return builder; + } + }; +} + +styles.visible = { + get() { + const builder = createBuilder(this, this._styler, true); + Object.defineProperty(this, 'visible', {value: builder}); + return builder; + } +}; + +const usedModels = ['rgb', 'hex', 'keyword', 'hsl', 'hsv', 'hwb', 'ansi', 'ansi256']; + +for (const model of usedModels) { + styles[model] = { + get() { + const {level} = this; + return function (...arguments_) { + const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); + return createBuilder(this, styler, this._isEmpty); + }; + } + }; +} + +for (const model of usedModels) { + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const {level} = this; + return function (...arguments_) { + 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, + level: { + enumerable: true, + get() { + return this._generator.level; + }, + set(level) { + this._generator.level = level; + } + } +}); + +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_) => { + if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) { + // Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}` + return applyStyle(builder, chalkTag(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(' ')); + }; + + // We alter the prototype because we must return a function, but there is + // no way to create a function with a different prototype + Object.setPrototypeOf(builder, proto); + + builder._generator = self; + builder._styler = _styler; + builder._isEmpty = _isEmpty; + + return builder; +}; + +const applyStyle = (self, string) => { + if (self.level <= 0 || !string) { + return self._isEmpty ? '' : string; + } + + let styler = self._styler; + + if (styler === undefined) { + 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; +}; + +let template; +const chalkTag = (chalk, ...strings) => { + const [firstString] = strings; + + if (!isArray(firstString) || !isArray(firstString.raw)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return strings.join(' '); + } + + const arguments_ = strings.slice(1); + const parts = [firstString.raw[0]]; + + for (let i = 1; i < firstString.length; i++) { + parts.push( + String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), + String(firstString.raw[i]) + ); + } + + if (template === undefined) { + template = require('./templates'); + } + + return template(chalk, parts.join('')); +}; + +Object.defineProperties(Chalk.prototype, styles); + +const chalk = Chalk(); // eslint-disable-line new-cap +chalk.supportsColor = stdoutColor; +chalk.stderr = Chalk({level: stderrColor ? stderrColor.level : 0}); // eslint-disable-line new-cap +chalk.stderr.supportsColor = stderrColor; + +module.exports = chalk; diff --git a/package.json b/package.json index 67eafc8a..0b99a2fc 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ }, "homepage": "https://github.com/rtfeldman/node-test-runner#readme", "dependencies": { - "chalk": "^4.1.2", "chokidar": "^3.5.3", "commander": "^9.4.1", "cross-spawn": "^7.0.6", From 7b9598cefb2cf6833c97b0ea2e15e2d18ab4e1c0 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:55:47 +0100 Subject: [PATCH 02/34] Format chalk with prettier --- lib/chalk.js | 353 +++++++++++++++++++++++++++------------------------ 1 file changed, 189 insertions(+), 164 deletions(-) diff --git a/lib/chalk.js b/lib/chalk.js index 75ec6635..3bbaf8c7 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -1,229 +1,254 @@ 'use strict'; const ansiStyles = require('ansi-styles'); -const {stdout: stdoutColor, stderr: stderrColor} = require('supports-color'); -const { - stringReplaceAll, - stringEncaseCRLFWithFirstIndex -} = require('./util'); +const { stdout: stdoutColor, stderr: stderrColor } = require('supports-color'); +const { stringReplaceAll, stringEncaseCRLFWithFirstIndex } = require('./util'); -const {isArray} = Array; +const { isArray } = Array; // `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = [ - 'ansi', - 'ansi', - 'ansi256', - 'ansi16m' -]; +const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; const styles = Object.create(null); const applyOptions = (object, options = {}) => { - if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) { - throw new Error('The `level` option should be an integer from 0 to 3'); - } - - // Detect level if not set manually - const colorLevel = stdoutColor ? stdoutColor.level : 0; - object.level = options.level === undefined ? colorLevel : options.level; + if ( + options.level && + !( + Number.isInteger(options.level) && + options.level >= 0 && + options.level <= 3 + ) + ) { + throw new Error('The `level` option should be an integer from 0 to 3'); + } + + // Detect level if not set manually + const colorLevel = stdoutColor ? stdoutColor.level : 0; + object.level = options.level === undefined ? colorLevel : options.level; }; class ChalkClass { - constructor(options) { - // eslint-disable-next-line no-constructor-return - return chalkFactory(options); - } + constructor(options) { + // eslint-disable-next-line no-constructor-return + return chalkFactory(options); + } } -const chalkFactory = options => { - const chalk = {}; - applyOptions(chalk, options); +const chalkFactory = (options) => { + const chalk = {}; + applyOptions(chalk, options); - chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); + chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); - chalk.template.constructor = () => { - throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.'); - }; + chalk.template.constructor = () => { + throw new Error( + '`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.' + ); + }; - chalk.template.Instance = ChalkClass; + chalk.template.Instance = ChalkClass; - return chalk.template; + return chalk.template; }; function Chalk(options) { - return chalkFactory(options); + return chalkFactory(options); } for (const [styleName, style] of Object.entries(ansiStyles)) { - styles[styleName] = { - get() { - const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); - Object.defineProperty(this, styleName, {value: builder}); - return builder; - } - }; + styles[styleName] = { + get() { + const builder = createBuilder( + this, + createStyler(style.open, style.close, this._styler), + this._isEmpty + ); + Object.defineProperty(this, styleName, { value: builder }); + return builder; + }, + }; } styles.visible = { - get() { - const builder = createBuilder(this, this._styler, true); - Object.defineProperty(this, 'visible', {value: builder}); - return builder; - } + get() { + const builder = createBuilder(this, this._styler, true); + Object.defineProperty(this, 'visible', { value: builder }); + return builder; + }, }; -const usedModels = ['rgb', 'hex', 'keyword', 'hsl', 'hsv', 'hwb', 'ansi', 'ansi256']; +const usedModels = [ + 'rgb', + 'hex', + 'keyword', + 'hsl', + 'hsv', + 'hwb', + 'ansi', + 'ansi256', +]; for (const model of usedModels) { - styles[model] = { - get() { - const {level} = this; - return function (...arguments_) { - const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); - return createBuilder(this, styler, this._isEmpty); - }; - } - }; + styles[model] = { + get() { + const { level } = this; + return function (...arguments_) { + const styler = createStyler( + ansiStyles.color[levelMapping[level]][model](...arguments_), + ansiStyles.color.close, + this._styler + ); + return createBuilder(this, styler, this._isEmpty); + }; + }, + }; } for (const model of usedModels) { - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const {level} = this; - return function (...arguments_) { - const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler); - return createBuilder(this, styler, this._isEmpty); - }; - } - }; + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const { level } = this; + return function (...arguments_) { + 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, - level: { - enumerable: true, - get() { - return this._generator.level; - }, - set(level) { - this._generator.level = level; - } - } + ...styles, + level: { + enumerable: true, + get() { + return this._generator.level; + }, + set(level) { + this._generator.level = level; + }, + }, }); 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 - }; + 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_) => { - if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) { - // Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}` - return applyStyle(builder, chalkTag(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(' ')); - }; - - // We alter the prototype because we must return a function, but there is - // no way to create a function with a different prototype - Object.setPrototypeOf(builder, proto); - - builder._generator = self; - builder._styler = _styler; - builder._isEmpty = _isEmpty; - - return builder; + const builder = (...arguments_) => { + if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) { + // Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}` + return applyStyle(builder, chalkTag(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(' ') + ); + }; + + // We alter the prototype because we must return a function, but there is + // no way to create a function with a different prototype + Object.setPrototypeOf(builder, proto); + + builder._generator = self; + builder._styler = _styler; + builder._isEmpty = _isEmpty; + + return builder; }; const applyStyle = (self, string) => { - if (self.level <= 0 || !string) { - return self._isEmpty ? '' : string; - } - - let styler = self._styler; - - if (styler === undefined) { - 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; + if (self.level <= 0 || !string) { + return self._isEmpty ? '' : string; + } + + let styler = self._styler; + + if (styler === undefined) { + 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; }; let template; const chalkTag = (chalk, ...strings) => { - const [firstString] = strings; + const [firstString] = strings; - if (!isArray(firstString) || !isArray(firstString.raw)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return strings.join(' '); - } + if (!isArray(firstString) || !isArray(firstString.raw)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return strings.join(' '); + } - const arguments_ = strings.slice(1); - const parts = [firstString.raw[0]]; + const arguments_ = strings.slice(1); + const parts = [firstString.raw[0]]; - for (let i = 1; i < firstString.length; i++) { - parts.push( - String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), - String(firstString.raw[i]) - ); - } + for (let i = 1; i < firstString.length; i++) { + parts.push( + String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), + String(firstString.raw[i]) + ); + } - if (template === undefined) { - template = require('./templates'); - } + if (template === undefined) { + template = require('./templates'); + } - return template(chalk, parts.join('')); + return template(chalk, parts.join('')); }; Object.defineProperties(Chalk.prototype, styles); const chalk = Chalk(); // eslint-disable-line new-cap chalk.supportsColor = stdoutColor; -chalk.stderr = Chalk({level: stderrColor ? stderrColor.level : 0}); // eslint-disable-line new-cap +chalk.stderr = Chalk({ level: stderrColor ? stderrColor.level : 0 }); // eslint-disable-line new-cap chalk.stderr.supportsColor = stderrColor; module.exports = chalk; From 33f2a7a84198217bede5240d2995b89caaeee449 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 19 Feb 2026 15:15:11 +0100 Subject: [PATCH 03/34] Vendor supports-color --- lib/chalk.js | 2 +- lib/supports-color.js | 206 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 lib/supports-color.js diff --git a/lib/chalk.js b/lib/chalk.js index 3bbaf8c7..f7c6459a 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -1,6 +1,6 @@ 'use strict'; const ansiStyles = require('ansi-styles'); -const { stdout: stdoutColor, stderr: stderrColor } = require('supports-color'); +const { stdout: stdoutColor, stderr: stderrColor } = require('./supports-color'); const { stringReplaceAll, stringEncaseCRLFWithFirstIndex } = require('./util'); const { isArray } = Array; diff --git a/lib/supports-color.js b/lib/supports-color.js new file mode 100644 index 00000000..01e35ca9 --- /dev/null +++ b/lib/supports-color.js @@ -0,0 +1,206 @@ +'use strict'; +/** +Based on https://github.com/chalk/supports-color v10.2.2 +but adapted to use CommonJs. + +MIT License + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +const os = require('os'); +const tty = require('tty'); +const hasFlag = require('has-flag'); + +const {env} = process; + +let flagForceColor; +if ( + hasFlag('no-color') + || hasFlag('no-colors') + || hasFlag('color=false') + || hasFlag('color=never') +) { + flagForceColor = 0; +} else if ( + hasFlag('color') + || hasFlag('colors') + || hasFlag('color=true') + || hasFlag('color=always') +) { + flagForceColor = 1; +} + +function envForceColor() { + if (!('FORCE_COLOR' in env)) { + return; + } + + if (env.FORCE_COLOR === 'true') { + return 1; + } + + if (env.FORCE_COLOR === 'false') { + return 0; + } + + if (env.FORCE_COLOR.length === 0) { + return 1; + } + + const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); + + if (![0, 1, 2, 3].includes(level)) { + return; + } + + return level; +} + +function translateLevel(level) { + if (level === 0) { + return false; + } + + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3, + }; +} + +function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) { + const noFlagForceColor = envForceColor(); + if (noFlagForceColor !== undefined) { + flagForceColor = noFlagForceColor; + } + + const forceColor = sniffFlags ? flagForceColor : noFlagForceColor; + + if (forceColor === 0) { + return 0; + } + + if (sniffFlags) { + if (hasFlag('color=16m') + || hasFlag('color=full') + || hasFlag('color=truecolor')) { + return 3; + } + + if (hasFlag('color=256')) { + return 2; + } + } + + // Check for Azure DevOps pipelines. + // Has to be above the `!streamIsTTY` check. + if ('TF_BUILD' in env && 'AGENT_NAME' in env) { + return 1; + } + + if (haveStream && !streamIsTTY && forceColor === undefined) { + return 0; + } + + const min = forceColor || 0; + + if (env.TERM === 'dumb') { + return min; + } + + if (process.platform === 'win32') { + // Windows 10 build 10586 is the first Windows release that supports 256 colors. + // Windows 10 build 14931 is the first release that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(osRelease[0]) >= 10 + && Number(osRelease[2]) >= 10_586 + ) { + return Number(osRelease[2]) >= 14_931 ? 3 : 2; + } + + return 1; + } + + if ('CI' in env) { + if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => key in env)) { + return 3; + } + + if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } + + return min; + } + + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } + + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if (env.TERM === 'xterm-kitty') { + return 3; + } + + if (env.TERM === 'xterm-ghostty') { + return 3; + } + + if (env.TERM === 'wezterm') { + return 3; + } + + if ('TERM_PROGRAM' in env) { + const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': { + return version >= 3 ? 3 : 2; + } + + case 'Apple_Terminal': { + return 2; + } + // No default + } + } + + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + + if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + + if ('COLORTERM' in env) { + return 1; + } + + return min; +} + +function createSupportsColor(stream, options = {}) { + const level = _supportsColor(stream, { + streamIsTTY: stream && stream.isTTY, + ...options, + }); + + return translateLevel(level); +} + +module.exports = { + stdout: createSupportsColor({isTTY: tty.isatty(1)}), + stderr: createSupportsColor({isTTY: tty.isatty(2)}), +}; From 901128e75257db72fd59d432a5e6d80b4721d33b Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:57:36 +0100 Subject: [PATCH 04/34] Format supports-color with prettier --- lib/supports-color.js | 329 ++++++++++++++++++++++-------------------- 1 file changed, 170 insertions(+), 159 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 01e35ca9..9259c7ef 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -17,190 +17,201 @@ const os = require('os'); const tty = require('tty'); const hasFlag = require('has-flag'); -const {env} = process; +const { env } = process; let flagForceColor; if ( - hasFlag('no-color') - || hasFlag('no-colors') - || hasFlag('color=false') - || hasFlag('color=never') + hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false') || + hasFlag('color=never') ) { - flagForceColor = 0; + flagForceColor = 0; } else if ( - hasFlag('color') - || hasFlag('colors') - || hasFlag('color=true') - || hasFlag('color=always') + hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always') ) { - flagForceColor = 1; + flagForceColor = 1; } function envForceColor() { - if (!('FORCE_COLOR' in env)) { - return; - } + if (!('FORCE_COLOR' in env)) { + return; + } - if (env.FORCE_COLOR === 'true') { - return 1; - } + if (env.FORCE_COLOR === 'true') { + return 1; + } - if (env.FORCE_COLOR === 'false') { - return 0; - } + if (env.FORCE_COLOR === 'false') { + return 0; + } - if (env.FORCE_COLOR.length === 0) { - return 1; - } + if (env.FORCE_COLOR.length === 0) { + return 1; + } - const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); + const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); - if (![0, 1, 2, 3].includes(level)) { - return; - } + if (![0, 1, 2, 3].includes(level)) { + return; + } - return level; + return level; } function translateLevel(level) { - if (level === 0) { - return false; - } - - return { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3, - }; + if (level === 0) { + return false; + } + + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3, + }; } -function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) { - const noFlagForceColor = envForceColor(); - if (noFlagForceColor !== undefined) { - flagForceColor = noFlagForceColor; - } - - const forceColor = sniffFlags ? flagForceColor : noFlagForceColor; - - if (forceColor === 0) { - return 0; - } - - if (sniffFlags) { - if (hasFlag('color=16m') - || hasFlag('color=full') - || hasFlag('color=truecolor')) { - return 3; - } - - if (hasFlag('color=256')) { - return 2; - } - } - - // Check for Azure DevOps pipelines. - // Has to be above the `!streamIsTTY` check. - if ('TF_BUILD' in env && 'AGENT_NAME' in env) { - return 1; - } - - if (haveStream && !streamIsTTY && forceColor === undefined) { - return 0; - } - - const min = forceColor || 0; - - if (env.TERM === 'dumb') { - return min; - } - - if (process.platform === 'win32') { - // Windows 10 build 10586 is the first Windows release that supports 256 colors. - // Windows 10 build 14931 is the first release that supports 16m/TrueColor. - const osRelease = os.release().split('.'); - if ( - Number(osRelease[0]) >= 10 - && Number(osRelease[2]) >= 10_586 - ) { - return Number(osRelease[2]) >= 14_931 ? 3 : 2; - } - - return 1; - } - - if ('CI' in env) { - if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => key in env)) { - return 3; - } - - if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') { - return 1; - } - - return min; - } - - if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; - } - - if (env.COLORTERM === 'truecolor') { - return 3; - } - - if (env.TERM === 'xterm-kitty') { - return 3; - } - - if (env.TERM === 'xterm-ghostty') { - return 3; - } - - if (env.TERM === 'wezterm') { - return 3; - } - - if ('TERM_PROGRAM' in env) { - const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); - - switch (env.TERM_PROGRAM) { - case 'iTerm.app': { - return version >= 3 ? 3 : 2; - } - - case 'Apple_Terminal': { - return 2; - } - // No default - } - } - - if (/-256(color)?$/i.test(env.TERM)) { - return 2; - } - - if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { - return 1; - } - - if ('COLORTERM' in env) { - return 1; - } - - return min; +function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) { + const noFlagForceColor = envForceColor(); + if (noFlagForceColor !== undefined) { + flagForceColor = noFlagForceColor; + } + + const forceColor = sniffFlags ? flagForceColor : noFlagForceColor; + + if (forceColor === 0) { + return 0; + } + + if (sniffFlags) { + if ( + hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor') + ) { + return 3; + } + + if (hasFlag('color=256')) { + return 2; + } + } + + // Check for Azure DevOps pipelines. + // Has to be above the `!streamIsTTY` check. + if ('TF_BUILD' in env && 'AGENT_NAME' in env) { + return 1; + } + + if (haveStream && !streamIsTTY && forceColor === undefined) { + return 0; + } + + const min = forceColor || 0; + + if (env.TERM === 'dumb') { + return min; + } + + if (process.platform === 'win32') { + // Windows 10 build 10586 is the first Windows release that supports 256 colors. + // Windows 10 build 14931 is the first release that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10_586) { + return Number(osRelease[2]) >= 14_931 ? 3 : 2; + } + + return 1; + } + + if ('CI' in env) { + if ( + ['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some((key) => key in env) + ) { + return 3; + } + + if ( + ['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some( + (sign) => sign in env + ) || + env.CI_NAME === 'codeship' + ) { + return 1; + } + + return min; + } + + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } + + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if (env.TERM === 'xterm-kitty') { + return 3; + } + + if (env.TERM === 'xterm-ghostty') { + return 3; + } + + if (env.TERM === 'wezterm') { + return 3; + } + + if ('TERM_PROGRAM' in env) { + const version = Number.parseInt( + (env.TERM_PROGRAM_VERSION || '').split('.')[0], + 10 + ); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': { + return version >= 3 ? 3 : 2; + } + + case 'Apple_Terminal': { + return 2; + } + // No default + } + } + + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + + if ( + /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM) + ) { + return 1; + } + + if ('COLORTERM' in env) { + return 1; + } + + return min; } function createSupportsColor(stream, options = {}) { - const level = _supportsColor(stream, { - streamIsTTY: stream && stream.isTTY, - ...options, - }); + const level = _supportsColor(stream, { + streamIsTTY: stream && stream.isTTY, + ...options, + }); - return translateLevel(level); + return translateLevel(level); } module.exports = { - stdout: createSupportsColor({isTTY: tty.isatty(1)}), - stderr: createSupportsColor({isTTY: tty.isatty(2)}), + stdout: createSupportsColor({ isTTY: tty.isatty(1) }), + stderr: createSupportsColor({ isTTY: tty.isatty(2) }), }; From 9df75a0d1e0f3a73b8804e3bc770e2a552392233 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:00:56 +0100 Subject: [PATCH 05/34] Simplify chalk to the project's usage --- lib/chalk.js | 255 +++------------------------------------------------ 1 file changed, 11 insertions(+), 244 deletions(-) diff --git a/lib/chalk.js b/lib/chalk.js index f7c6459a..5919de6b 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -1,254 +1,21 @@ 'use strict'; -const ansiStyles = require('ansi-styles'); -const { stdout: stdoutColor, stderr: stderrColor } = require('./supports-color'); -const { stringReplaceAll, stringEncaseCRLFWithFirstIndex } = require('./util'); +const { stdout } = require('./supports-color'); -const { isArray } = Array; - -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; - -const styles = Object.create(null); - -const applyOptions = (object, options = {}) => { - if ( - options.level && - !( - Number.isInteger(options.level) && - options.level >= 0 && - options.level <= 3 - ) - ) { - throw new Error('The `level` option should be an integer from 0 to 3'); - } - - // Detect level if not set manually - const colorLevel = stdoutColor ? stdoutColor.level : 0; - object.level = options.level === undefined ? colorLevel : options.level; -}; - -class ChalkClass { - constructor(options) { - // eslint-disable-next-line no-constructor-return - return chalkFactory(options); - } -} - -const chalkFactory = (options) => { - const chalk = {}; - applyOptions(chalk, options); - - chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); - - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); - - chalk.template.constructor = () => { - throw new Error( - '`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.' - ); - }; - - chalk.template.Instance = ChalkClass; - - return chalk.template; +const colors = { + blue: { open: '\x1B[34m', close: '\x1B[39m' }, + red: { open: '\x1B[31m', close: '\x1B[39m' }, }; -function Chalk(options) { - return chalkFactory(options); -} - -for (const [styleName, style] of Object.entries(ansiStyles)) { - styles[styleName] = { - get() { - const builder = createBuilder( - this, - createStyler(style.open, style.close, this._styler), - this._isEmpty - ); - Object.defineProperty(this, styleName, { value: builder }); - return builder; - }, - }; -} - -styles.visible = { - get() { - const builder = createBuilder(this, this._styler, true); - Object.defineProperty(this, 'visible', { value: builder }); - return builder; - }, -}; - -const usedModels = [ - 'rgb', - 'hex', - 'keyword', - 'hsl', - 'hsv', - 'hwb', - 'ansi', - 'ansi256', -]; - -for (const model of usedModels) { - styles[model] = { - get() { - const { level } = this; - return function (...arguments_) { - const styler = createStyler( - ansiStyles.color[levelMapping[level]][model](...arguments_), - ansiStyles.color.close, - this._styler - ); - return createBuilder(this, styler, this._isEmpty); - }; - }, - }; -} - -for (const model of usedModels) { - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const { level } = this; - return function (...arguments_) { - 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, - level: { - enumerable: true, - get() { - return this._generator.level; - }, - set(level) { - this._generator.level = level; - }, - }, -}); +const chalk = { supportsColor: !!stdout }; -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_) => { - if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) { - // Called as a template literal, for example: chalk.red`2 + 3 = {bold ${2+3}}` - return applyStyle(builder, chalkTag(builder, ...arguments_)); +for (const [styleName, style] of Object.entries(colors)) { + chalk[styleName] = (string) => { + if (!supportsColor || !string) { + return string; } - // 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(' ') - ); + return style.open + string + style.close; }; - - // We alter the prototype because we must return a function, but there is - // no way to create a function with a different prototype - Object.setPrototypeOf(builder, proto); - - builder._generator = self; - builder._styler = _styler; - builder._isEmpty = _isEmpty; - - return builder; -}; - -const applyStyle = (self, string) => { - if (self.level <= 0 || !string) { - return self._isEmpty ? '' : string; - } - - let styler = self._styler; - - if (styler === undefined) { - 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; -}; - -let template; -const chalkTag = (chalk, ...strings) => { - const [firstString] = strings; - - if (!isArray(firstString) || !isArray(firstString.raw)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return strings.join(' '); - } - - const arguments_ = strings.slice(1); - const parts = [firstString.raw[0]]; - - for (let i = 1; i < firstString.length; i++) { - parts.push( - String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), - String(firstString.raw[i]) - ); - } - - if (template === undefined) { - template = require('./templates'); - } - - return template(chalk, parts.join('')); -}; - -Object.defineProperties(Chalk.prototype, styles); - -const chalk = Chalk(); // eslint-disable-line new-cap -chalk.supportsColor = stdoutColor; -chalk.stderr = Chalk({ level: stderrColor ? stderrColor.level : 0 }); // eslint-disable-line new-cap -chalk.stderr.supportsColor = stderrColor; +} module.exports = chalk; From 7b714cddfc38ed771a4e4d4a76bdf871d82beced Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:04:21 +0100 Subject: [PATCH 06/34] Inline has-flag dependency --- lib/supports-color.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 9259c7ef..4c5eeafa 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -15,7 +15,13 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ const os = require('os'); const tty = require('tty'); -const hasFlag = require('has-flag'); + +function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process.argv) { + const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); + const position = argv.indexOf(prefix + flag); + const terminatorPosition = argv.indexOf('--'); + return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); +} const { env } = process; From 1780c22a17e2cafbd305e4fc9cacdf8566ba4bbc Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:04:54 +0100 Subject: [PATCH 07/34] Simplify hasFlag --- lib/supports-color.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 4c5eeafa..4eb365f5 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -16,10 +16,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI const os = require('os'); const tty = require('tty'); -function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process.argv) { +function hasFlag(flag) { const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); - const position = argv.indexOf(prefix + flag); - const terminatorPosition = argv.indexOf('--'); + const position = process.argv.indexOf(prefix + flag); + const terminatorPosition = process.argv.indexOf('--'); return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); } From e798ae3907cc39459127e4264804838fccdb301c Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:05:45 +0100 Subject: [PATCH 08/34] Add -- to flags --- lib/supports-color.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 4eb365f5..e72c6070 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -17,27 +17,29 @@ const os = require('os'); const tty = require('tty'); function hasFlag(flag) { - const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); - const position = process.argv.indexOf(prefix + flag); - const terminatorPosition = process.argv.indexOf('--'); - return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); + const position = process.argv.indexOf(flag); + const terminatorPosition = process.argv.indexOf('--'); + return ( + position !== -1 && + (terminatorPosition === -1 || position < terminatorPosition) + ); } const { env } = process; let flagForceColor; if ( - hasFlag('no-color') || - hasFlag('no-colors') || - hasFlag('color=false') || - hasFlag('color=never') + hasFlag('--no-color') || + hasFlag('--no-colors') || + hasFlag('--color=false') || + hasFlag('--color=never') ) { flagForceColor = 0; } else if ( - hasFlag('color') || - hasFlag('colors') || - hasFlag('color=true') || - hasFlag('color=always') + hasFlag('--color') || + hasFlag('--colors') || + hasFlag('--color=true') || + hasFlag('--color=always') ) { flagForceColor = 1; } @@ -95,14 +97,14 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) { if (sniffFlags) { if ( - hasFlag('color=16m') || - hasFlag('color=full') || - hasFlag('color=truecolor') + hasFlag('--color=16m') || + hasFlag('--color=full') || + hasFlag('--color=truecolor') ) { return 3; } - if (hasFlag('color=256')) { + if (hasFlag('--color=256')) { return 2; } } From 49398a465095145e50c455035eec4cff4f9d0715 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:06:08 +0100 Subject: [PATCH 09/34] Ignore terminatorPosition --- lib/supports-color.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index e72c6070..7dd050db 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -17,12 +17,7 @@ const os = require('os'); const tty = require('tty'); function hasFlag(flag) { - const position = process.argv.indexOf(flag); - const terminatorPosition = process.argv.indexOf('--'); - return ( - position !== -1 && - (terminatorPosition === -1 || position < terminatorPosition) - ); + return process.argv.indexOf(flag) !== -1; } const { env } = process; From 24c12226b427bf0f2f031f315fd65d7c8567090d Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:08:52 +0100 Subject: [PATCH 10/34] Use includes() --- lib/supports-color.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 7dd050db..0e42be10 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -16,25 +16,21 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI const os = require('os'); const tty = require('tty'); -function hasFlag(flag) { - return process.argv.indexOf(flag) !== -1; -} - const { env } = process; let flagForceColor; if ( - hasFlag('--no-color') || - hasFlag('--no-colors') || - hasFlag('--color=false') || - hasFlag('--color=never') + process.argv.includes('--no-color') || + process.argv.includes('--no-colors') || + process.argv.includes('--color=false') || + process.argv.includes('--color=never') ) { flagForceColor = 0; } else if ( - hasFlag('--color') || - hasFlag('--colors') || - hasFlag('--color=true') || - hasFlag('--color=always') + process.argv.includes('--color') || + process.argv.includes('--colors') || + process.argv.includes('--color=true') || + process.argv.includes('--color=always') ) { flagForceColor = 1; } @@ -92,14 +88,14 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) { if (sniffFlags) { if ( - hasFlag('--color=16m') || - hasFlag('--color=full') || - hasFlag('--color=truecolor') + process.argv.includes('--color=16m') || + process.argv.includes('--color=full') || + process.argv.includes('--color=truecolor') ) { return 3; } - if (hasFlag('--color=256')) { + if (process.argv.includes('--color=256')) { return 2; } } From 8467e1f1ef10492e7dff75bf15629449d40b60ed Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:26:52 +0100 Subject: [PATCH 11/34] Simplify supports-color to project's usage --- lib/chalk.js | 4 ++-- lib/supports-color.js | 20 +++++--------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/chalk.js b/lib/chalk.js index 5919de6b..a5baf4d6 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -1,12 +1,12 @@ 'use strict'; -const { stdout } = require('./supports-color'); +const supportsColor = require('./supports-color'); const colors = { blue: { open: '\x1B[34m', close: '\x1B[39m' }, red: { open: '\x1B[31m', close: '\x1B[39m' }, }; -const chalk = { supportsColor: !!stdout }; +const chalk = { supportsColor }; for (const [styleName, style] of Object.entries(colors)) { chalk[styleName] = (string) => { diff --git a/lib/supports-color.js b/lib/supports-color.js index 0e42be10..624f6b92 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -1,7 +1,7 @@ 'use strict'; /** Based on https://github.com/chalk/supports-color v10.2.2 -but adapted to use CommonJs. +but adapted to use CommonJs and simplified to our needs (only knowing whether stdout supports colors). MIT License @@ -62,16 +62,7 @@ function envForceColor() { } function translateLevel(level) { - if (level === 0) { - return false; - } - - return { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3, - }; + return level !== 0; } function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) { @@ -210,7 +201,6 @@ function createSupportsColor(stream, options = {}) { return translateLevel(level); } -module.exports = { - stdout: createSupportsColor({ isTTY: tty.isatty(1) }), - stderr: createSupportsColor({ isTTY: tty.isatty(2) }), -}; +const supportsColor = createSupportsColor({ isTTY: tty.isatty(1) }); + +module.exports = supportsColor; From 04f7e885cad339e356d7d9c6a21e7dae57688d4a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:33:03 +0100 Subject: [PATCH 12/34] Remove sniffFlags --- lib/supports-color.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 624f6b92..704e1679 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -65,30 +65,28 @@ function translateLevel(level) { return level !== 0; } -function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) { +function _supportsColor(haveStream, { streamIsTTY } = {}) { const noFlagForceColor = envForceColor(); if (noFlagForceColor !== undefined) { flagForceColor = noFlagForceColor; } - const forceColor = sniffFlags ? flagForceColor : noFlagForceColor; + const forceColor = flagForceColor; if (forceColor === 0) { return 0; } - if (sniffFlags) { - if ( - process.argv.includes('--color=16m') || - process.argv.includes('--color=full') || - process.argv.includes('--color=truecolor') - ) { - return 3; - } + if ( + process.argv.includes('--color=16m') || + process.argv.includes('--color=full') || + process.argv.includes('--color=truecolor') + ) { + return 3; + } - if (process.argv.includes('--color=256')) { - return 2; - } + if (process.argv.includes('--color=256')) { + return 2; } // Check for Azure DevOps pipelines. From 67278172334523315dffae1cc96e8545a461c4b5 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:35:13 +0100 Subject: [PATCH 13/34] Move flagForceColor to inside _supportsColor --- lib/supports-color.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 704e1679..8c0375b7 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -18,23 +18,6 @@ const tty = require('tty'); const { env } = process; -let flagForceColor; -if ( - process.argv.includes('--no-color') || - process.argv.includes('--no-colors') || - process.argv.includes('--color=false') || - process.argv.includes('--color=never') -) { - flagForceColor = 0; -} else if ( - process.argv.includes('--color') || - process.argv.includes('--colors') || - process.argv.includes('--color=true') || - process.argv.includes('--color=always') -) { - flagForceColor = 1; -} - function envForceColor() { if (!('FORCE_COLOR' in env)) { return; @@ -66,6 +49,23 @@ function translateLevel(level) { } function _supportsColor(haveStream, { streamIsTTY } = {}) { + let flagForceColor; + if ( + process.argv.includes('--no-color') || + process.argv.includes('--no-colors') || + process.argv.includes('--color=false') || + process.argv.includes('--color=never') + ) { + flagForceColor = 0; + } else if ( + process.argv.includes('--color') || + process.argv.includes('--colors') || + process.argv.includes('--color=true') || + process.argv.includes('--color=always') + ) { + flagForceColor = 1; + } + const noFlagForceColor = envForceColor(); if (noFlagForceColor !== undefined) { flagForceColor = noFlagForceColor; From 488ca1fbdddf68a3321960f795cb45f78eae2fe2 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:37:21 +0100 Subject: [PATCH 14/34] Move computation for flagForceColor in else condition --- lib/supports-color.js | 157 ++++++++++++++---------------------------- 1 file changed, 53 insertions(+), 104 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 8c0375b7..1fb67022 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -13,7 +13,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const os = require('os'); const tty = require('tty'); const { env } = process; @@ -24,15 +23,15 @@ function envForceColor() { } if (env.FORCE_COLOR === 'true') { - return 1; + return true; } if (env.FORCE_COLOR === 'false') { - return 0; + return false; } if (env.FORCE_COLOR.length === 0) { - return 1; + return true; } const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); @@ -41,86 +40,67 @@ function envForceColor() { return; } - return level; -} - -function translateLevel(level) { return level !== 0; } -function _supportsColor(haveStream, { streamIsTTY } = {}) { - let flagForceColor; - if ( - process.argv.includes('--no-color') || - process.argv.includes('--no-colors') || - process.argv.includes('--color=false') || - process.argv.includes('--color=never') - ) { - flagForceColor = 0; - } else if ( - process.argv.includes('--color') || - process.argv.includes('--colors') || - process.argv.includes('--color=true') || - process.argv.includes('--color=always') - ) { - flagForceColor = 1; +function _supportsColor(streamIsTTY) { + let forceColor = envForceColor(); + if (forceColor === undefined) { + if ( + process.argv.includes('--no-color') || + process.argv.includes('--no-colors') || + process.argv.includes('--color=false') || + process.argv.includes('--color=never') + ) { + return false; + } else if ( + process.argv.includes('--color') || + process.argv.includes('--colors') || + process.argv.includes('--color=true') || + process.argv.includes('--color=always') + ) { + forceColor = true; + } } - const noFlagForceColor = envForceColor(); - if (noFlagForceColor !== undefined) { - flagForceColor = noFlagForceColor; - } - - const forceColor = flagForceColor; - - if (forceColor === 0) { - return 0; + if (forceColor === false) { + return false; } if ( process.argv.includes('--color=16m') || process.argv.includes('--color=full') || - process.argv.includes('--color=truecolor') + process.argv.includes('--color=truecolor') || + process.argv.includes('--color=256') ) { - return 3; - } - - if (process.argv.includes('--color=256')) { - return 2; + return true; } // Check for Azure DevOps pipelines. // Has to be above the `!streamIsTTY` check. if ('TF_BUILD' in env && 'AGENT_NAME' in env) { - return 1; + return true; } - if (haveStream && !streamIsTTY && forceColor === undefined) { - return 0; + if (!streamIsTTY && forceColor === undefined) { + return false; } - const min = forceColor || 0; - - if (env.TERM === 'dumb') { - return min; + if (process.platform === 'win32') { + return true; } - if (process.platform === 'win32') { - // Windows 10 build 10586 is the first Windows release that supports 256 colors. - // Windows 10 build 14931 is the first release that supports 16m/TrueColor. - const osRelease = os.release().split('.'); - if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10_586) { - return Number(osRelease[2]) >= 14_931 ? 3 : 2; - } + const min = forceColor || false; - return 1; + if (env.TERM === 'dumb') { + return min; } if ('CI' in env) { if ( ['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some((key) => key in env) ) { - return 3; + return true; } if ( @@ -129,76 +109,45 @@ function _supportsColor(haveStream, { streamIsTTY } = {}) { ) || env.CI_NAME === 'codeship' ) { - return 1; + return true; } return min; } if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; - } - - if (env.COLORTERM === 'truecolor') { - return 3; + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION); } - if (env.TERM === 'xterm-kitty') { - return 3; - } - - if (env.TERM === 'xterm-ghostty') { - return 3; - } - - if (env.TERM === 'wezterm') { - return 3; + if ( + env.COLORTERM === 'truecolor' || + env.TERM === 'xterm-kitty' || + env.TERM === 'xterm-ghostty' || + env.TERM === 'wezterm' + ) { + return true; } - if ('TERM_PROGRAM' in env) { - const version = Number.parseInt( - (env.TERM_PROGRAM_VERSION || '').split('.')[0], - 10 - ); - switch (env.TERM_PROGRAM) { - case 'iTerm.app': { - return version >= 3 ? 3 : 2; - } - + case 'iTerm.app': case 'Apple_Terminal': { - return 2; + return true; } // No default } } - if (/-256(color)?$/i.test(env.TERM)) { - return 2; - } - if ( - /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM) + /-256(color)?$/i.test(env.TERM) || + /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test( + env.TERM + ) || + 'COLORTERM' in env ) { - return 1; - } - - if ('COLORTERM' in env) { - return 1; + return true; } return min; } -function createSupportsColor(stream, options = {}) { - const level = _supportsColor(stream, { - streamIsTTY: stream && stream.isTTY, - ...options, - }); - - return translateLevel(level); -} - -const supportsColor = createSupportsColor({ isTTY: tty.isatty(1) }); - -module.exports = supportsColor; +module.exports = _supportsColor(tty.isatty(1)); From 5942ad1c5167222b807ea385e36663ae811f7429 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:38:18 +0100 Subject: [PATCH 15/34] Merge flagForceColor and noFlagForceColor --- lib/supports-color.js | 128 +++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 1fb67022..47039f69 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -13,6 +13,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +const os = require('os'); const tty = require('tty'); const { env } = process; @@ -23,15 +24,15 @@ function envForceColor() { } if (env.FORCE_COLOR === 'true') { - return true; + return 1; } if (env.FORCE_COLOR === 'false') { - return false; + return 0; } if (env.FORCE_COLOR.length === 0) { - return true; + return 1; } const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); @@ -40,67 +41,83 @@ function envForceColor() { return; } + return level; +} + +function translateLevel(level) { return level !== 0; } -function _supportsColor(streamIsTTY) { - let forceColor = envForceColor(); - if (forceColor === undefined) { +function _supportsColor(haveStream, { streamIsTTY } = {}) { + let flagForceColor = envForceColor(); + if (flagForceColor === undefined) { if ( process.argv.includes('--no-color') || process.argv.includes('--no-colors') || process.argv.includes('--color=false') || process.argv.includes('--color=never') ) { - return false; + flagForceColor = 0; } else if ( process.argv.includes('--color') || process.argv.includes('--colors') || process.argv.includes('--color=true') || process.argv.includes('--color=always') ) { - forceColor = true; + flagForceColor = 1; } } - if (forceColor === false) { - return false; + const forceColor = flagForceColor; + + if (forceColor === 0) { + return 0; } if ( process.argv.includes('--color=16m') || process.argv.includes('--color=full') || - process.argv.includes('--color=truecolor') || - process.argv.includes('--color=256') + process.argv.includes('--color=truecolor') ) { - return true; + return 3; + } + + if (process.argv.includes('--color=256')) { + return 2; } // Check for Azure DevOps pipelines. // Has to be above the `!streamIsTTY` check. if ('TF_BUILD' in env && 'AGENT_NAME' in env) { - return true; + return 1; } - if (!streamIsTTY && forceColor === undefined) { - return false; + if (haveStream && !streamIsTTY && forceColor === undefined) { + return 0; } - if (process.platform === 'win32') { - return true; - } - - const min = forceColor || false; + const min = forceColor || 0; if (env.TERM === 'dumb') { return min; } + if (process.platform === 'win32') { + // Windows 10 build 10586 is the first Windows release that supports 256 colors. + // Windows 10 build 14931 is the first release that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10_586) { + return Number(osRelease[2]) >= 14_931 ? 3 : 2; + } + + return 1; + } + if ('CI' in env) { if ( ['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some((key) => key in env) ) { - return true; + return 3; } if ( @@ -109,45 +126,76 @@ function _supportsColor(streamIsTTY) { ) || env.CI_NAME === 'codeship' ) { - return true; + return 1; } return min; } if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION); + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; } - if ( - env.COLORTERM === 'truecolor' || - env.TERM === 'xterm-kitty' || - env.TERM === 'xterm-ghostty' || - env.TERM === 'wezterm' - ) { - return true; + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if (env.TERM === 'xterm-kitty') { + return 3; } + + if (env.TERM === 'xterm-ghostty') { + return 3; + } + + if (env.TERM === 'wezterm') { + return 3; + } + if ('TERM_PROGRAM' in env) { + const version = Number.parseInt( + (env.TERM_PROGRAM_VERSION || '').split('.')[0], + 10 + ); + switch (env.TERM_PROGRAM) { - case 'iTerm.app': + case 'iTerm.app': { + return version >= 3 ? 3 : 2; + } + case 'Apple_Terminal': { - return true; + return 2; } // No default } } + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + if ( - /-256(color)?$/i.test(env.TERM) || - /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test( - env.TERM - ) || - 'COLORTERM' in env + /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM) ) { - return true; + return 1; + } + + if ('COLORTERM' in env) { + return 1; } return min; } -module.exports = _supportsColor(tty.isatty(1)); +function createSupportsColor(stream, options = {}) { + const level = _supportsColor(stream, { + streamIsTTY: stream && stream.isTTY, + ...options, + }); + + return translateLevel(level); +} + +const supportsColor = createSupportsColor({ isTTY: tty.isatty(1) }); + +module.exports = supportsColor; From 5d72b6cf8803a90e05ae3affa499c419ad6cb671 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:39:26 +0100 Subject: [PATCH 16/34] Merge flagForceColor and forceColor --- lib/supports-color.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 47039f69..eb472168 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -49,27 +49,25 @@ function translateLevel(level) { } function _supportsColor(haveStream, { streamIsTTY } = {}) { - let flagForceColor = envForceColor(); - if (flagForceColor === undefined) { + let forceColor = envForceColor(); + if (forceColor === undefined) { if ( process.argv.includes('--no-color') || process.argv.includes('--no-colors') || process.argv.includes('--color=false') || process.argv.includes('--color=never') ) { - flagForceColor = 0; + forceColor = 0; } else if ( process.argv.includes('--color') || process.argv.includes('--colors') || process.argv.includes('--color=true') || process.argv.includes('--color=always') ) { - flagForceColor = 1; + forceColor = 1; } } - const forceColor = flagForceColor; - if (forceColor === 0) { return 0; } From c0d482509f3b13c1ef824e51b07be8159fdddcfb Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:40:51 +0100 Subject: [PATCH 17/34] Remove options and simplify arguments --- lib/supports-color.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index eb472168..c6ced762 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -48,7 +48,7 @@ function translateLevel(level) { return level !== 0; } -function _supportsColor(haveStream, { streamIsTTY } = {}) { +function _supportsColor(streamIsTTY) { let forceColor = envForceColor(); if (forceColor === undefined) { if ( @@ -90,7 +90,7 @@ function _supportsColor(haveStream, { streamIsTTY } = {}) { return 1; } - if (haveStream && !streamIsTTY && forceColor === undefined) { + if (!streamIsTTY && forceColor === undefined) { return 0; } @@ -185,15 +185,12 @@ function _supportsColor(haveStream, { streamIsTTY } = {}) { return min; } -function createSupportsColor(stream, options = {}) { - const level = _supportsColor(stream, { - streamIsTTY: stream && stream.isTTY, - ...options, - }); +function createSupportsColor(isTTY) { + const level = _supportsColor(isTTY); return translateLevel(level); } -const supportsColor = createSupportsColor({ isTTY: tty.isatty(1) }); +const supportsColor = createSupportsColor(tty.isatty(1)); module.exports = supportsColor; From 859ecb9ea9ce4ac07347cab79f2b3eac7b6f4eae Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:42:04 +0100 Subject: [PATCH 18/34] Require os only when necessary --- lib/supports-color.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index c6ced762..8a1a2e8b 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -13,7 +13,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const os = require('os'); const tty = require('tty'); const { env } = process; @@ -103,6 +102,7 @@ function _supportsColor(streamIsTTY) { if (process.platform === 'win32') { // Windows 10 build 10586 is the first Windows release that supports 256 colors. // Windows 10 build 14931 is the first release that supports 16m/TrueColor. + const os = require('os'); const osRelease = os.release().split('.'); if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10_586) { return Number(osRelease[2]) >= 14_931 ? 3 : 2; From 5a539664dc9bb916db64394e518c0d5d5e0dc003 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:44:32 +0100 Subject: [PATCH 19/34] Use booleans instead of numbers --- lib/supports-color.js | 66 ++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 8a1a2e8b..c6a8fe48 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -23,15 +23,15 @@ function envForceColor() { } if (env.FORCE_COLOR === 'true') { - return 1; + return true; } if (env.FORCE_COLOR === 'false') { - return 0; + return false; } if (env.FORCE_COLOR.length === 0) { - return 1; + return true; } const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); @@ -40,10 +40,6 @@ function envForceColor() { return; } - return level; -} - -function translateLevel(level) { return level !== 0; } @@ -56,19 +52,19 @@ function _supportsColor(streamIsTTY) { process.argv.includes('--color=false') || process.argv.includes('--color=never') ) { - forceColor = 0; + forceColor = false; } else if ( process.argv.includes('--color') || process.argv.includes('--colors') || process.argv.includes('--color=true') || process.argv.includes('--color=always') ) { - forceColor = 1; + forceColor = true; } } - if (forceColor === 0) { - return 0; + if (forceColor === false) { + return false; } if ( @@ -76,24 +72,24 @@ function _supportsColor(streamIsTTY) { process.argv.includes('--color=full') || process.argv.includes('--color=truecolor') ) { - return 3; + return true; } if (process.argv.includes('--color=256')) { - return 2; + return true; } // Check for Azure DevOps pipelines. // Has to be above the `!streamIsTTY` check. if ('TF_BUILD' in env && 'AGENT_NAME' in env) { - return 1; + return true; } if (!streamIsTTY && forceColor === undefined) { - return 0; + return false; } - const min = forceColor || 0; + const min = forceColor || false; if (env.TERM === 'dumb') { return min; @@ -105,17 +101,17 @@ function _supportsColor(streamIsTTY) { const os = require('os'); const osRelease = os.release().split('.'); if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10_586) { - return Number(osRelease[2]) >= 14_931 ? 3 : 2; + return true; } - return 1; + return true; } if ('CI' in env) { if ( ['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some((key) => key in env) ) { - return 3; + return true; } if ( @@ -124,30 +120,30 @@ function _supportsColor(streamIsTTY) { ) || env.CI_NAME === 'codeship' ) { - return 1; + return true; } return min; } if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION); } if (env.COLORTERM === 'truecolor') { - return 3; + return true; } if (env.TERM === 'xterm-kitty') { - return 3; + return true; } if (env.TERM === 'xterm-ghostty') { - return 3; + return true; } if (env.TERM === 'wezterm') { - return 3; + return true; } if ('TERM_PROGRAM' in env) { @@ -158,39 +154,31 @@ function _supportsColor(streamIsTTY) { switch (env.TERM_PROGRAM) { case 'iTerm.app': { - return version >= 3 ? 3 : 2; + return version >= 3; } case 'Apple_Terminal': { - return 2; + return true; } // No default } } if (/-256(color)?$/i.test(env.TERM)) { - return 2; + return true; } if ( /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM) ) { - return 1; + return true; } if ('COLORTERM' in env) { - return 1; + return true; } return min; } -function createSupportsColor(isTTY) { - const level = _supportsColor(isTTY); - - return translateLevel(level); -} - -const supportsColor = createSupportsColor(tty.isatty(1)); - -module.exports = supportsColor; +module.exports = _supportsColor(tty.isatty(1)); From e86a30e9e169d4b100ceff867499bd6ce3e876e7 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:46:22 +0100 Subject: [PATCH 20/34] Return early --- lib/supports-color.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index c6a8fe48..71b5c9b0 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -52,7 +52,7 @@ function _supportsColor(streamIsTTY) { process.argv.includes('--color=false') || process.argv.includes('--color=never') ) { - forceColor = false; + return false; } else if ( process.argv.includes('--color') || process.argv.includes('--colors') || From 368821549dbfb7012f368c4be8357a922996cbe6 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 11:50:46 +0100 Subject: [PATCH 21/34] Simplify branches --- lib/supports-color.js | 67 ++++++++++++------------------------------- 1 file changed, 18 insertions(+), 49 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 71b5c9b0..1fb67022 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -70,15 +70,12 @@ function _supportsColor(streamIsTTY) { if ( process.argv.includes('--color=16m') || process.argv.includes('--color=full') || - process.argv.includes('--color=truecolor') + process.argv.includes('--color=truecolor') || + process.argv.includes('--color=256') ) { return true; } - if (process.argv.includes('--color=256')) { - return true; - } - // Check for Azure DevOps pipelines. // Has to be above the `!streamIsTTY` check. if ('TF_BUILD' in env && 'AGENT_NAME' in env) { @@ -89,24 +86,16 @@ function _supportsColor(streamIsTTY) { return false; } + if (process.platform === 'win32') { + return true; + } + const min = forceColor || false; if (env.TERM === 'dumb') { return min; } - if (process.platform === 'win32') { - // Windows 10 build 10586 is the first Windows release that supports 256 colors. - // Windows 10 build 14931 is the first release that supports 16m/TrueColor. - const os = require('os'); - const osRelease = os.release().split('.'); - if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10_586) { - return true; - } - - return true; - } - if ('CI' in env) { if ( ['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some((key) => key in env) @@ -130,33 +119,17 @@ function _supportsColor(streamIsTTY) { return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION); } - if (env.COLORTERM === 'truecolor') { - return true; - } - - if (env.TERM === 'xterm-kitty') { - return true; - } - - if (env.TERM === 'xterm-ghostty') { - return true; - } - - if (env.TERM === 'wezterm') { + if ( + env.COLORTERM === 'truecolor' || + env.TERM === 'xterm-kitty' || + env.TERM === 'xterm-ghostty' || + env.TERM === 'wezterm' + ) { return true; } - if ('TERM_PROGRAM' in env) { - const version = Number.parseInt( - (env.TERM_PROGRAM_VERSION || '').split('.')[0], - 10 - ); - switch (env.TERM_PROGRAM) { - case 'iTerm.app': { - return version >= 3; - } - + case 'iTerm.app': case 'Apple_Terminal': { return true; } @@ -164,20 +137,16 @@ function _supportsColor(streamIsTTY) { } } - if (/-256(color)?$/i.test(env.TERM)) { - return true; - } - if ( - /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM) + /-256(color)?$/i.test(env.TERM) || + /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test( + env.TERM + ) || + 'COLORTERM' in env ) { return true; } - if ('COLORTERM' in env) { - return true; - } - return min; } From 9fc1ba2e54da94d9f1a07c2db4a79efb0e8fe426 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 15:58:01 +0100 Subject: [PATCH 22/34] Indicate where to find more styles --- lib/chalk.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/chalk.js b/lib/chalk.js index a5baf4d6..ef6f7495 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -1,6 +1,8 @@ 'use strict'; const supportsColor = require('./supports-color'); +// Find more colors/styles in +// https://github.com/chalk/ansi-styles/blob/main/index.js const colors = { blue: { open: '\x1B[34m', close: '\x1B[39m' }, red: { open: '\x1B[31m', close: '\x1B[39m' }, From 249e2fb6ba8060331ddd782565e01af41a545639 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 20 Feb 2026 16:48:23 +0100 Subject: [PATCH 23/34] Add flow checks for chalk --- lib/chalk.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/chalk.js b/lib/chalk.js index ef6f7495..2f439597 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -1,23 +1,20 @@ -'use strict'; +// @flow const supportsColor = require('./supports-color'); // Find more colors/styles in // https://github.com/chalk/ansi-styles/blob/main/index.js -const colors = { - blue: { open: '\x1B[34m', close: '\x1B[39m' }, - red: { open: '\x1B[31m', close: '\x1B[39m' }, -}; - -const chalk = { supportsColor }; -for (const [styleName, style] of Object.entries(colors)) { - chalk[styleName] = (string) => { - if (!supportsColor || !string) { - return string; - } +function red(string /*: string */) /*: string */ { + return supportsColor ? `\x1B[31m${string}\x1B[39m` : string; +} - return style.open + string + style.close; - }; +function blue(string /*: string */) /*: string */ { + return supportsColor ? `\x1B[34m${string}\x1B[39m` : string; } -module.exports = chalk; +module.exports = { + supportsColor, + + red, + blue, +}; From 09210f9370fc63a8f24e837964aab8587dd135e2 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 11:23:44 +0100 Subject: [PATCH 24/34] Don't check for invalid CLI flags --- lib/supports-color.js | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 1fb67022..fcf5078c 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -46,19 +46,9 @@ function envForceColor() { function _supportsColor(streamIsTTY) { let forceColor = envForceColor(); if (forceColor === undefined) { - if ( - process.argv.includes('--no-color') || - process.argv.includes('--no-colors') || - process.argv.includes('--color=false') || - process.argv.includes('--color=never') - ) { + if (process.argv.includes('--no-color')) { return false; - } else if ( - process.argv.includes('--color') || - process.argv.includes('--colors') || - process.argv.includes('--color=true') || - process.argv.includes('--color=always') - ) { + } else if (process.argv.includes('--color')) { forceColor = true; } } @@ -67,15 +57,6 @@ function _supportsColor(streamIsTTY) { return false; } - if ( - process.argv.includes('--color=16m') || - process.argv.includes('--color=full') || - process.argv.includes('--color=truecolor') || - process.argv.includes('--color=256') - ) { - return true; - } - // Check for Azure DevOps pipelines. // Has to be above the `!streamIsTTY` check. if ('TF_BUILD' in env && 'AGENT_NAME' in env) { From c51366535421ae54af4135d03c11068f51bfeb2b Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 11:27:51 +0100 Subject: [PATCH 25/34] Simplify FORCE_COLOR checks --- lib/supports-color.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index fcf5078c..9e27a999 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -18,29 +18,15 @@ const tty = require('tty'); const { env } = process; function envForceColor() { - if (!('FORCE_COLOR' in env)) { + if (!('FORCE_COLOR' in env) || env.FORCE_COLOR === '') { return; } - if (env.FORCE_COLOR === 'true') { - return true; - } - - if (env.FORCE_COLOR === 'false') { + if (env.FORCE_COLOR === 'false' || env.FORCE_COLOR === '0') { return false; } - if (env.FORCE_COLOR.length === 0) { - return true; - } - - const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); - - if (![0, 1, 2, 3].includes(level)) { - return; - } - - return level !== 0; + return true; } function _supportsColor(streamIsTTY) { From d63d47d98663371192e768b0541118c4e32b98db Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 11:55:23 +0100 Subject: [PATCH 26/34] Check for forceColor undefined early --- lib/supports-color.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 9e27a999..56a6a87a 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -31,16 +31,14 @@ function envForceColor() { function _supportsColor(streamIsTTY) { let forceColor = envForceColor(); - if (forceColor === undefined) { - if (process.argv.includes('--no-color')) { - return false; - } else if (process.argv.includes('--color')) { - forceColor = true; - } + if (forceColor !== undefined) { + return forceColor; } - if (forceColor === false) { + if (process.argv.includes('--no-color')) { return false; + } else if (process.argv.includes('--color')) { + return true; } // Check for Azure DevOps pipelines. @@ -49,7 +47,7 @@ function _supportsColor(streamIsTTY) { return true; } - if (!streamIsTTY && forceColor === undefined) { + if (!streamIsTTY) { return false; } @@ -57,7 +55,7 @@ function _supportsColor(streamIsTTY) { return true; } - const min = forceColor || false; + const min = false; if (env.TERM === 'dumb') { return min; From 44e73b36b5720b0a92a2c3b848e5d8c8f6606afe Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 11:56:39 +0100 Subject: [PATCH 27/34] Inline min --- lib/supports-color.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 56a6a87a..f71773a7 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -55,10 +55,8 @@ function _supportsColor(streamIsTTY) { return true; } - const min = false; - if (env.TERM === 'dumb') { - return min; + return false; } if ('CI' in env) { @@ -77,7 +75,7 @@ function _supportsColor(streamIsTTY) { return true; } - return min; + return false; } if ('TEAMCITY_VERSION' in env) { @@ -112,7 +110,7 @@ function _supportsColor(streamIsTTY) { return true; } - return min; + return false; } module.exports = _supportsColor(tty.isatty(1)); From b6ae3809957681fc1f6f93e1fe5b7d9b320eb314 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 12:04:52 +0100 Subject: [PATCH 28/34] Assume all terminals support colors --- lib/supports-color.js | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index f71773a7..3bb81bd1 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -55,10 +55,6 @@ function _supportsColor(streamIsTTY) { return true; } - if (env.TERM === 'dumb') { - return false; - } - if ('CI' in env) { if ( ['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some((key) => key in env) @@ -78,34 +74,15 @@ function _supportsColor(streamIsTTY) { return false; } - if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION); - } - - if ( - env.COLORTERM === 'truecolor' || - env.TERM === 'xterm-kitty' || - env.TERM === 'xterm-ghostty' || - env.TERM === 'wezterm' - ) { - return true; - } - if ('TERM_PROGRAM' in env) { - switch (env.TERM_PROGRAM) { - case 'iTerm.app': - case 'Apple_Terminal': { - return true; - } - // No default - } + if (env.TERM === 'dumb') { + return false; } if ( - /-256(color)?$/i.test(env.TERM) || - /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test( - env.TERM - ) || - 'COLORTERM' in env + 'TERM' in env || + 'COLORTERM' in env || + 'TERM_PROGRAM' in env || + 'TEAMCITY_VERSION' in env ) { return true; } From 8b43284d24296cb1b402b709164128c02afafdf9 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 12:06:14 +0100 Subject: [PATCH 29/34] Check for tty when needed --- lib/supports-color.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 3bb81bd1..0e7fb5ff 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -29,7 +29,7 @@ function envForceColor() { return true; } -function _supportsColor(streamIsTTY) { +function _supportsColor() { let forceColor = envForceColor(); if (forceColor !== undefined) { return forceColor; @@ -42,12 +42,12 @@ function _supportsColor(streamIsTTY) { } // Check for Azure DevOps pipelines. - // Has to be above the `!streamIsTTY` check. + // Has to be above the tty check. if ('TF_BUILD' in env && 'AGENT_NAME' in env) { return true; } - if (!streamIsTTY) { + if (!tty.isatty(1)) { return false; } @@ -90,4 +90,4 @@ function _supportsColor(streamIsTTY) { return false; } -module.exports = _supportsColor(tty.isatty(1)); +module.exports = _supportsColor(); From c6754873538ed5df032506bb4eac6371ebf88d37 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 12:08:56 +0100 Subject: [PATCH 30/34] Assume all CIs support colors --- lib/supports-color.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 0e7fb5ff..92a83500 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -56,22 +56,7 @@ function _supportsColor() { } if ('CI' in env) { - if ( - ['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some((key) => key in env) - ) { - return true; - } - - if ( - ['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some( - (sign) => sign in env - ) || - env.CI_NAME === 'codeship' - ) { - return true; - } - - return false; + return true; } if (env.TERM === 'dumb') { From 02a06d843074338614713999ee6f409ef7672579 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 12:14:26 +0100 Subject: [PATCH 31/34] Switch branches --- lib/supports-color.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 92a83500..b58a424a 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -18,15 +18,15 @@ const tty = require('tty'); const { env } = process; function envForceColor() { - if (!('FORCE_COLOR' in env) || env.FORCE_COLOR === '') { - return; - } + if ('FORCE_COLOR' in env && env.FORCE_COLOR !== '') { + if (env.FORCE_COLOR === 'false' || env.FORCE_COLOR === '0') { + return false; + } - if (env.FORCE_COLOR === 'false' || env.FORCE_COLOR === '0') { - return false; + return true; } - return true; + return; } function _supportsColor() { From ff53bebfb7b6362b6a66d5dfec361af94c267c1f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 12:14:54 +0100 Subject: [PATCH 32/34] Merge 2 functions --- lib/supports-color.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index b58a424a..c5ff9997 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -17,7 +17,7 @@ const tty = require('tty'); const { env } = process; -function envForceColor() { +function supportsColor() { if ('FORCE_COLOR' in env && env.FORCE_COLOR !== '') { if (env.FORCE_COLOR === 'false' || env.FORCE_COLOR === '0') { return false; @@ -26,15 +26,6 @@ function envForceColor() { return true; } - return; -} - -function _supportsColor() { - let forceColor = envForceColor(); - if (forceColor !== undefined) { - return forceColor; - } - if (process.argv.includes('--no-color')) { return false; } else if (process.argv.includes('--color')) { @@ -75,4 +66,4 @@ function _supportsColor() { return false; } -module.exports = _supportsColor(); +module.exports = supportsColor(); From 80e0f4b07adb109ab75e8ad6313b202766c25119 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 12:15:29 +0100 Subject: [PATCH 33/34] Switch branches --- lib/supports-color.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index c5ff9997..7a449fdd 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -19,11 +19,10 @@ const { env } = process; function supportsColor() { if ('FORCE_COLOR' in env && env.FORCE_COLOR !== '') { - if (env.FORCE_COLOR === 'false' || env.FORCE_COLOR === '0') { - return false; + if (env.FORCE_COLOR !== 'false' && env.FORCE_COLOR !== '0') { + return true; } - - return true; + return false; } if (process.argv.includes('--no-color')) { From 5d745b6e44333bb8e1b77c0c35d7576722238f59 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Feb 2026 12:16:26 +0100 Subject: [PATCH 34/34] Use return statement --- lib/supports-color.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/supports-color.js b/lib/supports-color.js index 7a449fdd..cec1a5cd 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -19,10 +19,7 @@ const { env } = process; function supportsColor() { if ('FORCE_COLOR' in env && env.FORCE_COLOR !== '') { - if (env.FORCE_COLOR !== 'false' && env.FORCE_COLOR !== '0') { - return true; - } - return false; + return env.FORCE_COLOR !== 'false' && env.FORCE_COLOR !== '0'; } if (process.argv.includes('--no-color')) {