Skip to content

Commit 3fa1634

Browse files
committed
fix: prevent duplicated components
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
1 parent 93f0d11 commit 3fa1634

File tree

4 files changed

+43
-148
lines changed

4 files changed

+43
-148
lines changed

src/_helpers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ export interface ValidPackageJSON {
4040
version: string
4141
}
4242

43-
export interface PackageDescription {
43+
export interface PackageDescription<PJ = any> {
4444
path: string
45-
packageJson: NonNullable<any>
45+
packageJson: NonNullable<PJ>
4646
}
4747

4848

4949
const PACKAGE_MANIFEST_FILENAME = 'package.json'
5050

51-
export function getPackageDescription(path: string): PackageDescription | undefined {
51+
export function getPackageDescription(path: string): PackageDescription<ValidPackageJSON> | undefined {
5252
const isSubDirOfNodeModules = isSubDirectoryOfNodeModulesFolder(path)
5353

5454
while (isAbsolute(path)) {

src/extractor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class Extractor {
5050
this.#leGatherer = leFetcher
5151
}
5252

53-
generateComponents (modules: Iterable<Module>, collectEvidence: boolean, logger?: WebpackLogger): Iterable<CDX.Models.Component> {
53+
generateComponents (modules: Iterable<Module>, collectEvidence: boolean, logger?: WebpackLogger): Map<string, CDX.Models.Component> {
5454
const pkgs = new Map<string, CDX.Models.Component>()
5555
const components = new Map<Module, CDX.Models.Component>()
5656

@@ -85,7 +85,7 @@ export class Extractor {
8585
this.#linkDependencies(components)
8686

8787
logger?.log('done building Components from modules...')
88-
return pkgs.values()
88+
return pkgs
8989
}
9090

9191
/**

src/plugin.ts

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ export class CycloneDxWebpackPlugin {
206206

207207
const bom = new CDX.Models.Bom()
208208
bom.metadata.lifecycles.add(CDX.Enums.LifecyclePhase.Build)
209-
bom.metadata.component = this.#makeRootComponent(compilation.compiler.context, cdxComponentBuilder, logger.getChildLogger('RootComponentBuilder'))
210209

211210
const serializeOptions: CDX.Serialize.Types.SerializerOptions & CDX.Serialize.Types.NormalizerOptions = {
212211
sortLists: this.reproducibleResults,
@@ -247,6 +246,9 @@ export class CycloneDxWebpackPlugin {
247246
}
248247
}
249248

249+
const rcDesc = getPackageDescription(compilation.compiler.context)
250+
?? {path: compilation.compiler.context}
251+
250252
compilation.hooks.afterOptimizeTree.tap(
251253
pluginName,
252254
(_, modules) => {
@@ -259,23 +261,38 @@ export class CycloneDxWebpackPlugin {
259261
)
260262

261263
thisLogger.log('generating components...')
262-
for (const component of extractor.generateComponents(modules, this.collectEvidence, thisLogger.getChildLogger('Extractor'))) {
263-
if (bom.metadata.component !== undefined &&
264-
bom.metadata.component.group === component.group &&
265-
bom.metadata.component.name === component.name &&
266-
bom.metadata.component.version === component.version
267-
) {
268-
// metadata matches this exact component.
269-
// -> so the component is actually treated as the root component.
270-
thisLogger.debug('update bom.metadata.component - replace', bom.metadata.component, 'with', component)
271-
bom.metadata.component = component
264+
const components= extractor.generateComponents(modules, this.collectEvidence, thisLogger.getChildLogger('Extractor'))
265+
const rcComponentDetected = components.get(rcDesc.path)
266+
if (undefined!==rcComponentDetected) {
267+
if (this.rootComponentAutodetect){
268+
thisLogger.debug('add to bom.metadata.component', rcComponentDetected)
269+
bom.metadata.component = rcComponentDetected
270+
components.delete(rcDesc.path)
272271
} else {
272+
const rcComponent = cdxComponentBuilder.makeComponent({
273+
name: this.rootComponentName,
274+
version: this.rootComponentVersion,
275+
})
276+
if (rcComponent !== undefined) {
277+
rcComponent.dependencies = rcComponentDetected.dependencies
278+
for (const {dependencies} of components.values()) {
279+
if (dependencies.delete(rcComponentDetected.bomRef)) {
280+
dependencies.add(rcComponent.bomRef)
281+
}
282+
}
283+
thisLogger.debug('add to bom.metadata.component', rcComponentDetected)
284+
bom.metadata.component = rcComponent
285+
components.delete(rcDesc.path)
286+
}
287+
}
288+
}
289+
for (const component of components.values()) {
273290
thisLogger.debug('add to bom.components', component)
274291
bom.components.add(component)
275-
}
276292
}
277293
thisLogger.log('generated components.')
278294

295+
279296
thisLogger.log('finalizing BOM...')
280297
this.#finalizeBom(bom, cdxComponentBuilder, cdxPurlFactory, logger.getChildLogger('BomFinalizer'))
281298
thisLogger.log('finalized BOM.')
@@ -368,25 +385,6 @@ export class CycloneDxWebpackPlugin {
368385
}
369386
}
370387

371-
#makeRootComponent (
372-
path: string,
373-
builder: CDX.Builders.FromNodePackageJson.ComponentBuilder,
374-
logger: WebpackLogger
375-
): CDX.Models.Component | undefined {
376-
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- expected */
377-
const thisPackageJson = this.rootComponentAutodetect
378-
? getPackageDescription(path)?.packageJson
379-
: { name: this.rootComponentName, version: this.rootComponentVersion }
380-
if (thisPackageJson === undefined) { return undefined }
381-
normalizePackageManifest(
382-
383-
thisPackageJson,
384-
w => { logger.debug('normalizePackageJson from PkgPath', path, 'caused:', w) }
385-
)
386-
387-
return builder.makeComponent(thisPackageJson)
388-
}
389-
390388
#finalizeBom (
391389
bom: CDX.Models.Bom,
392390
cdxComponentBuilder: CDX.Builders.FromNodePackageJson.ComponentBuilder,
@@ -410,12 +408,13 @@ export class CycloneDxWebpackPlugin {
410408
bom.metadata.tools.components.add(toolC)
411409
}
412410

413-
if (bom.metadata.component !== undefined) {
414-
this.#addRootComponentExtRefs(bom.metadata.component, logger)
411+
const rComponent = bom.metadata.component
412+
if (rComponent !== undefined) {
413+
this.#addRootComponentExtRefs(rComponent, logger)
415414
/* eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- ack */
416-
bom.metadata.component.type = this.rootComponentType as CDX.Models.Component['type']
417-
bom.metadata.component.purl = cdxPurlFactory.makeFromComponent(bom.metadata.component)
418-
bom.metadata.component.bomRef.value = bom.metadata.component.purl?.toString()
415+
rComponent.type = this.rootComponentType as CDX.Models.Component['type']
416+
rComponent.purl = cdxPurlFactory.makeFromComponent(rComponent)
417+
rComponent.bomRef.value = rComponent.purl?.toString()
419418
}
420419
/* eslint-enable no-param-reassign */
421420
}

tests/integration/__snapshots__/index.test.js.snap

Lines changed: 3 additions & 107 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)