diff --git a/src/lib/formatters/colors.ts b/src/lib/formatters/colors.ts index 301077842..24f282a81 100644 --- a/src/lib/formatters/colors.ts +++ b/src/lib/formatters/colors.ts @@ -51,11 +51,12 @@ export const boldUnderline = (text: string): string => * `string-width` treats OSC 8 sequences as zero-width, so column sizing * in tables is not affected. * - * @param text - Display text - * @param url - Target URL + * @param text - Display text (also used as the link target when `url` is omitted) + * @param url - Target URL. Defaults to `text`, which is convenient when the + * display text is already the full URL. * @returns Text wrapped in OSC 8 hyperlink escape sequences */ -export function terminalLink(text: string, url: string): string { +export function terminalLink(text: string, url: string = text): string { // OSC 8 ; params ; URI BEL text OSC 8 ; ; BEL // \x1b] opens the OSC sequence; \x07 (BEL) terminates it. // Using BEL instead of ST (\x1b\\) for broad terminal compatibility. diff --git a/src/lib/init/clack-utils.ts b/src/lib/init/clack-utils.ts index 3d7cc262c..ea85211d8 100644 --- a/src/lib/init/clack-utils.ts +++ b/src/lib/init/clack-utils.ts @@ -5,6 +5,7 @@ */ import { cancel, isCancel } from "@clack/prompts"; +import { terminalLink } from "../formatters/colors.js"; import { SENTRY_DOCS_URL } from "./constants.js"; export class WizardCancelledError extends Error { @@ -17,7 +18,7 @@ export class WizardCancelledError extends Error { export function abortIfCancelled(value: T | symbol): T { if (isCancel(value)) { cancel( - `Setup cancelled. You can visit ${SENTRY_DOCS_URL} to set up manually.` + `Setup cancelled. You can visit ${terminalLink(SENTRY_DOCS_URL)} to set up manually.` ); throw new WizardCancelledError(); } diff --git a/src/lib/init/formatters.ts b/src/lib/init/formatters.ts index feebf3df6..ed39cd965 100644 --- a/src/lib/init/formatters.ts +++ b/src/lib/init/formatters.ts @@ -5,6 +5,7 @@ */ import { cancel, log, note, outro } from "@clack/prompts"; +import { terminalLink } from "../formatters/colors.js"; import { featureLabel } from "./clack-utils.js"; import { EXIT_DEPENDENCY_INSTALL_FAILED, @@ -41,10 +42,10 @@ function buildSummaryLines(output: WizardOutput): string[] { lines.push(`Commands: ${output.commands.join("; ")}`); } if (output.sentryProjectUrl) { - lines.push(`Project: ${output.sentryProjectUrl}`); + lines.push(`Project: ${terminalLink(output.sentryProjectUrl)}`); } if (output.docsUrl) { - lines.push(`Docs: ${output.docsUrl}`); + lines.push(`Docs: ${terminalLink(output.docsUrl)}`); } const changedFiles = output.changedFiles; @@ -103,7 +104,7 @@ export function formatError(result: WorkflowRunResult): void { const docsUrl = inner?.docsUrl; if (docsUrl) { - log.info(`Docs: ${docsUrl}`); + log.info(`Docs: ${terminalLink(docsUrl)}`); } cancel("Setup failed"); diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index e278669db..3fff48713 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -13,6 +13,7 @@ import { captureException } from "@sentry/bun"; import { formatBanner } from "../banner.js"; import { CLI_VERSION } from "../constants.js"; import { getAuthToken } from "../db/auth.js"; +import { terminalLink } from "../formatters/colors.js"; import { abortIfCancelled, STEP_LABELS, @@ -251,7 +252,7 @@ export async function runWizard(options: WizardOptions): Promise { log.info( "This wizard uses AI to analyze your project and configure Sentry." + - `\nFor manual setup: ${SENTRY_DOCS_URL}` + `\nFor manual setup: ${terminalLink(SENTRY_DOCS_URL)}` ); const tracingOptions = { diff --git a/test/lib/formatters/colors.test.ts b/test/lib/formatters/colors.test.ts index 1365b395d..c72e6c608 100644 --- a/test/lib/formatters/colors.test.ts +++ b/test/lib/formatters/colors.test.ts @@ -127,4 +127,13 @@ describe("terminalLink", () => { const stripped = result.replace(/\x1b\]8;;[^\x07]*\x07/g, ""); expect(stripped).toBe("display"); }); + + test("uses text as URL when url is omitted", () => { + const result = terminalLink("https://example.com"); + expect(result).toContain("]8;;https://example.com"); + expect(result).toContain("https://example.com"); + expect(result).toBe( + terminalLink("https://example.com", "https://example.com") + ); + }); });