open source is social v-it.org
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 sol pbc
3
4export function parseGitUrl(input) {
5 if (typeof input !== 'string' || input.trim() === '') {
6 throw new Error('Invalid git URL: ' + String(input));
7 }
8
9 const raw = input.trim();
10 const normalizedError = () => new Error('Invalid git URL: ' + raw);
11
12 let host = '';
13 let path = '';
14
15 if (raw.includes('@') && !raw.includes('://')) {
16 const atIndex = raw.lastIndexOf('@');
17 const hostAndPath = raw.slice(atIndex + 1);
18 const colonIndex = hostAndPath.indexOf(':');
19 if (colonIndex === -1) {
20 throw normalizedError();
21 }
22 host = hostAndPath.slice(0, colonIndex);
23 path = hostAndPath.slice(colonIndex + 1);
24 if (!host || !path) {
25 throw normalizedError();
26 }
27 } else if (raw.includes('://')) {
28 let url;
29 try {
30 url = new URL(raw);
31 } catch {
32 throw normalizedError();
33 }
34 host = url.hostname;
35 path = url.pathname;
36 if (!host) {
37 throw normalizedError();
38 }
39 } else {
40 const slashIndex = raw.indexOf('/');
41 if (slashIndex !== -1) {
42 const maybeHost = raw.slice(0, slashIndex);
43 if (maybeHost.includes('.')) {
44 host = maybeHost;
45 path = raw.slice(slashIndex + 1);
46 if (!host) {
47 throw normalizedError();
48 }
49 } else {
50 throw normalizedError();
51 }
52 } else {
53 throw normalizedError();
54 }
55 }
56
57 const cleanedPath = path.replace(/^\/+|\/+$/g, '').replace(/\.git$/, '');
58 const segments = cleanedPath.split('/').filter(Boolean);
59
60 if (segments.length === 0 || segments.length >= 3) {
61 throw normalizedError();
62 }
63
64 let org = '';
65 let repo = '';
66 if (segments.length === 1) {
67 repo = segments[0];
68 } else {
69 org = segments[0];
70 repo = segments[1];
71 }
72
73 host = host.toLowerCase();
74 org = org.toLowerCase();
75 repo = repo.toLowerCase();
76
77 if (!repo) {
78 throw normalizedError();
79 }
80
81 return { host, org, repo };
82}
83
84export function toBeacon(input) {
85 const { host, org, repo } = parseGitUrl(input);
86 return org ? `${host}/${org}/${repo}` : `${host}//${repo}`;
87}
88
89export function beaconToHttps(input) {
90 if (typeof input === 'string' && input.startsWith('vit:')) {
91 const path = input.slice(4);
92 if (!path) throw new Error('Invalid beacon URI: ' + input);
93 return 'https://' + path.replace(/\/\//, '/');
94 }
95 const { host, org, repo } = parseGitUrl(input);
96 return org ? `https://${host}/${org}/${repo}` : `https://${host}/${repo}`;
97}