supports-hyperlinks
Version:
Detect whether a terminal supports hyperlinks
135 lines (109 loc) • 2.75 kB
JavaScript
import process from 'node:process';
import {createSupportsColor} from 'supports-color';
import hasFlag from 'has-flag';
function parseVersion(versionString = '') {
if (/^\d{3,4}$/.test(versionString)) {
// Env var doesn't always use dots. example: 4601 => 46.1.0
const match = /(\d{1,2})(\d{2})/.exec(versionString) ?? [];
return {
major: 0,
minor: Number.parseInt(match[1], 10),
patch: Number.parseInt(match[2], 10),
};
}
const versions = (versionString ?? '').split('.').map(n => Number.parseInt(n, 10));
return {
major: versions[0],
minor: versions[1],
patch: versions[2],
};
}
// eslint-disable-next-line complexity
export function createSupportsHyperlinks(stream) {
const {
CI,
FORCE_HYPERLINK,
NETLIFY,
TEAMCITY_VERSION,
TERM_PROGRAM,
TERM_PROGRAM_VERSION,
VTE_VERSION,
TERM,
} = process.env;
if (FORCE_HYPERLINK) {
return !(FORCE_HYPERLINK.length > 0 && Number.parseInt(FORCE_HYPERLINK, 10) === 0);
}
if (hasFlag('no-hyperlink') || hasFlag('no-hyperlinks') || hasFlag('hyperlink=false') || hasFlag('hyperlink=never')) {
return false;
}
if (hasFlag('hyperlink=true') || hasFlag('hyperlink=always')) {
return true;
}
// Netlify does not run a TTY, it does not need `supportsColor` check
if (NETLIFY) {
return true;
}
// If they specify no colors, they probably don't want hyperlinks.
if (!createSupportsColor(stream)) {
return false;
}
if (stream && !stream.isTTY) {
return false;
}
// Windows Terminal
if ('WT_SESSION' in process.env) {
return true;
}
if (process.platform === 'win32') {
return false;
}
if (CI) {
return false;
}
if (TEAMCITY_VERSION) {
return false;
}
if (TERM_PROGRAM) {
const version = parseVersion(TERM_PROGRAM_VERSION);
switch (TERM_PROGRAM) {
case 'iTerm.app': {
if (version.major === 3) {
return version.minor >= 1;
}
return version.major > 3;
}
case 'WezTerm': {
return version.major >= 20_200_620;
}
case 'vscode': {
// eslint-disable-next-line no-mixed-operators
return version.major > 1 || version.major === 1 && version.minor >= 72;
}
case 'ghostty': {
return true;
}
// No default
}
}
if (VTE_VERSION) {
// 0.50.0 was supposed to support hyperlinks, but throws a segfault
if (VTE_VERSION === '0.50.0') {
return false;
}
const version = parseVersion(VTE_VERSION);
return version.major > 0 || version.minor >= 50;
}
switch (TERM) {
case 'alacritty': {
// Support added in v0.11 (2022-10-13)
return true;
}
// No default
}
return false;
}
const supportsHyperlinks = {
stdout: createSupportsHyperlinks(process.stdout),
stderr: createSupportsHyperlinks(process.stderr),
};
export default supportsHyperlinks;