From 663cd82ea3f405eab5bf8c63fc23a10af5bcdf70 Mon Sep 17 00:00:00 2001 From: rootsec1 Date: Mon, 31 Aug 2026 12:33:05 +0000 Subject: [PATCH] fix: preserve production dependencies in omitted SBOMs --- lib/commands/sbom.js | 16 +++++++-------- test/lib/commands/sbom.js | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/lib/commands/sbom.js b/lib/commands/sbom.js index 38fb383834e66..70f195c43a696 100644 --- a/lib/commands/sbom.js +++ b/lib/commands/sbom.js @@ -50,7 +50,9 @@ class SBOM extends BaseCommand { // Build the selector and query the tree for the list of nodes const selector = this.#buildSelector({ wsNodes }) log.info('sbom', `Using dependency selector: ${selector}`) - const items = await tree.querySelectorAll(selector) + const omit = new Set(this.npm.flatOptions.omit) + const items = [...await tree.querySelectorAll(selector)] + .filter(node => !node.shouldOmit(omit)) const errors = items.flatMap(node => detectErrors(node)) if (errors.length) { @@ -72,23 +74,19 @@ class SBOM extends BaseCommand { return this.exec(args) } - // Build the selector from all of the specified filter options + // Build the selector from the specified workspace options #buildSelector ({ wsNodes }) { let selector - const omit = this.npm.flatOptions.omit const workspacesEnabled = this.npm.flatOptions.workspacesEnabled - // If omit is specified, omit all nodes and their children which match the specified selectors - const omits = omit.reduce((acc, o) => `${acc}:not(.${o})`, '') - if (!workspacesEnabled) { // If workspaces are disabled, omit all workspace nodes and their children - selector = `:root > :not(.workspace)${omits},:root > :not(.workspace) *${omits},:extraneous` + selector = ':root > :not(.workspace),:root > :not(.workspace) *,:extraneous' } else if (wsNodes && wsNodes.length > 0) { // If one or more workspaces are selected, select only those workspaces and their children - selector = wsNodes.map(ws => `#${ws.name},#${ws.name} *${omits}`).join(',') + selector = wsNodes.map(ws => `#${ws.name},#${ws.name} *`).join(',') } else { - selector = `:root *${omits},:extraneous` + selector = ':root *,:extraneous' } // Always include the root node diff --git a/test/lib/commands/sbom.js b/test/lib/commands/sbom.js index a0cbca0ed4e31..28a12d1ac34cb 100644 --- a/test/lib/commands/sbom.js +++ b/test/lib/commands/sbom.js @@ -155,6 +155,48 @@ t.test('sbom', async t => { t.matchSnapshot(result()) }) + t.test('--omit dev keeps production dependencies also referenced as dev', async t => { + const config = { + 'sbom-format': 'spdx', + omit: ['dev'], + } + const { result, sbom } = await mockSbom(t, { + config, + prefixDir: { + 'package.json': JSON.stringify({ + name: 'test-npm-sbom', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + }, + devDependencies: { + chai: '^1.0.0', + }, + }), + node_modules: { + foo: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + dependencies: { + chai: '^1.0.0', + }, + }), + }, + chai: { + 'package.json': JSON.stringify({ + name: 'chai', + version: '1.0.0', + }), + }, + }, + }, + }) + await sbom.exec([]) + const packages = JSON.parse(result()).packages.map(pkg => pkg.name) + t.strictSame(packages, ['test-npm-sbom', 'chai', 'foo']) + }) + t.test('--omit optional', async t => { const config = { 'sbom-format': 'spdx',