From 1bc8bd29d7fefc809591e596b502e160a7dfceb1 Mon Sep 17 00:00:00 2001 From: David Goss Date: Sat, 30 Apr 2022 15:21:57 +0100 Subject: [PATCH 01/13] install supports-color --- package-lock.json | 143 ++++++++++++++++++++++++++++++++++++---------- package.json | 2 + 2 files changed, 114 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5c8aebbf..844696c8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "semver": "7.3.5", "stack-chain": "^2.0.0", "string-argv": "^0.3.1", + "supports-color": "^8.1.1", "tmp": "^0.2.1", "util-arity": "^1.1.0", "verror": "^1.10.0", @@ -71,6 +72,7 @@ "@types/sinon-chai": "3.2.8", "@types/sinonjs__fake-timers": "8.1.2", "@types/stream-buffers": "3.0.4", + "@types/supports-color": "^8.1.1", "@types/tmp": "0.2.3", "@types/verror": "1.10.5", "@typescript-eslint/eslint-plugin": "5.17.0", @@ -1507,6 +1509,12 @@ "@types/node": "*" } }, + "node_modules/@types/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", + "dev": true + }, "node_modules/@types/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", @@ -2336,6 +2344,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -4611,6 +4630,18 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -5235,21 +5266,6 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6756,6 +6772,18 @@ "node": ">=0.3.1" } }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6976,14 +7004,17 @@ } }, "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-hyperlinks": { @@ -6999,6 +7030,18 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -8916,6 +8959,12 @@ "@types/node": "*" } }, + "@types/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", + "dev": true + }, "@types/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", @@ -9507,6 +9556,16 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "check-error": { @@ -11222,6 +11281,17 @@ "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "istanbul-lib-source-maps": { @@ -11699,15 +11769,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -12832,6 +12893,15 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -13019,9 +13089,9 @@ "dev": true }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "requires": { "has-flag": "^4.0.0" } @@ -13034,6 +13104,17 @@ "requires": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "supports-preserve-symlinks-flag": { diff --git a/package.json b/package.json index edabe0816..6bf37823f 100644 --- a/package.json +++ b/package.json @@ -225,6 +225,7 @@ "semver": "7.3.5", "stack-chain": "^2.0.0", "string-argv": "^0.3.1", + "supports-color": "^8.1.1", "tmp": "^0.2.1", "util-arity": "^1.1.0", "verror": "^1.10.0", @@ -253,6 +254,7 @@ "@types/sinon-chai": "3.2.8", "@types/sinonjs__fake-timers": "8.1.2", "@types/stream-buffers": "3.0.4", + "@types/supports-color": "^8.1.1", "@types/tmp": "0.2.3", "@types/verror": "1.10.5", "@typescript-eslint/eslint-plugin": "5.17.0", From 71ced0496a6be1bc11c5819de6ee5a91ebbda65e Mon Sep 17 00:00:00 2001 From: David Goss Date: Sat, 30 Apr 2022 16:55:13 +0100 Subject: [PATCH 02/13] add test --- features/colors.feature | 28 +++++++++++++ features/step_definitions/file_steps.ts | 21 ++++++++++ package-lock.json | 52 +++++++++++++++++++++++++ package.json | 2 + 4 files changed, 103 insertions(+) create mode 100644 features/colors.feature diff --git a/features/colors.feature b/features/colors.feature new file mode 100644 index 000000000..4cadbf269 --- /dev/null +++ b/features/colors.feature @@ -0,0 +1,28 @@ +Feature: Colors + + As a developer + I want to control when/whether the output includes ANSI colors + So that I can be happy + + Background: + Given a file named "features/a.feature" with: + """ + Feature: + Scenario: + Given a step + """ + And a file named "features/step_definitions/steps.js" with: + """ + const {Given} = require('@cucumber/cucumber') + Given('a step', function() {}) + """ + + Scenario: no colored output by default for a file stream + When I run cucumber-js with `--format summary:summary.out` + Then it passes + Then the file "summary.out" doesn't contain ansi colors + + Scenario: colored output can be explicitly activated for a file stream + When I run cucumber-js with `--format summary:summary.out --format-options '{"colorsEnabled":true}'` + Then it passes + Then the file "summary.out" contains ansi colors diff --git a/features/step_definitions/file_steps.ts b/features/step_definitions/file_steps.ts index 5b3b00644..39e084e98 100644 --- a/features/step_definitions/file_steps.ts +++ b/features/step_definitions/file_steps.ts @@ -1,5 +1,6 @@ import { Given, Then } from '../../' import { expect } from 'chai' +import hasAnsi from 'has-ansi' import { normalizeText } from '../support/helpers' import fs from 'mz/fs' import fsExtra from 'fs-extra' @@ -50,3 +51,23 @@ Then( expect(actualContent).to.eql(expectedContent) } ) + +Then( + 'the file {string} contains ansi colors', + async function (this: World, filePath: string) { + filePath = Mustache.render(filePath, this) + const absoluteFilePath = path.resolve(this.tmpDir, filePath) + const content = await fs.readFile(absoluteFilePath, 'utf8') + expect(hasAnsi(content)).to.be.true + } +) + +Then( + "the file {string} doesn't contain ansi colors", + async function (this: World, filePath: string) { + filePath = Mustache.render(filePath, this) + const absoluteFilePath = path.resolve(this.tmpDir, filePath) + const content = await fs.readFile(absoluteFilePath, 'utf8') + expect(hasAnsi(content)).to.be.false + } +) diff --git a/package-lock.json b/package-lock.json index 844696c8e..e350ae57b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "@types/express": "4.17.13", "@types/fs-extra": "9.0.13", "@types/glob": "7.2.0", + "@types/has-ansi": "^5.0.0", "@types/lodash.merge": "^4.6.6", "@types/lodash.mergewith": "^4.6.6", "@types/mocha": "9.1.0", @@ -91,6 +92,7 @@ "express": "4.17.3", "fs-extra": "10.0.1", "genversion": "3.0.2", + "has-ansi": "^4.0.1", "mocha": "9.2.2", "mustache": "4.2.0", "nyc": "15.1.0", @@ -1337,6 +1339,12 @@ "@types/node": "*" } }, + "node_modules/@types/has-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/has-ansi/-/has-ansi-5.0.0.tgz", + "integrity": "sha512-1mwAilzr9t7E+h+KHaOE3Jxio9G3c2DSQTcjz/JV/xJ9DUuQastSiYmjJz4D07Vh12+tKM4owiFOe2+XOmvEig==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -4038,6 +4046,27 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-4.0.1.tgz", + "integrity": "sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -8785,6 +8814,12 @@ "@types/node": "*" } }, + "@types/has-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/has-ansi/-/has-ansi-5.0.0.tgz", + "integrity": "sha512-1mwAilzr9t7E+h+KHaOE3Jxio9G3c2DSQTcjz/JV/xJ9DUuQastSiYmjJz4D07Vh12+tKM4owiFOe2+XOmvEig==", + "dev": true + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -10869,6 +10904,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-4.0.1.tgz", + "integrity": "sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + } + } + }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", diff --git a/package.json b/package.json index 6bf37823f..6eb8e2f6d 100644 --- a/package.json +++ b/package.json @@ -242,6 +242,7 @@ "@types/express": "4.17.13", "@types/fs-extra": "9.0.13", "@types/glob": "7.2.0", + "@types/has-ansi": "^5.0.0", "@types/lodash.merge": "^4.6.6", "@types/lodash.mergewith": "^4.6.6", "@types/mocha": "9.1.0", @@ -273,6 +274,7 @@ "express": "4.17.3", "fs-extra": "10.0.1", "genversion": "3.0.2", + "has-ansi": "^4.0.1", "mocha": "9.2.2", "mustache": "4.2.0", "nyc": "15.1.0", From 30e22e2ad0841881b51c698e030ddb384bbaa338 Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 09:16:13 +0100 Subject: [PATCH 03/13] use supports-color, override if needed --- package-lock.json | 13 ------ package.json | 1 - src/api/formatters.ts | 6 --- src/formatter/builder.ts | 5 ++- src/formatter/get_color_fns.ts | 41 ++++++++++++------- src/formatter/helpers/issue_helpers_spec.ts | 3 +- src/formatter/helpers/summary_helpers_spec.ts | 3 +- src/types/supports-color/index.d.ts | 20 +++++++++ 8 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 src/types/supports-color/index.d.ts diff --git a/package-lock.json b/package-lock.json index e350ae57b..c3356ced6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,6 @@ "@types/sinon-chai": "3.2.8", "@types/sinonjs__fake-timers": "8.1.2", "@types/stream-buffers": "3.0.4", - "@types/supports-color": "^8.1.1", "@types/tmp": "0.2.3", "@types/verror": "1.10.5", "@typescript-eslint/eslint-plugin": "5.17.0", @@ -1517,12 +1516,6 @@ "@types/node": "*" } }, - "node_modules/@types/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", - "dev": true - }, "node_modules/@types/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", @@ -8994,12 +8987,6 @@ "@types/node": "*" } }, - "@types/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", - "dev": true - }, "@types/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", diff --git a/package.json b/package.json index 6eb8e2f6d..f442cffce 100644 --- a/package.json +++ b/package.json @@ -255,7 +255,6 @@ "@types/sinon-chai": "3.2.8", "@types/sinonjs__fake-timers": "8.1.2", "@types/stream-buffers": "3.0.4", - "@types/supports-color": "^8.1.1", "@types/tmp": "0.2.3", "@types/verror": "1.10.5", "@typescript-eslint/eslint-plugin": "5.17.0", diff --git a/src/api/formatters.ts b/src/api/formatters.ts index 734f8a842..6cc43653a 100644 --- a/src/api/formatters.ts +++ b/src/api/formatters.ts @@ -3,7 +3,6 @@ import { EventEmitter } from 'events' import { EventDataCollector } from '../formatter/helpers' import { ISupportCodeLibrary } from '../support_code_library_builder/types' import { promisify } from 'util' -import { doesNotHaveValue } from '../value_checker' import { WriteStream as TtyWriteStream } from 'tty' import FormatterBuilder from '../formatter/builder' import fs from 'mz/fs' @@ -54,11 +53,6 @@ export async function initializeFormatters({ : promisify(stream.end.bind(stream)), supportCodeLibrary, } - if (doesNotHaveValue(configuration.options.colorsEnabled)) { - typeOptions.parsedArgvOptions.colorsEnabled = ( - stream as TtyWriteStream - ).isTTY - } if (type === 'progress-bar' && !(stream as TtyWriteStream).isTTY) { logger.warn( `Cannot use 'progress-bar' formatter for output to '${target}' as not a TTY. Switching to 'progress' formatter.` diff --git a/src/formatter/builder.ts b/src/formatter/builder.ts index ac0419efd..45afb81f7 100644 --- a/src/formatter/builder.ts +++ b/src/formatter/builder.ts @@ -42,7 +42,10 @@ const FormatterBuilder = { type, options.cwd ) - const colorFns = getColorFns(options.parsedArgvOptions.colorsEnabled) + const colorFns = getColorFns( + options.stream, + options.parsedArgvOptions.colorsEnabled + ) const snippetBuilder = await FormatterBuilder.getStepDefinitionSnippetBuilder({ cwd: options.cwd, diff --git a/src/formatter/get_color_fns.ts b/src/formatter/get_color_fns.ts index 31df7684f..1b3618ecc 100644 --- a/src/formatter/get_color_fns.ts +++ b/src/formatter/get_color_fns.ts @@ -1,5 +1,8 @@ import chalk from 'chalk' +import { ColorInfo, supportsColor } from 'supports-color' import { TestStepResultStatus } from '@cucumber/messages' +import { Writable } from 'stream' +import { doesNotHaveValue } from '../value_checker' export type IColorFn = (text: string) => string @@ -13,26 +16,36 @@ export interface IColorFns { errorStack: IColorFn } -export default function getColorFns(enabled: boolean): IColorFns { +export default function getColorFns( + stream: Writable, + enabled?: boolean +): IColorFns { + const support: ColorInfo = supportsColor(stream, { sniffFlags: false }) + if (doesNotHaveValue(enabled)) { + enabled = !!support + } if (enabled) { + const chalkInstance = new chalk.Instance({ + level: support ? support.level : 1, + }) return { forStatus(status: TestStepResultStatus) { return { - AMBIGUOUS: chalk.red.bind(chalk), - FAILED: chalk.red.bind(chalk), - PASSED: chalk.green.bind(chalk), - PENDING: chalk.yellow.bind(chalk), - SKIPPED: chalk.cyan.bind(chalk), - UNDEFINED: chalk.yellow.bind(chalk), - UNKNOWN: chalk.yellow.bind(chalk), + AMBIGUOUS: chalkInstance.red.bind(chalk), + FAILED: chalkInstance.red.bind(chalk), + PASSED: chalkInstance.green.bind(chalk), + PENDING: chalkInstance.yellow.bind(chalk), + SKIPPED: chalkInstance.cyan.bind(chalk), + UNDEFINED: chalkInstance.yellow.bind(chalk), + UNKNOWN: chalkInstance.yellow.bind(chalk), }[status] }, - location: chalk.gray.bind(chalk), - tag: chalk.cyan.bind(chalk), - diffAdded: chalk.green.bind(chalk), - diffRemoved: chalk.red.bind(chalk), - errorMessage: chalk.red.bind(chalk), - errorStack: chalk.grey.bind(chalk), + location: chalkInstance.gray.bind(chalk), + tag: chalkInstance.cyan.bind(chalk), + diffAdded: chalkInstance.green.bind(chalk), + diffRemoved: chalkInstance.red.bind(chalk), + errorMessage: chalkInstance.red.bind(chalk), + errorStack: chalkInstance.grey.bind(chalk), } } else { return { diff --git a/src/formatter/helpers/issue_helpers_spec.ts b/src/formatter/helpers/issue_helpers_spec.ts index 9c2c350dc..2ff967bfa 100644 --- a/src/formatter/helpers/issue_helpers_spec.ts +++ b/src/formatter/helpers/issue_helpers_spec.ts @@ -7,6 +7,7 @@ import { getTestCaseAttempts } from '../../../test/formatter_helpers' import { reindent } from 'reindent-template-literals' import { getBaseSupportCodeLibrary } from '../../../test/fixtures/steps' import FormatterBuilder from '../builder' +import { PassThrough } from 'stream' async function testFormatIssue( sourceData: string, @@ -24,7 +25,7 @@ async function testFormatIssue( supportCodeLibrary, }) return formatIssue({ - colorFns: getColorFns(false), + colorFns: getColorFns(new PassThrough(), false), number: 1, snippetBuilder: await FormatterBuilder.getStepDefinitionSnippetBuilder({ cwd: 'project/', diff --git a/src/formatter/helpers/summary_helpers_spec.ts b/src/formatter/helpers/summary_helpers_spec.ts index 42cded1bb..b6208aa28 100644 --- a/src/formatter/helpers/summary_helpers_spec.ts +++ b/src/formatter/helpers/summary_helpers_spec.ts @@ -11,6 +11,7 @@ import { IRuntimeOptions } from '../../runtime' import { ISupportCodeLibrary } from '../../support_code_library_builder/types' import { doesNotHaveValue } from '../../value_checker' import * as messages from '@cucumber/messages' +import { PassThrough } from 'stream' interface ITestFormatSummaryOptions { runtimeOptions?: Partial @@ -53,7 +54,7 @@ async function testFormatSummary({ supportCodeLibrary, }) return formatSummary({ - colorFns: getColorFns(false), + colorFns: getColorFns(new PassThrough(), false), testCaseAttempts, testRunDuration: durationBetweenTimestamps( testRunStarted.timestamp, diff --git a/src/types/supports-color/index.d.ts b/src/types/supports-color/index.d.ts new file mode 100644 index 000000000..562a6233f --- /dev/null +++ b/src/types/supports-color/index.d.ts @@ -0,0 +1,20 @@ +declare module 'supports-color' { + import { Writable } from 'stream' + + export interface Options { + readonly sniffFlags?: boolean + } + + export type ColorSupportLevel = 0 | 1 | 2 | 3 + + export interface ColorSupport { + level: ColorSupportLevel + hasBasic: boolean + has256: boolean + has16m: boolean + } + + export type ColorInfo = ColorSupport | false + + export function supportsColor(stream: Writable, options?: Options): ColorInfo +} From 67a58bf973d1d83f96b5ceccc2b8f4317150abc5 Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 09:17:18 +0100 Subject: [PATCH 04/13] simplify wording --- features/colors.feature | 4 ++-- features/step_definitions/file_steps.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features/colors.feature b/features/colors.feature index 4cadbf269..a3130d5f0 100644 --- a/features/colors.feature +++ b/features/colors.feature @@ -20,9 +20,9 @@ Feature: Colors Scenario: no colored output by default for a file stream When I run cucumber-js with `--format summary:summary.out` Then it passes - Then the file "summary.out" doesn't contain ansi colors + Then the file "summary.out" doesn't contain colors Scenario: colored output can be explicitly activated for a file stream When I run cucumber-js with `--format summary:summary.out --format-options '{"colorsEnabled":true}'` Then it passes - Then the file "summary.out" contains ansi colors + Then the file "summary.out" contains colors diff --git a/features/step_definitions/file_steps.ts b/features/step_definitions/file_steps.ts index 39e084e98..218731b9e 100644 --- a/features/step_definitions/file_steps.ts +++ b/features/step_definitions/file_steps.ts @@ -53,7 +53,7 @@ Then( ) Then( - 'the file {string} contains ansi colors', + 'the file {string} contains colors', async function (this: World, filePath: string) { filePath = Mustache.render(filePath, this) const absoluteFilePath = path.resolve(this.tmpDir, filePath) @@ -63,7 +63,7 @@ Then( ) Then( - "the file {string} doesn't contain ansi colors", + "the file {string} doesn't contain colors", async function (this: World, filePath: string) { filePath = Mustache.render(filePath, this) const absoluteFilePath = path.resolve(this.tmpDir, filePath) From e69b8c5d541811dbd6f87d50f661306e6ed47b5e Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 10:01:40 +0100 Subject: [PATCH 05/13] look for FORCE_COLOR, refactor --- features/colors.feature | 8 ++++++- src/api/formatters.ts | 3 +++ src/api/run_cucumber.ts | 1 + src/formatter/builder.ts | 2 ++ src/formatter/get_color_fns.ts | 24 ++++++++++++------- src/formatter/helpers/issue_helpers_spec.ts | 2 +- src/formatter/helpers/summary_helpers_spec.ts | 2 +- src/formatter/progress_bar_formatter_spec.ts | 1 + src/types/supports-color/index.d.ts | 3 --- test/formatter_helpers.ts | 1 + 10 files changed, 33 insertions(+), 14 deletions(-) diff --git a/features/colors.feature b/features/colors.feature index a3130d5f0..816399ac8 100644 --- a/features/colors.feature +++ b/features/colors.feature @@ -1,3 +1,4 @@ +@spawn Feature: Colors As a developer @@ -22,7 +23,12 @@ Feature: Colors Then it passes Then the file "summary.out" doesn't contain colors - Scenario: colored output can be explicitly activated for a file stream + Scenario: colored output can be activated with the format option When I run cucumber-js with `--format summary:summary.out --format-options '{"colorsEnabled":true}'` Then it passes Then the file "summary.out" contains colors + + Scenario: colored output can be activated with FORCE_COLOR + When I run cucumber-js with arguments `--format summary:summary.out` and env `FORCE_COLOR=1` + Then it passes + Then the file "summary.out" contains colors diff --git a/src/api/formatters.ts b/src/api/formatters.ts index 6cc43653a..f01a16a10 100644 --- a/src/api/formatters.ts +++ b/src/api/formatters.ts @@ -13,6 +13,7 @@ import { Writable } from 'stream' import { IRunOptionsFormats } from './types' export async function initializeFormatters({ + env, cwd, stdout, logger, @@ -22,6 +23,7 @@ export async function initializeFormatters({ configuration, supportCodeLibrary, }: { + env: NodeJS.ProcessEnv cwd: string stdout: IFormatterStream logger: Console @@ -41,6 +43,7 @@ export async function initializeFormatters({ onStreamError() }) const typeOptions = { + env, cwd, eventBroadcaster, eventDataCollector, diff --git a/src/api/run_cucumber.ts b/src/api/run_cucumber.ts index cf5faa05c..096d728f1 100644 --- a/src/api/run_cucumber.ts +++ b/src/api/run_cucumber.ts @@ -55,6 +55,7 @@ export async function runCucumber( let formatterStreamError = false const cleanup = await initializeFormatters({ + env, cwd, stdout, logger, diff --git a/src/formatter/builder.ts b/src/formatter/builder.ts index 45afb81f7..264ecaa5d 100644 --- a/src/formatter/builder.ts +++ b/src/formatter/builder.ts @@ -26,6 +26,7 @@ interface IGetStepDefinitionSnippetBuilderOptions { } export interface IBuildOptions { + env: NodeJS.ProcessEnv cwd: string eventBroadcaster: EventEmitter eventDataCollector: EventDataCollector @@ -44,6 +45,7 @@ const FormatterBuilder = { ) const colorFns = getColorFns( options.stream, + options.env, options.parsedArgvOptions.colorsEnabled ) const snippetBuilder = diff --git a/src/formatter/get_color_fns.ts b/src/formatter/get_color_fns.ts index 1b3618ecc..ab1e8a7b6 100644 --- a/src/formatter/get_color_fns.ts +++ b/src/formatter/get_color_fns.ts @@ -18,16 +18,12 @@ export interface IColorFns { export default function getColorFns( stream: Writable, + env: NodeJS.ProcessEnv, enabled?: boolean ): IColorFns { - const support: ColorInfo = supportsColor(stream, { sniffFlags: false }) - if (doesNotHaveValue(enabled)) { - enabled = !!support - } - if (enabled) { - const chalkInstance = new chalk.Instance({ - level: support ? support.level : 1, - }) + const support: ColorInfo = detectSupport(stream, env, enabled) + if (support) { + const chalkInstance = new chalk.Instance(support) return { forStatus(status: TestStepResultStatus) { return { @@ -61,3 +57,15 @@ export default function getColorFns( } } } + +function detectSupport( + stream: Writable, + env: NodeJS.ProcessEnv, + enabled?: boolean +): ColorInfo { + const support: ColorInfo = supportsColor(stream, { sniffFlags: false }) + if ('FORCE_COLOR' in env || doesNotHaveValue(enabled)) { + return support + } + return enabled ? support || { level: 1 } : false +} diff --git a/src/formatter/helpers/issue_helpers_spec.ts b/src/formatter/helpers/issue_helpers_spec.ts index 2ff967bfa..0c00b90d1 100644 --- a/src/formatter/helpers/issue_helpers_spec.ts +++ b/src/formatter/helpers/issue_helpers_spec.ts @@ -25,7 +25,7 @@ async function testFormatIssue( supportCodeLibrary, }) return formatIssue({ - colorFns: getColorFns(new PassThrough(), false), + colorFns: getColorFns(new PassThrough(), {}, false), number: 1, snippetBuilder: await FormatterBuilder.getStepDefinitionSnippetBuilder({ cwd: 'project/', diff --git a/src/formatter/helpers/summary_helpers_spec.ts b/src/formatter/helpers/summary_helpers_spec.ts index b6208aa28..0c80d8cee 100644 --- a/src/formatter/helpers/summary_helpers_spec.ts +++ b/src/formatter/helpers/summary_helpers_spec.ts @@ -54,7 +54,7 @@ async function testFormatSummary({ supportCodeLibrary, }) return formatSummary({ - colorFns: getColorFns(new PassThrough(), false), + colorFns: getColorFns(new PassThrough(), {}, false), testCaseAttempts, testRunDuration: durationBetweenTimestamps( testRunStarted.timestamp, diff --git a/src/formatter/progress_bar_formatter_spec.ts b/src/formatter/progress_bar_formatter_spec.ts index cc893d4ee..0def59cef 100644 --- a/src/formatter/progress_bar_formatter_spec.ts +++ b/src/formatter/progress_bar_formatter_spec.ts @@ -59,6 +59,7 @@ async function testProgressBarFormatter({ } const passThrough = new PassThrough() const progressBarFormatter = (await FormatterBuilder.build('progress-bar', { + env: {}, cwd: '', eventBroadcaster, eventDataCollector: new EventDataCollector(eventBroadcaster), diff --git a/src/types/supports-color/index.d.ts b/src/types/supports-color/index.d.ts index 562a6233f..61a768d05 100644 --- a/src/types/supports-color/index.d.ts +++ b/src/types/supports-color/index.d.ts @@ -9,9 +9,6 @@ declare module 'supports-color' { export interface ColorSupport { level: ColorSupportLevel - hasBasic: boolean - has256: boolean - has16m: boolean } export type ColorInfo = ColorSupport | false diff --git a/test/formatter_helpers.ts b/test/formatter_helpers.ts index 3a0008854..51b13f69b 100644 --- a/test/formatter_helpers.ts +++ b/test/formatter_helpers.ts @@ -61,6 +61,7 @@ export async function testFormatter({ } const passThrough = new PassThrough() await FormatterBuilder.build(type, { + env: {}, cwd: '', eventBroadcaster, eventDataCollector, From 017f7f2dd2ce66409ab6cb989c4d4fa4d18b8e9c Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 10:51:54 +0100 Subject: [PATCH 06/13] check for FORCE_COLOR precedence --- features/colors.feature | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/features/colors.feature b/features/colors.feature index 816399ac8..4788ffc23 100644 --- a/features/colors.feature +++ b/features/colors.feature @@ -17,18 +17,23 @@ Feature: Colors const {Given} = require('@cucumber/cucumber') Given('a step', function() {}) """ + And a file named "cucumber.json" with: + """ + { "default": { "format": ["summary:summary.out"] } } + """ Scenario: no colored output by default for a file stream - When I run cucumber-js with `--format summary:summary.out` - Then it passes + When I run cucumber-js Then the file "summary.out" doesn't contain colors Scenario: colored output can be activated with the format option - When I run cucumber-js with `--format summary:summary.out --format-options '{"colorsEnabled":true}'` - Then it passes + When I run cucumber-js with `--format-options '{"colorsEnabled":true}'` Then the file "summary.out" contains colors Scenario: colored output can be activated with FORCE_COLOR - When I run cucumber-js with arguments `--format summary:summary.out` and env `FORCE_COLOR=1` - Then it passes + When I run cucumber-js with env `FORCE_COLOR=1` + Then the file "summary.out" contains colors + + Scenario: FORCE_COLOR takes precedence over the format option + When I run cucumber-js with arguments `--format-options '{"colorsEnabled":false}'` and env `FORCE_COLOR=1` Then the file "summary.out" contains colors From f5103c7987ac25f9b9a63e8697e2d2732f0bab0b Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 17:31:06 +0100 Subject: [PATCH 07/13] docs --- docs/formatters.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/formatters.md b/docs/formatters.md index f611989fd..6ab87f001 100644 --- a/docs/formatters.md +++ b/docs/formatters.md @@ -39,9 +39,17 @@ This option is repeatable, so you can use it multiple times and the objects will Some options offered by built-in formatters: -- `colorsEnabled` - if set to `false`, colors in terminal output are disabled +- `colorsEnabled` - see below - `printAttachments` - if set to `false`, attachments won't be part of progress bars and summary reports +## Colored output + +Many formatters, including the built-in ones, emit some colored output. By default, Cucumber will automatically detect the colors support of the output stream and decide whether to emit colors accordingly. This check comes via the supports-colors library and is pretty comprehensive, including awareness of commonly-used operating systems and CI platforms that represent edge cases. + +If you'd like to override the auto-detected behaviour, you can provide the `colorsEnabled` format option - either `true` to forcibly emit colors, or `false` to forcibly disable them. + +It's worth noting that this option only influences output that Cucumber is in control of. Other tools in your stack such as assertion libraries may have their own way of handling colors. For this reason we'd recommend setting the `FORCE_COLOR` environment variable if you want to forcibly enable or disable colors, as a variety of tools (including Cucumber) will honour it. + ## Built-in formatters ### `summary` From b360146001e5ea160b03412c67abb288bc500f48 Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 17:31:55 +0100 Subject: [PATCH 08/13] tweak feature wording --- features/colors.feature | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/features/colors.feature b/features/colors.feature index 4788ffc23..05f6cc537 100644 --- a/features/colors.feature +++ b/features/colors.feature @@ -2,8 +2,7 @@ Feature: Colors As a developer - I want to control when/whether the output includes ANSI colors - So that I can be happy + I want to control when/whether the output includes colors Background: Given a file named "features/a.feature" with: From 3ec4d34614d710bf7e144a306f2af4156466083b Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 17:34:27 +0100 Subject: [PATCH 09/13] update docs a bit --- docs/formatters.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/formatters.md b/docs/formatters.md index 6ab87f001..0213eb91a 100644 --- a/docs/formatters.md +++ b/docs/formatters.md @@ -44,11 +44,11 @@ Some options offered by built-in formatters: ## Colored output -Many formatters, including the built-in ones, emit some colored output. By default, Cucumber will automatically detect the colors support of the output stream and decide whether to emit colors accordingly. This check comes via the supports-colors library and is pretty comprehensive, including awareness of commonly-used operating systems and CI platforms that represent edge cases. +Many formatters, including the built-in ones, emit some colored output. By default, Cucumber will automatically detect the colors support of the output stream and decide whether to emit colors accordingly. This check comes via the [supports-colors](https://github.com/chalk/supports-color) library and is pretty comprehensive, including awareness of commonly-used operating systems and CI platforms that represent edge cases. -If you'd like to override the auto-detected behaviour, you can provide the `colorsEnabled` format option - either `true` to forcibly emit colors, or `false` to forcibly disable them. +If you'd like to override the auto-detection behaviour, you can provide the `colorsEnabled` format option - either `true` to forcibly emit colors, or `false` to forcibly disable them. -It's worth noting that this option only influences output that Cucumber is in control of. Other tools in your stack such as assertion libraries may have their own way of handling colors. For this reason we'd recommend setting the `FORCE_COLOR` environment variable if you want to forcibly enable or disable colors, as a variety of tools (including Cucumber) will honour it. +It's worth noting that this option only influences output that Cucumber is in control of. Other tools in your stack such as assertion libraries might have their own way of handling colors. For this reason we'd recommend setting the `FORCE_COLOR` environment variable if you want to forcibly enable (by setting it to `1`) or disable (by setting it to `0`) colors, as a variety of tools (including Cucumber) will honour it. ## Built-in formatters From babf3b75037b62cf6a888a487e5297db03a9da89 Mon Sep 17 00:00:00 2001 From: David Goss Date: Sun, 1 May 2022 17:37:55 +0100 Subject: [PATCH 10/13] ditch the sniffFlags option --- src/formatter/get_color_fns.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/formatter/get_color_fns.ts b/src/formatter/get_color_fns.ts index ab1e8a7b6..3887436ee 100644 --- a/src/formatter/get_color_fns.ts +++ b/src/formatter/get_color_fns.ts @@ -63,7 +63,7 @@ function detectSupport( env: NodeJS.ProcessEnv, enabled?: boolean ): ColorInfo { - const support: ColorInfo = supportsColor(stream, { sniffFlags: false }) + const support: ColorInfo = supportsColor(stream) if ('FORCE_COLOR' in env || doesNotHaveValue(enabled)) { return support } From e33a0c1a30ea0e06d729a978749874dbf6c7737f Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 3 May 2022 16:52:00 +0100 Subject: [PATCH 11/13] add comment --- src/formatter/get_color_fns.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/formatter/get_color_fns.ts b/src/formatter/get_color_fns.ts index 3887436ee..8fdcc7b18 100644 --- a/src/formatter/get_color_fns.ts +++ b/src/formatter/get_color_fns.ts @@ -64,6 +64,7 @@ function detectSupport( enabled?: boolean ): ColorInfo { const support: ColorInfo = supportsColor(stream) + // if we find FORCE_COLOR, we can let the supports-color library handle that if ('FORCE_COLOR' in env || doesNotHaveValue(enabled)) { return support } From 77f4245b103ad41d38e887c40e4efbddc87ce0b4 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 3 May 2022 16:53:59 +0100 Subject: [PATCH 12/13] add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2e753da..35f569025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) on how to contribute to Cucumber. ## [Unreleased] +### Changed +- Fix issues with colored output, support `FORCE_COLOR` environment variable as an override ([#2026](https://github.com/cucumber/cucumber-js/pull/2026)) ## [8.1.2] - 2022-04-22 ### Added From 64882edff297f44aeec77708f0f4ececfa30814e Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 3 May 2022 16:54:56 +0100 Subject: [PATCH 13/13] Update docs/formatters.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aurélien Reeves --- docs/formatters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/formatters.md b/docs/formatters.md index 0213eb91a..c7490002e 100644 --- a/docs/formatters.md +++ b/docs/formatters.md @@ -39,7 +39,7 @@ This option is repeatable, so you can use it multiple times and the objects will Some options offered by built-in formatters: -- `colorsEnabled` - see below +- `colorsEnabled` - [see below](#colored-output) - `printAttachments` - if set to `false`, attachments won't be part of progress bars and summary reports ## Colored output