Skip to main content
Glama
git-url-parser.ts3.75 kB
import { ParsedGitUrl, GitError } from './types.js'; export class GitUrlParser { private static readonly GIT_URL_PATTERNS = [ // HTTPS patterns /^https?:\/\/([^\/]+)\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/, // SSH patterns /^git@([^:]+):([^\/]+)\/([^\/]+?)(?:\.git)?$/, // Git protocol patterns /^git:\/\/([^\/]+)\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/ ]; private static readonly KNOWN_PROVIDERS = { 'github.com': 'github', 'gitlab.com': 'gitlab', 'bitbucket.org': 'bitbucket' } as const; static parse(url: string): ParsedGitUrl { if (!url || typeof url !== 'string') { throw new GitError('Invalid URL: URL must be a non-empty string', 'INVALID_URL', url); } const trimmedUrl = url.trim(); // Try HTTPS pattern const httpsMatch = trimmedUrl.match(/^https?:\/\/([^\/]+)\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/); if (httpsMatch) { const [, host, owner, repo] = httpsMatch; return { protocol: 'https', provider: this.getProvider(host), host, owner, repo: this.cleanRepoName(repo), originalUrl: trimmedUrl }; } // Try SSH pattern const sshMatch = trimmedUrl.match(/^git@([^:]+):([^\/]+)\/([^\/]+?)(?:\.git)?$/); if (sshMatch) { const [, host, owner, repo] = sshMatch; return { protocol: 'ssh', provider: this.getProvider(host), host, owner, repo: this.cleanRepoName(repo), originalUrl: trimmedUrl }; } // Try git protocol pattern const gitMatch = trimmedUrl.match(/^git:\/\/([^\/]+)\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/); if (gitMatch) { const [, host, owner, repo] = gitMatch; return { protocol: 'git', provider: this.getProvider(host), host, owner, repo: this.cleanRepoName(repo), originalUrl: trimmedUrl }; } throw new GitError( `Unsupported git URL format: ${trimmedUrl}. Supported formats: https://github.com/owner/repo, git@github.com:owner/repo, git://github.com/owner/repo`, 'UNSUPPORTED_URL_FORMAT', trimmedUrl ); } static isGitUrl(url: string): boolean { try { this.parse(url); return true; } catch { return false; } } static validateUrl(url: string): { valid: boolean; error?: string } { try { this.parse(url); return { valid: true }; } catch (error) { return { valid: false, error: error instanceof GitError ? error.message : 'Unknown error parsing URL' }; } } static normalizeUrl(parsedUrl: ParsedGitUrl): string { // Convert to HTTPS format for consistency return `https://${parsedUrl.host}/${parsedUrl.owner}/${parsedUrl.repo}.git`; } static buildCloneUrl(parsedUrl: ParsedGitUrl, useToken?: string): string { if (parsedUrl.protocol === 'ssh') { return `git@${parsedUrl.host}:${parsedUrl.owner}/${parsedUrl.repo}.git`; } if (useToken && parsedUrl.provider === 'github') { return `https://${useToken}@${parsedUrl.host}/${parsedUrl.owner}/${parsedUrl.repo}.git`; } if (useToken && parsedUrl.provider === 'gitlab') { return `https://oauth2:${useToken}@${parsedUrl.host}/${parsedUrl.owner}/${parsedUrl.repo}.git`; } return `https://${parsedUrl.host}/${parsedUrl.owner}/${parsedUrl.repo}.git`; } private static getProvider(host: string): ParsedGitUrl['provider'] { const normalizedHost = host.toLowerCase(); return (this.KNOWN_PROVIDERS[normalizedHost as keyof typeof this.KNOWN_PROVIDERS]) || 'custom'; } private static cleanRepoName(repo: string): string { return repo.replace(/\.git$/, ''); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JonnoC/CodeRAG'

If you have feedback or need assistance with the MCP directory API, please join our Discord server