source dump of claude code
at main 178 lines 6.1 kB view raw
1import { getCwd } from './cwd.js' 2import { logForDebugging } from './debug.js' 3import { getRemoteUrl } from './git.js' 4 5export type ParsedRepository = { 6 host: string 7 owner: string 8 name: string 9} 10 11const repositoryWithHostCache = new Map<string, ParsedRepository | null>() 12 13export function clearRepositoryCaches(): void { 14 repositoryWithHostCache.clear() 15} 16 17export async function detectCurrentRepository(): Promise<string | null> { 18 const result = await detectCurrentRepositoryWithHost() 19 if (!result) return null 20 // Only return results for github.com to avoid breaking downstream consumers 21 // that assume the result is a github.com repository. 22 // Use detectCurrentRepositoryWithHost() for GHE support. 23 if (result.host !== 'github.com') return null 24 return `${result.owner}/${result.name}` 25} 26 27/** 28 * Like detectCurrentRepository, but also returns the host (e.g. "github.com" 29 * or a GHE hostname). Callers that need to construct URLs against a specific 30 * GitHub host should use this variant. 31 */ 32export async function detectCurrentRepositoryWithHost(): Promise<ParsedRepository | null> { 33 const cwd = getCwd() 34 35 if (repositoryWithHostCache.has(cwd)) { 36 return repositoryWithHostCache.get(cwd) ?? null 37 } 38 39 try { 40 const remoteUrl = await getRemoteUrl() 41 logForDebugging(`Git remote URL: ${remoteUrl}`) 42 if (!remoteUrl) { 43 logForDebugging('No git remote URL found') 44 repositoryWithHostCache.set(cwd, null) 45 return null 46 } 47 48 const parsed = parseGitRemote(remoteUrl) 49 logForDebugging( 50 `Parsed repository: ${parsed ? `${parsed.host}/${parsed.owner}/${parsed.name}` : null} from URL: ${remoteUrl}`, 51 ) 52 repositoryWithHostCache.set(cwd, parsed) 53 return parsed 54 } catch (error) { 55 logForDebugging(`Error detecting repository: ${error}`) 56 repositoryWithHostCache.set(cwd, null) 57 return null 58 } 59} 60 61/** 62 * Synchronously returns the cached github.com repository for the current cwd 63 * as "owner/name", or null if it hasn't been resolved yet or the host is not 64 * github.com. Call detectCurrentRepository() first to populate the cache. 65 * 66 * Callers construct github.com URLs, so GHE hosts are filtered out here. 67 */ 68export function getCachedRepository(): string | null { 69 const parsed = repositoryWithHostCache.get(getCwd()) 70 if (!parsed || parsed.host !== 'github.com') return null 71 return `${parsed.owner}/${parsed.name}` 72} 73 74/** 75 * Parses a git remote URL into host, owner, and name components. 76 * Accepts any host (github.com, GHE instances, etc.). 77 * 78 * Supports: 79 * https://host/owner/repo.git 80 * git@host:owner/repo.git 81 * ssh://git@host/owner/repo.git 82 * git://host/owner/repo.git 83 * https://host/owner/repo (no .git) 84 * 85 * Note: repo names can contain dots (e.g., cc.kurs.web) 86 */ 87export function parseGitRemote(input: string): ParsedRepository | null { 88 const trimmed = input.trim() 89 90 // SSH format: git@host:owner/repo.git 91 const sshMatch = trimmed.match(/^git@([^:]+):([^/]+)\/([^/]+?)(?:\.git)?$/) 92 if (sshMatch?.[1] && sshMatch[2] && sshMatch[3]) { 93 if (!looksLikeRealHostname(sshMatch[1])) return null 94 return { 95 host: sshMatch[1], 96 owner: sshMatch[2], 97 name: sshMatch[3], 98 } 99 } 100 101 // URL format: https://host/owner/repo.git, ssh://git@host/owner/repo, git://host/owner/repo 102 const urlMatch = trimmed.match( 103 /^(https?|ssh|git):\/\/(?:[^@]+@)?([^/:]+(?::\d+)?)\/([^/]+)\/([^/]+?)(?:\.git)?$/, 104 ) 105 if (urlMatch?.[1] && urlMatch[2] && urlMatch[3] && urlMatch[4]) { 106 const protocol = urlMatch[1] 107 const hostWithPort = urlMatch[2] 108 const hostWithoutPort = hostWithPort.split(':')[0] ?? '' 109 if (!looksLikeRealHostname(hostWithoutPort)) return null 110 // Only preserve port for HTTPS — SSH/git ports are not usable for constructing 111 // web URLs (e.g. ssh://git@ghe.corp.com:2222 → port 2222 is SSH, not HTTPS). 112 const host = 113 protocol === 'https' || protocol === 'http' 114 ? hostWithPort 115 : hostWithoutPort 116 return { 117 host, 118 owner: urlMatch[3], 119 name: urlMatch[4], 120 } 121 } 122 123 return null 124} 125 126/** 127 * Parses a git remote URL or "owner/repo" string and returns "owner/repo". 128 * Only returns results for github.com hosts — GHE URLs return null. 129 * Use parseGitRemote() for GHE support. 130 * Also accepts plain "owner/repo" strings for backward compatibility. 131 */ 132export function parseGitHubRepository(input: string): string | null { 133 const trimmed = input.trim() 134 135 // Try parsing as a full remote URL first. 136 // Only return results for github.com hosts — existing callers (VS Code extension, 137 // bridge) assume this function is GitHub.com-specific. Use parseGitRemote() directly 138 // for GHE support. 139 const parsed = parseGitRemote(trimmed) 140 if (parsed) { 141 if (parsed.host !== 'github.com') return null 142 return `${parsed.owner}/${parsed.name}` 143 } 144 145 // If no URL pattern matched, check if it's already in owner/repo format 146 if ( 147 !trimmed.includes('://') && 148 !trimmed.includes('@') && 149 trimmed.includes('/') 150 ) { 151 const parts = trimmed.split('/') 152 if (parts.length === 2 && parts[0] && parts[1]) { 153 // Remove .git extension if present 154 const repo = parts[1].replace(/\.git$/, '') 155 return `${parts[0]}/${repo}` 156 } 157 } 158 159 logForDebugging(`Could not parse repository from: ${trimmed}`) 160 return null 161} 162 163/** 164 * Checks whether a hostname looks like a real domain name rather than an 165 * SSH config alias. A simple dot-check is not enough because aliases like 166 * "github.com-work" still contain a dot. We additionally require that the 167 * last segment (the TLD) is purely alphabetic — real TLDs (com, org, io, net) 168 * never contain hyphens or digits. 169 */ 170function looksLikeRealHostname(host: string): boolean { 171 if (!host.includes('.')) return false 172 const lastSegment = host.split('.').pop() 173 if (!lastSegment) return false 174 // Real TLDs are purely alphabetic (e.g., "com", "org", "io"). 175 // SSH aliases like "github.com-work" have a last segment "com-work" which 176 // contains a hyphen. 177 return /^[a-zA-Z]+$/.test(lastSegment) 178}