mirror of
https://github.com/nodejs/node.git
synced 2024-12-01 07:53:06 +01:00
063afa85fe
PR-URL: https://github.com/nodejs/node/pull/53207 Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
183 lines
4.6 KiB
JavaScript
183 lines
4.6 KiB
JavaScript
|
|
const crypto = require('node:crypto')
|
|
const normalizeData = require('normalize-package-data')
|
|
const npa = require('npm-package-arg')
|
|
const ssri = require('ssri')
|
|
|
|
const SPDX_SCHEMA_VERSION = 'SPDX-2.3'
|
|
const SPDX_DATA_LICENSE = 'CC0-1.0'
|
|
const SPDX_IDENTIFER = 'SPDXRef-DOCUMENT'
|
|
|
|
const NO_ASSERTION = 'NOASSERTION'
|
|
|
|
const REL_DESCRIBES = 'DESCRIBES'
|
|
const REL_PREREQ = 'PREREQUISITE_FOR'
|
|
const REL_OPTIONAL = 'OPTIONAL_DEPENDENCY_OF'
|
|
const REL_DEV = 'DEV_DEPENDENCY_OF'
|
|
const REL_DEP = 'DEPENDENCY_OF'
|
|
|
|
const REF_CAT_PACKAGE_MANAGER = 'PACKAGE-MANAGER'
|
|
const REF_TYPE_PURL = 'purl'
|
|
|
|
const spdxOutput = ({ npm, nodes, packageType }) => {
|
|
const rootNode = nodes.find(node => node.isRoot)
|
|
const childNodes = nodes.filter(node => !node.isRoot && !node.isLink)
|
|
const rootID = rootNode.pkgid
|
|
const uuid = crypto.randomUUID()
|
|
const ns = `http://spdx.org/spdxdocs/${npa(rootID).escapedName}-${rootNode.version}-${uuid}`
|
|
|
|
const relationships = []
|
|
const seen = new Set()
|
|
for (let node of nodes) {
|
|
if (node.isLink) {
|
|
node = node.target
|
|
}
|
|
|
|
if (seen.has(node)) {
|
|
continue
|
|
}
|
|
seen.add(node)
|
|
|
|
const rels = [...node.edgesOut.values()]
|
|
// Filter out edges that are linking to nodes not in the list
|
|
.filter(edge => nodes.find(n => n === edge.to))
|
|
.map(edge => toSpdxRelationship(node, edge))
|
|
.filter(rel => rel)
|
|
|
|
relationships.push(...rels)
|
|
}
|
|
|
|
const extraRelationships = nodes.filter(node => node.extraneous)
|
|
.map(node => toSpdxRelationship(rootNode, { to: node, type: 'optional' }))
|
|
|
|
relationships.push(...extraRelationships)
|
|
|
|
const bom = {
|
|
spdxVersion: SPDX_SCHEMA_VERSION,
|
|
dataLicense: SPDX_DATA_LICENSE,
|
|
SPDXID: SPDX_IDENTIFER,
|
|
name: rootID,
|
|
documentNamespace: ns,
|
|
creationInfo: {
|
|
created: new Date().toISOString(),
|
|
creators: [
|
|
`Tool: npm/cli-${npm.version}`,
|
|
],
|
|
},
|
|
documentDescribes: [toSpdxID(rootNode)],
|
|
packages: [toSpdxItem(rootNode, { packageType }), ...childNodes.map(toSpdxItem)],
|
|
relationships: [
|
|
{
|
|
spdxElementId: SPDX_IDENTIFER,
|
|
relatedSpdxElement: toSpdxID(rootNode),
|
|
relationshipType: REL_DESCRIBES,
|
|
},
|
|
...relationships,
|
|
],
|
|
}
|
|
|
|
return bom
|
|
}
|
|
|
|
const toSpdxItem = (node, { packageType }) => {
|
|
normalizeData(node.package)
|
|
|
|
// Calculate purl from package spec
|
|
let spec = npa(node.pkgid)
|
|
spec = (spec.type === 'alias') ? spec.subSpec : spec
|
|
const purl = npa.toPurl(spec) + (isGitNode(node) ? `?vcs_url=${node.resolved}` : '')
|
|
|
|
/* For workspace nodes, use the location from their linkNode */
|
|
let location = node.location
|
|
if (node.isWorkspace && node.linksIn.size > 0) {
|
|
location = node.linksIn.values().next().value.location
|
|
}
|
|
|
|
let license = node.package?.license
|
|
if (license) {
|
|
if (typeof license === 'object') {
|
|
license = license.type
|
|
}
|
|
}
|
|
|
|
const pkg = {
|
|
name: node.packageName,
|
|
SPDXID: toSpdxID(node),
|
|
versionInfo: node.version,
|
|
packageFileName: location,
|
|
description: node.package?.description || undefined,
|
|
primaryPackagePurpose: packageType ? packageType.toUpperCase() : undefined,
|
|
downloadLocation: (node.isLink ? undefined : node.resolved) || NO_ASSERTION,
|
|
filesAnalyzed: false,
|
|
homepage: node.package?.homepage || NO_ASSERTION,
|
|
licenseDeclared: license || NO_ASSERTION,
|
|
externalRefs: [
|
|
{
|
|
referenceCategory: REF_CAT_PACKAGE_MANAGER,
|
|
referenceType: REF_TYPE_PURL,
|
|
referenceLocator: purl,
|
|
},
|
|
],
|
|
}
|
|
|
|
if (node.integrity) {
|
|
const integrity = ssri.parse(node.integrity, { single: true })
|
|
pkg.checksums = [{
|
|
algorithm: integrity.algorithm.toUpperCase(),
|
|
checksumValue: integrity.hexDigest(),
|
|
}]
|
|
}
|
|
return pkg
|
|
}
|
|
|
|
const toSpdxRelationship = (node, edge) => {
|
|
let type
|
|
switch (edge.type) {
|
|
case 'peer':
|
|
type = REL_PREREQ
|
|
break
|
|
case 'optional':
|
|
type = REL_OPTIONAL
|
|
break
|
|
case 'dev':
|
|
type = REL_DEV
|
|
break
|
|
default:
|
|
type = REL_DEP
|
|
}
|
|
|
|
return {
|
|
spdxElementId: toSpdxID(edge.to),
|
|
relatedSpdxElement: toSpdxID(node),
|
|
relationshipType: type,
|
|
}
|
|
}
|
|
|
|
const toSpdxID = (node) => {
|
|
let name = node.packageName
|
|
|
|
// Strip leading @ for scoped packages
|
|
name = name.replace(/^@/, '')
|
|
|
|
// Replace slashes with dots
|
|
name = name.replace(/\//g, '.')
|
|
|
|
return `SPDXRef-Package-${name}-${node.version}`
|
|
}
|
|
|
|
const isGitNode = (node) => {
|
|
if (!node.resolved) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const { type } = npa(node.resolved)
|
|
return type === 'git' || type === 'hosted'
|
|
} catch (err) {
|
|
/* istanbul ignore next */
|
|
return false
|
|
}
|
|
}
|
|
|
|
module.exports = { spdxOutput }
|