| author | |
| committer | |
| log | ba873b8181c3741603a365e75785457064a52ffd |
| tree | b8410d88010ab21cf0fd76cf2697c4960c609a4f |
| parent | 3fedf52467dd8d2734a7aca4c6c7da421c12474f |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 1 insertions(+), 67 deletions(-)
apps/web/src/lib/openPullRequestLink.test.ts+1-30| ... | ... | @@ -1,14 +1,12 @@ |
| 1 | import { describe, expect, it, vi } from "vite-plus/test"; | |
| 1 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | 2 | |
| 3 | 3 | import { |
| 4 | 4 | changeRequestRepositoryUrl, |
| 5 | 5 | findProjectForChangeRequest, |
| 6 | 6 | gitHubPullRequestBrowserUrl, |
| 7 | 7 | matchesLinkedPullRequestUrl, |
| 8 | openPullRequestLink, | |
| 9 | 8 | parseChangeRequestUrl, |
| 10 | 9 | pullRequestCandidateUrlFromReferenceAutolink, |
| 11 | PullRequestLinkOpenError, | |
| 12 | 10 | shouldOpenPullRequestExternally, |
| 13 | 11 | } from "./openPullRequestLink"; |
| 14 | 12 | import { ProjectId, type RepositoryIdentity } from "@t3tools/contracts"; |
| ... | ... | @@ -184,33 +182,6 @@ describe("matchesLinkedPullRequestUrl", () => { |
| 184 | 182 | }); |
| 185 | 183 | }); |
| 186 | 184 | |
| 187 | describe("openPullRequestLink", () => { | |
| 188 | it("opens the requested pull request URL", async () => { | |
| 189 | const openExternal = vi.fn(async () => undefined); | |
| 190 | const targetUrl = "https://github.com/pingdotgg/t3code/pull/123"; | |
| 191 | ||
| 192 | await openPullRequestLink({ openExternal }, targetUrl); | |
| 193 | ||
| 194 | expect(openExternal).toHaveBeenCalledExactlyOnceWith(targetUrl); | |
| 195 | }); | |
| 196 | ||
| 197 | it("reports bridge failures with a safe target origin", async () => { | |
| 198 | const cause = new Error("desktop shell unavailable"); | |
| 199 | const targetUrl = "https://github.com/pingdotgg/t3code/pull/123?token=secret"; | |
| 200 | const openExternal = vi.fn(async () => Promise.reject(cause)); | |
| 201 | ||
| 202 | const result = openPullRequestLink({ openExternal }, targetUrl); | |
| 203 | ||
| 204 | await expect(result).rejects.toEqual( | |
| 205 | new PullRequestLinkOpenError({ | |
| 206 | targetOrigin: "https://github.com", | |
| 207 | cause, | |
| 208 | }), | |
| 209 | ); | |
| 210 | await expect(result).rejects.not.toHaveProperty("message", expect.stringContaining("secret")); | |
| 211 | }); | |
| 212 | }); | |
| 213 | ||
| 214 | 185 | describe("shouldOpenPullRequestExternally", () => { |
| 215 | 186 | it("uses the browser for command-click and control-click", () => { |
| 216 | 187 | expect(shouldOpenPullRequestExternally({ metaKey: true, ctrlKey: false })).toBe(true); |
apps/web/src/lib/openPullRequestLink.ts-37| ... | ... | @@ -1,12 +1,10 @@ |
| 1 | 1 | import type { |
| 2 | 2 | EnvironmentId, |
| 3 | LocalApi, | |
| 4 | 3 | RepositoryIdentity, |
| 5 | 4 | ScopedThreadRef, |
| 6 | 5 | ThreadLinkedPullRequest, |
| 7 | 6 | } from "@t3tools/contracts"; |
| 8 | 7 | import { useNavigate } from "@tanstack/react-router"; |
| 9 | import * as Schema from "effect/Schema"; | |
| 10 | 8 | import { type MouseEvent, useCallback } from "react"; |
| 11 | 9 | |
| 12 | 10 | import { pullRequestHostOf, type SourceControlProviderKind } from "@t3tools/contracts"; |
| ... | ... | @@ -19,41 +17,6 @@ import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell"; |
| 19 | 17 | import { useProjects, useServerConfigs } from "../state/entities"; |
| 20 | 18 | import { usePrimaryEnvironmentId } from "../state/environments"; |
| 21 | 19 | |
| 22 | export class PullRequestLinkOpenError extends Schema.TaggedErrorClass<PullRequestLinkOpenError>()( | |
| 23 | "PullRequestLinkOpenError", | |
| 24 | { | |
| 25 | targetOrigin: Schema.NullOr(Schema.String), | |
| 26 | cause: Schema.Defect(), | |
| 27 | }, | |
| 28 | ) { | |
| 29 | static fromCause(targetUrl: string, cause: unknown): PullRequestLinkOpenError { | |
| 30 | let targetOrigin: string | null = null; | |
| 31 | try { | |
| 32 | targetOrigin = new URL(targetUrl).origin; | |
| 33 | } catch { | |
| 34 | // Keep malformed URLs out of diagnostics while preserving the open failure below. | |
| 35 | } | |
| 36 | return new PullRequestLinkOpenError({ targetOrigin, cause }); | |
| 37 | } | |
| 38 | ||
| 39 | override get message(): string { | |
| 40 | return this.targetOrigin === null | |
| 41 | ? "Unable to open pull request link." | |
| 42 | : `Unable to open pull request link at ${this.targetOrigin}.`; | |
| 43 | } | |
| 44 | } | |
| 45 | ||
| 46 | export async function openPullRequestLink( | |
| 47 | shell: Pick<LocalApi["shell"], "openExternal">, | |
| 48 | targetUrl: string, | |
| 49 | ): Promise<void> { | |
| 50 | try { | |
| 51 | await shell.openExternal(targetUrl); | |
| 52 | } catch (cause) { | |
| 53 | throw PullRequestLinkOpenError.fromCause(targetUrl, cause); | |
| 54 | } | |
| 55 | } | |
| 56 | ||
| 57 | 20 | /** Builds a GitHub URL that remains available when the pull request API cannot be read. */ |
| 58 | 21 | export function gitHubPullRequestBrowserUrl( |
| 59 | 22 | identity: RepositoryIdentity | null | undefined, |