• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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