Skip to content

Commit 8fab311

Browse files
authored
FromPackageJson builders and factories (#47)
builders and factories that generate data models from PackageJson-like structures Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 5677db6 commit 8fab311

31 files changed

+648
-23
lines changed

README.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ written in _TypeScript_ and compiled for the target.
3232

3333
## Capabilities
3434

35-
* Enums for the following use cases
35+
* Enums for the following use cases:
3636
* `AttachmentEncoding`
3737
* `ComponentScope`
3838
* `ComponentType`
3939
* `ExternalReferenceType`
4040
* `HashAlgorithm`
41-
* Data models for the following use cases
41+
* Data models for the following use cases:
4242
* `Attachment`
4343
* `Bom`
4444
* `BomRef`, `BomRefRepository`
@@ -51,7 +51,11 @@ written in _TypeScript_ and compiled for the target.
5151
* `OrganizationalEntity`
5252
* `SWID`
5353
* `Tool`, `ToolRepository`
54-
* Factory, that can create data models from any license descriptor string
54+
* Factories for the following use cases:
55+
* Create data models from any license descriptor string
56+
* Specific to _Node.js_: create data models from PackageJson-like data structures
57+
* Builders for the following use cases:
58+
* Specific to _Node.js_: create deep data models from PackageJson-like data structures
5559
* Implementation of the [_CycloneDX_ Specification][CycloneDX-spec] for the following versions:
5660
* `1.4`
5761
* `1.3`

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@
5959
"xmlbuilder2": "^3.0.2"
6060
},
6161
"browser": "./dist.web/lib.js",
62-
"types": "./src/_index.node.ts",
63-
"main": "./dist.node/_index.node.js",
64-
"exports": "./dist.node/_index.node.js",
62+
"types": "./src/index.node.ts",
63+
"main": "./dist.node/index.node.js",
64+
"exports": "./dist.node/index.node.js",
6565
"directories": {
6666
"doc": "./docs",
6767
"src": "./src",

src/builders/fromPackageJson.node.ts

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
import { PackageURL } from 'packageurl-js'
21+
22+
import * as Enums from '../enums'
23+
import { ExternalReferenceType } from '../enums'
24+
import * as Models from '../models'
25+
import * as Factories from '../factories/index.node'
26+
import { PackageJson, splitNameGroup } from '../helpers/packageJson'
27+
28+
/**
29+
* @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json PackageJson spec}
30+
*/
31+
32+
export class ToolBuilder {
33+
readonly #extRefFactory: Factories.FromPackageJson.ExternalReferenceFactory
34+
35+
constructor (extRefFactory: Factories.FromPackageJson.ExternalReferenceFactory) {
36+
this.#extRefFactory = extRefFactory
37+
}
38+
39+
makeTool (data: PackageJson): Models.Tool | undefined {
40+
const [name, vendor] = typeof data.name === 'string'
41+
? splitNameGroup(data.name)
42+
: []
43+
44+
return new Models.Tool({
45+
vendor,
46+
name,
47+
version: (typeof data.version === 'string')
48+
? data.version
49+
: undefined,
50+
externalReferences: new Models.ExternalReferenceRepository(this.#extRefFactory.makeExternalReferences(data))
51+
})
52+
}
53+
}
54+
55+
export class ComponentBuilder {
56+
readonly #extRefFactory: Factories.FromPackageJson.ExternalReferenceFactory
57+
readonly #licenseFactory: Factories.LicenseFactory
58+
59+
constructor (extRefFactory: Factories.FromPackageJson.ExternalReferenceFactory, licenseFactory: Factories.LicenseFactory) {
60+
this.#extRefFactory = extRefFactory
61+
this.#licenseFactory = licenseFactory
62+
}
63+
64+
makeComponent (data: PackageJson, type: Enums.ComponentType = Enums.ComponentType.Library): Models.Component | undefined {
65+
if (typeof data.name !== 'string') {
66+
return undefined
67+
}
68+
69+
const [name, group] = splitNameGroup(data.name)
70+
if (name.length === 0) {
71+
return undefined
72+
}
73+
74+
/** @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json#author} */
75+
const author = typeof data.author === 'string'
76+
? data.author
77+
: (typeof data.author?.name === 'string'
78+
? data.author.name
79+
: undefined)
80+
/** @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json#description-1} */
81+
const description = typeof data.description === 'string'
82+
? data.description
83+
: undefined
84+
/** @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json#version} */
85+
const version = typeof data.version === 'string'
86+
? data.version
87+
: undefined
88+
const externalReferences = this.#extRefFactory.makeExternalReferences(data)
89+
/** @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json#license} */
90+
const license = typeof data.license === 'string'
91+
? this.#licenseFactory.makeFromString(data.license)
92+
: undefined
93+
94+
return new Models.Component(type, name, {
95+
author,
96+
description,
97+
externalReferences: new Models.ExternalReferenceRepository(externalReferences),
98+
group,
99+
licenses: new Models.LicenseRepository(
100+
license === undefined
101+
? []
102+
: [license]
103+
),
104+
version,
105+
purl: this.#makePurl(name, group, version, externalReferences)
106+
})
107+
}
108+
109+
#makePurl (
110+
name: string,
111+
group: string | undefined,
112+
version: string | undefined,
113+
externalReferences: Models.ExternalReference[]
114+
): PackageURL {
115+
const qualifiers: { [key: string]: string } = {}
116+
const subpath = undefined
117+
118+
const vcsUrl = externalReferences.filter(
119+
({ type }) => type === ExternalReferenceType.VCS
120+
)[0]?.url.toString()
121+
if (vcsUrl !== undefined) {
122+
qualifiers.vcs_url = vcsUrl
123+
}
124+
125+
return new PackageURL(
126+
'npm', group, name, version, qualifiers, subpath)
127+
}
128+
}

src/builders/index.node.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
export * as FromPackageJson from './fromPackageJson.node'

src/factories/fromPackageJson.node.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
import * as Models from '../models'
21+
import * as Enums from '../enums'
22+
import { isNotUndefined } from '../helpers/notUndefined'
23+
import { PackageJson } from '../helpers/packageJson'
24+
25+
/**
26+
* @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json PackageJson spec}
27+
*/
28+
29+
export class ExternalReferenceFactory {
30+
makeExternalReferences (data: PackageJson): Models.ExternalReference[] {
31+
const refs: Array<Models.ExternalReference | undefined> = []
32+
33+
try { refs.push(this.makeVcs(data)) } catch { /* pass */ }
34+
try { refs.push(this.makeHomepage(data)) } catch { /* pass */ }
35+
try { refs.push(this.makeIssueTracker(data)) } catch { /* pass */ }
36+
37+
return refs.filter(isNotUndefined)
38+
}
39+
40+
makeVcs (data: PackageJson): Models.ExternalReference | undefined {
41+
/** @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json#repository the spec} */
42+
const repository = data.repository
43+
let url
44+
let comment: string | undefined
45+
if (typeof repository === 'object') {
46+
url = repository.url
47+
comment = 'as detected from PackageJson property "repository.url"'
48+
if (typeof repository.directory === 'string' && typeof url === 'string' && url.length > 0) {
49+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
50+
url += '#' + repository.directory
51+
comment += ' and "repository.directory"'
52+
}
53+
} else {
54+
url = repository
55+
comment = 'as detected from PackageJson property "repository"'
56+
}
57+
return typeof url === 'string' && url.length > 0
58+
? new Models.ExternalReference(url, Enums.ExternalReferenceType.VCS, { comment })
59+
: undefined
60+
}
61+
62+
makeHomepage (data: PackageJson): Models.ExternalReference | undefined {
63+
/** @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json#homepage the spec} */
64+
const url = data.homepage
65+
return typeof url === 'string' && url.length > 0
66+
? new Models.ExternalReference(
67+
url, Enums.ExternalReferenceType.Website,
68+
{ comment: 'as detected from PackageJson property "homepage"' })
69+
: undefined
70+
}
71+
72+
makeIssueTracker (data: PackageJson): Models.ExternalReference | undefined {
73+
/** @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bugs the spec} */
74+
const bugs = data.bugs
75+
let url
76+
let comment: string | undefined
77+
if (typeof bugs === 'object') {
78+
url = bugs.url
79+
comment = 'as detected from PackageJson property "bugs.url"'
80+
} else {
81+
url = bugs
82+
comment = 'as detected from PackageJson property "bugs"'
83+
}
84+
return typeof url === 'string' && url.length > 0
85+
? new Models.ExternalReference(url, Enums.ExternalReferenceType.IssueTracker, { comment })
86+
: undefined
87+
}
88+
}

src/factories/index.common.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
// not everything is public, yet
21+
22+
export * from './license'

src/factories/index.node.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
export * from './index.common'
21+
22+
export * as FromPackageJson from './fromPackageJson.node'

src/factories/index.ts renamed to src/factories/index.web.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ SPDX-License-Identifier: Apache-2.0
1717
Copyright (c) OWASP Foundation. All Rights Reserved.
1818
*/
1919

20-
export * from './licenseFactory'
20+
export * from './index.common'
File renamed without changes.

src/helpers/packageJson.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
/**
21+
* Split name and group from a package's name.
22+
* Returns a tuple: [name, ?group]
23+
*/
24+
export function splitNameGroup (data: string): [string, string?] {
25+
return data[0] === '@'
26+
? data.split('/', 2).reverse() as [string, string?]
27+
: [data]
28+
}
29+
30+
export interface PackageJson {
31+
name?: string
32+
version?: string
33+
description?: string
34+
license?: string
35+
author?: string | {
36+
name?: string
37+
email?: string
38+
}
39+
bugs?: string | {
40+
url?: string
41+
}
42+
homepage?: string
43+
repository?: string | {
44+
url?: string
45+
directory?: string
46+
}
47+
// .. to be continued
48+
}

0 commit comments

Comments
 (0)