Blame · Line-by-line history
import-helper.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 80bed05 | 1 | /** |
| 2 | * Small helpers for the GitHub import flow (/import). | |
| 3 | * | |
| 4 | * Kept in its own file so we can stay inside the rule that says we may | |
| 5 | * only edit src/routes/import.tsx and add a helper here. No DB or git | |
| 6 | * process coupling — this is pure parsing/normalization. | |
| 7 | */ | |
| 8 | ||
| 9 | export interface ParsedGithubUrl { | |
| 10 | owner: string; | |
| 11 | repo: string; | |
| 12 | } | |
| 13 | ||
| 14 | /** | |
| 15 | * Parse a GitHub URL into { owner, repo }. Accepts: | |
| 16 | * - https://github.com/foo/bar | |
| 17 | * - https://github.com/foo/bar.git | |
| 18 | * - http://github.com/foo/bar/ | |
| 19 | * - git@github.com:foo/bar.git | |
| 20 | * - github.com/foo/bar | |
| 21 | * - foo/bar | |
| 22 | * | |
| 23 | * Returns null if the URL cannot be parsed. | |
| 24 | */ | |
| 25 | export function parseGithubUrl(raw: string): ParsedGithubUrl | null { | |
| 26 | const input = (raw || "").trim(); | |
| 27 | if (!input) return null; | |
| 28 | ||
| 29 | // SSH form: git@github.com:owner/repo(.git)? | |
| 30 | const ssh = input.match(/^git@github\.com:([^/]+)\/([^/\s]+?)(?:\.git)?\/?$/i); | |
| 31 | if (ssh) return { owner: ssh[1], repo: stripDotGit(ssh[2]) }; | |
| 32 | ||
| 33 | // HTTP(S) / bare host form | |
| 34 | const http = input.match( | |
| 35 | /^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/\s]+)\/([^/\s?#]+?)(?:\.git)?\/?(?:[?#].*)?$/i | |
| 36 | ); | |
| 37 | if (http) return { owner: http[1], repo: stripDotGit(http[2]) }; | |
| 38 | ||
| 39 | // owner/repo shorthand | |
| 40 | const short = input.match(/^([^/\s]+)\/([^/\s]+?)(?:\.git)?$/); | |
| 41 | if (short) return { owner: short[1], repo: stripDotGit(short[2]) }; | |
| 42 | ||
| 43 | return null; | |
| 44 | } | |
| 45 | ||
| 46 | function stripDotGit(name: string): string { | |
| 47 | return name.replace(/\.git$/i, ""); | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Repository names on gluecron follow GitHub's rough rules: letters, | |
| 52 | * digits, hyphens, underscores, dots. We normalize by replacing anything | |
| 53 | * else with a hyphen so an imported repo is always addressable. | |
| 54 | */ | |
| 55 | export function sanitizeRepoName(name: string): string { | |
| 56 | const cleaned = name.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^-+|-+$/g, ""); | |
| 57 | return cleaned || "imported-repo"; | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Build the clone URL that `git clone --bare --mirror` will use. When a | |
| 62 | * token is supplied we inject it so private repos are reachable. | |
| 63 | */ | |
| 64 | export function buildCloneUrl(cloneUrl: string, token: string | null): string { | |
| 65 | if (!token) return cloneUrl; | |
| 66 | return cloneUrl.replace("https://github.com/", `https://${token}@github.com/`); | |
| 67 | } |