1/**
2 * The hosts GitCafe lives on, and how a remote URL names one of its repositories. GitCafe has no
3 * self-hosted installs, so the list is fixed rather than discovered.
4 *
5 * @module source-control-gitcafe/server/gitCafeHosts
6 */
7
8export const GITCAFE_HOSTS = ["git.cafe", "staging.git.cafe"] as const;
9export type GitCafeHost = (typeof GITCAFE_HOSTS)[number];
10
11/** The host as GitCafe spells it, or null for any host GitCafe does not live on. */
12export function gitCafeHost(host: string): GitCafeHost | null {
13 const normalized = host.trim().toLowerCase();
14 return GITCAFE_HOSTS.find((candidate) => candidate === normalized) ?? null;
15}
16
17export function isGitCafeHost(host: string): boolean {
18 return gitCafeHost(host) !== null;
19}
20
21const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
22const REMOTE_PATTERNS = [
23 /^https?:\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/(.+)$/iu,
24 /^ssh:\/\/[^@/]+@([^/:]+)(?::\d+)?\/(.+)$/iu,
25 /^[^@/:]+@([^/:]+):(.+)$/u,
26];
27
28/** An `owner/name` repository path, without the `.git` suffix a clone URL carries. */
29export function gitCafeRepositoryPath(path: string): string | null {
30 const repository = path
31 .trim()
32 .replace(/\/+$/u, "")
33 .replace(/\.git$/u, "");
34 return REPOSITORY.test(repository) ? repository : null;
35}
36
37/** The host and `owner/name` of an https or ssh remote on a GitCafe host; null for anything else. */
38export function parseGitCafeRemote(
39 url: string,
40): { readonly host: GitCafeHost; readonly repository: string } | null {
41 for (const pattern of REMOTE_PATTERNS) {
42 const match = pattern.exec(url.trim());
43 if (!match?.[1] || !match[2]) continue;
44 const host = gitCafeHost(match[1]);
45 const repository = gitCafeRepositoryPath(match[2]);
46 return host && repository ? { host, repository } : null;
47 }
48 return null;
49}