Skip to content

Commit

Permalink
fix: interpret FORCE_COLOR=false or FORCE_COLOR=0 as force disable co…
Browse files Browse the repository at this point in the history
…lors
  • Loading branch information
webdiscus committed Apr 15, 2024
1 parent 898804b commit c1ef85e
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 39 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change log

## 3.1.1 (2024-04-15)

- fix: interpret FORCE_COLOR=false or FORCE_COLOR=0 as force disable colors\
others values, e.g., FORCE_COLOR=true or FORCE_COLOR=1 - force enable colors.\
See https://force-color.org.

## 3.1.0 (2024-04-10)

- feat: add detection of color support when using PM2 process manager
Expand Down
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ See the [features comparison](#compare) and [benchmarks](#benchmark) of most pop

## 🔆 What's New in v3

- **NEW** added detection of supported color space: TrueColor, 256 colors, 16 colors, no color (black & white)
- **NEW** added detection of supported color space: TrueColor, 256 colors, 16 colors, no colors (black & white)
- **NEW** added fallback to supported color space: TrueColor —> 256 colors —> 16 colors —> no colors

> #### ⚠️ Warning
Expand Down Expand Up @@ -553,12 +553,13 @@ Defaults, the output in terminal console is colored and output in a file is unco
To force disable or enable colored output use environment variables `NO_COLOR` and `FORCE_COLOR`.

The `NO_COLOR` variable should be presents with any not empty value.
The value is not important, e.g., `NO_COLOR=1` `NO_COLOR=true` disable colors.
The value is not important, e.g., `NO_COLOR=1` `NO_COLOR=true` disable colors.\
See standard description by [NO_COLOR](https://no-color.org/).

The `FORCE_COLOR` variable should be presents with one of values:\
`FORCE_COLOR=0` force disable colors\
`FORCE_COLOR=1` force enable colors
`FORCE_COLOR=0` force disable colors\
`FORCE_COLOR=1` force enable colors\
See standard description by [FORCE_COLOR](https://force-color.org/).

For example, _app.js_:

Expand Down Expand Up @@ -619,7 +620,7 @@ Ansis automatically detects the supported color space:
- TrueColor
- ANSI 256 colors
- ANSI 16 colors
- black & white (no color)
- black & white (no colors)

There is no standard way to detect which color space is supported.
The most common way to detect color support is to check the `TERM` and `COLORTERM` environment variables.
Expand Down Expand Up @@ -665,18 +666,18 @@ npm run compare

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/compare-colorize-libraries?file=index.js)

| Library<br><nobr>________________</nobr><br> - name<br> - bundle size<br> - named import<br>- naming colors | ANSI base colors | ANSI 256<br>colors | True<br>Color | Chained<br>syntax | Nested<br>template strings | New<br>Line | Fallbacks | Supports<br>ENV vars<br>CLI flags |
|:------------------------------------------------------------------------------------------------------------|:----------------:|:------------------:|:-------------:|:-----------------:|:--------------------------:|:-----------:|------------------------------------|:---------------------------------------------------------|
| [`colors.js`][colors.js]<br>**18.1KB**<br><nobr>`❌ named import`</nobr><br>`❌ standard` | `16` colors |||||| no color | `FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`colors-cli`][colors-cli]<br>**8.6KB**<br><nobr>`❌ named import`</nobr><br>`❌ standard` | `16` colors |||||| no color | `--no-color`<br>`--color` |
| [`cli-color`][cli-color]<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `16` colors |||||| 16 colors<br>no color | `NO_COLOR` |
| [`ansi-colors`][ansi-colors]<br>**5.8KB**<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `16` colors ||||||| `FORCE_COLOR` |
| [`colorette`][colorette]<br>**3.3KB**<br><nobr>`✅ named import`</nobr><br>`✅ standard` | `16` colors |||||| no color | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`picocolors`][picocolors]<br>**2.6KB**<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `8` colors |||||| no color | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`kleur`][kleur]<br>**2.7KB**<br><nobr>`✅ named import`</nobr><br>`✅ standard` | `8` colors |||||| no color | `NO_COLOR`<br>`FORCE_COLOR` |
| [`kolorist`][kolorist]<br>**6.8KB**<br><nobr>`✅ named import`</nobr><br>`❌ standard` | `16` colors |||||| 256 color<br>❌<br>no color | `NO_COLOR`<br>`FORCE_COLOR` |
| [`chalk`][chalk]<br>**15KB**<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `16` colors |||||| 256 color<br>16 colors<br>no color | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`ansis`][ansis]<br>**3.4KB**<br><nobr>`✅ named import`</nobr><br>`✅ standard` | `16` colors |||||| 256 color<br>16 colors<br>no color | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| Library<br><nobr>________________</nobr><br> - name<br> - bundle size<br> - named import<br>- naming colors | ANSI base colors | ANSI 256<br>colors | True<br>Color | Chained<br>syntax | Nested<br>template strings | New<br>Line | Fallbacks | Supports<br>ENV vars<br>CLI flags |
|:------------------------------------------------------------------------------------------------------------|:----------------:|:------------------:|:-------------:|:-----------------:|:--------------------------:|:-----------:|-------------------------------------|:---------------------------------------------------------|
| [`colors.js`][colors.js]<br>**18.1KB**<br><nobr>`❌ named import`</nobr><br>`❌ standard` | `16` colors |||||| no colors | `FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`colors-cli`][colors-cli]<br>**8.6KB**<br><nobr>`❌ named import`</nobr><br>`❌ standard` | `16` colors |||||| no colors | `--no-color`<br>`--color` |
| [`cli-color`][cli-color]<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `16` colors |||||| 16 colors<br>no colors | `NO_COLOR` |
| [`ansi-colors`][ansi-colors]<br>**5.8KB**<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `16` colors |||||| | `FORCE_COLOR` |
| [`colorette`][colorette]<br>**3.3KB**<br><nobr>`✅ named import`</nobr><br>`✅ standard` | `16` colors |||||| no colors | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`picocolors`][picocolors]<br>**2.6KB**<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `8` colors |||||| no colors | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`kleur`][kleur]<br>**2.7KB**<br><nobr>`✅ named import`</nobr><br>`✅ standard` | `8` colors |||||| no colors | `NO_COLOR`<br>`FORCE_COLOR` |
| [`kolorist`][kolorist]<br>**6.8KB**<br><nobr>`✅ named import`</nobr><br>`❌ standard` | `16` colors |||||| 256 color<br>❌<br>no colors | `NO_COLOR`<br>`FORCE_COLOR` |
| [`chalk`][chalk]<br>**15KB**<br><nobr>`❌ named import`</nobr><br>`✅ standard` | `16` colors |||||| 256 color<br>16 colors<br>no colors | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |
| [`ansis`][ansis]<br>**3.4KB**<br><nobr>`✅ named import`</nobr><br>`✅ standard` | `16` colors |||||| 256 color<br>16 colors<br>no colors | `NO_COLOR`<br>`FORCE_COLOR`<br>`--no-color`<br>`--color` |

