mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
4.8 KiB
199 lines
4.8 KiB
var url = require('url')
|
|
var assert = require('assert')
|
|
var util = require('util')
|
|
var semver = require('semver')
|
|
var HostedGit = require('hosted-git-info')
|
|
|
|
module.exports = npa
|
|
|
|
var isWindows = process.platform === 'win32' || global.FAKE_WINDOWS
|
|
var slashRe = isWindows ? /\\|[/]/ : /[/]/
|
|
|
|
var parseName = /^(?:@([^/]+?)[/])?([^/]+?)$/
|
|
var nameAt = /^(@([^/]+?)[/])?([^/]+?)@/
|
|
var debug = util.debuglog
|
|
? util.debuglog('npa')
|
|
: /\bnpa\b/i.test(process.env.NODE_DEBUG || '')
|
|
? function () {
|
|
console.error('NPA: ' + util.format.apply(util, arguments).split('\n').join('\nNPA: '))
|
|
}
|
|
: function () {}
|
|
|
|
function validName (name) {
|
|
if (!name) {
|
|
debug('not a name %j', name)
|
|
return false
|
|
}
|
|
var n = name.trim()
|
|
if (!n || n.charAt(0) === '.' ||
|
|
!n.match(/^[a-zA-Z0-9]/) ||
|
|
n.match(/[/()&?#|<>@:%\s\\*'"!~`]/) ||
|
|
n.toLowerCase() === 'node_modules' ||
|
|
n !== encodeURIComponent(n) ||
|
|
n.toLowerCase() === 'favicon.ico') {
|
|
debug('not a valid name %j', name)
|
|
return false
|
|
}
|
|
return n
|
|
}
|
|
|
|
function npa (arg) {
|
|
assert.equal(typeof arg, 'string')
|
|
arg = arg.trim()
|
|
|
|
var res = new Result()
|
|
res.raw = arg
|
|
res.scope = null
|
|
res.escapedName = null
|
|
|
|
// See if it's something like foo@...
|
|
var nameparse = arg.match(nameAt)
|
|
debug('nameparse', nameparse)
|
|
if (nameparse && validName(nameparse[3]) &&
|
|
(!nameparse[2] || validName(nameparse[2]))) {
|
|
res.name = (nameparse[1] || '') + nameparse[3]
|
|
res.escapedName = escapeName(res.name)
|
|
if (nameparse[2]) {
|
|
res.scope = '@' + nameparse[2]
|
|
}
|
|
arg = arg.substr(nameparse[0].length)
|
|
} else {
|
|
res.name = null
|
|
}
|
|
|
|
res.rawSpec = arg
|
|
res.spec = arg
|
|
|
|
var urlparse = url.parse(arg)
|
|
debug('urlparse', urlparse)
|
|
|
|
// windows paths look like urls
|
|
// don't be fooled!
|
|
if (isWindows && urlparse && urlparse.protocol &&
|
|
urlparse.protocol.match(/^[a-zA-Z]:$/)) {
|
|
debug('windows url-ish local path', urlparse)
|
|
urlparse = {}
|
|
}
|
|
|
|
if (urlparse.protocol || HostedGit.fromUrl(arg)) {
|
|
return parseUrl(res, arg, urlparse)
|
|
}
|
|
|
|
// at this point, it's not a url, and not hosted
|
|
// If it's a valid name, and doesn't already have a name, then assume
|
|
// $name@"" range
|
|
//
|
|
// if it's got / chars in it, then assume that it's local.
|
|
|
|
if (res.name) {
|
|
if (arg === '') arg = 'latest'
|
|
var version = semver.valid(arg, true)
|
|
var range = semver.validRange(arg, true)
|
|
// foo@...
|
|
if (version) {
|
|
res.spec = version
|
|
res.type = 'version'
|
|
} else if (range) {
|
|
res.spec = range
|
|
res.type = 'range'
|
|
} else if (slashRe.test(arg)) {
|
|
parseLocal(res, arg)
|
|
} else {
|
|
res.type = 'tag'
|
|
res.spec = arg
|
|
}
|
|
} else {
|
|
var p = arg.match(parseName)
|
|
if (p && validName(p[2]) &&
|
|
(!p[1] || validName(p[1]))) {
|
|
res.type = 'tag'
|
|
res.spec = 'latest'
|
|
res.rawSpec = ''
|
|
res.name = arg
|
|
res.escapedName = escapeName(res.name)
|
|
if (p[1]) {
|
|
res.scope = '@' + p[1]
|
|
}
|
|
} else {
|
|
parseLocal(res, arg)
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
function escapeName (name) {
|
|
// scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
|
|
return name && name.replace('/', '%2f')
|
|
}
|
|
|
|
function parseLocal (res, arg) {
|
|
// turns out nearly every character is allowed in fs paths
|
|
if (/\0/.test(arg)) {
|
|
throw new Error('Invalid Path: ' + JSON.stringify(arg))
|
|
}
|
|
res.type = 'local'
|
|
res.spec = arg
|
|
}
|
|
|
|
function parseUrl (res, arg, urlparse) {
|
|
var gitHost = HostedGit.fromUrl(arg)
|
|
if (gitHost) {
|
|
res.type = 'hosted'
|
|
res.spec = gitHost.toString()
|
|
res.hosted = {
|
|
type: gitHost.type,
|
|
ssh: gitHost.ssh(),
|
|
sshUrl: gitHost.sshurl(),
|
|
httpsUrl: gitHost.https(),
|
|
gitUrl: gitHost.git(),
|
|
shortcut: gitHost.shortcut(),
|
|
directUrl: gitHost.file('package.json')
|
|
}
|
|
return res
|
|
}
|
|
// check the protocol, and then see if it's git or not
|
|
switch (urlparse.protocol) {
|
|
case 'git:':
|
|
case 'git+http:':
|
|
case 'git+https:':
|
|
case 'git+rsync:':
|
|
case 'git+ftp:':
|
|
case 'git+ssh:':
|
|
case 'git+file:':
|
|
res.type = 'git'
|
|
res.spec = arg.replace(/^git[+]/, '')
|
|
break
|
|
|
|
case 'http:':
|
|
case 'https:':
|
|
res.type = 'remote'
|
|
res.spec = arg
|
|
break
|
|
|
|
case 'file:':
|
|
res.type = 'local'
|
|
if (isWindows && arg.match(/^file:\/\/\/?[a-z]:/i)) {
|
|
// Windows URIs usually parse all wrong, so we just take matters
|
|
// into our own hands, in this case.
|
|
res.spec = arg.replace(/^file:\/\/\/?/i, '')
|
|
} else {
|
|
res.spec = urlparse.pathname
|
|
}
|
|
break
|
|
|
|
default:
|
|
throw new Error('Unsupported URL Type: ' + arg)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
function Result () {
|
|
if (!(this instanceof Result)) return new Result()
|
|
}
|
|
Result.prototype.name = null
|
|
Result.prototype.type = null
|
|
Result.prototype.spec = null
|
|
Result.prototype.raw = null
|
|
Result.prototype.hosted = null
|
|
|