diff --git a/HISTORY.md b/HISTORY.md index cc9f2f85c..9b6ac93fe 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file. +* Added + * `Contrib.FromNodePackageJson.Builders.ComponentBuilder` may populate properties with engine constraint ([#1506] via [#1507]) + +[#1506]: https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1506 +[#1507]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1507 + ## 10.1.1 -- 2026-08-10 Maintenance release. diff --git a/src/contrib/fromNodePackageJson/builders.ts b/src/contrib/fromNodePackageJson/builders.ts index 938b831d6..d1e42906e 100644 --- a/src/contrib/fromNodePackageJson/builders.ts +++ b/src/contrib/fromNodePackageJson/builders.ts @@ -30,6 +30,7 @@ import { ComponentType } from '../../enums/componentType' import { Component } from '../../models/component' import { ExternalReferenceRepository } from '../../models/externalReference' import { LicenseRepository } from '../../models/license' +import { Property, PropertyRepository } from '../../models/property' import { Tool } from '../../models/tool' import type { LicenseFactory } from '../license/factories' import { splitNameGroup } from './_helpers/packageJson' @@ -144,7 +145,23 @@ export class ComponentBuilder { externalReferences: new ExternalReferenceRepository(externalReferences), group, licenses, + properties: this.#makeEngineProperties(data.engines), version }) } + + #makeEngineProperties (engines: unknown): PropertyRepository { + const properties = new PropertyRepository() + if (engines === null || typeof engines !== 'object' || Array.isArray(engines)) { + return properties + } + + for (const [engine, constraint] of Object.entries(engines)) { + if (typeof constraint === 'string') { + properties.add(new Property(`cdx:npm:package:constraint:engine:${engine}`, constraint)) + } + } + + return properties + } } diff --git a/src/contrib/fromNodePackageJson/types.ts b/src/contrib/fromNodePackageJson/types.ts index 66864118e..cadea5455 100644 --- a/src/contrib/fromNodePackageJson/types.ts +++ b/src/contrib/fromNodePackageJson/types.ts @@ -47,6 +47,7 @@ export interface NodePackageJson { url?: string directory?: string } + engines?: Record // ... to be continued dist?: any // see https://github.com/CycloneDX/cyclonedx-node-npm/issues/1300 } diff --git a/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js b/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js index 883e1debe..f5436c829 100644 --- a/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js +++ b/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js @@ -61,6 +61,10 @@ suite('integration: Contrib.FromNodePackageJson.Builders.ComponentBuilder', () = repository: { type: 'git', url: 'https://github.com/foo/bar.git' + }, + engines: { + node: '>=20.18.0', + npm: '^10.8.2' } // to be continued }, @@ -84,10 +88,49 @@ suite('integration: Contrib.FromNodePackageJson.Builders.ComponentBuilder', () = new Models.NamedLicense(`dummy license ${salt}`), new Models.NamedLicense(`some license ${salt}`), ]), + properties: new Models.PropertyRepository([ + new Models.Property('cdx:npm:package:constraint:engine:node', '>=20.18.0'), + new Models.Property('cdx:npm:package:constraint:engine:npm', '^10.8.2'), + ]), version: `1.33.7-alpha.23.${salt}` } ) ], + [ + 'ignore malformed engine constraints', + { + name: 'foo', + engines: { + node: 22, + npm: null + } + }, + new Models.Component(Enums.ComponentType.Library, 'foo') + ], + [ + 'ignore array engines', + { + name: 'foo', + engines: ['node', '>=20.18.0'] + }, + new Models.Component(Enums.ComponentType.Library, 'foo') + ], + [ + 'ignore null engines', + { + name: 'foo', + engines: null + }, + new Models.Component(Enums.ComponentType.Library, 'foo') + ], + [ + 'ignore string engines', + { + name: 'foo', + engines: '>=20.18.0' + }, + new Models.Component(Enums.ComponentType.Library, 'foo') + ], [ // Even though https://npmjs.org does not allow it, // there is nothing wrong with a package name that contains more than one slash(/).