> **Note**
>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ansis",
"version": "3.1.0",
"version": "3.1.1",
"description": "Colorize terminal with ANSI colors & styles",
"keywords": [
"ansi",
Expand Down Expand Up @@ -74,7 +74,7 @@
"test:unit": "vitest run ./test/unit.test.js",
"test:ansi16": "vitest run ./test/ansi16.test.js",
"test:ansi256": "vitest run ./test/ansi256.test.js",
"test:index": "vitest run ./test/index.test.js",
"test:functional": "vitest run ./test/functional.test.js",
"test:flags": "vitest run ./test/flags.test.js",
"test:package": "vitest run ./test/package.test.js",
"test:cjs": "node ./test/package/cjs/test.cjs",
Expand Down
2 changes: 1 addition & 1 deletion package.npm.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ansis",
"version": "3.1.0",
"version": "3.1.1",
"description": "Colorize terminal with ANSI colors & styles",
"keywords": [
"ansi",
Expand Down
20 changes: 14 additions & 6 deletions src/color-support.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const getColorSpace = (mockThis) => {
// Node -> `argv`, Deno -> `args`
const argv = proc.argv || proc.args || [];
let env = proc.env || {};
let colorSpace;
let colorSpace = -1;

if (isDeno) {
try {
Expand All @@ -107,29 +107,37 @@ export const getColorSpace = (mockThis) => {
}
}

// When FORCE_COLOR is present and not an empty string (regardless of its value, except `false` or `0`),
// it should force the addition of ANSI color.
// See https://force-color.org

const FORCE_COLOR = 'FORCE_COLOR';
const hasForceColor = FORCE_COLOR in env;
const forceColorValue = env[FORCE_COLOR];
const forceColor = forceColorValue === 'true' || parseInt(forceColorValue, 10) > 0;
const forceColorNum = parseInt(forceColorValue);
const forceColor = forceColorValue === 'false' ? SPACE_MONO : isNaN(forceColorNum) ? SPACE_TRUE_COLORS : forceColorNum;

const isForceDisabled = 'NO_COLOR' in env
|| (hasForceColor && !forceColor)
|| forceColor === SPACE_MONO
// --no-color --color=false --color=never
|| hasFlag(/^-{1,2}(no-color|color=(false|never))$/);

// --color --color=true --color=always
const isForceEnabled = (hasForceColor && forceColor) || hasFlag(/^-{1,2}color=?(true|always)?$/);
const isForceEnabled = (FORCE_COLOR in env && forceColor) || hasFlag(/^-{1,2}color=?(true|always)?$/);

// when Next.JS runtime is `edge`, process.stdout is undefined, but colors output is supported
// runtime values supported colors: `nodejs`, `edge`, `experimental-edge`
const isNextJS = (env.NEXT_RUNTIME || '').indexOf('edge') > -1;

// PM2 does not set process.stdout.isTTY, but colors may be supported (depends on actual terminal)
const isPM2 = 'PM2_HOME' in env && 'pm_id' in env;

// whether the output is supported
const isTTY = isNextJS || isPM2 || (isDeno ? Deno.isatty(1) : stdout && 'isTTY' in stdout);

// optimisation: placed here to reduce the size of the compiled bundle
if (isForceDisabled) return SPACE_MONO;

if (colorSpace == null) {
if (colorSpace < 0) {
// truecolor support starts from Windows 10 build 14931 (2016-09-21), in 2024 we assume modern Windows is used
colorSpace = isWin ? SPACE_TRUE_COLORS : detectColorSpace(env, isTTY);
}
Expand Down
2 changes: 1 addition & 1 deletion test/index.test.js → test/functional.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ describe('style tests', () => {
// });
});

describe('functional tests', () => {
describe('advanced features tests', () => {
test(`ansis('OK')`, () => {
const received = ansis('OK');
const expected = 'OK';
Expand Down
65 changes: 53 additions & 12 deletions test/unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,21 @@ describe('flags and options', () => {
expect(received).toEqual(expected);
});

test(`disable colors via --color=never`, () => {
const received = colorSpace({
process: {
platform: 'linux',
env: { TERM: 'xterm' },
argv: ['--color=never'],
stdout: { isTTY: true },
stderr: { isTTY: true },
},

});
const expected = SPACE_MONO;
expect(received).toEqual(expected);
});

test(`disable colors via NO_COLOR=1`, () => {
const received = colorSpace({
process: {
Expand All @@ -309,14 +324,12 @@ describe('flags and options', () => {
expect(received).toEqual(expected);
});

test(`disable colors via FORCE_COLOR=0`, () => {
test(`not exists FORCE_COLOR`, () => {
const received = colorSpace({
process: {
platform: 'linux',
env: { FORCE_COLOR: '0', TERM: 'xterm' },
env: {},
argv: [],
stdout: { isTTY: true },
stderr: { isTTY: true },
},

});
Expand All @@ -339,6 +352,34 @@ describe('flags and options', () => {
expect(received).toEqual(expected);
});

test(`disable colors via FORCE_COLOR=0`, () => {
const received = colorSpace({
process: {
platform: 'linux',
env: { FORCE_COLOR: '0', TERM: 'xterm' },
argv: [],
stdout: { isTTY: true },
stderr: { isTTY: true },
},

});
const expected = SPACE_MONO;
expect(received).toEqual(expected);
});

test(`enable colors via FORCE_COLOR=true`, () => {
const received = colorSpace({
process: {
platform: 'linux',
env: { FORCE_COLOR: 'true' },
argv: [],
},

});
const expected = SPACE_TRUE_COLORS;
expect(received).toEqual(expected);
});

test(`enable colors via FORCE_COLOR=1`, () => {
const received = colorSpace({
process: {
Expand All @@ -352,11 +393,11 @@ describe('flags and options', () => {
expect(received).toEqual(expected);
});

test(`enable colors via FORCE_COLOR=true`, () => {
test(`enable colors via FORCE_COLOR=something`, () => {
const received = colorSpace({
process: {
platform: 'linux',
env: { FORCE_COLOR: 'true' },
env: { FORCE_COLOR: 'something' },
argv: [],
},

Expand Down Expand Up @@ -651,9 +692,9 @@ describe('Node.JS different env', () => {
const received = colorSpace({
process: {
env: {
PM2_HOME: "/var/www/",
pm_id: "1",
COLORTERM: 'truecolor'
PM2_HOME: '/var/www/',
pm_id: '1',
COLORTERM: 'truecolor',
},
argv: [],
stdout: {},
Expand All @@ -669,9 +710,9 @@ describe('Node.JS different env', () => {
const received = colorSpace({
process: {
env: {
PM2_HOME: "/var/www/",
pm_id: "1",
TERM: 'dumb'
PM2_HOME: '/var/www/',
pm_id: '1',
TERM: 'dumb',
},
argv: [],
stdout: {},
Expand Down

0 comments on commit c1ef85e

Please sign in to comment.