/** * Shared github.com remote URL → owner/repo parse (#676 B/D7). * One authority for collector admission origin and upstream head owner. */ /** Parse owner from a github.com remote URL; undefined when not a GitHub owner/repo remote. */ export function ownerFromGitHubRemoteUrl(remoteUrl: string): string | undefined { const ownerRepo = ownerRepoFromGitHubRemoteUrl(remoteUrl); if (ownerRepo === undefined) return undefined; return ownerRepo.split("/")[0]!.toLowerCase(); } /** Parse owner/repo identity from a github.com remote URL; undefined when not exact. */ export function ownerRepoFromGitHubRemoteUrl(remoteUrl: string): string | undefined { const trimmed = remoteUrl.trim(); // git@github.com:owner/repo.git — exact owner/repo identity only. const scp = /^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i.exec(trimmed); if (scp) { return `${scp[1]}/${stripGitSuffix(scp[2]!)}`; } // ssh://git@github.com/owner/repo(.git) — exact owner/repo identity only. const ssh = /^ssh:\/\/git@github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec( trimmed, ); if (ssh) { return `${ssh[1]}/${stripGitSuffix(ssh[2]!)}`; } // https://github.com/owner/repo(.git) and git://github.com/... — exact two-segment path. let parsed: URL; try { parsed = new URL(trimmed); } catch { return undefined; } if (!/^github\.com$/i.test(parsed.hostname)) return undefined; // Non-identity URL material (query/hash/extra path) is not a repository remote. if (parsed.search !== "" || parsed.hash !== "") return undefined; const parts = parsed.pathname.split("/").filter((p) => p.length > 0); if (parts.length !== 2) return undefined; return `${parts[0]}/${stripGitSuffix(parts[1]!)}`; } function stripGitSuffix(name: string): string { return name.toLowerCase().endsWith(".git") ? name.slice(0, -4) : name; }