1'use strict' 2 3const parseUrl = require('./parse-url') 4 5// look for github shorthand inputs, such as npm/cli 6const isGitHubShorthand = (arg) => { 7 // it cannot contain whitespace before the first # 8 // it cannot start with a / because that's probably an absolute file path 9 // but it must include a slash since repos are username/repository 10 // it cannot start with a . because that's probably a relative file path 11 // it cannot start with an @ because that's a scoped package if it passes the other tests 12 // it cannot contain a : before a # because that tells us that there's a protocol 13 // a second / may not exist before a # 14 const firstHash = arg.indexOf('#') 15 const firstSlash = arg.indexOf('/') 16 const secondSlash = arg.indexOf('/', firstSlash + 1) 17 const firstColon = arg.indexOf(':') 18 const firstSpace = /\s/.exec(arg) 19 const firstAt = arg.indexOf('@') 20 21 const spaceOnlyAfterHash = !firstSpace || (firstHash > -1 && firstSpace.index > firstHash) 22 const atOnlyAfterHash = firstAt === -1 || (firstHash > -1 && firstAt > firstHash) 23 const colonOnlyAfterHash = firstColon === -1 || (firstHash > -1 && firstColon > firstHash) 24 const secondSlashOnlyAfterHash = secondSlash === -1 || (firstHash > -1 && secondSlash > firstHash) 25 const hasSlash = firstSlash > 0 26 // if a # is found, what we really want to know is that the character 27 // immediately before # is not a / 28 const doesNotEndWithSlash = firstHash > -1 ? arg[firstHash - 1] !== '/' : !arg.endsWith('/') 29 const doesNotStartWithDot = !arg.startsWith('.') 30 31 return spaceOnlyAfterHash && hasSlash && doesNotEndWithSlash && 32 doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash && 33 secondSlashOnlyAfterHash 34} 35 36module.exports = (giturl, opts, { gitHosts, protocols }) => { 37 if (!giturl) { 38 return 39 } 40 41 const correctedUrl = isGitHubShorthand(giturl) ? `github:${giturl}` : giturl 42 const parsed = parseUrl(correctedUrl, protocols) 43 if (!parsed) { 44 return 45 } 46 47 const gitHostShortcut = gitHosts.byShortcut[parsed.protocol] 48 const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.') 49 ? parsed.hostname.slice(4) 50 : parsed.hostname] 51 const gitHostName = gitHostShortcut || gitHostDomain 52 if (!gitHostName) { 53 return 54 } 55 56 const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain] 57 let auth = null 58 if (protocols[parsed.protocol]?.auth && (parsed.username || parsed.password)) { 59 auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}` 60 } 61 62 let committish = null 63 let user = null 64 let project = null 65 let defaultRepresentation = null 66 67 try { 68 if (gitHostShortcut) { 69 let pathname = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname 70 const firstAt = pathname.indexOf('@') 71 // we ignore auth for shortcuts, so just trim it out 72 if (firstAt > -1) { 73 pathname = pathname.slice(firstAt + 1) 74 } 75 76 const lastSlash = pathname.lastIndexOf('/') 77 if (lastSlash > -1) { 78 user = decodeURIComponent(pathname.slice(0, lastSlash)) 79 // we want nulls only, never empty strings 80 if (!user) { 81 user = null 82 } 83 project = decodeURIComponent(pathname.slice(lastSlash + 1)) 84 } else { 85 project = decodeURIComponent(pathname) 86 } 87 88 if (project.endsWith('.git')) { 89 project = project.slice(0, -4) 90 } 91 92 if (parsed.hash) { 93 committish = decodeURIComponent(parsed.hash.slice(1)) 94 } 95 96 defaultRepresentation = 'shortcut' 97 } else { 98 if (!gitHostInfo.protocols.includes(parsed.protocol)) { 99 return 100 } 101 102 const segments = gitHostInfo.extract(parsed) 103 if (!segments) { 104 return 105 } 106 107 user = segments.user && decodeURIComponent(segments.user) 108 project = decodeURIComponent(segments.project) 109 committish = decodeURIComponent(segments.committish) 110 defaultRepresentation = protocols[parsed.protocol]?.name || parsed.protocol.slice(0, -1) 111 } 112 } catch (err) { 113 /* istanbul ignore else */ 114 if (err instanceof URIError) { 115 return 116 } else { 117 throw err 118 } 119 } 120 121 return [gitHostName, user, auth, project, committish, defaultRepresentation, opts] 122} 123