WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
at f4056f7901fca651977b2b95bfefefe9e685a00a 101 lines 2.5 kB view raw
1/** 2 * Git utilities for parsing and validating tangled.org remote URLs 3 */ 4 5import { isValidHandle, isValidTangledDid } from './validation.js'; 6 7export interface ParsedTangledRemote { 8 owner: string; 9 ownerType: 'did' | 'handle'; 10 name: string; 11 protocol: 'ssh' | 'https'; 12} 13 14/** 15 * Check if a Git remote URL is a tangled.org URL 16 * @param url - Git remote URL 17 * @returns true if URL points to tangled.org 18 */ 19export function isTangledRemote(url: string): boolean { 20 // Match tangled.org in SSH or HTTPS URLs 21 return ( 22 url.includes('tangled.org') && 23 (url.startsWith('git@tangled.org:') || 24 url.startsWith('ssh://git@tangled.org') || 25 url.startsWith('https://tangled.org')) 26 ); 27} 28 29/** 30 * Parse a tangled.org Git remote URL to extract owner and repo name 31 * @param url - Git remote URL 32 * @returns Parsed remote info or null if not a valid tangled URL 33 */ 34export function parseTangledRemote(url: string): ParsedTangledRemote | null { 35 if (!isTangledRemote(url)) { 36 return null; 37 } 38 39 let path: string; 40 let protocol: 'ssh' | 'https'; 41 42 // Parse based on protocol 43 if (url.startsWith('https://tangled.org')) { 44 // HTTPS: https://tangled.org/owner/repo 45 protocol = 'https'; 46 path = url.replace(/^https:\/\/tangled\.org\//, ''); 47 } else if (url.startsWith('ssh://git@tangled.org')) { 48 // SSH with ssh:// prefix: ssh://git@tangled.org/owner/repo.git 49 protocol = 'ssh'; 50 path = url.replace(/^ssh:\/\/git@tangled\.org\//, ''); 51 } else if (url.startsWith('git@tangled.org:')) { 52 // SSH shorthand: git@tangled.org:owner/repo.git 53 protocol = 'ssh'; 54 path = url.replace(/^git@tangled\.org:/, ''); 55 } else { 56 return null; 57 } 58 59 // Remove trailing slashes 60 path = path.replace(/\/+$/, ''); 61 62 // Remove .git extension if present 63 path = path.replace(/\.git$/, ''); 64 65 // Split path into owner and repo name 66 const parts = path.split('/'); 67 if (parts.length < 2) { 68 return null; 69 } 70 71 const owner = parts[0]; 72 const name = parts[1]; 73 74 // Validate that we have both parts 75 if (!owner || !name) { 76 return null; 77 } 78 79 // Determine owner type based on format 80 let ownerType: 'did' | 'handle'; 81 if (owner.startsWith('did:plc:')) { 82 ownerType = 'did'; 83 // Validate DID format 84 if (!isValidTangledDid(owner)) { 85 return null; 86 } 87 } else { 88 ownerType = 'handle'; 89 // Validate handle format 90 if (!isValidHandle(owner)) { 91 return null; 92 } 93 } 94 95 return { 96 owner, 97 ownerType, 98 name, 99 protocol, 100 }; 101}