├── .gitignore ├── bin.js ├── .travis.yml ├── browser.js ├── test ├── browser.js └── basic.js ├── LICENSE ├── package.json ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .nyc_output/ 4 | nyc_output/ 5 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var colorSupport = require('./')({alwaysReturn: true }) 3 | console.log(JSON.stringify(colorSupport, null, 2)) 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 10 5 | - 8 6 | - 6 7 | 8 | os: 9 | - linux 10 | - windows 11 | 12 | cache: 13 | directories: 14 | - $HOME/.npm 15 | 16 | notifications: 17 | email: false 18 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | module.exports = colorSupport({ alwaysReturn: true }, colorSupport) 2 | 3 | function colorSupport(options, obj) { 4 | obj = obj || {} 5 | options = options || {} 6 | obj.level = 0 7 | obj.hasBasic = false 8 | obj.has256 = false 9 | obj.has16m = false 10 | if (!options.alwaysReturn) { 11 | return false 12 | } 13 | return obj 14 | } 15 | -------------------------------------------------------------------------------- /test/browser.js: -------------------------------------------------------------------------------- 1 | var colorSupport = require('../browser.js') 2 | var t = require('tap') 3 | 4 | // t.match() gets confused by functions 5 | t.match(Object.create(colorSupport), { 6 | level: 0, 7 | hasBasic: false, 8 | has256: false, 9 | has16m: false 10 | }) 11 | 12 | t.match(colorSupport({ alwaysReturn: true }), { 13 | level: 0, 14 | hasBasic: false, 15 | has256: false, 16 | has16m: false 17 | }) 18 | 19 | t.equal(colorSupport(), false) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) Isaac Z. Schlueter and Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color-support", 3 | "version": "1.1.3", 4 | "description": "A module which will endeavor to guess your terminal's level of color support.", 5 | "main": "index.js", 6 | "browser": "browser.js", 7 | "bin": "bin.js", 8 | "devDependencies": { 9 | "tap": "^12.0.1" 10 | }, 11 | "scripts": { 12 | "test": "tap test/*.js --100 -J", 13 | "preversion": "npm test", 14 | "postversion": "npm publish", 15 | "postpublish": "git push origin --all; git push origin --tags" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/isaacs/color-support.git" 20 | }, 21 | "keywords": [ 22 | "terminal", 23 | "color", 24 | "support", 25 | "xterm", 26 | "truecolor", 27 | "256" 28 | ], 29 | "author": "Isaac Z. Schlueter (http://blog.izs.me/)", 30 | "license": "ISC", 31 | "files": [ 32 | "browser.js", 33 | "index.js", 34 | "bin.js" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // call it on itself so we can test the export val for basic stuff 2 | module.exports = colorSupport({ alwaysReturn: true }, colorSupport) 3 | 4 | function hasNone (obj, options) { 5 | obj.level = 0 6 | obj.hasBasic = false 7 | obj.has256 = false 8 | obj.has16m = false 9 | if (!options.alwaysReturn) { 10 | return false 11 | } 12 | return obj 13 | } 14 | 15 | function hasBasic (obj) { 16 | obj.hasBasic = true 17 | obj.has256 = false 18 | obj.has16m = false 19 | obj.level = 1 20 | return obj 21 | } 22 | 23 | function has256 (obj) { 24 | obj.hasBasic = true 25 | obj.has256 = true 26 | obj.has16m = false 27 | obj.level = 2 28 | return obj 29 | } 30 | 31 | function has16m (obj) { 32 | obj.hasBasic = true 33 | obj.has256 = true 34 | obj.has16m = true 35 | obj.level = 3 36 | return obj 37 | } 38 | 39 | function colorSupport (options, obj) { 40 | options = options || {} 41 | 42 | obj = obj || {} 43 | 44 | // if just requesting a specific level, then return that. 45 | if (typeof options.level === 'number') { 46 | switch (options.level) { 47 | case 0: 48 | return hasNone(obj, options) 49 | case 1: 50 | return hasBasic(obj) 51 | case 2: 52 | return has256(obj) 53 | case 3: 54 | return has16m(obj) 55 | } 56 | } 57 | 58 | obj.level = 0 59 | obj.hasBasic = false 60 | obj.has256 = false 61 | obj.has16m = false 62 | 63 | if (typeof process === 'undefined' || 64 | !process || 65 | !process.stdout || 66 | !process.env || 67 | !process.platform) { 68 | return hasNone(obj, options) 69 | } 70 | 71 | var env = options.env || process.env 72 | var stream = options.stream || process.stdout 73 | var term = options.term || env.TERM || '' 74 | var platform = options.platform || process.platform 75 | 76 | if (!options.ignoreTTY && !stream.isTTY) { 77 | return hasNone(obj, options) 78 | } 79 | 80 | if (!options.ignoreDumb && term === 'dumb' && !env.COLORTERM) { 81 | return hasNone(obj, options) 82 | } 83 | 84 | if (platform === 'win32') { 85 | return hasBasic(obj) 86 | } 87 | 88 | if (env.TMUX) { 89 | return has256(obj) 90 | } 91 | 92 | if (!options.ignoreCI && (env.CI || env.TEAMCITY_VERSION)) { 93 | if (env.TRAVIS) { 94 | return has256(obj) 95 | } else { 96 | return hasNone(obj, options) 97 | } 98 | } 99 | 100 | // TODO: add more term programs 101 | switch (env.TERM_PROGRAM) { 102 | case 'iTerm.app': 103 | var ver = env.TERM_PROGRAM_VERSION || '0.' 104 | if (/^[0-2]\./.test(ver)) { 105 | return has256(obj) 106 | } else { 107 | return has16m(obj) 108 | } 109 | 110 | case 'HyperTerm': 111 | case 'Hyper': 112 | return has16m(obj) 113 | 114 | case 'MacTerm': 115 | return has16m(obj) 116 | 117 | case 'Apple_Terminal': 118 | return has256(obj) 119 | } 120 | 121 | if (/^xterm-256/.test(term)) { 122 | return has256(obj) 123 | } 124 | 125 | if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(term)) { 126 | return hasBasic(obj) 127 | } 128 | 129 | if (env.COLORTERM) { 130 | return hasBasic(obj) 131 | } 132 | 133 | return hasNone(obj, options) 134 | } 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # color-support 2 | 3 | A module which will endeavor to guess your terminal's level of color 4 | support. 5 | 6 | [![Build Status](https://travis-ci.org/isaacs/color-support.svg?branch=master)](https://travis-ci.org/isaacs/color-support) [![Coverage Status](https://coveralls.io/repos/github/isaacs/color-support/badge.svg?branch=master)](https://coveralls.io/github/isaacs/color-support?branch=master) 7 | 8 | This is similar to `supports-color`, but it does not read 9 | `process.argv`. 10 | 11 | 1. If not in a node environment, not supported. 12 | 13 | 2. If stdout is not a TTY, not supported, unless the `ignoreTTY` 14 | option is set. 15 | 16 | 3. If the `TERM` environ is `dumb`, not supported, unless the 17 | `ignoreDumb` option is set. 18 | 19 | 4. If on Windows, then support 16 colors. 20 | 21 | 5. If using Tmux, then support 256 colors. 22 | 23 | 7. Handle continuous-integration servers. If `CI` or 24 | `TEAMCITY_VERSION` are set in the environment, and `TRAVIS` is not 25 | set, then color is not supported, unless `ignoreCI` option is set. 26 | 27 | 6. Guess based on the `TERM_PROGRAM` environ. These terminals support 28 | 16m colors: 29 | 30 | - `iTerm.app` version 3.x supports 16m colors, below support 256 31 | - `MacTerm` supports 16m colors 32 | - `Apple_Terminal` supports 256 colors 33 | - Have more things that belong on this list? Send a PR! 34 | 35 | 8. Make a guess based on the `TERM` environment variable. Any 36 | `xterm-256color` will get 256 colors. Any screen, xterm, vt100, 37 | color, ansi, cygwin, or linux `TERM` will get 16 colors. 38 | 39 | 9. If `COLORTERM` environment variable is set, then support 16 colors. 40 | 41 | 10. At this point, we assume that color is not supported. 42 | 43 | ## USAGE 44 | 45 | ```javascript 46 | var testColorSupport = require('color-support') 47 | var colorSupport = testColorSupport(/* options object */) 48 | 49 | if (!colorSupport) { 50 | console.log('color is not supported') 51 | } else if (colorSupport.has16m) { 52 | console.log('\x1b[38;2;102;194;255m16m colors\x1b[0m') 53 | } else if (colorSupport.has256) { 54 | console.log('\x1b[38;5;119m256 colors\x1b[0m') 55 | } else if (colorSupport.hasBasic) { 56 | console.log('\x1b[31mbasic colors\x1b[0m') 57 | } else { 58 | console.log('this is impossible, but colors are not supported') 59 | } 60 | ``` 61 | 62 | If you don't have any options to set, you can also just look at the 63 | flags which will all be set on the test function itself. (Of course, 64 | this doesn't return a falsey value when colors aren't supported, and 65 | doesn't allow you to set options.) 66 | 67 | ```javascript 68 | var colorSupport = require('color-support') 69 | 70 | if (colorSupport.has16m) { 71 | console.log('\x1b[38;2;102;194;255m16m colors\x1b[0m') 72 | } else if (colorSupport.has256) { 73 | console.log('\x1b[38;5;119m256 colors\x1b[0m') 74 | } else if (colorSupport.hasBasic) { 75 | console.log('\x1b[31mbasic colors\x1b[0m') 76 | } else { 77 | console.log('colors are not supported') 78 | } 79 | ``` 80 | 81 | ## Options 82 | 83 | You can pass in the following options. 84 | 85 | * ignoreTTY - default false. Ignore the `isTTY` check. 86 | * ignoreDumb - default false. Ignore `TERM=dumb` environ check. 87 | * ignoreCI - default false. Ignore `CI` environ check. 88 | * env - Object for environment vars. Defaults to `process.env`. 89 | * stream - Stream for `isTTY` check. Defaults to `process.stdout`. 90 | * term - String for `TERM` checking. Defaults to `env.TERM`. 91 | * alwaysReturn - default false. Return an object when colors aren't 92 | supported (instead of returning `false`). 93 | * level - A number from 0 to 3. This will return a result for the 94 | specified level. This is useful if you want to be able to set the 95 | color support level explicitly as a number in an environment 96 | variable or config, but then use the object flags in your program. 97 | Except for `alwaysReturn` to return an object for level 0, all other 98 | options are ignored, since no checking is done if a level is 99 | explicitly set. 100 | 101 | ## Return Value 102 | 103 | If no color support is available, then `false` is returned by default, 104 | unless the `alwaysReturn` flag is set to `true`. This is so that the 105 | simple question of "can I use colors or not" can treat any truthy 106 | return as "yes". 107 | 108 | Otherwise, the return object has the following fields: 109 | 110 | * `level` - A number from 0 to 3 111 | * `0` - No color support 112 | * `1` - Basic (16) color support 113 | * `2` - 256 color support 114 | * `3` - 16 million (true) color support 115 | * `hasBasic` - Boolean 116 | * `has256` - Boolean 117 | * `has16m` - Boolean 118 | 119 | ## CLI 120 | 121 | You can run the `color-support` bin from the command line which will 122 | just dump the values as this module calculates them in whatever env 123 | it's run. It takes no command line arguments. 124 | 125 | ## Credits 126 | 127 | This is a spiritual, if not actual, fork of 128 | [supports-color](http://npm.im/supports-color) by the ever prolific 129 | [Sindre Sorhus](http://npm.im/~sindresorhus). 130 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | var t = require('tap') 2 | var colorSupport = require('../') 3 | 4 | var hasNone = { 5 | level: 0, 6 | hasBasic: false, 7 | has256: false, 8 | has16m: false 9 | } 10 | 11 | var hasBasic = { 12 | level: 1, 13 | hasBasic: true, 14 | has256: false, 15 | has16m: false 16 | } 17 | 18 | var has256 = { 19 | level: 2, 20 | hasBasic: true, 21 | has256: true, 22 | has16m: false 23 | } 24 | 25 | var has16m = { 26 | level: 3, 27 | hasBasic: true, 28 | has256: true, 29 | has16m: true 30 | } 31 | 32 | t.test('set fields on top level', function (t) { 33 | t.match(colorSupport.level, Number) 34 | t.match(colorSupport.hasBasic, Boolean) 35 | t.match(colorSupport.has256, Boolean) 36 | t.match(colorSupport.has16m, Boolean) 37 | 38 | // flags on object match what you get calling it with no opts 39 | // t.match() gets confused by functions, and we need to ensure that 40 | // *something* gets returned when colors are unsupported 41 | t.match(Object.create(colorSupport), colorSupport({ alwaysReturn: true })) 42 | t.end() 43 | }) 44 | 45 | t.test('non-node = no colors', function (t) { 46 | var p = process.env 47 | process.env = null 48 | var supported = colorSupport() 49 | process.env = p 50 | t.notOk(supported) 51 | t.end() 52 | }) 53 | 54 | t.test('colors depend on being a tty', function (t) { 55 | var notty = { isTTY: false } 56 | t.notOk(colorSupport({ stream: notty })) 57 | 58 | // if we ignore the tty, it's the same either way tho 59 | t.same(colorSupport({ 60 | stream: notty, 61 | ignoreTTY: true 62 | }), colorSupport({ ignoreTTY: true })) 63 | 64 | // or if it is a tty, same as ignoring it 65 | t.same(colorSupport({ 66 | stream: { isTTY: true } 67 | }), colorSupport({ ignoreTTY: true })) 68 | 69 | t.end() 70 | }) 71 | 72 | t.test('TERM=dumb', function (t) { 73 | t.notOk(colorSupport({ term: 'dumb', ignoreTTY: true, env: {} })) 74 | 75 | t.same(colorSupport({ 76 | term: 'dumb', 77 | ignoreTTY: true, 78 | ignoreDumb: true 79 | }), colorSupport({ ignoreTTY: true })) 80 | 81 | // with COLORTERM set, term=dumb is ignored 82 | t.same(colorSupport({ 83 | term: 'dumb', 84 | ignoreTTY: true, 85 | alwaysReturn: true, 86 | env: { 87 | COLORTERM: 'yolo' 88 | } 89 | }), colorSupport({ 90 | ignoreTTY: true, 91 | alwaysReturn: true, 92 | env: { 93 | COLORTERM:'yolo' 94 | } 95 | })) 96 | t.end() 97 | }) 98 | 99 | t.test('win32', function (t) { 100 | t.same(colorSupport({ 101 | ignoreTTY: true, 102 | ignoreDumb: true, 103 | platform: 'win32' 104 | }), hasBasic) 105 | t.end() 106 | }) 107 | 108 | t.test('tmux', function (t) { 109 | t.same(colorSupport({ 110 | ignoreTTY: true, 111 | ignoreDumb: true, 112 | platform: 'linux', 113 | env: { TMUX: 'some junk' } 114 | }), has256) 115 | t.end() 116 | }) 117 | 118 | t.test('TERM_PROGRAM', function (t) { 119 | t.same(colorSupport({ 120 | ignoreTTY: true, 121 | ignoreDumb: true, 122 | platform: 'darwin', 123 | env: { 124 | TERM_PROGRAM: 'iTerm.app' 125 | } 126 | }), has256) 127 | 128 | t.same(colorSupport({ 129 | ignoreTTY: true, 130 | ignoreDumb: true, 131 | platform: 'darwin', 132 | env: { 133 | TERM_PROGRAM: 'Hyper', 134 | TERM_PROGRAM_VERSION: '0.8.9' 135 | } 136 | }), has16m) 137 | 138 | t.same(colorSupport({ 139 | ignoreTTY: true, 140 | ignoreDumb: true, 141 | platform: 'darwin', 142 | env: { 143 | TERM_PROGRAM: 'HyperTerm', 144 | TERM_PROGRAM_VERSION: '0.7.0' 145 | } 146 | }), has16m) 147 | 148 | t.same(colorSupport({ 149 | ignoreTTY: true, 150 | ignoreDumb: true, 151 | platform: 'darwin', 152 | env: { 153 | TERM_PROGRAM: 'iTerm.app', 154 | TERM_PROGRAM_VERSION: '3.0.4' 155 | } 156 | }), has16m) 157 | 158 | t.same(colorSupport({ 159 | ignoreTTY: true, 160 | ignoreDumb: true, 161 | platform: 'darwin', 162 | env: { 163 | TERM_PROGRAM: 'MacTerm' 164 | } 165 | }), has16m) 166 | 167 | t.same(colorSupport({ 168 | ignoreTTY: true, 169 | ignoreDumb: true, 170 | platform: 'darwin', 171 | env: { 172 | TERM_PROGRAM: 'Apple_Terminal' 173 | } 174 | }), has256) 175 | 176 | t.end() 177 | }) 178 | 179 | t.test('colors on CI servers', function (t) { 180 | var options = { 181 | ignoreTTY: true, 182 | alwaysReturn: true, 183 | ignoreDumb: true, 184 | platform: 'darwin', 185 | env: { 186 | CI: '1' 187 | } 188 | } 189 | t.same(colorSupport(options), hasNone) 190 | options.env.TRAVIS = '1' 191 | t.same(colorSupport(options), has256) 192 | options.env = { TEAMCITY_VERSION: '1' } 193 | t.same(colorSupport(options), hasNone) 194 | 195 | options.ignoreCI = true 196 | t.same(colorSupport(options), colorSupport({ 197 | ignoreTTY: true, 198 | alwaysReturn: true, 199 | ignoreDumb: true, 200 | platform: 'darwin', 201 | env: {} 202 | })) 203 | t.end() 204 | }) 205 | 206 | t.test('TERM=stuff', function (t) { 207 | var options = { 208 | ignoreTTY: true, 209 | alwaysReturn: true, 210 | ignoreDumb: true, 211 | platform: 'definitely not windows', 212 | env: {} 213 | } 214 | 215 | options.term = 'xterm-256etc' 216 | t.same(colorSupport(options), has256) 217 | 218 | options.term = 'screen that is blue, of death' 219 | t.same(colorSupport(options), hasBasic) 220 | 221 | options.term = 'xterm-something else' 222 | t.same(colorSupport(options), hasBasic) 223 | 224 | options.term = 'vt100 and 1 dalmations' 225 | t.same(colorSupport(options), hasBasic) 226 | 227 | options.term = 'are we human or are we dansir' 228 | t.same(colorSupport(options), hasBasic) 229 | 230 | options.term = 'check out dat linux, gpl af' 231 | t.same(colorSupport(options), hasBasic) 232 | 233 | t.end() 234 | }) 235 | 236 | t.test('env.COLORTERM makes it basic at least', function (t) { 237 | var options = { 238 | ignoreTTY: true, 239 | alwaysReturn: true, 240 | ignoreDumb: true, 241 | platform: 'definitely not windows', 242 | env: { COLORTERM: 'yeah whatever' } 243 | } 244 | 245 | t.same(colorSupport(options), hasBasic) 246 | 247 | t.end() 248 | }) 249 | 250 | t.test('mutates an object passed in', function (t) { 251 | var obj = { level: 'up' } 252 | colorSupport({}, obj) 253 | t.match(obj.level, Number) 254 | t.end() 255 | }) 256 | 257 | t.test('getting a specific level', function (t) { 258 | t.match(colorSupport({ level: 0, alwaysReturn: true }), hasNone) 259 | t.notOk(colorSupport({ level: 0 })) 260 | t.match(colorSupport({ level: 1 }), hasBasic) 261 | t.match(colorSupport({ level: 2 }), has256) 262 | t.match(colorSupport({ level: 3 }), has16m) 263 | t.match(colorSupport({ level: 4 }), colorSupport()) 264 | t.end() 265 | }) 266 | --------------------------------------------------------------------------------