From af7eaac5018ed821d72d43d08f1d7e49e7491453 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:15:50 -0400 Subject: [PATCH] hosted-git-info@4.0.1 --- node_modules/hosted-git-info/README.md | 4 +- node_modules/hosted-git-info/git-host-info.js | 211 ++++++++---- node_modules/hosted-git-info/git-host.js | 222 ++++++------- node_modules/hosted-git-info/index.js | 299 ++++++++++++------ node_modules/hosted-git-info/package.json | 26 +- package-lock.json | 14 +- package.json | 2 +- 7 files changed, 452 insertions(+), 326 deletions(-) diff --git a/node_modules/hosted-git-info/README.md b/node_modules/hosted-git-info/README.md index 7b723f6b9e213..8740406029626 100644 --- a/node_modules/hosted-git-info/README.md +++ b/node_modules/hosted-git-info/README.md @@ -22,7 +22,7 @@ var info = hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git", opts) If the URL can't be matched with a git host, `null` will be returned. We can match git, ssh and https urls. Additionally, we can match ssh connect strings (`git@github.com:npm/hosted-git-info`) and shortcuts (eg, -`github:npm/hosted-git-info`). Github specifically, is detected in the case +`github:npm/hosted-git-info`). GitHub specifically, is detected in the case of a third, unprefixed, form: `npm/hosted-git-info`. If it does match, the returned object has properties of: @@ -129,5 +129,5 @@ SSH connect strings will be normalized into `git+ssh` URLs. ## Supported hosts -Currently this supports Github, Bitbucket and Gitlab. Pull requests for +Currently this supports GitHub, Bitbucket and GitLab. Pull requests for additional hosts welcome. diff --git a/node_modules/hosted-git-info/git-host-info.js b/node_modules/hosted-git-info/git-host-info.js index da3348fa7b817..360d7b096be61 100644 --- a/node_modules/hosted-git-info/git-host-info.js +++ b/node_modules/hosted-git-info/git-host-info.js @@ -1,79 +1,154 @@ 'use strict' +const maybeJoin = (...args) => args.every(arg => arg) ? args.join('') : '' +const maybeEncode = (arg) => arg ? encodeURIComponent(arg) : '' -var gitHosts = module.exports = { - github: { - // First two are insecure and generally shouldn't be used any more, but - // they are still supported. - 'protocols': [ 'git', 'http', 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'github.com', - 'treepath': 'tree', - 'filetemplate': 'https://{auth@}raw.githubusercontent.com/{user}/{project}/{committish}/{path}', - 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'gittemplate': 'git://{auth@}{domain}/{user}/{project}.git{#committish}', - 'tarballtemplate': 'https://codeload.{domain}/{user}/{project}/tar.gz/{committish}' - }, - bitbucket: { - 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'bitbucket.org', - 'treepath': 'src', - 'tarballtemplate': 'https://{domain}/{user}/{project}/get/{committish}.tar.gz' - }, - gitlab: { - 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'gitlab.com', - 'treepath': 'tree', - 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{projectPath}.git{#committish}', - 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}', - 'pathmatch': /^\/([^/]+)\/((?!.*(\/-\/|\/repository(\/[^/]+)?\/archive\.tar\.gz)).*?)(?:\.git|\/)?$/ - }, - gist: { - 'protocols': [ 'git', 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'gist.github.com', - 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]{7,})(?:[.]git)?$/, - 'filetemplate': 'https://gist.githubusercontent.com/{user}/{project}/raw{/committish}/{path}', - 'bugstemplate': 'https://{domain}/{project}', - 'gittemplate': 'git://{domain}/{project}.git{#committish}', - 'sshtemplate': 'git@{domain}:/{project}.git{#committish}', - 'sshurltemplate': 'git+ssh://git@{domain}/{project}.git{#committish}', - 'browsetemplate': 'https://{domain}/{project}{/committish}', - 'browsefiletemplate': 'https://{domain}/{project}{/committish}{#path}', - 'docstemplate': 'https://{domain}/{project}{/committish}', - 'httpstemplate': 'git+https://{domain}/{project}.git{#committish}', - 'shortcuttemplate': '{type}:{project}{#committish}', - 'pathtemplate': '{project}{#committish}', - 'tarballtemplate': 'https://codeload.github.com/gist/{project}/tar.gz/{committish}', - 'hashformat': function (fragment) { - return 'file-' + formatHashFragment(fragment) +const defaults = { + sshtemplate: ({ domain, user, project, committish }) => `git@${domain}:${user}/${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, user, project, committish }) => `git+ssh://git@${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, user, project, committish, treepath }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, user, project, committish, treepath, path, fragment, hashformat }) => `https://${domain}/${user}/${project}/${treepath}/${maybeEncode(committish || 'master')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`, + docstemplate: ({ domain, user, project, treepath, committish }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}#readme`, + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ domain, user, project, committish, path }) => `https://${domain}/${user}/${project}/raw/${maybeEncode(committish) || 'master'}/${path}`, + shortcuttemplate: ({ type, user, project, committish }) => `${type}:${user}/${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ user, project, committish }) => `${user}/${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, user, project }) => `https://${domain}/${user}/${project}/issues`, + hashformat: formatHashFragment +} + +const gitHosts = {} +gitHosts.github = Object.assign({}, defaults, { + // First two are insecure and generally shouldn't be used any more, but + // they are still supported. + protocols: ['git:', 'http:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'github.com', + treepath: 'tree', + filetemplate: ({ auth, user, project, committish, path }) => `https://${maybeJoin(auth, '@')}raw.githubusercontent.com/${user}/${project}/${maybeEncode(committish) || 'master'}/${path}`, + gittemplate: ({ auth, domain, user, project, committish }) => `git://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://codeload.${domain}/${user}/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, type, committish] = url.pathname.split('/', 5) + if (type && type !== 'tree') { + return + } + + if (!type) { + committish = url.hash.slice(1) + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return } + + return { user, project, committish } } -} +}) -var gitHostDefaults = { - 'sshtemplate': 'git@{domain}:{user}/{project}.git{#committish}', - 'sshurltemplate': 'git+ssh://git@{domain}/{user}/{project}.git{#committish}', - 'browsetemplate': 'https://{domain}/{user}/{project}{/tree/committish}', - 'browsefiletemplate': 'https://{domain}/{user}/{project}/{treepath}/{committish}/{path}{#fragment}', - 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#readme', - 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{project}.git{#committish}', - 'filetemplate': 'https://{domain}/{user}/{project}/raw/{committish}/{path}', - 'shortcuttemplate': '{type}:{user}/{project}{#committish}', - 'pathtemplate': '{user}/{project}{#committish}', - 'pathmatch': /^[/]([^/]+)[/]([^/]+?)(?:[.]git|[/])?$/, - 'hashformat': formatHashFragment -} +gitHosts.bitbucket = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'bitbucket.org', + treepath: 'src', + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/get/${maybeEncode(committish) || 'master'}.tar.gz`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (['get'].includes(aux)) { + return + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gitlab = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gitlab.com', + treepath: 'tree', + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/repository/archive.tar.gz?ref=${maybeEncode(committish) || 'master'}`, + extract: (url) => { + const path = url.pathname.slice(1) + if (path.includes('/-/')) { + return + } + + const segments = path.split('/') + let project = segments.pop() + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + const user = segments.join('/') + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gist = Object.assign({}, defaults, { + protocols: ['git:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gist.github.com', + sshtemplate: ({ domain, project, committish }) => `git@${domain}:${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, project, committish }) => `git+ssh://git@${domain}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, project, committish, path, hashformat }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`, + docstemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + httpstemplate: ({ domain, project, committish }) => `git+https://${domain}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ user, project, committish, path }) => `https://gist.githubusercontent.com/${user}/${project}/raw${maybeJoin('/', maybeEncode(committish))}/${path}`, + shortcuttemplate: ({ type, project, committish }) => `${type}:${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ project, committish }) => `${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, project }) => `https://${domain}/${project}`, + gittemplate: ({ domain, project, committish }) => `git://${domain}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ project, committish }) => `https://codeload.github.com/gist/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (aux === 'raw') { + return + } + + if (!project) { + if (!user) { + return + } + + project = user + user = null + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } -Object.keys(gitHosts).forEach(function (name) { - Object.keys(gitHostDefaults).forEach(function (key) { - if (gitHosts[name][key]) return - gitHosts[name][key] = gitHostDefaults[key] - }) - gitHosts[name].protocols_re = RegExp('^(' + - gitHosts[name].protocols.map(function (protocol) { - return protocol.replace(/([\\+*{}()[\]$^|])/g, '\\$1') - }).join('|') + '):$') + return { user, project, committish: url.hash.slice(1) } + }, + hashformat: function (fragment) { + return fragment && 'file-' + formatHashFragment(fragment) + } }) +const names = Object.keys(gitHosts) +gitHosts.byShortcut = {} +gitHosts.byDomain = {} +for (const name of names) { + gitHosts.byShortcut[`${name}:`] = name + gitHosts.byDomain[gitHosts[name].domain] = name +} + function formatHashFragment (fragment) { return fragment.toLowerCase().replace(/^\W+|\/|\W+$/g, '').replace(/\W+/g, '-') } + +module.exports = gitHosts diff --git a/node_modules/hosted-git-info/git-host.js b/node_modules/hosted-git-info/git-host.js index f9b1ec7456320..8a975e92e58bb 100644 --- a/node_modules/hosted-git-info/git-host.js +++ b/node_modules/hosted-git-info/git-host.js @@ -1,156 +1,110 @@ 'use strict' -var gitHosts = require('./git-host-info.js') -/* eslint-disable node/no-deprecated-api */ - -// copy-pasta util._extend from node's source, to avoid pulling -// the whole util module into peoples' webpack bundles. -/* istanbul ignore next */ -var extend = Object.assign || function _extend (target, source) { - // Don't do anything if source isn't an object - if (source === null || typeof source !== 'object') return target - - const keys = Object.keys(source) - let i = keys.length - while (i--) { - target[keys[i]] = source[keys[i]] - } - return target -} +const gitHosts = require('./git-host-info.js') + +class GitHost { + constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) { + Object.assign(this, gitHosts[type]) + this.type = type + this.user = user + this.auth = auth + this.project = project + this.committish = committish + this.default = defaultRepresentation + this.opts = opts + } -module.exports = GitHost -function GitHost (type, user, auth, project, committish, defaultRepresentation, opts) { - var gitHostInfo = this - gitHostInfo.type = type - Object.keys(gitHosts[type]).forEach(function (key) { - gitHostInfo[key] = gitHosts[type][key] - }) - gitHostInfo.user = user - gitHostInfo.auth = auth - gitHostInfo.project = project - gitHostInfo.committish = committish - gitHostInfo.default = defaultRepresentation - gitHostInfo.opts = opts || {} -} + hash () { + return this.committish ? `#${this.committish}` : '' + } -GitHost.prototype.hash = function () { - return this.committish ? '#' + this.committish : '' -} + ssh (opts) { + return this._fill(this.sshtemplate, opts) + } + + _fill (template, opts) { + if (typeof template === 'function') { + const options = { ...this, ...this.opts, ...opts } + + // the path should always be set so we don't end up with 'undefined' in urls + if (!options.path) { + options.path = '' + } + + // template functions will insert the leading slash themselves + if (options.path.startsWith('/')) { + options.path = options.path.slice(1) + } -GitHost.prototype._fill = function (template, opts) { - if (!template) return - var vars = extend({}, opts) - vars.path = vars.path ? vars.path.replace(/^[/]+/g, '') : '' - opts = extend(extend({}, this.opts), opts) - var self = this - Object.keys(this).forEach(function (key) { - if (self[key] != null && vars[key] == null) vars[key] = self[key] - }) - var rawAuth = vars.auth - var rawcommittish = vars.committish - var rawFragment = vars.fragment - var rawPath = vars.path - var rawProject = vars.project - Object.keys(vars).forEach(function (key) { - var value = vars[key] - if ((key === 'path' || key === 'project') && typeof value === 'string') { - vars[key] = value.split('/').map(function (pathComponent) { - return encodeURIComponent(pathComponent) - }).join('/') - } else if (key !== 'domain') { - vars[key] = encodeURIComponent(value) + if (options.noCommittish) { + options.committish = null + } + + const result = template(options) + return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result } - }) - vars['auth@'] = rawAuth ? rawAuth + '@' : '' - vars['#fragment'] = rawFragment ? '#' + this.hashformat(rawFragment) : '' - vars.fragment = vars.fragment ? vars.fragment : '' - vars['#path'] = rawPath ? '#' + this.hashformat(rawPath) : '' - vars['/path'] = vars.path ? '/' + vars.path : '' - vars.projectPath = rawProject.split('/').map(encodeURIComponent).join('/') - if (opts.noCommittish) { - vars['#committish'] = '' - vars['/tree/committish'] = '' - vars['/committish'] = '' - vars.committish = '' - } else { - vars['#committish'] = rawcommittish ? '#' + rawcommittish : '' - vars['/tree/committish'] = vars.committish - ? '/' + vars.treepath + '/' + vars.committish - : '' - vars['/committish'] = vars.committish ? '/' + vars.committish : '' - vars.committish = vars.committish || 'master' - } - var res = template - Object.keys(vars).forEach(function (key) { - res = res.replace(new RegExp('[{]' + key + '[}]', 'g'), vars[key]) - }) - if (opts.noGitPlus) { - return res.replace(/^git[+]/, '') - } else { - return res + + return null } -} -GitHost.prototype.ssh = function (opts) { - return this._fill(this.sshtemplate, opts) -} + sshurl (opts) { + return this._fill(this.sshurltemplate, opts) + } -GitHost.prototype.sshurl = function (opts) { - return this._fill(this.sshurltemplate, opts) -} + browse (path, fragment, opts) { + // not a string, treat path as opts + if (typeof path !== 'string') { + return this._fill(this.browsetemplate, path) + } -GitHost.prototype.browse = function (P, F, opts) { - if (typeof P === 'string') { - if (typeof F !== 'string') { - opts = F - F = null + if (typeof fragment !== 'string') { + opts = fragment + fragment = null } - return this._fill(this.browsefiletemplate, extend({ - fragment: F, - path: P - }, opts)) - } else { - return this._fill(this.browsetemplate, P) + return this._fill(this.browsefiletemplate, { ...opts, fragment, path }) } -} -GitHost.prototype.docs = function (opts) { - return this._fill(this.docstemplate, opts) -} + docs (opts) { + return this._fill(this.docstemplate, opts) + } -GitHost.prototype.bugs = function (opts) { - return this._fill(this.bugstemplate, opts) -} + bugs (opts) { + return this._fill(this.bugstemplate, opts) + } -GitHost.prototype.https = function (opts) { - return this._fill(this.httpstemplate, opts) -} + https (opts) { + return this._fill(this.httpstemplate, opts) + } -GitHost.prototype.git = function (opts) { - return this._fill(this.gittemplate, opts) -} + git (opts) { + return this._fill(this.gittemplate, opts) + } -GitHost.prototype.shortcut = function (opts) { - return this._fill(this.shortcuttemplate, opts) -} + shortcut (opts) { + return this._fill(this.shortcuttemplate, opts) + } -GitHost.prototype.path = function (opts) { - return this._fill(this.pathtemplate, opts) -} + path (opts) { + return this._fill(this.pathtemplate, opts) + } -GitHost.prototype.tarball = function (opts_) { - var opts = extend({}, opts_, { noCommittish: false }) - return this._fill(this.tarballtemplate, opts) -} + tarball (opts) { + return this._fill(this.tarballtemplate, { ...opts, noCommittish: false }) + } -GitHost.prototype.file = function (P, opts) { - return this._fill(this.filetemplate, extend({ path: P }, opts)) -} + file (path, opts) { + return this._fill(this.filetemplate, { ...opts, path }) + } -GitHost.prototype.getDefaultRepresentation = function () { - return this.default -} + getDefaultRepresentation () { + return this.default + } + + toString (opts) { + if (this.default && typeof this[this.default] === 'function') { + return this[this.default](opts) + } -GitHost.prototype.toString = function (opts) { - if (this.default && typeof this[this.default] === 'function') return this[this.default](opts) - return this.sshurl(opts) + return this.sshurl(opts) + } } +module.exports = GitHost diff --git a/node_modules/hosted-git-info/index.js b/node_modules/hosted-git-info/index.js index 8b3eaba3da7fb..f35c570c46b59 100644 --- a/node_modules/hosted-git-info/index.js +++ b/node_modules/hosted-git-info/index.js @@ -1,11 +1,11 @@ 'use strict' -var url = require('url') -var gitHosts = require('./git-host-info.js') -var GitHost = module.exports = require('./git-host.js') -var LRU = require('lru-cache') -var cache = new LRU({max: 1000}) +const url = require('url') +const gitHosts = require('./git-host-info.js') +const GitHost = module.exports = require('./git-host.js') +const LRU = require('lru-cache') +const cache = new LRU({ max: 1000 }) -var protocolToRepresentationMap = { +const protocolToRepresentationMap = { 'git+ssh:': 'sshurl', 'git+https:': 'https', 'ssh:': 'sshurl', @@ -16,7 +16,7 @@ function protocolToRepresentation (protocol) { return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) } -var authProtocols = { +const authProtocols = { 'git:': true, 'https:': true, 'git+https:': true, @@ -24,9 +24,14 @@ var authProtocols = { 'git+http:': true } +const knownProtocols = Object.keys(gitHosts.byShortcut).concat(['http:', 'https:', 'git:', 'git+ssh:', 'git+https:', 'ssh:']) + module.exports.fromUrl = function (giturl, opts) { - if (typeof giturl !== 'string') return - var key = giturl + JSON.stringify(opts || {}) + if (typeof giturl !== 'string') { + return + } + + const key = giturl + JSON.stringify(opts || {}) if (!cache.has(key)) { cache.set(key, fromUrl(giturl, opts)) @@ -36,111 +41,197 @@ module.exports.fromUrl = function (giturl, opts) { } function fromUrl (giturl, opts) { - if (giturl == null || giturl === '') return - var url = fixupUnqualifiedGist( - isGitHubShorthand(giturl) ? 'github:' + giturl : giturl - ) - var parsed = parseGitUrl(url) - var shortcutMatch = url.match(/^([^:]+):(?:[^@]+@)?(?:([^/]*)\/)?([^#]+)/) - var matches = Object.keys(gitHosts).map(function (gitHostName) { - try { - var gitHostInfo = gitHosts[gitHostName] - var auth = null - if (parsed.auth && authProtocols[parsed.protocol]) { - auth = parsed.auth + if (!giturl) { + return + } + + const url = isGitHubShorthand(giturl) ? 'github:' + giturl : correctProtocol(giturl) + const parsed = parseGitUrl(url) + if (!parsed) { + return parsed + } + + const gitHostShortcut = gitHosts.byShortcut[parsed.protocol] + const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.') ? parsed.hostname.slice(4) : parsed.hostname] + const gitHostName = gitHostShortcut || gitHostDomain + if (!gitHostName) { + return + } + + const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain] + let auth = null + if (authProtocols[parsed.protocol] && (parsed.username || parsed.password)) { + auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}` + } + + let committish = null + let user = null + let project = null + let defaultRepresentation = null + + try { + if (gitHostShortcut) { + let pathname = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname + const firstAt = pathname.indexOf('@') + // we ignore auth for shortcuts, so just trim it out + if (firstAt > -1) { + pathname = pathname.slice(firstAt + 1) } - var committish = parsed.hash ? decodeURIComponent(parsed.hash.substr(1)) : null - var user = null - var project = null - var defaultRepresentation = null - if (shortcutMatch && shortcutMatch[1] === gitHostName) { - user = shortcutMatch[2] && decodeURIComponent(shortcutMatch[2]) - project = decodeURIComponent(shortcutMatch[3].replace(/\.git$/, '')) - defaultRepresentation = 'shortcut' - } else { - if (parsed.host && parsed.host !== gitHostInfo.domain && parsed.host.replace(/^www[.]/, '') !== gitHostInfo.domain) return - if (!gitHostInfo.protocols_re.test(parsed.protocol)) return - if (!parsed.path) return - var pathmatch = gitHostInfo.pathmatch - var matched = parsed.path.match(pathmatch) - if (!matched) return - /* istanbul ignore else */ - if (matched[1] !== null && matched[1] !== undefined) { - user = decodeURIComponent(matched[1].replace(/^:/, '')) + + const lastSlash = pathname.lastIndexOf('/') + if (lastSlash > -1) { + user = decodeURIComponent(pathname.slice(0, lastSlash)) + // we want nulls only, never empty strings + if (!user) { + user = null } - project = decodeURIComponent(matched[2]) - defaultRepresentation = protocolToRepresentation(parsed.protocol) + project = decodeURIComponent(pathname.slice(lastSlash + 1)) + } else { + project = decodeURIComponent(pathname) + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (parsed.hash) { + committish = decodeURIComponent(parsed.hash.slice(1)) + } + + defaultRepresentation = 'shortcut' + } else { + if (!gitHostInfo.protocols.includes(parsed.protocol)) { + return } - return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) - } catch (ex) { - /* istanbul ignore else */ - if (ex instanceof URIError) { - } else throw ex + + const segments = gitHostInfo.extract(parsed) + if (!segments) { + return + } + + user = segments.user && decodeURIComponent(segments.user) + project = decodeURIComponent(segments.project) + committish = decodeURIComponent(segments.committish) + defaultRepresentation = protocolToRepresentation(parsed.protocol) } - }).filter(function (gitHostInfo) { return gitHostInfo }) - if (matches.length !== 1) return - return matches[0] -} + } catch (err) { + /* istanbul ignore else */ + if (err instanceof URIError) { + return + } else { + throw err + } + } -function isGitHubShorthand (arg) { - // Note: This does not fully test the git ref format. - // See https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html - // - // The only way to do this properly would be to shell out to - // git-check-ref-format, and as this is a fast sync function, - // we don't want to do that. Just let git fail if it turns - // out that the commit-ish is invalid. - // GH usernames cannot start with . or - - return /^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(arg) + return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) } -function fixupUnqualifiedGist (giturl) { - // necessary for round-tripping gists - var parsed = url.parse(giturl) - if (parsed.protocol === 'gist:' && parsed.host && !parsed.path) { - return parsed.protocol + '/' + parsed.host - } else { - return giturl +// accepts input like git:github.com:user/repo and inserts the // after the first : +const correctProtocol = (arg) => { + const firstColon = arg.indexOf(':') + const proto = arg.slice(0, firstColon + 1) + if (knownProtocols.includes(proto)) { + return arg } -} -function parseGitUrl (giturl) { - var matched = giturl.match(/^([^@]+)@([^:/]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/) - if (!matched) { - var legacy = url.parse(giturl) - if (legacy.auth) { - // git urls can be in the form of scp-style/ssh-connect strings, like - // git+ssh://user@host.com:some/path, which the legacy url parser - // supports, but WhatWG url.URL class does not. However, the legacy - // parser de-urlencodes the username and password, so something like - // https://user%3An%40me:p%40ss%3Aword@x.com/ becomes - // https://user:n@me:p@ss:word@x.com/ which is all kinds of wrong. - // Pull off just the auth and host, so we dont' get the confusing - // scp-style URL, then pass that to the WhatWG parser to get the - // auth properly escaped. - const authmatch = giturl.match(/[^@]+@[^:/]+/) - /* istanbul ignore else - this should be impossible */ - if (authmatch) { - var whatwg = new url.URL(authmatch[0]) - legacy.auth = whatwg.username || '' - if (whatwg.password) legacy.auth += ':' + whatwg.password - } + const firstAt = arg.indexOf('@') + if (firstAt > -1) { + if (firstAt > firstColon) { + return `git+ssh://${arg}` + } else { + return arg } - return legacy } - return { - protocol: 'git+ssh:', - slashes: true, - auth: matched[1], - host: matched[2], - port: null, - hostname: matched[2], - hash: matched[4], - search: null, - query: null, - pathname: '/' + matched[3], - path: '/' + matched[3], - href: 'git+ssh://' + matched[1] + '@' + matched[2] + - '/' + matched[3] + (matched[4] || '') + + const doubleSlash = arg.indexOf('//') + if (doubleSlash === firstColon + 1) { + return arg + } + + return arg.slice(0, firstColon + 1) + '//' + arg.slice(firstColon + 1) +} + +// look for github shorthand inputs, such as npm/cli +const isGitHubShorthand = (arg) => { + // it cannot contain whitespace before the first # + // it cannot start with a / because that's probably an absolute file path + // but it must include a slash since repos are username/repository + // it cannot start with a . because that's probably a relative file path + // it cannot start with an @ because that's a scoped package if it passes the other tests + // it cannot contain a : before a # because that tells us that there's a protocol + // a second / may not exist before a # + const firstHash = arg.indexOf('#') + const firstSlash = arg.indexOf('/') + const secondSlash = arg.indexOf('/', firstSlash + 1) + const firstColon = arg.indexOf(':') + const firstSpace = /\s/.exec(arg) + const firstAt = arg.indexOf('@') + + const spaceOnlyAfterHash = !firstSpace || (firstHash > -1 && firstSpace.index > firstHash) + const atOnlyAfterHash = firstAt === -1 || (firstHash > -1 && firstAt > firstHash) + const colonOnlyAfterHash = firstColon === -1 || (firstHash > -1 && firstColon > firstHash) + const secondSlashOnlyAfterHash = secondSlash === -1 || (firstHash > -1 && secondSlash > firstHash) + const hasSlash = firstSlash > 0 + // if a # is found, what we really want to know is that the character immediately before # is not a / + const doesNotEndWithSlash = firstHash > -1 ? arg[firstHash - 1] !== '/' : !arg.endsWith('/') + const doesNotStartWithDot = !arg.startsWith('.') + + return spaceOnlyAfterHash && hasSlash && doesNotEndWithSlash && doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash && secondSlashOnlyAfterHash +} + +// attempt to correct an scp style url so that it will parse with `new URL()` +const correctUrl = (giturl) => { + const firstAt = giturl.indexOf('@') + const lastHash = giturl.lastIndexOf('#') + let firstColon = giturl.indexOf(':') + let lastColon = giturl.lastIndexOf(':', lastHash > -1 ? lastHash : Infinity) + + let corrected + if (lastColon > firstAt) { + // the last : comes after the first @ (or there is no @) + // like it would in: + // proto://hostname.com:user/repo + // username@hostname.com:user/repo + // :password@hostname.com:user/repo + // username:password@hostname.com:user/repo + // proto://username@hostname.com:user/repo + // proto://:password@hostname.com:user/repo + // proto://username:password@hostname.com:user/repo + // then we replace the last : with a / to create a valid path + corrected = giturl.slice(0, lastColon) + '/' + giturl.slice(lastColon + 1) + // // and we find our new : positions + firstColon = corrected.indexOf(':') + lastColon = corrected.lastIndexOf(':') + } + + if (firstColon === -1 && giturl.indexOf('//') === -1) { + // we have no : at all + // as it would be in: + // username@hostname.com/user/repo + // then we prepend a protocol + corrected = `git+ssh://${corrected}` + } + + return corrected +} + +// try to parse the url as its given to us, if that throws +// then we try to clean the url and parse that result instead +// THIS FUNCTION SHOULD NEVER THROW +const parseGitUrl = (giturl) => { + let result + try { + result = new url.URL(giturl) + } catch (err) {} + + if (result) { + return result } + + const correctedUrl = correctUrl(giturl) + try { + result = new url.URL(correctedUrl) + } catch (err) {} + + return result } diff --git a/node_modules/hosted-git-info/package.json b/node_modules/hosted-git-info/package.json index 32712269f0427..930e3b693b980 100644 --- a/node_modules/hosted-git-info/package.json +++ b/node_modules/hosted-git-info/package.json @@ -1,7 +1,7 @@ { "name": "hosted-git-info", - "version": "3.0.8", - "description": "Provides metadata and conversions from repository urls for Github, Bitbucket and Gitlab", + "version": "4.0.1", + "description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab", "main": "index.js", "repository": { "type": "git", @@ -20,20 +20,21 @@ }, "homepage": "https://github.com/npm/hosted-git-info", "scripts": { - "prerelease": "npm t", - "postrelease": "npm publish && git push --follow-tags", "posttest": "standard", - "release": "standard-version -s", - "test:coverage": "tap --coverage-report=html -J --100 --no-esm test/*.js", - "test": "tap -J --100 --no-esm test/*.js" + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "preversion": "npm test", + "snap": "tap", + "test": "tap", + "test:coverage": "tap --coverage-report=html" }, "dependencies": { "lru-cache": "^6.0.0" }, "devDependencies": { - "standard": "^11.0.1", - "standard-version": "^4.4.0", - "tap": "^12.7.0" + "standard": "^16.0.3", + "standard-version": "^9.1.0", + "tap": "^14.11.0" }, "files": [ "index.js", @@ -42,5 +43,10 @@ ], "engines": { "node": ">=10" + }, + "tap": { + "color": 1, + "coverage": true, + "esm": false } } diff --git a/package-lock.json b/package-lock.json index 617bf031ba9ec..0ba6c23bb5867 100644 --- a/package-lock.json +++ b/package-lock.json @@ -269,7 +269,7 @@ "columnify": "~1.5.4", "glob": "^7.1.4", "graceful-fs": "^4.2.6", - "hosted-git-info": "^3.0.8", + "hosted-git-info": "^4.0.1", "ini": "^2.0.0", "init-package-json": "^2.0.2", "is-cidr": "^4.0.2", @@ -3763,9 +3763,9 @@ } }, "node_modules/hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", "inBundle": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13222,9 +13222,9 @@ } }, "hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", "requires": { "lru-cache": "^6.0.0" } diff --git a/package.json b/package.json index 26a502586caaa..874a48fcd988b 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "columnify": "~1.5.4", "glob": "^7.1.4", "graceful-fs": "^4.2.6", - "hosted-git-info": "^3.0.8", + "hosted-git-info": "^4.0.1", "ini": "^2.0.0", "init-package-json": "^2.0.2", "is-cidr": "^4.0.2",