From 722d3152ea9b141ab7599e317926d80397c4c270 Mon Sep 17 00:00:00 2001 From: CAOShurong <170531907+CAOShurong@users.noreply.github.com> Date: Fri, 14 Aug 2026 14:43:21 +0800 Subject: [PATCH 1/3] Add npm engine constraint properties Populate CycloneDX npm taxonomy properties from package.json engine constraints and ignore malformed non-string values. Signed-off-by: CAOShurong <170531907+CAOShurong@users.noreply.github.com> --- HISTORY.md | 5 +++++ src/contrib/fromNodePackageJson/builders.ts | 19 +++++++++++++++++++ src/contrib/fromNodePackageJson/types.ts | 1 + ...son.Builders.ComponentBuilder.node.test.js | 19 +++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index cc9f2f85c..5cc436a48 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file. +* Added + * Populate npm engine constraint properties from `package.json` ([#1506]) + +[#1506]: https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1506 + ## 10.1.1 -- 2026-08-10 Maintenance release. diff --git a/src/contrib/fromNodePackageJson/builders.ts b/src/contrib/fromNodePackageJson/builders.ts index 938b831d6..fc267edc0 100644 --- a/src/contrib/fromNodePackageJson/builders.ts +++ b/src/contrib/fromNodePackageJson/builders.ts @@ -30,12 +30,28 @@ 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' import type { ExternalReferenceFactory } from './factories' import type { NodePackageJson } from './types' +function 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 +} + /** * Node-specific ToolBuilder. */ @@ -120,6 +136,8 @@ export class ComponentBuilder { const externalReferences = this.#extRefFactory.makeExternalReferences(data) + const properties = makeEngineProperties(data.engines) + const licenses = new LicenseRepository() if (typeof data.license === 'string') { /* see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#license */ @@ -144,6 +162,7 @@ export class ComponentBuilder { externalReferences: new ExternalReferenceRepository(externalReferences), group, licenses, + properties, version }) } 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..3596abef6 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,25 @@ 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') + ], [ // Even though https://npmjs.org does not allow it, // there is nothing wrong with a package name that contains more than one slash(/). From 7e7166167f90dabe8d5d8dc7f1e21849ba88e63d Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Fri, 14 Aug 2026 11:54:02 +0200 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Jan Kowalleck Signed-off-by: Jan Kowalleck --- HISTORY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 5cc436a48..9b6ac93fe 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,9 +7,10 @@ All notable changes to this project will be documented in this file. * Added - * Populate npm engine constraint properties from `package.json` ([#1506]) + * `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 From e32880582ad280c20afd48a4c04af1e51bc14844 Mon Sep 17 00:00:00 2001 From: CAOShurong <170531907+CAOShurong@users.noreply.github.com> Date: Sat, 15 Aug 2026 11:58:29 +0800 Subject: [PATCH 3/3] Apply review: move engine properties into ComponentBuilder and cover malformed engines Signed-off-by: CAOShurong <170531907+CAOShurong@users.noreply.github.com> --- src/contrib/fromNodePackageJson/builders.ts | 34 +++++++++---------- ...son.Builders.ComponentBuilder.node.test.js | 24 +++++++++++++ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/contrib/fromNodePackageJson/builders.ts b/src/contrib/fromNodePackageJson/builders.ts index fc267edc0..d1e42906e 100644 --- a/src/contrib/fromNodePackageJson/builders.ts +++ b/src/contrib/fromNodePackageJson/builders.ts @@ -37,21 +37,6 @@ import { splitNameGroup } from './_helpers/packageJson' import type { ExternalReferenceFactory } from './factories' import type { NodePackageJson } from './types' -function 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 -} - /** * Node-specific ToolBuilder. */ @@ -136,8 +121,6 @@ export class ComponentBuilder { const externalReferences = this.#extRefFactory.makeExternalReferences(data) - const properties = makeEngineProperties(data.engines) - const licenses = new LicenseRepository() if (typeof data.license === 'string') { /* see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#license */ @@ -162,8 +145,23 @@ export class ComponentBuilder { externalReferences: new ExternalReferenceRepository(externalReferences), group, licenses, - properties, + 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/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js b/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js index 3596abef6..f5436c829 100644 --- a/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js +++ b/tests/integration/Contrib.FromNodePackageJson.Builders.ComponentBuilder.node.test.js @@ -107,6 +107,30 @@ suite('integration: Contrib.FromNodePackageJson.Builders.ComponentBuilder', () = }, 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(/).