/** * Detects the workspace's forge repo coordinates (host / owner / repo) from `git remote get-url origin`. * * Supports both HTTPS forms (`https://git.example.com/owner/repo.git`) and SSH * forms (`git@git.example.com:owner/repo.git`, with or without a leading * `ssh://`). The pure URL parser is exported separately so it's unit-testable * without spawning a child process. */ import { execFile } from 'node:child_process' export interface Remote { host: string owner: string repo: string } const HTTPS_RE = /^https?:\/\/(?:[^@]+@)?([^/:]+)(?::\d+)?\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/ const SSH_RE = /^(?:ssh:\/\/)?(?:[^@]+@)?([^/:]+)[:/]([^/]+)\/([^/]+?)(?:\.git)?\/?$/ export function parseRemoteUrl(url: string): Remote | undefined { const trimmed = url.trim() if (!trimmed) return undefined const https = trimmed.match(HTTPS_RE) if (https) return { host: https[1], owner: https[2], repo: https[3] } const ssh = trimmed.match(SSH_RE) if (ssh) return { host: ssh[1], owner: ssh[2], repo: ssh[3] } return undefined } /** * Resolves `{host, owner, repo}` for the origin remote of the given workspace * root, or `undefined` if git fails or the URL is unparseable. */ export function detectRepo(workspaceRoot: string): Promise { return new Promise((resolve) => { execFile( 'git', ['-C', workspaceRoot, 'remote', 'get-url', 'origin'], { encoding: 'utf8' }, (err, stdout) => { if (err) { resolve(undefined) return } resolve(parseRemoteUrl(stdout)) }, ) }) }