ljharb/purl

a PURL parser and serializer

★ 4Forks 0JavaScriptGitHub ↗Compare

Project website ↗

README

purl Version Badge

github actions coverage License Downloads

npm badge

A PURL (Package URL) parser and serializer, implementing the TC54 PURL specification.

Install

npm install purl

CLI Usage

purl [options] <input>

Arguments

  • input - An npm package specifier or PURL string

Options

  • --check, -c - Validate the package exists on its registry and show latest version
  • --json - Output only JSON to stdout (no colored PURL output)
  • --help - Show help message
  • --version - Show version number

Behavior

  • If input is a valid PURL string, outputs the normalized PURL on stdout
  • If input is an npm package specifier, converts it to a PURL and outputs on stdout
  • Parse information (including package URL if available) is output on stderr as JSON
  • With --json, outputs only JSON to stdout (useful for scripting)
  • Exit code 1 if input is invalid or validation fails

Examples

purl [email protected]           # outputs pkg:npm/[email protected]
purl @babel/[email protected]        # outputs pkg:npm/%40babel/[email protected]
purl express                  # outputs pkg:npm/express
purl 'pkg:pypi/[email protected]' # outputs pkg:pypi/[email protected]
purl -c [email protected]        # validates package exists, then outputs PURL

Supported Registry Checks (--check)

npm, pypi, gem, cargo, nuget, hex, maven, composer, pub, hackage, cocoapods

API

import {
	PURL,
	parse,
	stringify,
	valid,
	normalize,
	eq,
	compare,
	type,
	namespace,
	name,
	version,
	qualifiers,
	subpath,
	fromNPM,
	url,
	validate,
	validateTypes,
} from 'purl';

PURL class

import PURL from 'purl/purl';

const purl = new PURL('pkg:npm/[email protected]');

purl.type;       // 'npm'
purl.namespace;  // null
purl.name;       // 'lodash'
purl.version;    // '4.17.21'
purl.qualifiers; // null
purl.subpath;    // null

String(purl);    // 'pkg:npm/[email protected]'

// From components
const purl2 = new PURL({
	type: 'npm',
	namespace: '@babel',
	name: 'core',
	version: '7.0.0',
});
String(purl2);   // 'pkg:npm/%40babel/[email protected]'

// Instance methods
purl.equals(other);  // boolean
purl.compare(other); // -1 | 0 | 1

parse(purl)

Parse a PURL string into a PURL object. Returns null if invalid.

import parse from 'purl/parse';

parse('pkg:npm/[email protected]'); // PURL instance
parse('invalid');                // null

stringify(components)

Convert PURL components to a canonical PURL string.

import stringify from 'purl/stringify';

stringify({ type: 'npm', name: 'lodash', version: '4.17.21' });
// 'pkg:npm/[email protected]'

valid(purl)

Returns the normalized PURL string if valid, null otherwise.

import valid from 'purl/valid';

valid('pkg:NPM/[email protected]'); // 'pkg:npm/[email protected]'
valid('invalid');                // null

normalize(purl)

Normalize a PURL string. Throws if invalid.

import normalize from 'purl/normalize';

normalize('pkg:NPM/[email protected]'); // 'pkg:npm/[email protected]'
normalize('invalid');                // throws

eq(a, b)

Compare two PURLs for equality.

import eq from 'purl/eq';

eq('pkg:npm/[email protected]', 'pkg:NPM/[email protected]'); // true
eq('pkg:npm/[email protected]', 'pkg:npm/[email protected]'); // false

compare(a, b)

Compare two PURLs for sorting. Returns -1, 0, or 1.

import compare from 'purl/compare';

['pkg:npm/bbb', 'pkg:npm/aaa'].sort(compare);
// ['pkg:npm/aaa', 'pkg:npm/bbb']

Component accessors

Each accessor returns the component value or null if invalid/missing.

import type from 'purl/type';
import namespace from 'purl/namespace';
import name from 'purl/name';
import version from 'purl/version';
import qualifiers from 'purl/qualifiers';
import subpath from 'purl/subpath';

type('pkg:npm/[email protected]');      // 'npm'
namespace('pkg:npm/%40babel/core');  // '@babel'
name('pkg:npm/[email protected]');      // 'lodash'
version('pkg:npm/[email protected]');   // '4.17.21'
qualifiers('pkg:npm/foo?a=b');       // { a: 'b' }
subpath('pkg:npm/foo#lib/index.js'); // 'lib/index.js'

fromNPM(specifier)

Convert an npm package specifier to a PURL object.

import fromNPM from 'purl/from-npm';

fromNPM('[email protected]');    // PURL for pkg:npm/[email protected]
fromNPM('@babel/[email protected]'); // PURL for pkg:npm/%40babel/[email protected]
fromNPM('lodash@^4.0.0');     // PURL for pkg:npm/lodash (no version for ranges)

url(purl)

Get the registry URL for a PURL.

import url from 'purl/url';

url('pkg:npm/[email protected]');       // 'https://www.npmjs.com/package/lodash/v/4.17.21'
url('pkg:pypi/[email protected]');     // 'https://pypi.org/project/requests/2.28.0/'
url('pkg:github/ljharb/[email protected]');  // 'https://github.com/ljharb/qs/tree/6.11.0'

Supported URL Types

bioconductor, bitbucket, cargo, chrome-extension, clojars, cocoapods, composer, conan, conda, cpan, deno, docker, elm, gem, github, golang, hackage, hex, homebrew, huggingface, luarocks, maven, npm, nuget, pub, pypi, swift, vscode-extension

validate(purl)

Validate a PURL against its package registry. Returns a promise.

import validate, { supportedTypes } from 'purl/validate';

const result = await validate('pkg:npm/[email protected]');
// { valid: true, latestVersion: '4.17.21' }

const result2 = await validate('pkg:npm/nonexistent-package-xyz');
// { valid: false, error: 'npm package "nonexistent-package-xyz" not found', latestVersion: null }

supportedTypes; // ['npm', 'pypi', 'gem', 'cargo', 'nuget', 'hex', 'maven', 'composer', 'pub', 'hackage', 'cocoapods']

Tests

Clone the repo, npm install, and run npm test.

Versions before v2

Prior to v2, purl was a different package. v0 was just a placeholder, but v1 can be found both on archive.org and on the original branch of this repo.

Contributors

ljharb

Issues