from-url.js 4.1 KB

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