Part 2:  Building & Stringifying PURLs

Build PURLs with the fluent `PurlBuilder` API; serialize back to canonical `pkg:...` string form.

Topics: Anatomy Building Parsing Validation URL Ecosystems Comparison Security Architecture Builders Contributing Converters Hardening Release Tour VERS
src/package-url-builder.ts
43 sections
Section 1 of 43
22import { PackageURL } from './package-url.js'
23import {
24 ArrayPrototypeMap,
25 ObjectEntries,
26 ObjectFromEntries,
27} from '@socketsecurity/lib/primordials'
28
29import type { QualifiersObject } from './purl-component.js'
Section 2 of 43
57export class PurlBuilder {
Section 3 of 43
76 private _type?: string | undefined
Section 4 of 43
79 private _namespace?: string | undefined
Section 5 of 43
82 private _name?: string | undefined
Section 6 of 43
85 private _version?: string | undefined
Section 7 of 43
88 private _qualifiers?: Record<string, string> | undefined
Section 8 of 43
91 private _subpath?: string | undefined
Section 9 of 43
102 build(): PackageURL {
103 return new PackageURL(
104 this._type,
105 this._namespace,
106 this._name,
107 this._version,
108 this._qualifiers,
109 this._subpath,
110 )
111 }
Section 10 of 43
120 name(name: string): this {
121 this._name = name
122 return this
123 }
Section 11 of 43
133 namespace(namespace: string): this {
134 this._namespace = namespace
135 return this
136 }
Section 12 of 43
144 qualifier(key: string, value: string): this {
145 if (!this._qualifiers) {
146 this._qualifiers = { __proto__: null } as unknown as Record<
147 string,
148 string
149 >
150 }
151 this._qualifiers[key] = value
152 return this
153 }
Section 13 of 43
163 qualifiers(qualifiers: Record<string, string>): this {
164 this._qualifiers = { __proto__: null, ...qualifiers } as unknown as Record<
165 string,
166 string
167 >
168 return this
169 }
Section 14 of 43
178 subpath(subpath: string): this {
179 this._subpath = subpath
180 return this
181 }
Section 15 of 43
186 type(type: string): this {
187 this._type = type
188 return this
189 }
Section 16 of 43
197 version(version: string): this {
198 this._version = version
199 return this
200 }
Section 17 of 43
207 static bitbucket(): PurlBuilder {
208 return new PurlBuilder().type('bitbucket')
209 }
Section 18 of 43
216 static cargo(): PurlBuilder {
217 return new PurlBuilder().type('cargo')
218 }
Section 19 of 43
225 static cocoapods(): PurlBuilder {
226 return new PurlBuilder().type('cocoapods')
227 }
Section 20 of 43
234 static composer(): PurlBuilder {
235 return new PurlBuilder().type('composer')
236 }
Section 21 of 43
243 static conan(): PurlBuilder {
244 return new PurlBuilder().type('conan')
245 }
Section 22 of 43
252 static conda(): PurlBuilder {
253 return new PurlBuilder().type('conda')
254 }
Section 23 of 43
261 static cran(): PurlBuilder {
262 return new PurlBuilder().type('cran')
263 }
Section 24 of 43
271 static create(): PurlBuilder {
272 return new PurlBuilder()
273 }
Section 25 of 43
280 static deb(): PurlBuilder {
281 return new PurlBuilder().type('deb')
282 }
Section 26 of 43
289 static docker(): PurlBuilder {
290 return new PurlBuilder().type('docker')
291 }
Section 27 of 43
299 static from(purl: PackageURL): PurlBuilder {
300 const builder = new PurlBuilder()
301 if (purl.type !== undefined) {
302 builder._type = purl.type
303 }
304 if (purl.namespace !== undefined) {
305 builder._namespace = purl.namespace
306 }
307 if (purl.name !== undefined) {
308 builder._name = purl.name
309 }
310 if (purl.version !== undefined) {
311 builder._version = purl.version
312 }
313 if (purl.qualifiers !== undefined) {
314 const qualifiersObj = purl.qualifiers as QualifiersObject
315 builder._qualifiers = ObjectFromEntries(
316 ArrayPrototypeMap(ObjectEntries(qualifiersObj), ([key, value]) => [
317 key,
318 String(value),
319 ]),
320 ) as Record<string, string>
321 }
322 if (purl.subpath !== undefined) {
323 builder._subpath = purl.subpath
324 }
325 return builder
326 }
Section 28 of 43
333 static gem(): PurlBuilder {
334 return new PurlBuilder().type('gem')
335 }
Section 29 of 43
342 static github(): PurlBuilder {
343 return new PurlBuilder().type('github')
344 }
Section 30 of 43
351 static gitlab(): PurlBuilder {
352 return new PurlBuilder().type('gitlab')
353 }
Section 31 of 43
360 static golang(): PurlBuilder {
361 return new PurlBuilder().type('golang')
362 }
Section 32 of 43
369 static hackage(): PurlBuilder {
370 return new PurlBuilder().type('hackage')
371 }
Section 33 of 43
378 static hex(): PurlBuilder {
379 return new PurlBuilder().type('hex')
380 }
Section 34 of 43
387 static huggingface(): PurlBuilder {
388 return new PurlBuilder().type('huggingface')
389 }
Section 35 of 43
396 static luarocks(): PurlBuilder {
397 return new PurlBuilder().type('luarocks')
398 }
Section 36 of 43
405 static maven(): PurlBuilder {
406 return new PurlBuilder().type('maven')
407 }
Section 37 of 43
414 static npm(): PurlBuilder {
415 return new PurlBuilder().type('npm')
416 }
Section 38 of 43
423 static nuget(): PurlBuilder {
424 return new PurlBuilder().type('nuget')
425 }
Section 39 of 43
432 static oci(): PurlBuilder {
433 return new PurlBuilder().type('oci')
434 }
Section 40 of 43
441 static pub(): PurlBuilder {
442 return new PurlBuilder().type('pub')
443 }
Section 41 of 43
450 static pypi(): PurlBuilder {
451 return new PurlBuilder().type('pypi')
452 }
Section 42 of 43
459 static rpm(): PurlBuilder {
460 return new PurlBuilder().type('rpm')
461 }
Section 43 of 43
468 static swift(): PurlBuilder {
469 return new PurlBuilder().type('swift')
470 }
471}
src/stringify.ts
3 sections
1 2 3
Section 1 of 3
5import {
6 encodeComponent,
7 encodeName,
8 encodeNamespace,
9 encodeQualifiers,
10 encodeSubpath,
11 encodeVersion,
12} from './encode.js'
13import { isNonEmptyString } from './strings.js'
14
15import type { PackageURL } from './package-url.js'
16import type { QualifiersObject } from './purl-component.js'
Section 2 of 3
36export function stringifySpec(purl: PackageURL): string {
37 const {
38 name,
39 namespace,
40 qualifiers,
41 subpath,
42 version,
43 }: {
44 name?: string | undefined
45 namespace?: string | undefined
46 qualifiers?: QualifiersObject | undefined
47 subpath?: string | undefined
48 version?: string | undefined
49 } = purl
50 let specStr = ''
51 if (isNonEmptyString(namespace)) {
52 specStr = `${encodeNamespace(namespace)}/`
53 }
54 specStr = `${specStr}${encodeName(name)}`
55 if (isNonEmptyString(version)) {
56 specStr = `${specStr}@${encodeVersion(version)}`
57 }
58 if (qualifiers) {
59 specStr = `${specStr}?${encodeQualifiers(qualifiers)}`
60 }
61 if (isNonEmptyString(subpath)) {
62 specStr = `${specStr}#${encodeSubpath(subpath)}`
63 }
64 return specStr
65}
Section 3 of 3
83export function stringify(purl: PackageURL): string {
84 const type = isNonEmptyString(purl.type) ? encodeComponent(purl.type) : ''
85 return `pkg:${type}/${stringifySpec(purl)}`
86}