hosted-git-info
Advanced tools
Comparing version 3.0.8 to 4.0.0
'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 | ||
} | ||
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('|') + '):$') | ||
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) | ||
} | ||
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 |
216
git-host.js
'use strict' | ||
var gitHosts = require('./git-host-info.js') | ||
/* eslint-disable node/no-deprecated-api */ | ||
const gitHosts = require('./git-host-info.js') | ||
// 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 | ||
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 | ||
} | ||
const keys = Object.keys(source) | ||
let i = keys.length | ||
while (i--) { | ||
target[keys[i]] = source[keys[i]] | ||
hash () { | ||
return this.committish ? `#${this.committish}` : '' | ||
} | ||
return target | ||
} | ||
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 || {} | ||
} | ||
ssh (opts) { | ||
return this._fill(this.sshtemplate, opts) | ||
} | ||
GitHost.prototype.hash = function () { | ||
return this.committish ? '#' + this.committish : '' | ||
} | ||
_fill (template, opts) { | ||
if (typeof template === 'function') { | ||
const options = { ...this, ...this.opts, ...opts } | ||
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) | ||
// 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) | ||
} | ||
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' | ||
return null | ||
} | ||
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 | ||
sshurl (opts) { | ||
return this._fill(this.sshurltemplate, opts) | ||
} | ||
} | ||
GitHost.prototype.ssh = function (opts) { | ||
return this._fill(this.sshtemplate, opts) | ||
} | ||
browse (path, fragment, opts) { | ||
// not a string, treat path as opts | ||
if (typeof path !== 'string') { | ||
return this._fill(this.browsetemplate, path) | ||
} | ||
GitHost.prototype.sshurl = function (opts) { | ||
return this._fill(this.sshurltemplate, opts) | ||
} | ||
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 | ||
} | ||
GitHost.prototype.toString = function (opts) { | ||
if (this.default && typeof this[this.default] === 'function') return this[this.default](opts) | ||
return this.sshurl(opts) | ||
toString (opts) { | ||
if (this.default && typeof this[this.default] === 'function') { | ||
return this[this.default](opts) | ||
} | ||
return this.sshurl(opts) | ||
} | ||
} | ||
module.exports = GitHost |
298
index.js
'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', | ||
@@ -19,3 +19,3 @@ 'git+https:': 'https', | ||
var authProtocols = { | ||
const authProtocols = { | ||
'git:': true, | ||
@@ -28,6 +28,11 @@ 'https:': 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)) { | ||
@@ -41,109 +46,194 @@ cache.set(key, fromUrl(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' | ||
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(pathname.slice(lastSlash + 1)) | ||
} 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(/^:/, '')) | ||
} | ||
project = decodeURIComponent(matched[2]) | ||
defaultRepresentation = protocolToRepresentation(parsed.protocol) | ||
project = decodeURIComponent(pathname) | ||
} | ||
return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) | ||
} catch (ex) { | ||
/* istanbul ignore else */ | ||
if (ex instanceof URIError) { | ||
} else throw ex | ||
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 | ||
} | ||
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 | ||
const doesNotEndWithSlash = !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 | ||
} |
{ | ||
"name": "hosted-git-info", | ||
"version": "3.0.8", | ||
"description": "Provides metadata and conversions from repository urls for Github, Bitbucket and Gitlab", | ||
"version": "4.0.0", | ||
"description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab", | ||
"main": "index.js", | ||
@@ -23,8 +23,9 @@ "repository": { | ||
"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" | ||
}, | ||
@@ -35,5 +36,5 @@ "dependencies": { | ||
"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" | ||
}, | ||
@@ -47,3 +48,8 @@ "files": [ | ||
"node": ">=10" | ||
}, | ||
"tap": { | ||
"color": 1, | ||
"coverage": true, | ||
"esm": false | ||
} | ||
} |
@@ -25,3 +25,3 @@ # hosted-git-info | ||
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`. | ||
@@ -133,3 +133,3 @@ | ||
Currently this supports Github, Bitbucket and Gitlab. Pull requests for | ||
Currently this supports GitHub, Bitbucket and GitLab. Pull requests for | ||
additional hosts welcome. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
29624
419
2