diff --git a/.github/funding.yml b/.github/funding.yml
index dfe6f26..6aec148 100644
--- a/.github/funding.yml
+++ b/.github/funding.yml
@@ -1,2 +1 @@
github: [sindresorhus, Qix-]
-tidelift: npm/supports-color
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d50ada6..346585c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -10,12 +10,11 @@ jobs:
fail-fast: false
matrix:
node-version:
+ - 20
- 18
- - 16
- - 14
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
diff --git a/browser.js b/browser.js
index 1ffde64..db2580a 100644
--- a/browser.js
+++ b/browser.js
@@ -1,14 +1,18 @@
/* eslint-env browser */
const level = (() => {
- if (navigator.userAgentData) {
+ if (!('navigator' in globalThis)) {
+ return 0;
+ }
+
+ if (globalThis.navigator.userAgentData) {
const brand = navigator.userAgentData.brands.find(({brand}) => brand === 'Chromium');
if (brand?.version > 93) {
return 3;
}
}
- if (/\b(Chrome|Chromium)\//.test(navigator.userAgent)) {
+ if (/\b(Chrome|Chromium)\//.test(globalThis.navigator.userAgent)) {
return 1;
}
diff --git a/index.d.ts b/index.d.ts
index 9cab8d1..db44a78 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,13 +1,13 @@
import type {WriteStream} from 'node:tty';
-export interface Options {
+export type Options = {
/**
Whether `process.argv` should be sniffed for `--color` and `--no-color` flags.
@default true
*/
readonly sniffFlags?: boolean;
-}
+};
/**
Levels:
@@ -21,7 +21,7 @@ export type ColorSupportLevel = 0 | 1 | 2 | 3;
/**
Detect whether the terminal supports color.
*/
-export interface ColorSupport {
+export type ColorSupport = {
/**
The color level.
*/
@@ -41,7 +41,7 @@ export interface ColorSupport {
Whether Truecolor 16 million colors are supported.
*/
has16m: boolean;
-}
+};
export type ColorInfo = ColorSupport | false;
diff --git a/index.js b/index.js
index 575a8fe..6c6a433 100644
--- a/index.js
+++ b/index.js
@@ -31,17 +31,29 @@ if (
}
function envForceColor() {
- if ('FORCE_COLOR' in env) {
- if (env.FORCE_COLOR === 'true') {
- return 1;
- }
+ if (!('FORCE_COLOR' in env)) {
+ return;
+ }
- if (env.FORCE_COLOR === 'false') {
- return 0;
- }
+ if (env.FORCE_COLOR === 'true') {
+ return 1;
+ }
+
+ if (env.FORCE_COLOR === 'false') {
+ return 0;
+ }
- return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
+ if (env.FORCE_COLOR.length === 0) {
+ return 1;
}
+
+ const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
+
+ if (![0, 1, 2, 3].includes(level)) {
+ return;
+ }
+
+ return level;
}
function translateLevel(level) {
diff --git a/index.test-d.ts b/index.test-d.ts
index c71fa2f..609d0e6 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -1,6 +1,6 @@
import {stdout, stderr} from 'node:process';
import {expectType} from 'tsd';
-import supportsColor, {createSupportsColor, Options, ColorInfo} from './index.js';
+import supportsColor, {createSupportsColor, type Options, type ColorInfo} from './index.js';
const options: Options = {};
diff --git a/package.json b/package.json
index 7386847..d13e654 100644
--- a/package.json
+++ b/package.json
@@ -12,15 +12,16 @@
},
"type": "module",
"exports": {
+ "types": "./index.d.ts",
"node": "./index.js",
"default": "./browser.js"
},
+ "sideEffects": false,
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"scripts": {
- "//test": "xo && ava && tsd",
- "test": "tsd"
+ "test": "xo && ava && tsd"
},
"files": [
"index.js",
@@ -51,10 +52,13 @@
"16m"
],
"devDependencies": {
- "@types/node": "^20.3.2",
- "ava": "^5.3.1",
- "import-fresh": "^3.3.0",
- "tsd": "^0.18.0",
- "xo": "^0.54.2"
+ "@types/node": "^20.11.17",
+ "ava": "^6.1.1",
+ "tsd": "^0.30.4",
+ "xo": "^0.57.0"
+ },
+ "ava": {
+ "serial": true,
+ "workerThreads": false
}
}
diff --git a/readme.md b/readme.md
index a72c349..5c19271 100644
--- a/readme.md
+++ b/readme.md
@@ -4,8 +4,8 @@
## Install
-```
-$ npm install supports-color
+```sh
+npm install supports-color
```
## Usage
@@ -73,17 +73,3 @@ Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color=
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
-
----
-
-
-
----
diff --git a/test.js b/test.js
index 7987aa7..84874b7 100644
--- a/test.js
+++ b/test.js
@@ -1,11 +1,18 @@
+import {randomInt} from 'node:crypto';
import process from 'node:process';
import os from 'node:os';
import tty from 'node:tty';
import test from 'ava';
-import importFresh from 'import-fresh';
const currentNodeVersion = process.versions.node;
+const importFresh = async moduleName => import(`${moduleName}?${randomInt(100_000_000)}`);
+
+const importMain = async () => {
+ const {default: main} = await importFresh('./index.js');
+ return main;
+};
+
test.beforeEach(() => {
Object.defineProperty(process, 'platform', {
value: 'linux',
@@ -21,265 +28,264 @@ test.beforeEach(() => {
tty.isatty = () => true;
});
-// FIXME
-test.failing('return true if `FORCE_COLOR` is in env', t => {
+test('return true if `FORCE_COLOR` is in env', async t => {
process.stdout.isTTY = false;
- process.env.FORCE_COLOR = true;
- const result = importFresh('./index.js');
+ process.env.FORCE_COLOR = 'true';
+ const result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 1);
});
-test('return true if `FORCE_COLOR` is in env, but honor 256', t => {
+test('return true if `FORCE_COLOR` is in env, but honor 256', async t => {
process.argv = ['--color=256'];
- process.env.FORCE_COLOR = true;
- const result = importFresh('./index.js');
+ process.env.FORCE_COLOR = 'true';
+ const result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 2);
});
-test('return true if `FORCE_COLOR` is in env, but honor 256 #2', t => {
+test('return true if `FORCE_COLOR` is in env, but honor 256 #2', async t => {
process.argv = ['--color=256'];
process.env.FORCE_COLOR = '1';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 2);
});
-test('CLI color flags precede other color support checks', t => {
+test('CLI color flags precede other color support checks', async t => {
process.env.COLORTERM = 'truecolor';
process.argv = ['--color=256'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
- t.is(result.stdout.level, 2)
+ t.is(result.stdout.level, 2);
});
-test('`FORCE_COLOR` environment variable precedes other color support checks', t => {
+test('`FORCE_COLOR` environment variable precedes other color support checks', async t => {
process.env.COLORTERM = 'truecolor';
process.env.FORCE_COLOR = '2';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
- t.is(result.stdout.level, 2)
+ t.is(result.stdout.level, 2);
});
-test('return false if `FORCE_COLOR` is in env and is 0', t => {
+test('return false if `FORCE_COLOR` is in env and is 0', async t => {
process.env.FORCE_COLOR = '0';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('do not cache `FORCE_COLOR`', t => {
+test('do not cache `FORCE_COLOR`', async t => {
process.env.FORCE_COLOR = '0';
- const result = importFresh('./index.js');
+ const {default: result, createSupportsColor} = await importFresh('./index.js');
t.false(result.stdout);
process.env.FORCE_COLOR = '1';
- const updatedStdOut = result.supportsColor({isTTY: tty.isatty(1)});
+ const updatedStdOut = createSupportsColor({isTTY: tty.isatty(1)});
t.truthy(updatedStdOut);
});
-test('return false if not TTY', t => {
+test('return false if not TTY', async t => {
process.stdout.isTTY = false;
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return false if --no-color flag is used', t => {
+test('return false if --no-color flag is used', async t => {
process.env = {TERM: 'xterm-256color'};
process.argv = ['--no-color'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return false if --no-colors flag is used', t => {
+test('return false if --no-colors flag is used', async t => {
process.env = {TERM: 'xterm-256color'};
process.argv = ['--no-colors'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return true if --color flag is used', t => {
+test('return true if --color flag is used', async t => {
process.argv = ['--color'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return true if --colors flag is used', t => {
+test('return true if --colors flag is used', async t => {
process.argv = ['--colors'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return true if `COLORTERM` is in env', t => {
+test('return true if `COLORTERM` is in env', async t => {
process.env.COLORTERM = true;
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('support `--color=true` flag', t => {
+test('support `--color=true` flag', async t => {
process.argv = ['--color=true'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('support `--color=always` flag', t => {
+test('support `--color=always` flag', async t => {
process.argv = ['--color=always'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('support `--color=false` flag', t => {
+test('support `--color=false` flag', async t => {
process.env = {TERM: 'xterm-256color'};
process.argv = ['--color=false'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('support `--color=256` flag', t => {
+test('support `--color=256` flag', async t => {
process.argv = ['--color=256'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('level should be 2 if `--color=256` flag is used', t => {
+test('level should be 2 if `--color=256` flag is used', async t => {
process.argv = ['--color=256'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 2);
t.true(result.stdout.has256);
});
-test('support `--color=16m` flag', t => {
+test('support `--color=16m` flag', async t => {
process.argv = ['--color=16m'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('support `--color=full` flag', t => {
+test('support `--color=full` flag', async t => {
process.argv = ['--color=full'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('support `--color=truecolor` flag', t => {
+test('support `--color=truecolor` flag', async t => {
process.argv = ['--color=truecolor'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('level should be 3 if `--color=16m` flag is used', t => {
+test('level should be 3 if `--color=16m` flag is used', async t => {
process.argv = ['--color=16m'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 3);
t.true(result.stdout.has256);
t.true(result.stdout.has16m);
});
-test('ignore post-terminator flags', t => {
+test('ignore post-terminator flags', async t => {
process.argv = ['--color', '--', '--no-color'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('allow tests of the properties on false', t => {
+test('allow tests of the properties on false', async t => {
process.env = {TERM: 'xterm-256color'};
process.argv = ['--no-color'];
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.hasBasic, undefined);
t.is(result.stdout.has256, undefined);
t.is(result.stdout.has16m, undefined);
t.false(result.stdout.level > 0);
});
-test('return false if `CI` is in env', t => {
+test('return false if `CI` is in env', async t => {
process.env.CI = 'AppVeyor';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return true if `TRAVIS` is in env', t => {
+test('return true if `TRAVIS` is in env', async t => {
process.env = {CI: 'Travis', TRAVIS: '1'};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return true if `CIRCLECI` is in env', t => {
+test('return true if `CIRCLECI` is in env', async t => {
process.env = {CI: true, CIRCLECI: true};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return true if `APPVEYOR` is in env', t => {
+test('return true if `APPVEYOR` is in env', async t => {
process.env = {CI: true, APPVEYOR: true};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return true if `GITLAB_CI` is in env', t => {
+test('return true if `GITLAB_CI` is in env', async t => {
process.env = {CI: true, GITLAB_CI: true};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return true if `BUILDKITE` is in env', t => {
+test('return true if `BUILDKITE` is in env', async t => {
process.env = {CI: true, BUILDKITE: true};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return true if `DRONE` is in env', t => {
+test('return true if `DRONE` is in env', async t => {
process.env = {CI: true, DRONE: true};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
});
-test('return level 3 if `GITEA_ACTIONS` is in env', t => {
+test('return level 3 if `GITEA_ACTIONS` is in env', async t => {
process.env = {CI: true, GITEA_ACTIONS: true};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 3);
});
-test('return true if Codeship is in env', t => {
+test('return true if Codeship is in env', async t => {
process.env = {CI: true, CI_NAME: 'codeship'};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result);
});
-test('return false if `TEAMCITY_VERSION` is in env and is < 9.1', t => {
+test('return false if `TEAMCITY_VERSION` is in env and is < 9.1', async t => {
process.env.TEAMCITY_VERSION = '9.0.5 (build 32523)';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return level 1 if `TEAMCITY_VERSION` is in env and is >= 9.1', t => {
+test('return level 1 if `TEAMCITY_VERSION` is in env and is >= 9.1', async t => {
process.env.TEAMCITY_VERSION = '9.1.0 (build 32523)';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 1);
});
-test('support rxvt', t => {
+test('support rxvt', async t => {
process.env = {TERM: 'rxvt'};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 1);
});
-test('prefer level 2/xterm over COLORTERM', t => {
+test('prefer level 2/xterm over COLORTERM', async t => {
process.env = {COLORTERM: '1', TERM: 'xterm-256color'};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 2);
});
-test('support screen-256color', t => {
+test('support screen-256color', async t => {
process.env = {TERM: 'screen-256color'};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 2);
});
-test('support putty-256color', t => {
+test('support putty-256color', async t => {
process.env = {TERM: 'putty-256color'};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 2);
});
-test('level should be 3 when using iTerm 3.0', t => {
+test('level should be 3 when using iTerm 3.0', async t => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
});
@@ -287,11 +293,11 @@ test('level should be 3 when using iTerm 3.0', t => {
TERM_PROGRAM: 'iTerm.app',
TERM_PROGRAM_VERSION: '3.0.10',
};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 3);
});
-test('level should be 2 when using iTerm 2.9', t => {
+test('level should be 2 when using iTerm 2.9', async t => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
});
@@ -299,11 +305,11 @@ test('level should be 2 when using iTerm 2.9', t => {
TERM_PROGRAM: 'iTerm.app',
TERM_PROGRAM_VERSION: '2.9.3',
};
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 2);
});
-test('return level 1 if on Windows earlier than 10 build 10586', t => {
+test('return level 1 if on Windows earlier than 10 build 10586', async t => {
Object.defineProperty(process, 'platform', {
value: 'win32',
});
@@ -311,11 +317,11 @@ test('return level 1 if on Windows earlier than 10 build 10586', t => {
value: '8.0.0',
});
os.release = () => '10.0.10240';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 1);
});
-test('return level 2 if on Windows 10 build 10586 or later', t => {
+test('return level 2 if on Windows 10 build 10586 or later', async t => {
Object.defineProperty(process, 'platform', {
value: 'win32',
});
@@ -323,11 +329,11 @@ test('return level 2 if on Windows 10 build 10586 or later', t => {
value: '8.0.0',
});
os.release = () => '10.0.10586';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 2);
});
-test('return level 3 if on Windows 10 build 14931 or later', t => {
+test('return level 3 if on Windows 10 build 14931 or later', async t => {
Object.defineProperty(process, 'platform', {
value: 'win32',
});
@@ -335,81 +341,81 @@ test('return level 3 if on Windows 10 build 14931 or later', t => {
value: '8.0.0',
});
os.release = () => '10.0.14931';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.is(result.stdout.level, 3);
});
-test('return level 2 when FORCE_COLOR is set when not TTY in xterm256', t => {
+test('return level 2 when FORCE_COLOR is set when not TTY in xterm256', async t => {
process.stdout.isTTY = false;
- process.env.FORCE_COLOR = true;
+ process.env.FORCE_COLOR = 'true';
process.env.TERM = 'xterm-256color';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 2);
});
-test('supports setting a color level using FORCE_COLOR', t => {
+test('supports setting a color level using FORCE_COLOR', async t => {
let result;
process.env.FORCE_COLOR = '1';
- result = importFresh('./index.js');
+ result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 1);
process.env.FORCE_COLOR = '2';
- result = importFresh('./index.js');
+ result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 2);
process.env.FORCE_COLOR = '3';
- result = importFresh('./index.js');
+ result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 3);
process.env.FORCE_COLOR = '0';
- result = importFresh('./index.js');
+ result = await importMain();
t.false(result.stdout);
});
-test('FORCE_COLOR maxes out at a value of 3', t => {
+test('FORCE_COLOR maxes out at a value of 3', async t => {
process.env.FORCE_COLOR = '4';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 3);
});
-test('FORCE_COLOR works when set via command line (all values are strings)', t => {
+test('FORCE_COLOR works when set via command line (all values are strings)', async t => {
let result;
process.env.FORCE_COLOR = 'true';
- result = importFresh('./index.js');
+ result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 1);
process.stdout.isTTY = false;
process.env.FORCE_COLOR = 'true';
process.env.TERM = 'xterm-256color';
- result = importFresh('./index.js');
+ result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 2);
process.env.FORCE_COLOR = 'false';
- result = importFresh('./index.js');
+ result = await importMain();
t.false(result.stdout);
});
-test('return false when `TERM` is set to dumb', t => {
+test('return false when `TERM` is set to dumb', async t => {
process.env.TERM = 'dumb';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return false when `TERM` is set to dumb when `TERM_PROGRAM` is set', t => {
+test('return false when `TERM` is set to dumb when `TERM_PROGRAM` is set', async t => {
process.env.TERM = 'dumb';
process.env.TERM_PROGRAM = 'Apple_Terminal';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return false when `TERM` is set to dumb when run on Windows', t => {
+test('return false when `TERM` is set to dumb when run on Windows', async t => {
Object.defineProperty(process, 'platform', {
value: 'win32',
});
@@ -418,30 +424,30 @@ test('return false when `TERM` is set to dumb when run on Windows', t => {
});
os.release = () => '10.0.14931';
process.env.TERM = 'dumb';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.false(result.stdout);
});
-test('return level 1 when `TERM` is set to dumb when `FORCE_COLOR` is set', t => {
+test('return level 1 when `TERM` is set to dumb when `FORCE_COLOR` is set', async t => {
process.env.FORCE_COLOR = '1';
process.env.TERM = 'dumb';
- const result = importFresh('./index.js');
+ const result = await importMain();
t.truthy(result.stdout);
t.is(result.stdout.level, 1);
});
-test('ignore flags when sniffFlags=false', t => {
+test('ignore flags when sniffFlags=false', async t => {
process.argv = ['--color=256'];
process.env.TERM = 'dumb';
- const result = importFresh('./index.js');
+ const {default: result, createSupportsColor} = await importFresh('./index.js');
t.truthy(result.stdout);
t.is(result.stdout.level, 2);
- const sniffResult = result.supportsColor(process.stdout, {sniffFlags: true});
+ const sniffResult = createSupportsColor(process.stdout, {sniffFlags: true});
t.truthy(sniffResult);
t.is(sniffResult.level, 2);
- const nosniffResult = result.supportsColor(process.stdout, {sniffFlags: false});
+ const nosniffResult = createSupportsColor(process.stdout, {sniffFlags: false});
t.falsy(nosniffResult);
});