Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional feat, chore, and fixes #73

Merged
merged 15 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 97 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,75 @@
# packageurl-js

### Installing:
### Installing

To install `packageurl-js` in your project, simply run:
```
```bash
npm install packageurl-js
```

This command will download the `packageurl-js` npm package for use in your application.

### Local Development:
Clone the `packageurl-js` repo and `cd` into the directory.
### Local Development

Then run:
```
Clone the `packageurl-js` repo and `cd` into the directory.

Then run:
```bash
npm install
```

### Testing
To run the test suite:
```

To run the test suite:
```bash
npm test
```

### Usage Examples

#### Import ES6 Module
#### Importing

As an ES6 module
```js
import { PackageURL } from 'packageurl-js'
```
import { PackageURL } from 'packageurl-js';
```

#### Import CommonJs Module

```
const { PackageURL } = require('packageurl-js');
As a CommonJS module
```js
const { PackageURL } = require('packageurl-js')
```

#### Parsing from a string
#### Parsing

```
const pkg = PackageURL.fromString('pkg:maven/org.springframework.integration/spring-integration-jms@5.5.5');
console.log(pkg);
```js
const purlStr = 'pkg:maven/org.springframework.integration/spring-integration-jms@5.5.5'
console.log(PackageURL.fromString(purlStr))
console.log(new PackageURL(...PackageURL.parseString(purlStr)))
```

=>
will both log

```
PackageURL {
type: 'maven',
name: 'spring-integration-jms',
namespace: 'org.springframework.integration',
version: '5.5.5',
qualifiers: null,
subpath: null
type: 'maven',
name: 'spring-integration-jms',
namespace: 'org.springframework.integration',
version: '5.5.5',
qualifiers: undefined,
subpath: undefined
}
```

#### Constructing

```
```js
const pkg = new PackageURL(
'maven',
'org.springframework.integration',
'spring-integration-jms',
'5.5.5',
undefined,
undefined);

console.log(pkg.toString());
'5.5.5'
)
console.log(pkg.toString())
```

=>
Expand All @@ -78,16 +80,73 @@ pkg:maven/org.springframework.integration/spring-integration-jms@5.5.5

#### Error Handling

```
```js
try {
PackageURL.fromString('not-a-purl');
} catch(ex) {
console.error(ex.message);
PackageURL.fromString('not-a-purl')
} catch (e) {
console.error(e.message)
}
```

=>

```
purl is missing the required "pkg" scheme component.
```
Invalid purl: missing required "pkg" scheme component
```

#### Helper Objects

Helpers for encoding, normalizing, and validating purl components and types can
be imported directly from the module or found on the PackageURL class as static
properties.
```js
import {
PackageURL,
PurlComponent,
PurlType
} from 'packageurl-js'

PurlComponent === PackageURL.Component // => true
PurlType === PackageURL.Type // => true
```

#### PurlComponent

Contains the following properties each with their own `encode`, `normalize`,
and `validate` methods, e.g. `PurlComponent.name.validate(nameStr)`:
- type
- namespace
- name
- version
- qualifiers
- qualifierKey
- qualifierValue
- subpath

#### PurlType

Contains the following properties each with their own `normalize`, and `validate`
methods, e.g. `PurlType.npm.validate(purlObj)`:
- alpm
- apk
- bitbucket
- bitnami
- composer
- conan
- cran
- deb
- github
- gitlab
- golang
- hex
- huggingface
- luarocks
- maven
- mlflow
- npm
- oci
- pub
- pypi
- qpkg
- rpm
- swift
12 changes: 6 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ SOFTWARE.
'use strict'

const {
Component,
KnownQualifierNames,
PackageURL,
Type
PurlComponent,
PurlQualifierNames,
PurlType
} = require('./src/package-url')

module.exports = {
Component,
KnownQualifierNames,
PackageURL,
Type
PurlComponent,
PurlQualifierNames,
PurlType
}
7 changes: 7 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

const LOOP_SENTINEL = 1_000_000

module.exports = {
LOOP_SENTINEL
}
71 changes: 71 additions & 0 deletions src/encode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict'

const { isObject } = require('./objects')
const { isNonEmptyString } = require('./strings')

const reusedSearchParams = new URLSearchParams()
const reusedSearchParamKey = '_'
const reusedSearchParamOffset = 2 // '_='.length

const { encodeURIComponent } = globalThis

function encodeNamespace(namespace) {
return isNonEmptyString(namespace)
? encodeURIComponent(namespace)
.replace(/%3A/g, ':')
.replace(/%2F/g, '/')
: ''
}

function encodeQualifierParam(param) {
if (isNonEmptyString(param)) {
// Param key and value are encoded with `percentEncodeSet` of
// 'application/x-www-form-urlencoded' and `spaceAsPlus` of `true`.
// https://url.spec.whatwg.org/#urlencoded-serializing
reusedSearchParams.set(reusedSearchParamKey, param)
return replacePlusSignWithPercentEncodedSpace(
reusedSearchParams.toString().slice(reusedSearchParamOffset)
)
}
return ''
}

function encodeQualifiers(qualifiers) {
if (isObject(qualifiers)) {
// Sort this list of qualifier strings lexicographically.
const qualifiersKeys = Object.keys(qualifiers).sort()
const searchParams = new URLSearchParams()
for (let i = 0, { length } = qualifiersKeys; i < length; i += 1) {
const key = qualifiersKeys[i]
searchParams.set(key, qualifiers[key])
}
return replacePlusSignWithPercentEncodedSpace(searchParams.toString())
}
return ''
}

function encodeSubpath(subpath) {
return isNonEmptyString(subpath)
? encodeURIComponent(subpath).replace(/%2F/g, '/')
: ''
}

function encodeVersion(version) {
return isNonEmptyString(version)
? encodeURIComponent(version).replace(/%3A/g, ':').replace(/%2B/g, '+')
: ''
}

function replacePlusSignWithPercentEncodedSpace(str) {
// Convert plus signs to %20 for better portability.
return str.replace(/\+/g, '%20')
}

module.exports = {
encodeNamespace,
encodeVersion,
encodeQualifiers,
encodeQualifierParam,
encodeSubpath,
encodeURIComponent
}
32 changes: 32 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict'

function createHelpersNamespaceObject(helpers, options_ = {}) {
const { comparator, ...defaults } = { __proto__: null, ...options_ }
const helperNames = Object.keys(helpers).sort()
const propNames = [
...new Set([...Object.values(helpers)].map(Object.keys).flat())
].sort(comparator)
const nsObject = Object.create(null)
for (let i = 0, { length } = propNames; i < length; i += 1) {
const propName = propNames[i]
const helpersForProp = Object.create(null)
for (
let j = 0, { length: length_j } = helperNames;
j < length_j;
j += 1
) {
const helperName = helperNames[j]
const helperValue =
helpers[helperName][propName] ?? defaults[helperName]
if (helperValue !== undefined) {
helpersForProp[helperName] = helperValue
}
}
nsObject[propName] = helpersForProp
}
return nsObject
}

module.exports = {
createHelpersNamespaceObject
}
13 changes: 13 additions & 0 deletions src/lang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

function isNullishOrEmptyString(value) {
return (
value === null ||
value === undefined ||
(typeof value === 'string' && value.length === 0)
)
}

module.exports = {
isNullishOrEmptyString
}
Loading
Loading