104 lines · 4.4 KB · TypeScript
RawDownload
import { useMemo } from "react";import { sourceControlRepositorySelector } from "@t3tools/shared/sourceControl";import type {  EnvironmentId,  ScopedThreadRef,  ThreadLinkedPullRequest,  ThreadPullRequestLink,} from "@t3tools/contracts";import {  isAtomCommandInterrupted,  squashAtomCommandFailure,} from "@t3tools/client-runtime/state/runtime";import {  planThreadPullRequestMutation,  threadPullRequestLinkMode,} from "@t3tools/client-runtime/thread-pull-request-compatibility";import {  threadPullRequestKeysEqual,  visibleThreadPullRequests,} from "@t3tools/shared/threadPullRequests";import {  findProjectForChangeRequest,  findProjectOnChangeRequestHost,  matchesLinkedPullRequestUrl,  parseChangeRequestUrl,} from "~/lib/openPullRequestLink";import { useProjects, useServerConfigs } from "~/state/entities";import { threadEnvironment } from "~/state/threads";import { useAtomCommand } from "~/state/use-atom-command"; /** Routes link actions through the command advertised by this environment. */export function usePullRequestLinking(environmentId: EnvironmentId | null | undefined) {  const configs = useServerConfigs();  const projects = useProjects();  const capabilities =    environmentId == null ? undefined : configs.get(environmentId)?.environment.capabilities;  const mode = threadPullRequestLinkMode(capabilities);  const link = useAtomCommand(threadEnvironment.linkPullRequest, { reportFailure: false });  const unlink = useAtomCommand(threadEnvironment.unlinkPullRequest, { reportFailure: false });  const updateMetadata = useAtomCommand(threadEnvironment.updateMetadata, { reportFailure: false });  return useMemo(() => {    const environmentProjects = projects.filter(      (project) => project.environmentId === environmentId,    );    const canLink = (url: string) => {      const parsed = parseChangeRequestUrl(url);      if (parsed === null || mode === "unsupported") return false;      return (        (mode === "multiple" ? findProjectOnChangeRequestHost : findProjectForChangeRequest)(          environmentProjects,          parsed,        ) !== undefined      );    };    const isLinked = (      thread: {        readonly pullRequests?: readonly ThreadPullRequestLink[];        readonly linkedPullRequest?: ThreadLinkedPullRequest | null | undefined;      } | null,      url: string,    ) => {      if (thread === null || mode === "unsupported") return false;      if (mode !== "multiple")        return (          thread.linkedPullRequest != null &&          matchesLinkedPullRequestUrl(thread.linkedPullRequest, url)        );      const parsed = parseChangeRequestUrl(url);      return (        parsed !== null &&        visibleThreadPullRequests(thread.pullRequests ?? []).some((entry) =>          threadPullRequestKeysEqual(entry, parsed),        )      );    };    const changeLink = async (threadRef: ScopedThreadRef, url: string, linked: boolean) => {      const parsed = parseChangeRequestUrl(url);      if (parsed === null || threadRef.environmentId !== environmentId || (linked && !canLink(url)))        throw new Error("The pull request is not available in this environment.");      const legacyProject = findProjectForChangeRequest(environmentProjects, parsed);      const mutation = planThreadPullRequestMutation({        capabilities,        threadId: threadRef.threadId,        reference: { ...parsed, url },        legacyProjectId: legacyProject?.id ?? null,        legacyRepository:          sourceControlRepositorySelector(legacyProject?.repositoryIdentity) ?? undefined,        linked,      });      if (mutation === null)        throw new Error("This environment does not support linking this pull request.");      const result = await (mutation.type === "thread.meta.update"        ? updateMetadata({ environmentId: threadRef.environmentId, input: mutation.input })        : mutation.type === "thread.pull-request.link"          ? link({ environmentId: threadRef.environmentId, input: mutation.input })          : unlink({ environmentId: threadRef.environmentId, input: mutation.input }));      if (result._tag === "Failure") {        if (isAtomCommandInterrupted(result)) throw new Error("Link update interrupted.");        throw squashAtomCommandFailure(result);      }    };    return { mode, canLink, isLinked, changeLink };  }, [capabilities, environmentId, link, mode, projects, unlink, updateMetadata]);}