index.js 2.7 KB

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