Skip to content

Commit c33b224

Browse files
committed
feat: properly filter workspace devDependencies in production mode
When using --prod flag in monorepo workspaces, devDependencies declared in workspace packages were not being filtered out correctly. This resulted in devDependencies appearing in production SBOMs. This fix adds proper tracking of workspace package devDependencies and filters them during dependency traversal when omitDevDependencies is enabled. Signed-off-by: Jose Luis Pereira <onaips@gmail.com>
1 parent e7c9100 commit c33b224

12 files changed

Lines changed: 32972 additions & 4 deletions

File tree

src/builders.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,17 @@ export class BomBuilder {
126126
rootPackage.dependencies.delete(dep)
127127
}
128128
}
129+
130+
// Build map of workspace packages' devDependencies for filtering
131+
const workspaceDevDeps = this.omitDevDependencies
132+
? this.gatherWorkspaceDevDependencies(workspace.project)
133+
: new Map<LocatorHash, Set<string>>()
134+
129135
for await (const component of this.gatherDependencies(
130136
rootComponent, rootPackage,
131137
workspace.project,
132-
fetchManifest, fetchLicenseEvidences
138+
fetchManifest, fetchLicenseEvidences,
139+
workspaceDevDeps
133140
)) {
134141
component.licenses.forEach(setLicensesDeclared)
135142

@@ -337,8 +344,51 @@ export class BomBuilder {
337344
}
338345
}
339346

340-
private * getDeps (pkg: Package, project: Project): Generator<Package> {
347+
/**
348+
* Gathers devDependencies from all workspace packages in the project.
349+
* Returns a map of package locatorHash to Set of devDependency names.
350+
*/
351+
private gatherWorkspaceDevDependencies (project: Project): Map<LocatorHash, Set<string>> {
352+
const workspaceDevDeps = new Map<LocatorHash, Set<string>>()
353+
354+
for (const workspace of project.workspaces) {
355+
const pkg = workspace.anchoredPackage
356+
// Only process workspace packages (not external dependencies)
357+
if (pkg.reference.startsWith('workspace:')) {
358+
const devDeps = new Set<string>()
359+
for (const depIdent of workspace.manifest.devDependencies.keys()) {
360+
devDeps.add(depIdent)
361+
}
362+
if (devDeps.size > 0) {
363+
workspaceDevDeps.set(pkg.locatorHash, devDeps)
364+
this.console.debug('DEBUG | workspace %s has %d devDependencies',
365+
structUtils.prettyLocatorNoColors(pkg),
366+
devDeps.size
367+
)
368+
}
369+
}
370+
}
371+
372+
return workspaceDevDeps
373+
}
374+
375+
private * getDeps (
376+
pkg: Package,
377+
project: Project,
378+
workspaceDevDeps: Map<LocatorHash, Set<string>>
379+
): Generator<Package> {
380+
const parentDevDeps = workspaceDevDeps.get(pkg.locatorHash)
381+
341382
for (const depDesc of pkg.dependencies.values()) {
383+
// Skip if this is a devDependency of the parent workspace package
384+
if (parentDevDeps?.has(depDesc.identHash) === true) {
385+
this.console.debug('DEBUG | skipping devDependency %s of workspace package %s',
386+
depDesc.identHash,
387+
structUtils.prettyLocatorNoColors(pkg)
388+
)
389+
continue
390+
}
391+
342392
const depRes = project.storedResolutions.get(depDesc.descriptorHash)
343393
if (typeof depRes === 'undefined') {
344394
throw new Error(`missing depRes for : ${depDesc.descriptorHash}`)
@@ -355,7 +405,8 @@ export class BomBuilder {
355405
component: Component, pkg: Package,
356406
project: Project,
357407
fetchManifest: ManifestFetcher,
358-
fetchLicenseEvidences: LicenseEvidenceFetcher
408+
fetchLicenseEvidences: LicenseEvidenceFetcher,
409+
workspaceDevDeps: Map<LocatorHash, Set<string>>
359410
): AsyncGenerator<Component> {
360411
// ATTENTION: multiple packages may have the same `identHash`, but the `locatorHash` is unique.
361412
const knownComponents = new Map<LocatorHash, Component>([[pkg.locatorHash, component]])
@@ -364,7 +415,7 @@ export class BomBuilder {
364415
let pendingEntry // eslint-disable-line @typescript-eslint/init-declarations -- ack
365416
while ((pendingEntry = pending.pop()) !== undefined) {
366417
const [pendingPkg, pendingComponent] = pendingEntry
367-
for (const depPkg of this.getDeps(pendingPkg, project)) {
418+
for (const depPkg of this.getDeps(pendingPkg, project, workspaceDevDeps)) {
368419
let depComponent = knownComponents.get(depPkg.locatorHash)
369420
if (depComponent === undefined) {
370421
const _depIDN = structUtils.prettyLocatorNoColors(depPkg)

0 commit comments

Comments
 (0)