(projectId: string | null)
| 16 | * normalizeProjectId(null) // returns null |
| 17 | */ |
| 18 | export function normalizeProjectId(projectId: string | null): string | null { |
| 19 | if (!projectId) { |
| 20 | return null; |
| 21 | } |
| 22 | |
| 23 | // Truncate to 256 characters first to prevent processing extremely long strings |
| 24 | const truncated = projectId.substring(0, 256); |
| 25 | |
| 26 | // Check if it looks like an HTTPS git URL (with or without .git) |
| 27 | // Must not have trailing spaces, query params, or fragments |
| 28 | // Accepts any hostname to support on-premise SCM systems |
| 29 | const httpsRepoPattern = /^https?:\/\/[^/]+\/([^\s?#]+?)(?:\.git)?$/i; |
| 30 | const httpsMatch = truncated.match(httpsRepoPattern); |
| 31 | if (httpsMatch) { |
| 32 | // Extract the path after the domain and get the last component |
| 33 | const repoPath = httpsMatch[1]; |
| 34 | const parts = repoPath.split('/'); |
| 35 | return parts[parts.length - 1]; |
| 36 | } |
| 37 | |
| 38 | // Check if it looks like an SSH git URL |
| 39 | // Must not have trailing spaces |
| 40 | // Accepts any hostname to support on-premise SCM systems |
| 41 | const sshGitPattern = /^git@[^:]+:([^\s]+?)(?:\.git)?$/i; |
| 42 | const sshMatch = truncated.match(sshGitPattern); |
| 43 | if (sshMatch) { |
| 44 | // Extract the path after the colon and get the last component |
| 45 | const repoPath = sshMatch[1]; |
| 46 | const parts = repoPath.split('/'); |
| 47 | return parts[parts.length - 1]; |
| 48 | } |
| 49 | |
| 50 | // If it's not a recognized git URL, return as-is (already truncated) |
| 51 | return truncated; |
| 52 | } |
no outgoing calls
no test coverage detected