| 131 | ``` |
| 132 | */ |
| 133 | export const parseGitUrl = url => { |
| 134 | if (typeof url !== 'string' || url.length === 0) { |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | const cleanUrl = url.split(/[?#]/, 1)[0]; |
| 139 | if (cleanUrl.length === 0) { |
| 140 | return; |
| 141 | } |
| 142 | |
| 143 | for (const {regex, transform} of GIT_URL_PATTERNS) { |
| 144 | const match = cleanUrl.match(regex); |
| 145 | if (match) { |
| 146 | const [, host, owner, repo] = match; |
| 147 | |
| 148 | // Remove .git suffix if present in the captured repo name |
| 149 | const cleanRepo = repo.endsWith('.git') ? repo.slice(0, -4) : repo; |
| 150 | |
| 151 | // Validate that none of the components are empty |
| 152 | if (!host) { |
| 153 | continue; |
| 154 | } |
| 155 | |
| 156 | // Validate that owner and repo contain at least one alphanumeric character |
| 157 | // This prevents pathological inputs like all dots or special chars |
| 158 | if (!isValidGitPathComponent(owner) || !isValidGitPathComponent(cleanRepo)) { |
| 159 | continue; |
| 160 | } |
| 161 | |
| 162 | return transform(host, owner, cleanRepo); |
| 163 | } |
| 164 | } |
| 165 | }; |
| 166 | |
| 167 | /** @type {(config: import('./package-manager/types.js').PackageManagerConfig) => Promise<string>} */ |
| 168 | export const getTagVersionPrefix = pMemoize(async config => { |
no test coverage detected
searching dependent graphs…