blob: a72696e6e025f9ebea7d3e4ced8626d89e0b4624 [file] [log] [blame]
Nikolay Vitkov924fc2c2025-01-03 09:24:381'use strict';
2const supportsColor = require('supports-color');
3const hasFlag = require('has-flag');
4
5/**
6@param {string} versionString
7@returns {{ major: number, minor: number, patch: number }}
8*/
9function parseVersion(versionString) {
10 if (/^\d{3,4}$/.test(versionString)) {
11 // Env var doesn't always use dots. example: 4601 => 46.1.0
12 const m = /(\d{1,2})(\d{2})/.exec(versionString) || [];
13 return {
14 major: 0,
15 minor: parseInt(m[1], 10),
16 patch: parseInt(m[2], 10)
17 };
18 }
19
20 const versions = (versionString || '').split('.').map(n => parseInt(n, 10));
21 return {
22 major: versions[0],
23 minor: versions[1],
24 patch: versions[2]
25 };
26}
27
28/**
29@param {{ isTTY?: boolean | undefined }} stream
30@returns {boolean}
31*/
32function supportsHyperlink(stream) {
33 const {
34 CI,
35 FORCE_HYPERLINK,
36 NETLIFY,
37 TEAMCITY_VERSION,
38 TERM_PROGRAM,
39 TERM_PROGRAM_VERSION,
40 VTE_VERSION
41 } = process.env;
42
43 if (FORCE_HYPERLINK) {
44 return !(FORCE_HYPERLINK.length > 0 && parseInt(FORCE_HYPERLINK, 10) === 0);
45 }
46
47 if (hasFlag('no-hyperlink') || hasFlag('no-hyperlinks') || hasFlag('hyperlink=false') || hasFlag('hyperlink=never')) {
48 return false;
49 }
50
51 if (hasFlag('hyperlink=true') || hasFlag('hyperlink=always')) {
52 return true;
53 }
54
55 // Netlify does not run a TTY, it does not need `supportsColor` check
56 if (NETLIFY) {
57 return true;
58 }
59
60 // If they specify no colors, they probably don't want hyperlinks.
61 if (!supportsColor.supportsColor(stream)) {
62 return false;
63 }
64
65 if (stream && !stream.isTTY) {
66 return false;
67 }
68
69 // Windows Terminal
70 if ('WT_SESSION' in process.env) {
71 return true;
72 }
73
74 if (process.platform === 'win32') {
75 return false;
76 }
77
78 if (CI) {
79 return false;
80 }
81
82 if (TEAMCITY_VERSION) {
83 return false;
84 }
85
86 if (TERM_PROGRAM) {
87 const version = parseVersion(TERM_PROGRAM_VERSION || '');
88
89 switch (TERM_PROGRAM) {
90 case 'iTerm.app':
91 if (version.major === 3) {
92 return version.minor >= 1;
93 }
94
95 return version.major > 3;
96 case 'WezTerm':
97 return version.major >= 20200620;
98 case 'vscode':
99 // eslint-disable-next-line no-mixed-operators
100 return version.major > 1 || version.major === 1 && version.minor >= 72;
101 // No default
102 }
103 }
104
105 if (VTE_VERSION) {
106 // 0.50.0 was supposed to support hyperlinks, but throws a segfault
107 if (VTE_VERSION === '0.50.0') {
108 return false;
109 }
110
111 const version = parseVersion(VTE_VERSION);
112 return version.major > 0 || version.minor >= 50;
113 }
114
115 return false;
116}
117
118module.exports = {
119 supportsHyperlink,
120 stdout: supportsHyperlink(process.stdout),
121 stderr: supportsHyperlink(process.stderr)
122};