Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
index.js228 linesDownload Raw Back to lib
1'use strict'
2
3const { LRUCache } = require('lru-cache')
4const hosts = require('./hosts.js')
5const fromUrl = require('./from-url.js')
6const parseUrl = require('./parse-url.js')
7
8const cache = new LRUCache({ max: 1000 })
9
10function unknownHostedUrl (url) {
11  try {
12    const {
13      protocol,
14      hostname,
15      pathname,
16    } = new URL(url)
17
18    if (!hostname) {
19      return null
20    }
21
22    const proto = /(?:git\+)http:$/.test(protocol) ? 'http:' : 'https:'
23    const path = pathname.replace(/\.git$/, '')
24    return `${proto}//${hostname}${path}`
25  } catch {
26    return null
27  }
28}
29
30class GitHost {
31  constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) {
32    Object.assign(this, GitHost.#gitHosts[type], {
33      type,
34      user,
35      auth,
36      project,
37      committish,
38      default: defaultRepresentation,
39      opts,
40    })
41  }
42
43  static #gitHosts = { byShortcut: {}, byDomain: {} }
44  static #protocols = {
45    'git+ssh:': { name: 'sshurl' },
46    'ssh:': { name: 'sshurl' },
47    'git+https:': { name: 'https', auth: true },
48    'git:': { auth: true },
49    'http:': { auth: true },
50    'https:': { auth: true },
51    'git+http:': { auth: true },
52  }
53
54  static addHost (name, host) {
55    GitHost.#gitHosts[name] = host
56    GitHost.#gitHosts.byDomain[host.domain] = name
57    GitHost.#gitHosts.byShortcut[`${name}:`] = name
58    GitHost.#protocols[`${name}:`] = { name }
59  }
60
61  static fromUrl (giturl, opts) {
62    if (typeof giturl !== 'string') {
63      return
64    }
65
66    const key = giturl + JSON.stringify(opts || {})
67
68    if (!cache.has(key)) {
69      const hostArgs = fromUrl(giturl, opts, {
70        gitHosts: GitHost.#gitHosts,
71        protocols: GitHost.#protocols,
72      })
73      cache.set(key, hostArgs ? new GitHost(...hostArgs) : undefined)
74    }
75
76    return cache.get(key)
77  }
78
79  static fromManifest (manifest, opts = {}) {
80    if (!manifest || typeof manifest !== 'object') {
81      return
82    }
83
84    const r = manifest.repository
85    // TODO: look into also checking the `bugs`/`homepage` URLs
86
87    const rurl = r && (
88      typeof r === 'string'
89        ? r
90        : typeof r === 'object' && typeof r.url === 'string'
91          ? r.url
92          : null
93    )
94
95    if (!rurl) {
96      throw new Error('no repository')
97    }
98
99    const info = (rurl && GitHost.fromUrl(rurl.replace(/^git\+/, ''), opts)) || null
100    if (info) {
101      return info
102    }
103    const unk = unknownHostedUrl(rurl)
104    return GitHost.fromUrl(unk, opts) || unk
105  }
106
107  static parseUrl (url) {
108    return parseUrl(url)
109  }
110
111  #fill (template, opts) {
112    if (typeof template !== 'function') {
113      return null
114    }
115
116    const options = { ...this, ...this.opts, ...opts }
117
118    // the path should always be set so we don't end up with 'undefined' in urls
119    if (!options.path) {
120      options.path = ''
121    }
122
123    // template functions will insert the leading slash themselves
124    if (options.path.startsWith('/')) {
125      options.path = options.path.slice(1)
126    }
127
128    if (options.noCommittish) {
129      options.committish = null
130    }
131
132    const result = template(options)
133    return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result
134  }
135
136  hash () {
137    return this.committish ? `#${this.committish}` : ''
138  }
139
140  ssh (opts) {
141    return this.#fill(this.sshtemplate, opts)
142  }
143
144  sshurl (opts) {
145    return this.#fill(this.sshurltemplate, opts)
146  }
147
148  browse (path, ...args) {
149    // not a string, treat path as opts
150    if (typeof path !== 'string') {
151      return this.#fill(this.browsetemplate, path)
152    }
153
154    if (typeof args[0] !== 'string') {
155      return this.#fill(this.browsetreetemplate, { ...args[0], path })
156    }
157
158    return this.#fill(this.browsetreetemplate, { ...args[1], fragment: args[0], path })
159  }
160
161  // If the path is known to be a file, then browseFile should be used. For some hosts
162  // the url is the same as browse, but for others like GitHub a file can use both `/tree/`
163  // and `/blob/` in the path. When using a default committish of `HEAD` then the `/tree/`
164  // path will redirect to a specific commit. Using the `/blob/` path avoids this and
165  // does not redirect to a different commit.
166  browseFile (path, ...args) {
167    if (typeof args[0] !== 'string') {
168      return this.#fill(this.browseblobtemplate, { ...args[0], path })
169    }
170
171    return this.#fill(this.browseblobtemplate, { ...args[1], fragment: args[0], path })
172  }
173
174  docs (opts) {
175    return this.#fill(this.docstemplate, opts)
176  }
177
178  bugs (opts) {
179    return this.#fill(this.bugstemplate, opts)
180  }
181
182  https (opts) {
183    return this.#fill(this.httpstemplate, opts)
184  }
185
186  git (opts) {
187    return this.#fill(this.gittemplate, opts)
188  }
189
190  shortcut (opts) {
191    return this.#fill(this.shortcuttemplate, opts)
192  }
193
194  path (opts) {
195    return this.#fill(this.pathtemplate, opts)
196  }
197
198  tarball (opts) {
199    return this.#fill(this.tarballtemplate, { ...opts, noCommittish: false })
200  }
201
202  file (path, opts) {
203    return this.#fill(this.filetemplate, { ...opts, path })
204  }
205
206  edit (path, opts) {
207    return this.#fill(this.edittemplate, { ...opts, path })
208  }
209
210  getDefaultRepresentation () {
211    return this.default
212  }
213
214  toString (opts) {
215    if (this.default && typeof this[this.default] === 'function') {
216      return this[this.default](opts)
217    }
218
219    return this.sshurl(opts)
220  }
221}
222
223for (const [name, host] of Object.entries(hosts)) {
224  GitHost.addHost(name, host)
225}
226
227module.exports = GitHost
228 
codekingpro/portable-devtools · Team Ai