Parse the things that can be arguments to `npm install`
npx @tessl/cli install tessl/npm-npm-package-arg@13.0.0npm-package-arg is a JavaScript library that parses package specifier strings used in npm commands like npm install. It supports various package specifier formats including version ranges, git repositories, URLs, local files and directories, scoped packages, and aliases, providing a standardized way to understand and manipulate npm package arguments.
npm install npm-package-argconst npa = require('npm-package-arg');For ES modules:
import npa from 'npm-package-arg';const npa = require('npm-package-arg');
// Parse a package specifier string
const result = npa('@scope/package@^1.2.3');
console.log(result.name); // '@scope/package'
console.log(result.type); // 'range'
console.log(result.fetchSpec); // '^1.2.3'
// Parse with explicit name and spec
const resolved = npa.resolve('lodash', '>=4.0.0');
console.log(resolved.name); // 'lodash'
console.log(resolved.type); // 'range'
console.log(resolved.fetchSpec); // '>=4.0.0'
// Convert to PURL format
const purl = npa.toPurl('express@4.18.2');
console.log(purl); // 'pkg:npm/express@4.18.2'npm-package-arg is built around several key components:
npa() function that intelligently detects specifier types and routes to appropriate parsersParses package specifier strings into structured Result objects, automatically detecting the specifier type and extracting relevant information.
/**
* Parse a package specifier string or Result object
* @param arg - Package specifier string or existing Result object
* @param where - Optional path to resolve file paths relative to (defaults to process.cwd())
* @returns Result object with parsed package information
* @throws Error for invalid package names, tags, or unsupported protocols
*/
function npa(arg, where);Resolves a package name and specifier separately, useful when you have the name and version/range as separate values.
/**
* Parse package name and specifier separately
* @param name - Package name (e.g., 'lodash' or '@scope/package')
* @param spec - Version specifier (e.g., '1.2.3', '^1.0.0', 'latest')
* @param where - Optional path to resolve file paths relative to (defaults to process.cwd())
* @returns Result object with parsed package information
* @throws Error for invalid package names, tags, or unsupported protocols
*/
npa.resolve = function(name, spec, where);Converts package arguments to Package URL (PURL) format, useful for package identification and registry operations.
/**
* Convert package argument to PURL (Package URL) format
* @param arg - Package specifier string (must resolve to 'version' type)
* @param reg - Optional registry URL (defaults to https://registry.npmjs.org)
* @returns String in PURL format
* @throws Error for non-version types or invalid package names
*/
npa.toPurl = function(arg, reg);Creates Result objects for representing parsed package specifiers with all relevant metadata.
/**
* Result object constructor for parsed package specifiers
* @param opts - Configuration object with parsing options
*/
npa.Result = function Result(opts);The core data structure returned by all parsing operations, containing comprehensive package information.
class Result {
constructor(opts);
/** Package specifier type: 'git', 'tag', 'version', 'range', 'file', 'directory', 'remote', 'alias' */
type;
/** True if specifier refers to a registry resource (tag, version, range types) */
registry;
/** Package name (e.g., 'lodash', '@scope/package') */
name;
/** Scope for scoped packages (e.g., '@scope') or null */
scope;
/** URL-encoded version of name for registry requests */
escapedName;
/** Original specifier part from input */
rawSpec;
/** Normalized specifier for package.json (null for registry deps) */
saveSpec;
/** Specifier for fetching the resource (null for hosted git shortcuts) */
fetchSpec;
/** Original unmodified input string */
raw;
/** Base path for relative file resolution */
where;
/** Semver specifier for git tags */
gitRange;
/** Specific commit/branch/tag for git dependencies */
gitCommittish;
/** Subdirectory path for git repositories */
gitSubdir;
/** hosted-git-info object for hosted git dependencies */
hosted;
/** For alias type, contains the target specifier Result */
subSpec;
/**
* Set and validate package name
* @param name - Package name to set
* @returns this (for chaining)
* @throws Error for invalid package names
*/
setName(name);
/**
* Convert Result to string representation
* @returns String representation of the package specifier
*/
toString();
/**
* Convert Result to JSON-serializable object
* @returns Plain object (excludes hosted property)
*/
toJSON();
}Custom error objects with specific error codes for different validation failures.
/** Error codes thrown by npm-package-arg */
const ERROR_CODES = {
/** Invalid package name */
EINVALIDPACKAGENAME: 'EINVALIDPACKAGENAME',
/** Invalid tag name */
EINVALIDTAGNAME: 'EINVALIDTAGNAME',
/** Invalid PURL type */
EINVALIDPURLTYPE: 'EINVALIDPURLTYPE',
/** Unsupported URL protocol */
EUNSUPPORTEDPROTOCOL: 'EUNSUPPORTEDPROTOCOL'
};npm-package-arg recognizes and parses the following package specifier formats:
foo@1.2.3 - Exact version numberfoo@^1.2.0, foo@~1.2.0 - Semantic version rangesfoo@latest, foo@beta - Distribution tagsgit+https://github.com/user/repo.git#branchgit+ssh://git@github.com/user/repo.git#taggithub:user/repo, bitbucket:user/repo, gitlab:user/repohttps://example.com/package.tgzfile:./package.tgz, ./local-package.tar.gzfile:../my-package, ./packages/utils@scope/package@1.0.0@scope/package@github:user/repoalias@npm:real-package@1.0.0All parsing functions can throw errors with specific error codes:
try {
const result = npa('invalid-package-name-@#$');
} catch (error) {
if (error.code === 'EINVALIDPACKAGENAME') {
console.log('Invalid package name:', error.message);
}
}
// Error codes:
// EINVALIDPACKAGENAME - Invalid package name format
// EINVALIDTAGNAME - Invalid distribution tag
// EINVALIDPURLTYPE - PURL generation only works with version types
// EUNSUPPORTEDPROTOCOL - Unsupported URL protocolconst npa = require('npm-package-arg');
// Registry package with version range
const semverRange = npa('lodash@^4.17.0');
console.log(semverRange.type); // 'range'
console.log(semverRange.fetchSpec); // '^4.17.0'
// Scoped package
const scoped = npa('@babel/core@7.20.0');
console.log(scoped.scope); // '@babel'
console.log(scoped.name); // '@babel/core'
console.log(scoped.type); // 'version'
// Git repository
const gitRepo = npa('git+https://github.com/lodash/lodash.git#4.17.21');
console.log(gitRepo.type); // 'git'
console.log(gitRepo.gitCommittish); // '4.17.21'
// Local file
const localFile = npa('./packages/my-util-1.0.0.tgz');
console.log(localFile.type); // 'file'
console.log(localFile.fetchSpec); // Absolute path to file
// Alias
const alias = npa('my-lodash@npm:lodash@4.17.21');
console.log(alias.type); // 'alias'
console.log(alias.subSpec.name); // 'lodash'
console.log(alias.subSpec.fetchSpec); // '4.17.21'const npa = require('npm-package-arg');
const result = npa('@vue/cli@latest');
// Check if it's a registry package
if (result.registry) {
console.log('Registry package:', result.name);
}
// Get string representation
console.log('String form:', result.toString()); // '@vue/cli@latest'
// Convert to JSON
const json = result.toJSON();
console.log('JSON:', JSON.stringify(json, null, 2));
// Generate PURL (only works with version types)
try {
const purl = npa.toPurl('express@4.18.2');
console.log('PURL:', purl); // 'pkg:npm/express@4.18.2'
} catch (error) {
console.log('Cannot generate PURL:', error.message);
}const path = require('path');
const npa = require('npm-package-arg');
// Resolve relative to specific directory
const basePath = '/project/root';
const result = npa('./packages/utils', basePath);
console.log('Resolved path:', result.fetchSpec);
console.log('Save spec:', result.saveSpec); // Relative path for package.json