(raw: string)
| 31 | } |
| 32 | |
| 33 | function parseGithubUrl(raw: string): ResolvedSource | undefined { |
| 34 | let url: URL; |
| 35 | try { |
| 36 | url = new URL(raw); |
| 37 | } catch { |
| 38 | return undefined; |
| 39 | } |
| 40 | if (url.protocol !== 'https:') return undefined; |
| 41 | if (url.hostname !== 'github.com' && url.hostname !== 'www.github.com') return undefined; |
| 42 | |
| 43 | const segments = url.pathname.split('/').filter((s) => s.length > 0); |
| 44 | const owner = segments[0]; |
| 45 | const repoRaw = segments[1]; |
| 46 | if (owner === undefined || repoRaw === undefined) return undefined; |
| 47 | |
| 48 | const repo = repoRaw.endsWith('.git') ? repoRaw.slice(0, -4) : repoRaw; |
| 49 | const rest = segments.slice(2); |
| 50 | |
| 51 | if (rest.length === 0) { |
| 52 | return { kind: 'github', owner, repo }; |
| 53 | } |
| 54 | |
| 55 | const head = rest[0]; |
| 56 | const second = rest[1]; |
| 57 | |
| 58 | if (head === 'tree' && rest.length >= 2) { |
| 59 | // `url.pathname` preserves percent-encoding (e.g. `release%231`). Decode |
| 60 | // each segment so the stored ref value is the human-readable Git ref name. |
| 61 | // The resolver re-encodes when building the codeload URL. |
| 62 | const refValue = decodeRefSegments(rest.slice(1)); |
| 63 | // We cannot tell branch from tag at parse time. For SHA-shaped values use |
| 64 | // kind: 'sha'; otherwise label as 'branch'. The resolver compensates by |
| 65 | // using codeload's short-form URL for 'branch' kinds, so codeload itself |
| 66 | // picks branch-or-tag — matching how `/tree/<x>` resolves in the GitHub UI. |
| 67 | const kind: GithubRef['kind'] = SHA_RE.test(refValue) ? 'sha' : 'branch'; |
| 68 | return { kind: 'github', owner, repo, ref: { kind, value: refValue } }; |
| 69 | } |
| 70 | |
| 71 | if (head === 'releases' && second === 'tag' && rest.length >= 3) { |
| 72 | // Recognize the canonical "this is a specific release" URL form. Earlier |
| 73 | // versions rejected it and pointed users at /tree/<tag>, but /tree/<tag> |
| 74 | // could not be parsed as a tag (only branch), which produced a 404 when |
| 75 | // codeload was asked for refs/heads/<tag-name>. |
| 76 | const tag = decodeRefSegments(rest.slice(2)); |
| 77 | return { kind: 'github', owner, repo, ref: { kind: 'tag', value: tag } }; |
| 78 | } |
| 79 | |
| 80 | if (head === 'commit' && rest.length >= 2) { |
| 81 | // Mirror the /releases/tag/ change for symmetry: a commit URL pinpoints a |
| 82 | // SHA, so accept it directly instead of bouncing users to /tree/<sha>. |
| 83 | const sha = decodeRefSegments(rest.slice(1)); |
| 84 | return { kind: 'github', owner, repo, ref: { kind: 'sha', value: sha } }; |
| 85 | } |
| 86 | |
| 87 | // /archive/refs/{heads,tags}/X.zip and any other path — fall through to zip-url. |
| 88 | return undefined; |
| 89 | } |
| 90 |
no test coverage detected