index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. 'use strict'
  2. const { LRUCache } = require('lru-cache')
  3. const hosts = require('./hosts.js')
  4. const fromUrl = require('./from-url.js')
  5. const parseUrl = require('./parse-url.js')
  6. const cache = new LRUCache({ max: 1000 })
  7. class GitHost {
  8. constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) {
  9. Object.assign(this, GitHost.#gitHosts[type], {
  10. type,
  11. user,
  12. auth,
  13. project,
  14. committish,
  15. default: defaultRepresentation,
  16. opts,
  17. })
  18. }
  19. static #gitHosts = { byShortcut: {}, byDomain: {} }
  20. static #protocols = {
  21. 'git+ssh:': { name: 'sshurl' },
  22. 'ssh:': { name: 'sshurl' },
  23. 'git+https:': { name: 'https', auth: true },
  24. 'git:': { auth: true },
  25. 'http:': { auth: true },
  26. 'https:': { auth: true },
  27. 'git+http:': { auth: true },
  28. }
  29. static addHost (name, host) {
  30. GitHost.#gitHosts[name] = host
  31. GitHost.#gitHosts.byDomain[host.domain] = name
  32. GitHost.#gitHosts.byShortcut[`${name}:`] = name
  33. GitHost.#protocols[`${name}:`] = { name }
  34. }
  35. static fromUrl (giturl, opts) {
  36. if (typeof giturl !== 'string') {
  37. return
  38. }
  39. const key = giturl + JSON.stringify(opts || {})
  40. if (!cache.has(key)) {
  41. const hostArgs = fromUrl(giturl, opts, {
  42. gitHosts: GitHost.#gitHosts,
  43. protocols: GitHost.#protocols,
  44. })
  45. cache.set(key, hostArgs ? new GitHost(...hostArgs) : undefined)
  46. }
  47. return cache.get(key)
  48. }
  49. static parseUrl (url) {
  50. return parseUrl(url)
  51. }
  52. #fill (template, opts) {
  53. if (typeof template !== 'function') {
  54. return null
  55. }
  56. const options = { ...this, ...this.opts, ...opts }
  57. // the path should always be set so we don't end up with 'undefined' in urls
  58. if (!options.path) {
  59. options.path = ''
  60. }
  61. // template functions will insert the leading slash themselves
  62. if (options.path.startsWith('/')) {
  63. options.path = options.path.slice(1)
  64. }
  65. if (options.noCommittish) {
  66. options.committish = null
  67. }
  68. const result = template(options)
  69. return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result
  70. }
  71. hash () {
  72. return this.committish ? `#${this.committish}` : ''
  73. }
  74. ssh (opts) {
  75. return this.#fill(this.sshtemplate, opts)
  76. }
  77. sshurl (opts) {
  78. return this.#fill(this.sshurltemplate, opts)
  79. }
  80. browse (path, ...args) {
  81. // not a string, treat path as opts
  82. if (typeof path !== 'string') {
  83. return this.#fill(this.browsetemplate, path)
  84. }
  85. if (typeof args[0] !== 'string') {
  86. return this.#fill(this.browsetreetemplate, { ...args[0], path })
  87. }
  88. return this.#fill(this.browsetreetemplate, { ...args[1], fragment: args[0], path })
  89. }
  90. // If the path is known to be a file, then browseFile should be used. For some hosts
  91. // the url is the same as browse, but for others like GitHub a file can use both `/tree/`
  92. // and `/blob/` in the path. When using a default committish of `HEAD` then the `/tree/`
  93. // path will redirect to a specific commit. Using the `/blob/` path avoids this and
  94. // does not redirect to a different commit.
  95. browseFile (path, ...args) {
  96. if (typeof args[0] !== 'string') {
  97. return this.#fill(this.browseblobtemplate, { ...args[0], path })
  98. }
  99. return this.#fill(this.browseblobtemplate, { ...args[1], fragment: args[0], path })
  100. }
  101. docs (opts) {
  102. return this.#fill(this.docstemplate, opts)
  103. }
  104. bugs (opts) {
  105. return this.#fill(this.bugstemplate, opts)
  106. }
  107. https (opts) {
  108. return this.#fill(this.httpstemplate, opts)
  109. }
  110. git (opts) {
  111. return this.#fill(this.gittemplate, opts)
  112. }
  113. shortcut (opts) {
  114. return this.#fill(this.shortcuttemplate, opts)
  115. }
  116. path (opts) {
  117. return this.#fill(this.pathtemplate, opts)
  118. }
  119. tarball (opts) {
  120. return this.#fill(this.tarballtemplate, { ...opts, noCommittish: false })
  121. }
  122. file (path, opts) {
  123. return this.#fill(this.filetemplate, { ...opts, path })
  124. }
  125. edit (path, opts) {
  126. return this.#fill(this.edittemplate, { ...opts, path })
  127. }
  128. getDefaultRepresentation () {
  129. return this.default
  130. }
  131. toString (opts) {
  132. if (this.default && typeof this[this.default] === 'function') {
  133. return this[this.default](opts)
  134. }
  135. return this.sshurl(opts)
  136. }
  137. }
  138. for (const [name, host] of Object.entries(hosts)) {
  139. GitHost.addHost(name, host)
  140. }
  141. module.exports = GitHost