blob: 0cb2efe1bba5eb0deec53bb86f1a859ae3604f8c [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*/
Nikolay Vitkovbb976f72025-03-12 12:20:1632// eslint-disable-next-line complexity
Nikolay Vitkov924fc2c2025-01-03 09:24:3833function supportsHyperlink(stream) {
34 const {
35 CI,
36 FORCE_HYPERLINK,
37 NETLIFY,
38 TEAMCITY_VERSION,
39 TERM_PROGRAM,
40 TERM_PROGRAM_VERSION,
Nikolay Vitkovbb976f72025-03-12 12:20:1641 VTE_VERSION,
42 TERM,
Nikolay Vitkov924fc2c2025-01-03 09:24:3843 } = process.env;
44
45 if (FORCE_HYPERLINK) {
46 return !(FORCE_HYPERLINK.length > 0 && parseInt(FORCE_HYPERLINK, 10) === 0);
47 }
48
49 if (hasFlag('no-hyperlink') || hasFlag('no-hyperlinks') || hasFlag('hyperlink=false') || hasFlag('hyperlink=never')) {
50 return false;
51 }
52
53 if (hasFlag('hyperlink=true') || hasFlag('hyperlink=always')) {
54 return true;
55 }
56
57 // Netlify does not run a TTY, it does not need `supportsColor` check
58 if (NETLIFY) {
59 return true;
60 }
61
62 // If they specify no colors, they probably don't want hyperlinks.
63 if (!supportsColor.supportsColor(stream)) {
64 return false;
65 }
66
67 if (stream && !stream.isTTY) {
68 return false;
69 }
70
71 // Windows Terminal
72 if ('WT_SESSION' in process.env) {
73 return true;
74 }
75
76 if (process.platform === 'win32') {
77 return false;
78 }
79
80 if (CI) {
81 return false;
82 }
83
84 if (TEAMCITY_VERSION) {
85 return false;
86 }
87
88 if (TERM_PROGRAM) {
89 const version = parseVersion(TERM_PROGRAM_VERSION || '');
90
91 switch (TERM_PROGRAM) {
92 case 'iTerm.app':
93 if (version.major === 3) {
94 return version.minor >= 1;
95 }
96
97 return version.major > 3;
98 case 'WezTerm':
99 return version.major >= 20200620;
100 case 'vscode':
101 // eslint-disable-next-line no-mixed-operators
102 return version.major > 1 || version.major === 1 && version.minor >= 72;
Nikolay Vitkovbb976f72025-03-12 12:20:16103 case 'ghostty':
104 return true;
Nikolay Vitkov924fc2c2025-01-03 09:24:38105 // No default
106 }
107 }
108
109 if (VTE_VERSION) {
110 // 0.50.0 was supposed to support hyperlinks, but throws a segfault
111 if (VTE_VERSION === '0.50.0') {
112 return false;
113 }
114
115 const version = parseVersion(VTE_VERSION);
116 return version.major > 0 || version.minor >= 50;
117 }
118
Nikolay Vitkovbb976f72025-03-12 12:20:16119 switch (TERM) {
120 case 'alacritty':
121 // Support added in v0.11 (2022-10-13)
122 return true;
123 // No default
124 }
125
Nikolay Vitkov924fc2c2025-01-03 09:24:38126 return false;
127}
128
129module.exports = {
130 supportsHyperlink,
131 stdout: supportsHyperlink(process.stdout),
132 stderr: supportsHyperlink(process.stderr)
133};