(source: string)
| 85 | } |
| 86 | |
| 87 | export function tryParseGithubUrl(source: string): GithubRepoInfo | null { |
| 88 | // Handle SCP-style SSH URLs. |
| 89 | if (source.startsWith('git@')) { |
| 90 | if (source.startsWith('git@github.com:')) { |
| 91 | // It's a GitHub SSH URL, so normalize it for the URL parser. |
| 92 | source = source.replace('git@github.com:', ''); |
| 93 | } else { |
| 94 | // It's another provider's SSH URL (e.g., gitlab), so not a GitHub repo. |
| 95 | return null; |
| 96 | } |
| 97 | } |
| 98 | // Default to a github repo path, so `source` can be just an org/repo |
| 99 | let parsedUrl: URL; |
| 100 | try { |
| 101 | // Use the standard URL constructor for backward compatibility. |
| 102 | parsedUrl = new URL(source, 'https://github.com'); |
| 103 | } catch (e) { |
| 104 | // Throw a TypeError to maintain a consistent error contract for invalid URLs. |
| 105 | // This avoids a breaking change for consumers who might expect a TypeError. |
| 106 | throw new TypeError(`Invalid repo URL: ${source}`, { cause: e }); |
| 107 | } |
| 108 | |
| 109 | if (!parsedUrl) { |
| 110 | throw new Error(`Invalid repo URL: ${source}`); |
| 111 | } |
| 112 | if (parsedUrl?.host !== 'github.com') { |
| 113 | return null; |
| 114 | } |
| 115 | // The pathname should be "/owner/repo". |
| 116 | const parts = parsedUrl?.pathname |
| 117 | .split('/') |
| 118 | // Remove the empty segments, fixes trailing and leading slashes |
| 119 | .filter((part) => part !== ''); |
| 120 | |
| 121 | if (parts?.length !== 2) { |
| 122 | throw new Error( |
| 123 | `Invalid GitHub repository source: ${source}. Expected "owner/repo" or a github repo uri.`, |
| 124 | ); |
| 125 | } |
| 126 | const owner = parts[0]; |
| 127 | const repo = parts[1].replace('.git', ''); |
| 128 | |
| 129 | return { |
| 130 | owner, |
| 131 | repo, |
| 132 | }; |
| 133 | } |
| 134 | |
| 135 | export async function fetchReleaseFromGithub( |
| 136 | owner: string, |
no outgoing calls
no test coverage detected