From c214314a14bcb174b12b3014b2b0a8de375029ae Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 13 Feb 2024 23:08:09 +0700 Subject: [PATCH] Require Node.js 18 Closes #151 --- .github/funding.yml | 1 - .github/workflows/main.yml | 7 +- browser.js | 8 +- index.d.ts | 8 +- index.js | 28 +++-- index.test-d.ts | 2 +- package.json | 20 +-- readme.md | 18 +-- test.js | 248 +++++++++++++++++++------------------ 9 files changed, 175 insertions(+), 165 deletions(-) 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-) - ---- - -
- - Get professional support for this package with a Tidelift subscription - -
- - Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. -
-
- ---- 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); });