[cli] Add Output#link() function to format terminal hyperlinks (#8370)

This adds a `link()` helper function to the `Output` class that is inspired by the `terminal-link` npm package.

The main difference with this version is that it's more tightly integrated with the `Output` class for the purposes of being able to toggle hyperlinks support on/off for [unit tests](4a54b19f46/packages/cli/test/unit/util/output/create-output.test.ts) by setting the `output.supportsHyperlink` boolean.

> **Note:** Since hyperlinks are still a relatively new feature, and users might not yet understand how to interact with them, we should only use this function for progressive enhancement scenarios at this time, and _not_ as part of a critical UX.
This commit is contained in:
Nathan Rajlich
2022-12-19 18:33:59 -08:00
committed by GitHub
parent 2a492fa7ec
commit 21f25f5eb0
5 changed files with 114 additions and 44 deletions

View File

@@ -1,4 +1,6 @@
import chalk from 'chalk';
import * as ansiEscapes from 'ansi-escapes';
import { supportsHyperlink as detectSupportsHyperlink } from 'supports-hyperlinks';
import renderLink from './link';
import wait, { StopSpinner } from './wait';
import type { WritableTTY } from '../../types';
@@ -8,24 +10,34 @@ const IS_TEST = process.env.NODE_ENV === 'test';
export interface OutputOptions {
debug?: boolean;
supportsHyperlink?: boolean;
}
export interface LogOptions {
color?: typeof chalk;
}
interface LinkOptions {
fallback?: false | (() => string);
}
export class Output {
stream: WritableTTY;
debugEnabled: boolean;
supportsHyperlink: boolean;
private spinnerMessage: string;
private _spinner: StopSpinner | null;
constructor(
stream: WritableTTY,
{ debug: debugEnabled = false }: OutputOptions = {}
{
debug: debugEnabled = false,
supportsHyperlink = detectSupportsHyperlink(stream),
}: OutputOptions = {}
) {
this.stream = stream;
this.debugEnabled = debugEnabled;
this.supportsHyperlink = supportsHyperlink;
this.spinnerMessage = '';
this._spinner = null;
}
@@ -167,4 +179,27 @@ export class Output {
return promise;
};
/**
* Returns an ANSI formatted hyperlink when support has been enabled.
*/
link = (
text: string,
url: string,
{ fallback }: LinkOptions = {}
): string => {
// Based on https://github.com/sindresorhus/terminal-link (MIT license)
if (!this.supportsHyperlink) {
// If the fallback has been explicitly disabled, don't modify the text itself
if (fallback === false) {
return renderLink(text);
}
return typeof fallback === 'function'
? fallback()
: `${text} (${renderLink(url)})`;
}
return ansiEscapes.link(chalk.cyan(text), url);
};
}