Repository: nodejs/node
Pull request: tty: improve color terminal color detection

This adds a couple new entries or increases the support depending on newer data.
I checked ncurses, tput, supports-color, and termstandard on github. Most updates are from supports-color.

Refs: https://github.com/nodejs/node/issues/57998

Changed files:
filename: lib/internal/tty.js
patch: @@ -24,7 +24,10 @@
 
 const {
   ArrayPrototypeSome,
+  ObjectEntries,
+  ObjectPrototypeHasOwnProperty: hasOwn,
   RegExpPrototypeExec,
+  SafeMap,
   StringPrototypeSplit,
   StringPrototypeToLowerCase,
 } = primordials;
@@ -64,17 +67,31 @@ const TERM_ENVS = {
   'rxvt-unicode-24bit': COLORS_16m,
   // https://bugs.launchpad.net/terminator/+bug/1030562
   'terminator': COLORS_16m,
+  'xterm-kitty': COLORS_16m,
 };
 
+const CI_ENVS_MAP = new SafeMap(ObjectEntries({
+  APPVEYOR: COLORS_256,
+  BUILDKITE: COLORS_256,
+  CIRCLECI: COLORS_16m,
+  DRONE: COLORS_256,
+  GITEA_ACTIONS: COLORS_16m,
+  GITHUB_ACTIONS: COLORS_16m,
+  GITLAB_CI: COLORS_256,
+  TRAVIS: COLORS_256,
+}));
+
 const TERM_ENVS_REG_EXP = [
   /ansi/,
   /color/,
   /linux/,
+  /direct/,
   /^con[0-9]*x[0-9]/,
   /^rxvt/,
   /^screen/,
   /^xterm/,
   /^vt100/,
+  /^vt220/,
 ];
 
 let warned = false;
@@ -155,19 +172,21 @@ function getColorDepth(env = process.env) {
   }
 
   if (env.TMUX) {
-    return COLORS_256;
+    return COLORS_16m;
+  }
+
+  // Azure DevOps
+  if (hasOwn(env, 'TF_BUILD') && hasOwn(env, 'AGENT_NAME')) {
+    return COLORS_16;
   }
 
-  if (env.CI) {
-    if ([
-      'APPVEYOR',
-      'BUILDKITE',
-      'CIRCLECI',
-      'DRONE',
-      'GITHUB_ACTIONS',
-      'GITLAB_CI',
-      'TRAVIS',
-    ].some((sign) => sign in env) || env.CI_NAME === 'codeship') {
+  if (hasOwn(env, 'CI')) {
+    for (const { 0: envName, 1: colors } of CI_ENVS_MAP) {
+      if (hasOwn(env, envName)) {
+        return colors;
+      }
+    }
+    if (env.CI_NAME === 'codeship') {
       return COLORS_256;
     }
     return COLORS_2;
@@ -198,6 +217,10 @@ function getColorDepth(env = process.env) {
   }
 
   if (env.TERM) {
+    if (RegExpPrototypeExec(/truecolor/, env.TERM) !== null) {
+      return COLORS_16m;
+    }
+
     if (RegExpPrototypeExec(/^xterm-256/, env.TERM) !== null) {
       return COLORS_256;
     }

filename: test/pseudo-tty/test-tty-color-support.js
patch: @@ -37,13 +37,18 @@ const writeStream = new WriteStream(fd);
   [{ COLORTERM: '1' }, 4],
   [{ COLORTERM: 'truecolor' }, 24],
   [{ COLORTERM: '24bit' }, 24],
-  [{ TMUX: '1' }, 8],
+  [{ TMUX: '1' }, 24],
   [{ CI: '1' }, 1],
-  [{ CI: '1', TRAVIS: '1' }, 8],
-  [{ CI: '1', CIRCLECI: '1' }, 8],
-  [{ CI: '1', APPVEYOR: '1' }, 8],
-  [{ CI: '1', GITLAB_CI: '1' }, 8],
+  [{ CI: '', APPVEYOR: '1' }, 8],
+  [{ CI: '1', BUILDKITE: '' }, 8],
   [{ CI: '1', CI_NAME: 'codeship' }, 8],
+  [{ CI: '1', CIRCLECI: '1' }, 24],
+  [{ CI: '1', DRONE: '' }, 8],
+  [{ CI: '1', GITEA_ACTIONS: '' }, 24],
+  [{ CI: '1', GITHUB_ACTIONS: '' }, 24],
+  [{ CI: '1', GITLAB_CI: '1' }, 8],
+  [{ CI: '1', TRAVIS: '1' }, 8],
+  [{ CI: '', TRAVIS: '' }, 8],
   [{ TEAMCITY_VERSION: '1.0.0' }, 1],
   [{ TEAMCITY_VERSION: '9.11.0' }, 4],
   [{ TERM_PROGRAM: 'iTerm.app' }, 8],
@@ -53,17 +58,22 @@ const writeStream = new WriteStream(fd);
   [{ TERM_PROGRAM: 'Hyper' }, 1],
   [{ TERM_PROGRAM: 'MacTerm' }, 24],
   [{ TERM_PROGRAM: 'Apple_Terminal' }, 8],
-  [{ TERM: 'xterm-256' }, 8],
   [{ TERM: 'ansi' }, 4],
   [{ TERM: 'ANSI' }, 4],
   [{ TERM: 'color' }, 4],
-  [{ TERM: 'linux' }, 4],
-  [{ TERM: 'fail' }, 1],
   [{ TERM: 'color', NODE_DISABLE_COLORS: '1' }, 1],
+  [{ TERM: 'console' }, 4],
+  [{ TERM: 'direct' }, 4],
   [{ TERM: 'dumb' }, 1],
   [{ TERM: 'dumb', COLORTERM: '1' }, 1],
+  [{ TERM: 'fail' }, 1],
+  [{ TERM: 'linux' }, 4],
   [{ TERM: 'terminator' }, 24],
-  [{ TERM: 'console' }, 4],
+  [{ TERM: 'vt100' }, 4],
+  [{ TERM: 'vt220' }, 4],
+  [{ TERM: 'xterm-256' }, 8],
+  [{ TERM: 'xterm-kitty' }, 24],
+  [{ TERM: 'xterm-truecolor' }, 24],
   [{ COLORTERM: '24bit', FORCE_COLOR: '' }, 4],
   [{ NO_COLOR: '1', FORCE_COLOR: '2' }, 8],
   [{ NODE_DISABLE_COLORS: '1', FORCE_COLOR: '3' }, 24],
@@ -72,6 +82,7 @@ const writeStream = new WriteStream(fd);
   [{ TMUX: '1', FORCE_COLOR: 0 }, 1],
   [{ NO_COLOR: 'true', FORCE_COLOR: 0, COLORTERM: 'truecolor' }, 1],
   [{ TERM: 'xterm-256color', COLORTERM: 'truecolor' }, 24],
+  [{ TF_BUILD: '', AGENT_NAME: '' }, 4],
 ].forEach(([env, depth], i) => {
   const actual = writeStream.getColorDepth(env);
   assert.strictEqual(