236 lines · 6.5 KB · TypeScript
RawDownload
import type { SourceControlProviderInfo, SourceControlProviderKind } from "@t3tools/contracts"; export interface ChangeRequestPresentation {  readonly icon: "github" | "gitlab" | "azure-devops" | "bitbucket" | "change-request";  readonly providerName: string;  readonly shortName: string;  readonly longName: string;  readonly pluralLongName: string;  readonly providerLongName: string;  readonly checkoutCommandExample?: string;  readonly urlExample: string;} export interface ChangeRequestTerminology {  readonly shortLabel: string;  readonly singular: string;} export const DEFAULT_CHANGE_REQUEST_TERMINOLOGY: ChangeRequestTerminology = {  shortLabel: "PR",  singular: "pull request",}; const GITHUB_CHANGE_REQUEST_PRESENTATION: ChangeRequestPresentation = {  icon: "github",  providerName: "GitHub",  shortName: "PR",  longName: "pull request",  pluralLongName: "pull requests",  providerLongName: "GitHub pull request",  checkoutCommandExample: "gh pr checkout 123",  urlExample: "https://github.com/owner/repo/pull/42",}; const GITLAB_CHANGE_REQUEST_PRESENTATION: ChangeRequestPresentation = {  icon: "gitlab",  providerName: "GitLab",  shortName: "MR",  longName: "merge request",  pluralLongName: "merge requests",  providerLongName: "GitLab merge request",  checkoutCommandExample: "glab mr checkout 123",  urlExample: "https://gitlab.com/group/project/-/merge_requests/42",}; const AZURE_DEVOPS_CHANGE_REQUEST_PRESENTATION: ChangeRequestPresentation = {  icon: "azure-devops",  providerName: "Azure DevOps",  shortName: "PR",  longName: "pull request",  pluralLongName: "pull requests",  providerLongName: "Azure DevOps pull request",  checkoutCommandExample: "az repos pr checkout --id 123",  urlExample: "https://dev.azure.com/org/project/_git/repo/pullrequest/42",}; const BITBUCKET_CHANGE_REQUEST_PRESENTATION: ChangeRequestPresentation = {  icon: "bitbucket",  providerName: "Bitbucket",  shortName: "PR",  longName: "pull request",  pluralLongName: "pull requests",  providerLongName: "Bitbucket pull request",  urlExample: "https://bitbucket.org/workspace/repo/pull-requests/42",}; const GENERIC_CHANGE_REQUEST_PRESENTATION: ChangeRequestPresentation = {  icon: "change-request",  providerName: "source control",  shortName: "change request",  longName: "change request",  pluralLongName: "change requests",  providerLongName: "change request",  urlExample: "#42",}; export function resolveChangeRequestPresentation(  provider: SourceControlProviderInfo | null | undefined,): ChangeRequestPresentation {  switch (provider?.kind) {    case "github":    case undefined:      return GITHUB_CHANGE_REQUEST_PRESENTATION;    case "gitlab":      return GITLAB_CHANGE_REQUEST_PRESENTATION;    case "azure-devops":      return AZURE_DEVOPS_CHANGE_REQUEST_PRESENTATION;    case "bitbucket":      return BITBUCKET_CHANGE_REQUEST_PRESENTATION;    case "unknown":      return GENERIC_CHANGE_REQUEST_PRESENTATION;  }} function resolveChangeRequestPresentationForKind(  kind: SourceControlProviderKind,): ChangeRequestPresentation {  return resolveChangeRequestPresentation({ kind, name: "", baseUrl: "" });} export function getChangeRequestTerminology(  provider: SourceControlProviderInfo | null | undefined,): ChangeRequestTerminology {  if (!provider) {    return DEFAULT_CHANGE_REQUEST_TERMINOLOGY;  }   const presentation = resolveChangeRequestPresentation(provider);  return {    shortLabel: presentation.shortName,    singular: presentation.longName,  };} export function getChangeRequestTerminologyForKind(  kind: SourceControlProviderKind,): ChangeRequestTerminology {  const presentation = resolveChangeRequestPresentationForKind(kind);  return {    shortLabel: presentation.shortName,    singular: presentation.longName,  };} const SCP_SSH_REMOTE_PATTERN = /^[a-zA-Z0-9._-]+@([^:/]+):/; export function isSshRemoteUrl(remoteUrl: string): boolean {  const trimmed = remoteUrl.trim();  return SCP_SSH_REMOTE_PATTERN.test(trimmed) || trimmed.toLowerCase().startsWith("ssh://");} function parseRemoteHost(remoteUrl: string): string | null {  const trimmed = remoteUrl.trim();  if (trimmed.length === 0) {    return null;  }   const scpMatch = SCP_SSH_REMOTE_PATTERN.exec(trimmed);  if (scpMatch?.[1]) {    return scpMatch[1].toLowerCase();  }   try {    return new URL(trimmed).host.toLowerCase();  } catch {    return null;  }} function parseHostName(host: string): string {  try {    return new URL(`https://${host}`).hostname.toLowerCase();  } catch {    return host.replace(/:\d+$/u, "").toLowerCase();  }} function toBaseUrl(host: string): string {  return `https://${host}`;} function hasDnsLabel(host: string, label: string): boolean {  return host.split(".").includes(label);} function isGitHubHost(host: string): boolean {  return host === "github.com" || hasDnsLabel(host, "github");} function isGitLabHost(host: string): boolean {  return host === "gitlab.com" || hasDnsLabel(host, "gitlab");} function isAzureDevOpsHost(host: string): boolean {  // `ssh.dev.azure.com` is the default Azure DevOps SSH clone host  // (git@ssh.dev.azure.com:v3/org/project/repo), so match any `*.dev.azure.com`  // subdomain, not just the bare `dev.azure.com`. Legacy hosts stay under  // `.visualstudio.com` (including `vs-ssh.visualstudio.com`).  return (    host === "dev.azure.com" ||    host.endsWith(".dev.azure.com") ||    host.endsWith(".visualstudio.com")  );} function isBitbucketHost(host: string): boolean {  return host === "bitbucket.org" || hasDnsLabel(host, "bitbucket");} export function detectSourceControlProviderFromRemoteUrl(  remoteUrl: string,): SourceControlProviderInfo | null {  const host = parseRemoteHost(remoteUrl);  if (!host) {    return null;  }  const hostname = parseHostName(host);   if (isGitHubHost(hostname)) {    return {      kind: "github",      name: hostname === "github.com" ? "GitHub" : "GitHub Self-Hosted",      baseUrl: toBaseUrl(host),    };  }   if (isGitLabHost(hostname)) {    return {      kind: "gitlab",      name: hostname === "gitlab.com" ? "GitLab" : "GitLab Self-Hosted",      baseUrl: toBaseUrl(host),    };  }   if (isAzureDevOpsHost(hostname)) {    return {      kind: "azure-devops",      name: "Azure DevOps",      baseUrl: toBaseUrl(host),    };  }   if (isBitbucketHost(hostname)) {    return {      kind: "bitbucket",      name: hostname === "bitbucket.org" ? "Bitbucket" : "Bitbucket Self-Hosted",      baseUrl: toBaseUrl(host),    };  }   return {    kind: "unknown",    name: host,    baseUrl: toBaseUrl(host),  };}