color-capable 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/index.js +255 -0
- package/package.json +63 -0
- package/test.js +429 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# color-capable
|
|
2
|
+
|
|
3
|
+
> Detect whether a terminal supports color
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install color-capable
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const supportsColor = require("color-capable")
|
|
15
|
+
|
|
16
|
+
if (supportsColor.stdout) {
|
|
17
|
+
console.log("Terminal stdout supports color")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (supportsColor.stdout.has256) {
|
|
21
|
+
console.log("Terminal stdout supports 256 colors")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (supportsColor.stderr.has16m) {
|
|
25
|
+
console.log("Terminal stderr supports 16 million colors (truecolor)")
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
Returns an `object` with a `stdout` and `stderr` property for testing either streams. Each property is an `Object`, or `false` if color is not supported.
|
|
32
|
+
|
|
33
|
+
The `stdout`/`stderr` objects specifies a level of support for color through a `.level` property and a corresponding flag:
|
|
34
|
+
|
|
35
|
+
- `.level = 1` and `.hasBasic = true`: Basic color support (16 colors)
|
|
36
|
+
- `.level = 2` and `.has256 = true`: 256 color support
|
|
37
|
+
- `.level = 3` and `.has16m = true`: Truecolor support (16 million colors)
|
|
38
|
+
|
|
39
|
+
### Custom instance
|
|
40
|
+
|
|
41
|
+
The package also exposes the named export `createSupportColor` function that takes an arbitrary write stream (for example, `process.stdout`) and an optional options object to (re-)evaluate color support for an arbitrary stream.
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const { createSupportsColor } = require("color-capable")
|
|
45
|
+
|
|
46
|
+
const stdoutSupportsColor = createSupportsColor(process.stdout)
|
|
47
|
+
|
|
48
|
+
if (stdoutSupportsColor) {
|
|
49
|
+
console.log("Terminal stdout supports color")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// `stdoutSupportsColor` is the same as `supportsColor.stdout`
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The options object supports a single boolean property `sniffFlags`. By default it is `true`, which instructs the detection to sniff `process.argv` for the multitude of `--color` flags (see _Info_ below). If `false`, then `process.argv` is not considered when determining color support.
|
|
56
|
+
|
|
57
|
+
## Info
|
|
58
|
+
|
|
59
|
+
It obeys the `--color` and `--no-color` CLI flags.
|
|
60
|
+
|
|
61
|
+
For situations where using `--color` is not possible, use the environment variable `FORCE_COLOR=1` (level 1), `FORCE_COLOR=2` (level 2), or `FORCE_COLOR=3` (level 3) to forcefully enable color, or `FORCE_COLOR=0` to forcefully disable. The use of `FORCE_COLOR` overrides all other color support checks.
|
|
62
|
+
|
|
63
|
+
Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color=16m` flags, respectively.
|
|
64
|
+
|
|
65
|
+
## Tests
|
|
66
|
+
This has tests. You can do a git clone and run npm test to try them out.
|
package/index.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
const process = require("process")
|
|
2
|
+
const os = require("os")
|
|
3
|
+
const tty = require("tty")
|
|
4
|
+
const getIntrinsic = require("es-intrinsic-cache")
|
|
5
|
+
const $String = getIntrinsic("String")
|
|
6
|
+
const $Number = getIntrinsic("Number")
|
|
7
|
+
const hasFlag = require("has-argv-flag")
|
|
8
|
+
const forEach = require("for-each")
|
|
9
|
+
const { min, isUndefined, not, and, or, add, multiply } = require("aura3")
|
|
10
|
+
const zero = require("@positive-numbers/zero")
|
|
11
|
+
const one = require("@positive-numbers/one")
|
|
12
|
+
const two = require("@positive-numbers/two")
|
|
13
|
+
const three = require("@positive-numbers/three")
|
|
14
|
+
const six = require("@positive-numbers/six")
|
|
15
|
+
const ten = require("@positive-numbers/ten")
|
|
16
|
+
const thirtyOne = require("@positive-numbers/thirty-one")
|
|
17
|
+
const seventy = require("@positive-numbers/seventy")
|
|
18
|
+
const eightySix = require("@positive-numbers/eighty-six")
|
|
19
|
+
const ninetyNine = require("@positive-numbers/ninety-nine")
|
|
20
|
+
const oneHundred = require("@positive-numbers/one-hundred")
|
|
21
|
+
const fourThousandNineHundred = multiply(seventy, seventy)
|
|
22
|
+
const tenThousand = multiply(oneHundred, oneHundred)
|
|
23
|
+
const tenThousandFiveHundredEightySix = add(multiply(oneHundred, add(ninetyNine, six)), eightySix)
|
|
24
|
+
const fourteenThousandNineHundredThirtyOne = add(tenThousand, add(fourThousandNineHundred, thirtyOne))
|
|
25
|
+
const True = require("true-value")
|
|
26
|
+
const False = require("false-value")
|
|
27
|
+
const stringifiedTrue = $String(True())
|
|
28
|
+
const stringifiedFalse = $String(False())
|
|
29
|
+
const isEqual = require("@10xly/strict-equals")
|
|
30
|
+
const parseInt = require("number.parseint")
|
|
31
|
+
const includes = require("array-includes")
|
|
32
|
+
const isZero = require("is-eq-zero")
|
|
33
|
+
const construct = require("construct-new")
|
|
34
|
+
const { TernaryCompare } = require("important-extremely-useful-classes")
|
|
35
|
+
const emptyString = require("empty-string")
|
|
36
|
+
|
|
37
|
+
const env = process.env
|
|
38
|
+
|
|
39
|
+
const listOfFlagsThatDenyColor = [
|
|
40
|
+
"no-color",
|
|
41
|
+
"no-colors",
|
|
42
|
+
"color=false",
|
|
43
|
+
"color=never"
|
|
44
|
+
]
|
|
45
|
+
const listOfFlagsThatHappilyAllowColor = [
|
|
46
|
+
"color",
|
|
47
|
+
"colors",
|
|
48
|
+
"color=true",
|
|
49
|
+
"color=always"
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
let flagForceColor
|
|
53
|
+
|
|
54
|
+
forEach(listOfFlagsThatDenyColor, function (flagThatDeniesColor) {
|
|
55
|
+
if (hasFlag(flagThatDeniesColor)) {
|
|
56
|
+
flagForceColor = zero
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
forEach(listOfFlagsThatHappilyAllowColor, function (flagThatHappilyAllowsColor) {
|
|
60
|
+
if (hasFlag(flagThatHappilyAllowsColor)) {
|
|
61
|
+
flagForceColor = one
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const NAME_OF_THE_ENV_FORCE_COLOR_FLAG = "FORCE_COLOR"
|
|
66
|
+
|
|
67
|
+
function envForceColor() {
|
|
68
|
+
if (not(NAME_OF_THE_ENV_FORCE_COLOR_FLAG in env)) {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isEqual(env[NAME_OF_THE_ENV_FORCE_COLOR_FLAG], stringifiedTrue)) {
|
|
73
|
+
return one
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isEqual(env[NAME_OF_THE_ENV_FORCE_COLOR_FLAG], stringifiedFalse)) {
|
|
77
|
+
return zero
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isEqual(env[NAME_OF_THE_ENV_FORCE_COLOR_FLAG].length, zero)) {
|
|
81
|
+
return zero
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const level = min(parseInt(env.FORCE_COLOR, ten), three)
|
|
85
|
+
|
|
86
|
+
if (not(includes([zero, one, two, three], level))) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return level
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function translateLevel(level) {
|
|
94
|
+
if (isZero(level)) {
|
|
95
|
+
return False()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
level,
|
|
100
|
+
hasBasic: True(),
|
|
101
|
+
has256: level >= two,
|
|
102
|
+
has16m: level >= three,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = True() } = {}) {
|
|
107
|
+
const noFlagForceColor = envForceColor()
|
|
108
|
+
if (not(isUndefined(noFlagForceColor))) {
|
|
109
|
+
flagForceColor = noFlagForceColor
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let forceColor = construct({
|
|
113
|
+
target: TernaryCompare,
|
|
114
|
+
args: [sniffFlags, () => flagForceColor, () => noFlagForceColor]
|
|
115
|
+
})
|
|
116
|
+
forceColor = forceColor.compare()
|
|
117
|
+
forceColor = forceColor()
|
|
118
|
+
|
|
119
|
+
if (isZero(forceColor)) {
|
|
120
|
+
return forceColor
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (sniffFlags) {
|
|
124
|
+
if (or(
|
|
125
|
+
hasFlag("color=16m"),
|
|
126
|
+
or(
|
|
127
|
+
hasFlag("color=full"),
|
|
128
|
+
hasFlag("color=truecolor")
|
|
129
|
+
)
|
|
130
|
+
)) {
|
|
131
|
+
return three
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (hasFlag("color=256")) {
|
|
135
|
+
return two
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check for Azure DevOps pipelines.
|
|
140
|
+
// Has to be above the `!streamIsTTY` check.
|
|
141
|
+
if (and("TF_BUILD" in env, "AGENT_NAME" in env)) {
|
|
142
|
+
return one
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (and(haveStream, and(not(streamIsTTY), isUndefined(forceColor)))) {
|
|
146
|
+
return zero
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const min = or(forceColor, zero)
|
|
150
|
+
|
|
151
|
+
if (isEqual(env.TERM, "dumb")) {
|
|
152
|
+
return min
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isEqual(process.platform, "win32")) {
|
|
156
|
+
// Windows 10 build 10586 is the first Windows release that supports 256 colors.
|
|
157
|
+
// Windows 10 build 14931 is the first release that supports 16m/TrueColor.
|
|
158
|
+
const osRelease = os.release().split(".")
|
|
159
|
+
if (
|
|
160
|
+
and(
|
|
161
|
+
$Number(osRelease[zero]) >= ten
|
|
162
|
+
, $Number(osRelease[two]) >= tenThousandFiveHundredEightySix)
|
|
163
|
+
) {
|
|
164
|
+
if ($Number(osRelease[two]) >= fourteenThousandNineHundredThirtyOne) {
|
|
165
|
+
return three
|
|
166
|
+
} else {
|
|
167
|
+
return two
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return one
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if ("CI" in env) {
|
|
175
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some(key => key in env)) {
|
|
176
|
+
return three
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some(sign => sign in env) || env.CI_NAME === "codeship") {
|
|
180
|
+
return one
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return min
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
187
|
+
if (/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION)) {
|
|
188
|
+
return one
|
|
189
|
+
} else {
|
|
190
|
+
return zero
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (isEqual(env.COLORTERM, "truecolor")) {
|
|
195
|
+
return three
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (isEqual(env.TERM, "xterm-kitty")) {
|
|
199
|
+
return two
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (isEqual(env.TERM, "xterm-ghostty")) {
|
|
203
|
+
return three
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (isEqual(env.TERM, "wezterm")) {
|
|
207
|
+
return three
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if ("TERM_PROGRAM" in env) {
|
|
211
|
+
const version = parseInt((or(env.TERM_PROGRAM_VERSION, emptyString)).split(".")[zero], ten)
|
|
212
|
+
|
|
213
|
+
if (isEqual(env.TERM_PROGRAM, "iTerm.app")) {
|
|
214
|
+
if (version >= three) {
|
|
215
|
+
return three
|
|
216
|
+
} else {
|
|
217
|
+
return two
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (isEqual(env.TERM_PROGRAM, "Apple_Terminal")) {
|
|
221
|
+
return two
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
226
|
+
return two
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
230
|
+
return one
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if ("COLORTERM" in env) {
|
|
234
|
+
return one
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return min
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function createSupportsColor(stream, options = {}) {
|
|
241
|
+
const level = _supportsColor(stream, {
|
|
242
|
+
streamIsTTY: and(stream, stream.isTTY),
|
|
243
|
+
...options,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
return translateLevel(level)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const supportsColor = {
|
|
250
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(one) }),
|
|
251
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(two) }),
|
|
252
|
+
createSupportsColor: createSupportsColor
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = supportsColor
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "color-capable",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Check if the terminal has support for color.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"supports",
|
|
7
|
+
"color",
|
|
8
|
+
"terminal",
|
|
9
|
+
"colors",
|
|
10
|
+
"support",
|
|
11
|
+
"has",
|
|
12
|
+
"has-color",
|
|
13
|
+
"supports-color",
|
|
14
|
+
"chalk4096",
|
|
15
|
+
"chalk"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/Chalk4096/color-capable#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/Chalk4096/color-capable/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Chalk4096/color-capable.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "10x'ly Made",
|
|
27
|
+
"type": "commonjs",
|
|
28
|
+
"main": "index.js",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "mocha test.js --timeout 10000000"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@10xly/strict-equals": "^1.0.0",
|
|
34
|
+
"@positive-numbers/eighty-six": "^3.0.0",
|
|
35
|
+
"@positive-numbers/ninety-nine": "^3.0.0",
|
|
36
|
+
"@positive-numbers/one": "^3.0.0",
|
|
37
|
+
"@positive-numbers/one-hundred": "^3.0.0",
|
|
38
|
+
"@positive-numbers/seventy": "^3.0.0",
|
|
39
|
+
"@positive-numbers/six": "^3.0.0",
|
|
40
|
+
"@positive-numbers/ten": "^3.0.0",
|
|
41
|
+
"@positive-numbers/thirty-one": "^3.0.0",
|
|
42
|
+
"@positive-numbers/three": "^3.0.0",
|
|
43
|
+
"@positive-numbers/two": "^3.0.0",
|
|
44
|
+
"@positive-numbers/zero": "^3.0.0",
|
|
45
|
+
"array-includes": "^3.1.9",
|
|
46
|
+
"aura3": "^1.0.3-enterprise.stable",
|
|
47
|
+
"construct-new": "^2.0.4",
|
|
48
|
+
"empty-string": "^1.1.1",
|
|
49
|
+
"es-intrinsic-cache": "^1.0.1",
|
|
50
|
+
"false-value": "^2.0.6",
|
|
51
|
+
"for-each": "^0.3.5",
|
|
52
|
+
"has-argv-flag": "^1.1.2",
|
|
53
|
+
"important-extremely-useful-classes": "^3.1.0",
|
|
54
|
+
"is-eq-zero": "^1.1.0",
|
|
55
|
+
"number.parseint": "^1.1.0",
|
|
56
|
+
"true-value": "^2.0.5"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"chai": "^6.2.2",
|
|
60
|
+
"mocha": "^11.7.5",
|
|
61
|
+
"proxyquire": "^2.1.3"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/* eslint-env mocha */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { expect } = require("chai");
|
|
5
|
+
const proxyquire = require("proxyquire").noCallThru();
|
|
6
|
+
|
|
7
|
+
function loadSupportsColor({
|
|
8
|
+
env = {},
|
|
9
|
+
platform = "linux",
|
|
10
|
+
osRelease = "5.0.0",
|
|
11
|
+
stdoutIsTTY = true,
|
|
12
|
+
stderrIsTTY = true,
|
|
13
|
+
argvFlags = [],
|
|
14
|
+
} = {}) {
|
|
15
|
+
const processStub = {
|
|
16
|
+
env: { ...env },
|
|
17
|
+
platform,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const osStub = {
|
|
21
|
+
release: () => osRelease,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const ttyStub = {
|
|
25
|
+
isatty: (fd) => {
|
|
26
|
+
if (fd === 1) return stdoutIsTTY;
|
|
27
|
+
if (fd === 2) return stderrIsTTY;
|
|
28
|
+
return false;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const hasArgvFlagStub = (flag) => argvFlags.includes(flag);
|
|
33
|
+
|
|
34
|
+
const supportsColor = proxyquire("./index", {
|
|
35
|
+
process: processStub,
|
|
36
|
+
os: osStub,
|
|
37
|
+
tty: ttyStub,
|
|
38
|
+
"has-argv-flag": hasArgvFlagStub,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { supportsColor, processStub, osStub, ttyStub };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("supportsColor", function () {
|
|
45
|
+
describe("createSupportsColor basic behavior", function () {
|
|
46
|
+
it("returns false when level is 0 (e.g. FORCE_COLOR=false)", function () {
|
|
47
|
+
const { supportsColor } = loadSupportsColor({
|
|
48
|
+
env: { FORCE_COLOR: "false" },
|
|
49
|
+
stdoutIsTTY: true,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
53
|
+
expect(result).to.equal(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns level 1 when FORCE_COLOR=true", function () {
|
|
57
|
+
const { supportsColor } = loadSupportsColor({
|
|
58
|
+
env: { FORCE_COLOR: "true" },
|
|
59
|
+
stdoutIsTTY: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
63
|
+
expect(result).to.deep.include({
|
|
64
|
+
level: 1,
|
|
65
|
+
hasBasic: true,
|
|
66
|
+
has256: false,
|
|
67
|
+
has16m: false,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns level 2 when FORCE_COLOR=2", function () {
|
|
72
|
+
const { supportsColor } = loadSupportsColor({
|
|
73
|
+
env: { FORCE_COLOR: "2" },
|
|
74
|
+
stdoutIsTTY: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
78
|
+
expect(result).to.deep.include({
|
|
79
|
+
level: 2,
|
|
80
|
+
hasBasic: true,
|
|
81
|
+
has256: true,
|
|
82
|
+
has16m: false,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("ignores invalid numeric FORCE_COLOR and falls back to TERM heuristics", function () {
|
|
87
|
+
const { supportsColor } = loadSupportsColor({
|
|
88
|
+
env: { FORCE_COLOR: "10", TERM: "xterm-256color" },
|
|
89
|
+
stdoutIsTTY: true,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
93
|
+
// no valid FORCE_COLOR => TERM-256color gives level 2
|
|
94
|
+
expect(result.level).to.equal(2);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("CLI flags (has-argv-flag)", function () {
|
|
99
|
+
it("forces flagForceColor = 0 with no-color", function () {
|
|
100
|
+
const { supportsColor } = loadSupportsColor({
|
|
101
|
+
argvFlags: ["no-color"],
|
|
102
|
+
env: { TERM: "xterm-256color" },
|
|
103
|
+
stdoutIsTTY: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
107
|
+
// translateLevel(0) => false
|
|
108
|
+
expect(result).to.equal(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("forces flagForceColor = 1 with color", function () {
|
|
112
|
+
const { supportsColor } = loadSupportsColor({
|
|
113
|
+
argvFlags: ["color"],
|
|
114
|
+
env: {},
|
|
115
|
+
stdoutIsTTY: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
119
|
+
expect(result.level).to.equal(1);
|
|
120
|
+
expect(result.hasBasic).to.equal(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns level 3 with color=16m flag", function () {
|
|
124
|
+
const { supportsColor } = loadSupportsColor({
|
|
125
|
+
argvFlags: ["color=16m"],
|
|
126
|
+
env: {},
|
|
127
|
+
stdoutIsTTY: true,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
131
|
+
expect(result.level).to.equal(3);
|
|
132
|
+
expect(result.has16m).to.equal(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("returns level 2 with color=256 flag", function () {
|
|
136
|
+
const { supportsColor } = loadSupportsColor({
|
|
137
|
+
argvFlags: ["color=256"],
|
|
138
|
+
env: {},
|
|
139
|
+
stdoutIsTTY: true,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
143
|
+
expect(result.level).to.equal(2);
|
|
144
|
+
expect(result.has256).to.equal(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("does not look at flags when sniffFlags = false", function () {
|
|
148
|
+
const { supportsColor } = loadSupportsColor({
|
|
149
|
+
argvFlags: ["color=16m"],
|
|
150
|
+
env: { TERM: "xterm-256color" },
|
|
151
|
+
stdoutIsTTY: true,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const result = supportsColor.createSupportsColor(
|
|
155
|
+
{ isTTY: true },
|
|
156
|
+
{ sniffFlags: false }
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// TERM=*-256color gives 2; color=16m is ignored
|
|
160
|
+
expect(result.level).to.equal(2);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("TTY behavior", function () {
|
|
165
|
+
it("returns false when stream is not TTY and no forceColor", function () {
|
|
166
|
+
const { supportsColor } = loadSupportsColor({
|
|
167
|
+
env: {},
|
|
168
|
+
stdoutIsTTY: false,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const result = supportsColor.createSupportsColor({ isTTY: false });
|
|
172
|
+
expect(result).to.equal(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("allows color when non-TTY but FORCE_COLOR=true", function () {
|
|
176
|
+
const { supportsColor } = loadSupportsColor({
|
|
177
|
+
env: { FORCE_COLOR: "true" },
|
|
178
|
+
stdoutIsTTY: false,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const result = supportsColor.createSupportsColor({ isTTY: false });
|
|
182
|
+
expect(result.level).to.equal(1);
|
|
183
|
+
expect(result.hasBasic).to.equal(true);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("Azure DevOps behavior", function () {
|
|
188
|
+
it("returns level 1 when TF_BUILD and AGENT_NAME exist", function () {
|
|
189
|
+
const { supportsColor } = loadSupportsColor({
|
|
190
|
+
env: { TF_BUILD: "1", AGENT_NAME: "agent" },
|
|
191
|
+
stdoutIsTTY: true,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
195
|
+
expect(result.level).to.equal(1);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("Windows behavior", function () {
|
|
200
|
+
it("returns level 1 for non-Windows 10 releases", function () {
|
|
201
|
+
const { supportsColor } = loadSupportsColor({
|
|
202
|
+
platform: "win32",
|
|
203
|
+
osRelease: "6.3.9600",
|
|
204
|
+
stdoutIsTTY: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
208
|
+
expect(result.level).to.equal(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("returns level 2 for Windows 10 build 10586+", function () {
|
|
212
|
+
const { supportsColor } = loadSupportsColor({
|
|
213
|
+
platform: "win32",
|
|
214
|
+
osRelease: "10.0.10586",
|
|
215
|
+
stdoutIsTTY: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
219
|
+
expect(result.level).to.equal(2);
|
|
220
|
+
expect(result.has256).to.equal(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("returns level 3 for Windows 10 build 14931+", function () {
|
|
224
|
+
const { supportsColor } = loadSupportsColor({
|
|
225
|
+
platform: "win32",
|
|
226
|
+
osRelease: "10.0.14931",
|
|
227
|
+
stdoutIsTTY: true,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
231
|
+
expect(result.level).to.equal(3);
|
|
232
|
+
expect(result.has16m).to.equal(true);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("CI behavior", function () {
|
|
237
|
+
it("returns level 3 on GitHub Actions", function () {
|
|
238
|
+
const { supportsColor } = loadSupportsColor({
|
|
239
|
+
env: { CI: "1", GITHUB_ACTIONS: "true" },
|
|
240
|
+
stdoutIsTTY: true,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
244
|
+
expect(result.level).to.equal(3);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("returns level 1 on Travis", function () {
|
|
248
|
+
const { supportsColor } = loadSupportsColor({
|
|
249
|
+
env: { CI: "1", TRAVIS: "true" },
|
|
250
|
+
stdoutIsTTY: true,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
254
|
+
expect(result.level).to.equal(1);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("returns min level when CI vendor is unknown", function () {
|
|
258
|
+
const { supportsColor } = loadSupportsColor({
|
|
259
|
+
env: { CI: "1" },
|
|
260
|
+
stdoutIsTTY: true,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
264
|
+
// min level when nothing else helps is 0 => false
|
|
265
|
+
expect(result === false || result.level === 0).to.equal(true);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("TEAMCITY behavior", function () {
|
|
270
|
+
it("returns level 1 for TEAMCITY_VERSION >= 9.1", function () {
|
|
271
|
+
const { supportsColor } = loadSupportsColor({
|
|
272
|
+
env: { TEAMCITY_VERSION: "9.1.0" },
|
|
273
|
+
stdoutIsTTY: true,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
277
|
+
expect(result.level).to.equal(1);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("returns false for TEAMCITY_VERSION < 9.1", function () {
|
|
281
|
+
const { supportsColor } = loadSupportsColor({
|
|
282
|
+
env: { TEAMCITY_VERSION: "8.1.0" },
|
|
283
|
+
stdoutIsTTY: true,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
287
|
+
expect(result).to.equal(false);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("TERM / COLORTERM / TERM_PROGRAM heuristics", function () {
|
|
292
|
+
it("returns level 3 when COLORTERM=truecolor", function () {
|
|
293
|
+
const { supportsColor } = loadSupportsColor({
|
|
294
|
+
env: { COLORTERM: "truecolor" },
|
|
295
|
+
stdoutIsTTY: true,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
299
|
+
expect(result.level).to.equal(3);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("returns level 2 when TERM=xterm-kitty", function () {
|
|
303
|
+
const { supportsColor } = loadSupportsColor({
|
|
304
|
+
env: { TERM: "xterm-kitty" },
|
|
305
|
+
stdoutIsTTY: true,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
309
|
+
expect(result.level).to.equal(2);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("returns level 3 when TERM=xterm-ghostty", function () {
|
|
313
|
+
const { supportsColor } = loadSupportsColor({
|
|
314
|
+
env: { TERM: "xterm-ghostty" },
|
|
315
|
+
stdoutIsTTY: true,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
319
|
+
expect(result.level).to.equal(3);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("returns level 3 when TERM=wezterm", function () {
|
|
323
|
+
const { supportsColor } = loadSupportsColor({
|
|
324
|
+
env: { TERM: "wezterm" },
|
|
325
|
+
stdoutIsTTY: true,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
329
|
+
expect(result.level).to.equal(3);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("returns level 2 when TERM ends with -256color", function () {
|
|
333
|
+
const { supportsColor } = loadSupportsColor({
|
|
334
|
+
env: { TERM: "xterm-256color" },
|
|
335
|
+
stdoutIsTTY: true,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
339
|
+
expect(result.level).to.equal(2);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("returns level 1 for generic color-capable TERM", function () {
|
|
343
|
+
const { supportsColor } = loadSupportsColor({
|
|
344
|
+
env: { TERM: "xterm" },
|
|
345
|
+
stdoutIsTTY: true,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
349
|
+
expect(result.level).to.equal(1);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("returns level 1 if COLORTERM is set even if TERM is weird", function () {
|
|
353
|
+
const { supportsColor } = loadSupportsColor({
|
|
354
|
+
env: { COLORTERM: "yes", TERM: "weirdterm" },
|
|
355
|
+
stdoutIsTTY: true,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
359
|
+
expect(result.level).to.equal(1);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("returns min (0) for TERM=dumb", function () {
|
|
363
|
+
const { supportsColor } = loadSupportsColor({
|
|
364
|
+
env: { TERM: "dumb" },
|
|
365
|
+
stdoutIsTTY: true,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
369
|
+
expect(result === false || result.level === 0).to.equal(true);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("returns level 3 for iTerm.app >= 3", function () {
|
|
373
|
+
const { supportsColor } = loadSupportsColor({
|
|
374
|
+
env: {
|
|
375
|
+
TERM_PROGRAM: "iTerm.app",
|
|
376
|
+
TERM_PROGRAM_VERSION: "3.1.0",
|
|
377
|
+
},
|
|
378
|
+
stdoutIsTTY: true,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
382
|
+
expect(result.level).to.equal(3);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("returns level 2 for iTerm.app < 3", function () {
|
|
386
|
+
const { supportsColor } = loadSupportsColor({
|
|
387
|
+
env: {
|
|
388
|
+
TERM_PROGRAM: "iTerm.app",
|
|
389
|
+
TERM_PROGRAM_VERSION: "2.9.0",
|
|
390
|
+
},
|
|
391
|
+
stdoutIsTTY: true,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
395
|
+
expect(result.level).to.equal(2);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("returns level 2 for Apple_Terminal", function () {
|
|
399
|
+
const { supportsColor } = loadSupportsColor({
|
|
400
|
+
env: {
|
|
401
|
+
TERM_PROGRAM: "Apple_Terminal",
|
|
402
|
+
TERM_PROGRAM_VERSION: "999.0",
|
|
403
|
+
},
|
|
404
|
+
stdoutIsTTY: true,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const result = supportsColor.createSupportsColor({ isTTY: true });
|
|
408
|
+
expect(result.level).to.equal(2);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe("top-level stdout and stderr", function () {
|
|
413
|
+
it("uses tty.isatty(1/2) for default stdout/stderr", function () {
|
|
414
|
+
const { supportsColor } = loadSupportsColor({
|
|
415
|
+
env: { TERM: "xterm-256color" },
|
|
416
|
+
stdoutIsTTY: true,
|
|
417
|
+
stderrIsTTY: false,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const { stdout, stderr } = supportsColor;
|
|
421
|
+
|
|
422
|
+
// stdout is TTY and TERM-256color => level >= 2
|
|
423
|
+
expect(stdout === false || stdout.level >= 2).to.equal(true);
|
|
424
|
+
|
|
425
|
+
// stderr is not TTY => likely false or 0
|
|
426
|
+
expect(stderr === false || stderr.level === 0).to.equal(true);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|