Skip to content

fix(diff): detect upgrades by version-independent purl coordinate - #58

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-jluf7s
Open

fix(diff): detect upgrades by version-independent purl coordinate#58
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-jluf7s

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

Fixes the tool's headline "upgraded dependencies" detection, which silently never fired for real-world SBOMs.

diff() keyed components by comp.purl ?? comp.name (src/diff.ts), but a purl embeds the version — e.g. pkg:npm/lodash@4.17.21. When a dependency is upgraded, its purl changes, so the two versions hash to different keys and the diff reports the change as one removed + one added instead of an upgrade.

Because real CycloneDX/SPDX SBOMs almost always carry purls (purl is the standard component identifier), this meant:

  • The upgraded array stayed empty in practice → the Markdown "Upgraded Components" table and the text-report ↑ Upgraded section were effectively dead for real inputs.
  • isMajorBump / the [MAJOR] flag — the most security-relevant signal — never surfaced.
  • Every dependency bump was double-counted as a removal and an addition, inflating churn and obscuring what actually changed.

The prior diff.test.ts even rationalized this as "correct behavior — different purls are different packages", which contradicts the package's own description ("Highlights added, removed, upgraded dependencies").

Change

Match components on their version-independent purl coordinate — the purl with version, qualifiers (?…), and subpath (#…) stripped — falling back to the lowercased name when no purl is present:

  • pkg:npm/lodash@4.17.20 and pkg:npm/lodash@4.17.21 → both key to pkg:npm/lodash → reported as an upgrade.
  • Scoped names like @babel/core are preserved: the scope's @ precedes a /, while the version @ follows the final path segment, so only the version is cut.
  • Qualifiers and subpaths are dropped before locating the version delimiter.

No public API or type changes — this only corrects how existing components are matched inside diff().

Tests

  • Replaced the test that asserted the old add/remove behavior with one asserting an upgrade is detected when purls are present (0 added, 0 removed, 1 upgraded).
  • Added coverage for scoped packages (@babel/core, with a major-bump assertion) and for purls carrying qualifiers/subpaths.
  • Full suite green: 40 passed; tsc --noEmit and eslint src clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_014nrrhhcYr3dDS5FztH9DSr


Generated by Claude Code

diff() keyed components by `comp.purl ?? comp.name`, but a purl embeds
the version (e.g. `pkg:npm/lodash@4.17.21`). A version bump therefore
changed the key, so upgrades were reported as a removal plus an addition
instead of an upgrade. Because real CycloneDX/SPDX SBOMs almost always
carry purls, the tool's headline "upgraded dependencies" feature — and
the isMajorBump / [MAJOR] signal — effectively never fired in practice.

Key components on the purl *coordinate* (purl with version, qualifiers,
and subpath stripped), falling back to the lowercased name. Scoped npm
names like `@babel/core` are preserved since the scope's `@` precedes a
`/`, while the version `@` follows the final path segment.

Replace the test that rationalized the old add/remove behavior with
coverage asserting upgrades are detected when purls are present, plus
scoped-package and qualifier/subpath cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014nrrhhcYr3dDS5FztH9DSr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants