Socket
Socket
Sign inDemoInstall

npm-package-arg

Package Overview
Dependencies
Maintainers
4
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

npm-package-arg - npm Package Compare versions

Comparing version 4.2.1 to 5.0.0

CHANGELOG.md

332

npa.js

@@ -1,155 +0,191 @@

var url = require('url')
var assert = require('assert')
var util = require('util')
var semver = require('semver')
var HostedGit = require('hosted-git-info')
'use strict'
module.exports = npa
module.exports.resolve = resolve
module.exports.Result = Result
var isWindows = process.platform === 'win32' || global.FAKE_WINDOWS
var slashRe = isWindows ? /\\|[/]/ : /[/]/
let url
let HostedGit
let semver
let path
let validatePackageName
let osenv
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: '))
const isWindows = process.platform === 'win32' || global.FAKE_WINDOWS
const hasSlashes = isWindows ? /\\|[/]/ : /[/]/
const isURL = /^(?:git[+])?[a-z]+:/i
const isFilename = /[.](?:tgz|tar.gz|tar)$/i
function npa (arg, where) {
let name
let spec
const nameEndsAt = arg[0] === '@' ? arg.slice(1).indexOf('@') + 1 : arg.indexOf('@')
const namePart = nameEndsAt > 0 ? arg.slice(0, nameEndsAt) : arg
if (isURL.test(arg)) {
spec = arg
} else if (namePart[0] !== '@' && (hasSlashes.test(namePart) || isFilename.test(namePart))) {
spec = arg
} else if (nameEndsAt > 0) {
name = namePart
spec = arg.slice(nameEndsAt + 1)
} else {
if (!validatePackageName) validatePackageName = require('validate-npm-package-name')
const valid = validatePackageName(arg)
if (valid.validForOldPackages) {
name = arg
} else {
spec = arg
}
: 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
return resolve(name, spec, where, arg)
}
function npa (arg) {
assert.equal(typeof arg, 'string')
arg = arg.trim()
const isFilespec = isWindows ? /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/ : /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
var res = new Result()
res.raw = arg
res.scope = null
res.escapedName = null
function resolve (name, spec, where, arg) {
const res = new Result({
raw: arg,
name: name,
rawSpec: spec,
fromArgument: arg != 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)
if (name) res.setName(name)
if (spec && (isFilespec.test(spec) || /^file:/i.test(spec))) {
return fromFile(res, where)
}
if (!HostedGit) HostedGit = require('hosted-git-info')
const hosted = HostedGit.fromUrl(spec, {noGitPlus: true, noCommittish: true})
if (hosted) {
return fromHostedGit(res, hosted)
} else if (spec && isURL.test(spec)) {
return fromURL(res)
} else if (spec && (hasSlashes.test(spec) || isFilename.test(spec))) {
return fromFile(res, where)
} else {
res.name = null
return fromRegistry(res)
}
}
res.rawSpec = arg
res.spec = arg
function invalidPackageName (name, valid) {
const err = new Error(`Invalid package name "${name}": ${valid.errors.join('; ')}`)
err.code = 'EINVALIDPACKAGENAME'
return err
}
function invalidTagName (name) {
const err = new Error(`Invalid tag name "${name}": Tags may not have any characters that encodeURIComponent encodes.`)
err.code = 'EINVALIDTAGNAME'
return err
}
var urlparse = url.parse(arg)
debug('urlparse', urlparse)
function Result (opts) {
this.type = opts.type
this.registry = opts.registry
this.where = opts.where
if (opts.raw == null) {
this.raw = opts.name ? opts.name + '@' + opts.rawSpec : opts.rawSpec
} else {
this.raw = opts.raw
}
this.name = undefined
this.escapedName = undefined
this.scope = undefined
this.rawSpec = opts.rawSpec == null ? '' : opts.rawSpec
this.saveSpec = opts.saveSpec
this.fetchSpec = opts.fetchSpec
if (opts.name) this.setName(opts.name)
this.gitRange = opts.gitRange
this.gitCommittish = opts.gitCommittish
this.hosted = opts.hosted
}
Result.prototype = {}
// 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 = {}
Result.prototype.setName = function (name) {
if (!validatePackageName) validatePackageName = require('validate-npm-package-name')
const valid = validatePackageName(name)
if (!valid.validForOldPackages) {
throw invalidPackageName(name, valid)
}
this.name = name
this.scope = name[0] === '@' ? name.slice(0, name.indexOf('/')) : undefined
// scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
this.escapedName = name.replace('/', '%2f')
return this
}
if (urlparse.protocol || HostedGit.fromUrl(arg)) {
return parseUrl(res, arg, urlparse)
Result.prototype.toJSON = function () {
const result = Object.assign({}, this)
delete result.hosted
return result
}
function setGitCommittish (res, committish) {
if (committish != null && committish.length >= 7 && committish.slice(0, 7) === 'semver:') {
res.gitRange = decodeURIComponent(committish.slice(7))
res.gitCommittish = null
} else if (committish == null || committish === '') {
res.gitCommittish = 'master'
} else {
res.gitCommittish = committish
}
return res
}
// 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.
const isAbsolutePath = /^[/]|^[A-Za-z]:/
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
}
function resolvePath (where, spec) {
if (isAbsolutePath.test(spec)) return spec
if (!path) path = require('path')
return path.resolve(where, spec)
}
function isAbsolute (dir) {
if (dir[0] === '/') return true
if (/^[A-Za-z]:/.test(dir)) return true
return false
}
function fromFile (res, where) {
if (!where) where = process.cwd()
res.type = isFilename.test(res.rawSpec) ? 'file' : 'directory'
res.where = where
const spec = res.rawSpec.replace(/\\/g, '/')
.replace(/^file:[/]*([A-Za-z]:)/, '$1') // drive name paths on windows
.replace(/^file:(?:[/]*([~./]))?/, '$1')
if (/^~[/]/.test(spec)) {
// this is needed for windows and for file:~/foo/bar
if (!osenv) osenv = require('osenv')
res.fetchSpec = resolvePath(osenv.home(), spec.slice(2))
res.saveSpec = 'file:' + spec
} 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]
}
res.fetchSpec = resolvePath(where, spec)
if (isAbsolute(spec)) {
res.saveSpec = 'file:' + spec
} else {
parseLocal(res, arg)
if (!path) path = require('path')
res.saveSpec = 'file:' + path.relative(where, res.fetchSpec)
}
}
return res
}
function escapeName (name) {
// scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
return name && name.replace('/', '%2f')
function fromHostedGit (res, hosted) {
res.type = 'git'
res.hosted = hosted
res.saveSpec = hosted.toString({noGitPlus: false, noCommittish: false})
res.fetchSpec = hosted.getDefaultRepresentation() === 'shortcut' ? null : hosted.toString()
return setGitCommittish(res, hosted.committish)
}
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 unsupportedURLType (protocol, spec) {
const err = new Error(`Unsupported URL Type "${protocol}": ${spec}`)
err.code = 'EUNSUPPORTEDPROTOCOL'
return err
}
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
}
function fromURL (res) {
if (!url) url = require('url')
const urlparse = url.parse(res.rawSpec)
res.saveSpec = res.rawSpec
// check the protocol, and then see if it's git or not

@@ -165,3 +201,6 @@ switch (urlparse.protocol) {

res.type = 'git'
res.spec = arg.replace(/^git[+]/, '')
setGitCommittish(res, urlparse.hash != null ? urlparse.hash.slice(1) : '')
urlparse.protocol = urlparse.protocol.replace(/^git[+]/, '')
delete urlparse.hash
res.fetchSpec = url.format(urlparse)
break

@@ -172,18 +211,7 @@

res.type = 'remote'
res.spec = arg
res.fetchSpec = res.saveSpec
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)
throw unsupportedURLType(urlparse.protocol, res.rawSpec)
}

