• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// MIT License
2
3// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23'use strict';
24
25const {
26  ArrayPrototypeSome,
27  RegExpPrototypeExec,
28  StringPrototypeSplit,
29  StringPrototypeToLowerCase,
30} = primordials;
31
32const { validateInteger } = require('internal/validators');
33
34let OSRelease;
35
36const COLORS_2 = 1;
37const COLORS_16 = 4;
38const COLORS_256 = 8;
39const COLORS_16m = 24;
40
41// Some entries were taken from `dircolors`
42// (https://linux.die.net/man/1/dircolors). The corresponding terminals might
43// support more than 16 colors, but this was not tested for.
44//
45// Copyright (C) 1996-2016 Free Software Foundation, Inc. Copying and
46// distribution of this file, with or without modification, are permitted
47// provided the copyright notice and this notice are preserved.
48const TERM_ENVS = {
49  'eterm': COLORS_16,
50  'cons25': COLORS_16,
51  'console': COLORS_16,
52  'cygwin': COLORS_16,
53  'dtterm': COLORS_16,
54  'gnome': COLORS_16,
55  'hurd': COLORS_16,
56  'jfbterm': COLORS_16,
57  'konsole': COLORS_16,
58  'kterm': COLORS_16,
59  'mlterm': COLORS_16,
60  'mosh': COLORS_16m,
61  'putty': COLORS_16,
62  'st': COLORS_16,
63  // https://github.com/da-x/rxvt-unicode/tree/v9.22-with-24bit-color
64  'rxvt-unicode-24bit': COLORS_16m,
65  // https://gist.github.com/XVilka/8346728#gistcomment-2823421
66  'terminator': COLORS_16m,
67};
68
69const TERM_ENVS_REG_EXP = [
70  /ansi/,
71  /color/,
72  /linux/,
73  /^con[0-9]*x[0-9]/,
74  /^rxvt/,
75  /^screen/,
76  /^xterm/,
77  /^vt100/,
78];
79
80let warned = false;
81function warnOnDeactivatedColors(env) {
82  if (warned)
83    return;
84  let name = '';
85  if (env.NODE_DISABLE_COLORS !== undefined)
86    name = 'NODE_DISABLE_COLORS';
87  if (env.NO_COLOR !== undefined) {
88    if (name !== '') {
89      name += "' and '";
90    }
91    name += 'NO_COLOR';
92  }
93
94  if (name !== '') {
95    process.emitWarning(
96      `The '${name}' env is ignored due to the 'FORCE_COLOR' env being set.`,
97      'Warning',
98    );
99    warned = true;
100  }
101}
102
103// The `getColorDepth` API got inspired by multiple sources such as
104// https://github.com/chalk/supports-color,
105// https://github.com/isaacs/color-support.
106function getColorDepth(env = process.env) {
107  // Use level 0-3 to support the same levels as `chalk` does. This is done for
108  // consistency throughout the ecosystem.
109  if (env.FORCE_COLOR !== undefined) {
110    switch (env.FORCE_COLOR) {
111      case '':
112      case '1':
113      case 'true':
114        warnOnDeactivatedColors(env);
115        return COLORS_16;
116      case '2':
117        warnOnDeactivatedColors(env);
118        return COLORS_256;
119      case '3':
120        warnOnDeactivatedColors(env);
121        return COLORS_16m;
122      default:
123        return COLORS_2;
124    }
125  }
126
127  if (env.NODE_DISABLE_COLORS !== undefined ||
128      // See https://no-color.org/
129      env.NO_COLOR !== undefined ||
130      // The "dumb" special terminal, as defined by terminfo, doesn't support
131      // ANSI color control codes.
132      // See https://invisible-island.net/ncurses/terminfo.ti.html#toc-_Specials
133      env.TERM === 'dumb') {
134    return COLORS_2;
135  }
136
137  if (process.platform === 'win32') {
138    // Lazy load for startup performance.
139    if (OSRelease === undefined) {
140      const { release } = require('os');
141      OSRelease = StringPrototypeSplit(release(), '.');
142    }
143    // Windows 10 build 10586 is the first Windows release that supports 256
144    // colors. Windows 10 build 14931 is the first release that supports
145    // 16m/TrueColor.
146    if (+OSRelease[0] >= 10) {
147      const build = +OSRelease[2];
148      if (build >= 14931)
149        return COLORS_16m;
150      if (build >= 10586)
151        return COLORS_256;
152    }
153
154    return COLORS_16;
155  }
156
157  if (env.TMUX) {
158    return COLORS_256;
159  }
160
161  if (env.CI) {
162    if ([
163      'APPVEYOR',
164      'BUILDKITE',
165      'CIRCLECI',
166      'DRONE',
167      'GITHUB_ACTIONS',
168      'GITLAB_CI',
169      'TRAVIS',
170    ].some((sign) => sign in env) || env.CI_NAME === 'codeship') {
171      return COLORS_256;
172    }
173    return COLORS_2;
174  }
175
176  if ('TEAMCITY_VERSION' in env) {
177    return RegExpPrototypeExec(/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/, env.TEAMCITY_VERSION) !== null ?
178      COLORS_16 : COLORS_2;
179  }
180
181  switch (env.TERM_PROGRAM) {
182    case 'iTerm.app':
183      if (!env.TERM_PROGRAM_VERSION ||
184        RegExpPrototypeExec(/^[0-2]\./, env.TERM_PROGRAM_VERSION) !== null
185      ) {
186        return COLORS_256;
187      }
188      return COLORS_16m;
189    case 'HyperTerm':
190    case 'MacTerm':
191      return COLORS_16m;
192    case 'Apple_Terminal':
193      return COLORS_256;
194  }
195
196  if (env.COLORTERM === 'truecolor' || env.COLORTERM === '24bit') {
197    return COLORS_16m;
198  }
199
200  if (env.TERM) {
201    if (RegExpPrototypeExec(/^xterm-256/, env.TERM) !== null) {
202      return COLORS_256;
203    }
204
205    const termEnv = StringPrototypeToLowerCase(env.TERM);
206
207    if (TERM_ENVS[termEnv]) {
208      return TERM_ENVS[termEnv];
209    }
210    if (ArrayPrototypeSome(TERM_ENVS_REG_EXP,
211                           (term) => RegExpPrototypeExec(term, termEnv) !== null)) {
212      return COLORS_16;
213    }
214  }
215  // Move 16 color COLORTERM below 16m and 256
216  if (env.COLORTERM) {
217    return COLORS_16;
218  }
219  return COLORS_2;
220}
221
222function hasColors(count, env) {
223  if (env === undefined &&
224      (count === undefined || (typeof count === 'object' && count !== null))) {
225    env = count;
226    count = 16;
227  } else {
228    validateInteger(count, 'count', 2);
229  }
230
231  return count <= 2 ** getColorDepth(env);
232}
233
234module.exports = {
235  getColorDepth,
236  hasColors,
237};
238