@@ -194,9 +222,23 @@

function Result () {
if (!(this instanceof Result)) return new Result()
function fromRegistry (res) {
res.registry = true
const spec = res.rawSpec === '' ? 'latest' : res.rawSpec
// no save spec for registry components as we save based on the fetched
// version, not on the argument so this can't compute that.
res.saveSpec = null
res.fetchSpec = spec
if (!semver) semver = require('semver')
const version = semver.valid(spec, true)
const range = semver.validRange(spec, true)
if (version) {
res.type = 'version'
} else if (range) {
res.type = 'range'
} else {
if (encodeURIComponent(spec) !== spec) {
throw invalidTagName(spec)
}
res.type = 'tag'
}
return res
}
Result.prototype.name = null
Result.prototype.type = null
Result.prototype.spec = null
Result.prototype.raw = null
Result.prototype.hosted = null
{
"name": "npm-package-arg",
"version": "4.2.1",
"version": "5.0.0",
"description": "Parse the things that can be arguments to `npm install`",

@@ -13,11 +13,13 @@ "main": "npa.js",

"dependencies": {
"hosted-git-info": "^2.1.5",
"semver": "^5.1.0"
"hosted-git-info": "^2.4.2",
"osenv": "^0.1.4",
"semver": "^5.1.0",
"validate-npm-package-name": "^3.0.0"
},
"devDependencies": {
"standard": "^7.1.2",
"tap": "^5.7.2"
"standard": "9.0.2",
"tap": "^10.3.0"
},
"scripts": {
"test": "standard && tap --coverage test/*.js"
"test": "standard && tap -J --coverage test/*.js"
},

@@ -24,0 +26,0 @@ "repository": {

# npm-package-arg
Parse package name and specifier passed to commands like `npm install` or
`npm cache add`. This just parses the text given-- it's worth noting that
`npm` has further logic it applies by looking at your disk to figure out
what ambiguous specifiers are. If you want that logic, please see
[realize-package-specifier].
Parses package name and specifier passed to commands like `npm install` or
`npm cache add`, or as found in `package.json` dependency sections.
[realize-package-specifier]: https://www.npmjs.org/package/realize-package-specifier
Arguments look like: `foo@1.2`, `@bar/foo@1.2`, `foo@user/foo`, `http://x.com/foo.tgz`,
`git+https://github.com/user/foo`, `bitbucket:user/foo`, `foo.tar.gz` or `bar`
## EXAMPLES

@@ -21,42 +13,7 @@

// Pass in the descriptor, and it'll return an object
var parsed = npa("@bar/foo@1.2")
// Returns an object like:
{
raw: '@bar/foo@1.2', // what was passed in
name: '@bar/foo', // the name of the package
escapedName: '@bar%2ffoo', // the escaped name, for making requests against a registry
scope: '@bar', // the scope of the package, or null
type: 'range', // the type of specifier this is
spec: '>=1.2.0 <1.3.0', // the expanded specifier
rawSpec: '1.2' // the specifier as passed in
}
// Parsing urls pointing at hosted git services produces a variation:
var parsed = npa("git+https://github.com/user/foo")
// Returns an object like:
{
raw: 'git+https://github.com/user/foo',
scope: null,
name: null,
escapedName: null,
rawSpec: 'git+https://github.com/user/foo',
spec: 'user/foo',
type: 'hosted',
hosted: {
type: 'github',
ssh: 'git@github.com:user/foo.git',
sshurl: 'git+ssh://git@github.com/user/foo.git',
https: 'https://github.com/user/foo.git',
directUrl: 'https://raw.githubusercontent.com/user/foo/master/package.json'
}
try {
var parsed = npa("@bar/foo@1.2")
} catch (ex) {
}
// Completely unreasonable invalid garbage throws an error
// Make sure you wrap this in a try/catch if you have not
// already sanitized the inputs!
assert.throws(function() {
npa("this is not \0 a valid package name or url")
})
```

@@ -68,9 +25,24 @@

* var result = npa(*arg*)
### var result = npa(*arg*[, *where*])
Parses *arg* and returns a result object detailing what *arg* is.
* *arg* - a string that you might pass to `npm install`, like:
`foo@1.2`, `@bar/foo@1.2`, `foo@user/foo`, `http://x.com/foo.tgz`,
`git+https://github.com/user/foo`, `bitbucket:user/foo`, `foo.tar.gz`,
`../foo/bar/` or `bar`. If the *arg* you provide doesn't have a specifier
part, eg `foo` then the specifier will default to `latest`.
* *where* - Optionally the path to resolve file paths relative to. Defaults to `process.cwd()`
*arg* -- a package descriptor, like: `foo@1.2`, or `foo@user/foo`, or
`http://x.com/foo.tgz`, or `git+https://github.com/user/foo`
**Throws** if the package name is invalid, a dist-tag is invalid or a URL's protocol is not supported.
### var result = npa.resolve(*name*, *spec*[, *where*])
* *name* - The name of the module you want to install. For example: `foo` or `@bar/foo`.
* *spec* - The specifier indicating where and how you can get this module. Something like:
`1.2`, `^1.7.17`, `http://x.com/foo.tgz`, `git+https://github.com/user/foo`,
`bitbucket:user/foo`, `file:foo.tar.gz` or `file:../foo/bar/`. If not
included then the default is `latest`.
* *where* - Optionally the path to resolve file paths relative to. Defaults to `process.cwd()`
**Throws** if the package name is invalid, a dist-tag is invalid or a URL's protocol is not supported.
## RESULT OBJECT

@@ -81,24 +53,13 @@

* `name` - If known, the `name` field expected in the resulting pkg.
* `type` - One of the following strings:
* `git` - A git repo
* `hosted` - A hosted project, from github, bitbucket or gitlab. Originally
either a full url pointing at one of these services or a shorthand like
`user/project` or `github:user/project` for github or `bitbucket:user/project`
for bitbucket.
* `tag` - A tagged version, like `"foo@latest"`
* `version` - A specific version number, like `"foo@1.2.3"`
* `range` - A version range, like `"foo@2.x"`
* `local` - A local file or folder path
* `file` - A local `.tar.gz`, `.tar` or `.tgz` file.
* `directory` - A local directory.
* `remote` - An http url (presumably to a tgz)
* `spec` - The "thing". URL, the range, git repo, etc.
* `hosted` - If type=hosted this will be an object with the following keys:
* `type` - github, bitbucket or gitlab
* `ssh` - The ssh path for this git repo
* `sshUrl` - The ssh URL for this git repo
* `httpsUrl` - The HTTPS URL for this git repo
* `directUrl` - The URL for the package.json in this git repo
* `raw` - The original un-modified string that was provided.
* `rawSpec` - The part after the `name@...`, as it was originally
provided.
* `registry` - If true this specifier refers to a resource hosted on a
registry. This is true for `tag`, `version` and `range` types.
* `name` - If known, the `name` field expected in the resulting pkg.
* `scope` - If a name is something like `@org/module` then the `scope`

@@ -110,5 +71,15 @@ field will be set to `@org`. If it doesn't have a scoped name, then

`name` is `null`, `escapedName` will also be `null`.
If you only include a name and no specifier part, eg, `foo` or `foo@` then
a default of `latest` will be used (as of 4.1.0). This is contrast with
previous behavior where `*` was used.
* `rawSpec` - The specifier part that was parsed out in calls to `npa(arg)`,
or the value of `spec` in calls to `npa.resolve(name, spec).
* `saveSpec` - The normalized specifier, for saving to package.json files.
`null` for registry dependencies.
* `fetchSpec` - The version of the specifier to be used to fetch this
resource. `null` for shortcuts to hosted git dependencies as there isn't
just one URL to try with them.
* `gitRange` - If set, this is a semver specifier to match against git tags with
* `gitCommittish` - If set, this is the specific committish to use with a git dependency.
* `hosted` - If `from === 'hosted'` then this will be a `hosted-git-info`
object. This property is not included when serializing the object as
JSON.
* `raw` - The original un-modified string that was provided. If called as
`npa.resolve(name, spec)` then this will be `name + '@' + spec`.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc